2010-10-20 9 views
6

Al usar Moose, ¿es posible crear un generador que cree múltiples atributos a la vez?¿Cómo puedo crear múltiples atributos con un solo creador en Moose?

Tengo un proyecto en el que el objeto tiene varios 'conjuntos' de campos; si se solicita algún miembro del conjunto, deseo continuar y rellenarlos todos. Mi suposición es que si necesito el nombre, también necesitaré la fecha de nacimiento, y dado que están en la misma tabla, es más rápido obtener ambas en una consulta.

No estoy seguro de si mi pregunta es lo suficientemente clara, pero espero que algún código de muestra lo deje en claro.

lo que tengo:

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; 
} 

Pero lo que quiero decir:

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 
} 

Respuesta

7

lo que hago en este caso, cuando no se quiere tener una separada objeto como en la respuesta de Ether, tiene un atributo construido lentamente para el estado intermedio. Así, por ejemplo:

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}; 
} 

Repita el mismo patrón que birthdate de nombre, etc.

leer cualquiera de los atributos individuales tratará de obtener datos de raw_row, cuyo constructor perezoso sólo ejecutar el SQL una vez . Como sus atributos son todos de solo lectura, no tiene que preocuparse por actualizar ningún estado de objeto si uno de ellos cambia.

Este patrón también es útil para documentos XML: el estado intermedio que guarda puede ser p. Ej. un DOM, con atributos individuales construidos perezosamente desde expresiones XPath o what-have-you.

+0

Bonito patrón, pero si hay muchos atributos, ¿hay alguna manera de evitar repetirlo?: build => '_build_attribute1' ... sub _build_attribute1 {my $ self = cambio; return $ self-> raw_row -> {attribute1}; } ¿una y otra vez? Tal vez hay una forma para _build_attribute para identificar el atributo que lo llamó? – yahermann

+0

@yahermann, creo que eso llega al punto del problema. Si pudieras hacer lo que sugieres, este problema no existiría. – UncleCarl

5

No, un constructor del atributo sólo puede devolver un valor a la vez. Puede construir ambos haciendo que cada constructor establezca el valor del otro atributo antes de volver, pero eso se pone feo rápidamente ...

Sin embargo,, si generalmente tiene dos datos que se combinan de alguna forma (por ejemplo procedentes de la misma consulta DB como en su caso), puede almacenar estos valores en un solo atributo como un objeto:

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. Eso puede funcionar, aunque parece que agrega complejidad cuando intento reducirlo. – RickF

+0

@RickF: crear dos atributos con un constructor será más complicado que almacenar dos atributos en un solo objeto. – Ether

+0

Probablemente tengas razón. Mi vacilación se debe principalmente a la resistencia del nivel de reacción visceral a simplemente recrear la compleja estructura de la tabla en forma de Moose. – RickF

Cuestiones relacionadas