2010-10-20 8 views
6

En utilisant Moose, est-il possible de créer un générateur qui construit plusieurs attributs à la fois?Comment puis-je créer plusieurs attributs avec un seul constructeur dans Moose?

J'ai un projet dans lequel l'objet a plusieurs 'ensembles' de champs - si un membre de l'ensemble est demandé, je veux aller de l'avant et les peupler tous. Mon hypothèse est que si j'ai besoin du nom, j'ai aussi besoin de la date de naissance, et comme ils sont dans la même table, il est plus rapide d'avoir les deux en une seule requête.

Je ne suis pas sûr si ma question est assez claire, mais j'espère que certains exemples de code le rendront clair.

Ce que je:

Package WidgetPerson; 
use Moose; 

has id => (is => 'ro', isa => 'Int'); 
has name => (is => 'ro', lazy => 1, builder => '_build_name'); 
has birthdate => (is => 'ro', lazy => 1, builder => '_build_birthdate'); 
has address => (is => 'ro', lazy => 1, builder => '_build_address'); 

sub _build_name { 
my $self = shift; 
my ($name) = $dbh->selectrow_array("SELECT name FROM people WHERE id = ?", {}, $self->id); 
return $name; 
} 
sub _build_birthdate { 
my $self = shift; 
my ($date) = $dbh->selectrow_array("SELECT birthdate FROM people WHERE id = ?", {}, $self->id); 
return $date; 
} 
sub _build_address { 
my $self = shift; 
my ($date) = $dbh->selectrow_array("SELECT address FROM addresses WHERE person_id = ?", {}, $self->id); 
return $date; 
} 

Mais ce que je veux est:

has name => (is => 'ro', isa => 'Str', lazy => 1, builder => '_build_stuff'); 
has birthdate => (is => 'ro', isa => 'Date', lazy => 1, builder => '_build_stuff'); 
has address => (is => 'ro', isa => 'Address', lazy => 1, builder => '_build_address'); 
sub _build_stuff { 
my $self = shift; 
my ($name, $date) = $dbh->selectrow_array("SELECT name, birthdate FROM people WHERE id = ?", {}, $self->id); 
$self->name($name); 
$self->birthdate($date); 
} 
sub _build_address { 
#same as before 
} 

Répondre

7

ce que je fais dans ce cas, quand je ne veux pas avoir séparé objet comme dans la réponse de Ether, est d'avoir un attribut construit paresseusement pour l'état intermédiaire. Ainsi, par exemple:

has raw_row => (is => 'ro', init_arg => undef, lazy => 1, builder => '_build_raw_row'); 
has birthdate => (is => 'ro', lazy => 1, builder => '_build_birthdate'); 

sub _build_raw_row { 
    $dbh->selectrow_hashref(...); 
} 

sub _build_birthdate { 
    my $self = shift; 
    return $self->raw_row->{birthdate}; 
} 

Répétez le même schéma que birthdate pour le nom, etc.

lecture l'un des attributs individuels va essayer d'obtenir des données de raw_row, dont le constructeur paresseux ne fonctionnera que SQL fois . Puisque vos attributs sont tous en lecture seule, vous n'avez pas à vous soucier de mettre à jour un état d'objet si l'un d'eux change.

Ce motif est également utile pour des documents XML, par exemple. un DOM, avec des attributs individuels construits paresseusement à partir d'expressions XPath ou what-have-you.

+0

Beau motif, mais s'il y a beaucoup d'attributs, existe-t-il un moyen d'éviter de répéter: build => '_build_attribute1' ... sub _build_attribute1 {my $ self = shift; return $ self-> raw_row -> {attribut1}; } encore et encore? Peut-être qu'il existe un moyen pour _build_attribute d'identifier l'attribut qui l'a appelé? – yahermann

+0

@yahermann, je pense que cela va au point de la question. Si vous pouviez faire ce que vous suggérez, ce problème n'existerait pas. – UncleCarl

5

Non, un constructeur d'attribut ne peut retourner une valeur à la fois. Vous pouvez construire les deux en faisant en sorte que chaque constructeur prenne la valeur de l'autre attribut avant de revenir, mais cela devient laid assez rapidement ...

Cependant,, si vous avez généralement deux données qui vont ensemble d'une manière ou d'une autre (par exemple en provenance de la même requête DB comme dans votre cas), vous pouvez stocker ces valeurs dans un seul attribut comme un objet:

has birth_info => (
    is => 'ro', isa => 'MyApp::Data::BirthInfo', 
    lazy => 1, 
    default => sub { 
     MyApp::Data::BirthInfo->new(shift->some_id) 
    }, 
    handles => [ qw(birthdate name) ], 
); 

package MyApp::Data::BirthInfo; 
use Moose; 
has some_id => (
    is => 'ro', isa => 'Int', 
    trigger => sub { 
     # perhaps this object self-populates from the DB when you assign its id? 
     # or use some other mechanism to load the row in an ORMish way (perhaps BUILD) 
    } 
); 
has birthdate => (
    is => 'ro', isa => 'Str', 
); 
has name => (
    is => 'ro', isa => 'Str', 
); 
+0

Hm. Cela peut fonctionner, bien qu'il semble que cela ajoute de la complexité là où j'essaie de le réduire. – RickF

+0

@RickF: construire deux attributs avec un constructeur va être plus compliqué que de stocker deux attributs sur un seul objet. – Ether

+0

Vous avez probablement raison. Mon hésitation est largement due à la résistance au niveau de la réaction intestinale à la simple recréation de la structure de la table complexe en forme d'orignal. – RickF

Questions connexes