Esto sigue en mi previous question sobre tipos estructurados Moose. Me disculpo por la duración de la pregunta. Quería asegurarme de haber incluido todos los detalles necesarios.Moose coerción y constructores
MyApp::Type::Field
define un tipo estructurado. Utilizo la coerción para permitir que su atributo value
se establezca más fácilmente desde mi clase Person
(ver ejemplo a continuación). Tenga en cuenta que en mi aplicación real, donde el tipo de campo se usa para algo más que el nombre de una persona, también coacciono desde un HashRef.
que también tienen que establecer el MyApp::Type::Field
size
y required
atributos de sólo lectura de MyApp::Person
en tiempo de compilación. Puedo hacer esto usando un método de compilación, pero no se llama si se usa coerción, ya que mi coerción crea un nuevo objeto directamente, sin usar el método de compilación.
Puedo evitar esto agregando un modificador de método around
a MyApp::Person
(ver ejemplo a continuación), pero esto se siente complicado. El modificador de método around
se llama con frecuencia, pero solo necesito establecer los atributos de solo lectura una vez.
¿Hay una manera mejor de hacer esto, mientras se permite la coacción? La clase MyApp::Type::Field
no puede inicializar size
y required
mediante valores predeterminados o constructores, ya que no tiene forma de saber cuáles deberían ser los valores.
Puede ser simplemente el caso que renuncie a la coerción a favor de no tener el modificador around
.
MyApp::Type::Field
coerce 'MyApp::Type::Field'
=> from 'Str'
=> via { MyApp::Type::Field->new(value => $_) };
has 'value' => (is => 'rw');
has 'size' => (is => 'ro', isa => 'Int', writer => '_set_size', predicate => 'has_size');
has 'required' => (is => 'ro', isa => 'Bool', writer => '_set_required', predicate => 'has_required');
MyApp::Person
has name => (is => 'rw', isa => 'MyApp::Type::Field', lazy => 1, builder => '_build_name', coerce => 1);
sub _build_name {
print "Building name\n";
return MyApp::Type::Field->new(size => 255, required => 1);
}
MyApp::Test
print "Create new person with coercion\n";
my $person = MyApp::Person->new();
print "Set name\n";
$person->name('Joe Bloggs');
print "Name set\n";
printf ("Name: %s [%d][%d]\n\n", $person->name->value, $person->name->size, $person->name->required);
print "Create new person without coercion\n";
$person = MyApp::Person->new();
print "Set name\n";
$person->name->value('Joe Bloggs');
print "Name set\n";
printf ("Name: %s [%d][%d]\n\n", $person->name->value, $person->name->size, $person->name->required);
Lienzo:
Create new person with coercion
Set name
Name set
Name: Joe Bloggs [0][0]
Create new person without coercion
Set name
Building name
Name set
Name: Joe Bloggs [255][2]
Añadir un modificador around
método para MyApp::Person
, y cambiar el constructor para que no establece size
y required
:
around 'name' => sub {
my $orig = shift;
my $self = shift;
print "Around name\n";
unless ($self->$orig->has_size) {
print "Setting size\n";
$self->$orig->_set_size(255);
};
unless ($self->$orig->has_required) {
print "Setting required\n";
$self->$orig->_set_required(1);
};
$self->$orig(@_);
};
sub _build_name {
print "Building name\n";
return MyApp::Type::Field->new();
}
cuando se ejecuta MyApp::Test
, size
y son required
establecer dos veces.
Create new person with coercion
Set name
Around name
Building name
Setting size
Setting required
Name set
Around name
Setting size
Setting required
Around name
Around name
Name: Joe Bloggs [255][3]
Create new person without coercion
Set name
Around name
Building name
Name set
Around name
Around name
Around name
Name: Joe Bloggs [255][4]
Solución propuesta
daotoad's sugerencia de crear un subtipo para cada atributo MyApp::Person
y coaccionar a esa subtipo de un Str
en un MyApp::Type::Field
funciona bastante bien. Incluso puedo crear múltiples subtipos, coerciones y atributos envolviendo todo en un bucle for. Esto es muy útil para crear atributos múltiples con propiedades similares.
En el ejemplo siguiente, he configurado la delegación usando handles
, de modo que $person->get_first_name
se traduce a $person->first_name->value
. La adición de un escritor da proporciona un colocador equivalente, haciendo que la interfaz de la clase bastante limpio:
package MyApp::Type::Field;
use Moose;
has 'value' => (
is => 'rw',
);
has 'size' => (
is => 'ro',
isa => 'Int',
writer => '_set_size',
);
has 'required' => (
is => 'ro',
isa => 'Bool',
writer => '_set_required',
);
__PACKAGE__->meta->make_immutable;
1;
package MyApp::Person;
use Moose;
use Moose::Util::TypeConstraints;
use namespace::autoclean;
{
my $attrs = {
title => { size => 5, required => 0 },
first_name => { size => 45, required => 1 },
last_name => { size => 45, required => 1 },
};
foreach my $attr (keys %{$attrs}) {
my $subtype = 'MyApp::Person::' . ucfirst $attr;
subtype $subtype => as 'MyApp::Type::Field';
coerce $subtype
=> from 'Str'
=> via { MyApp::Type::Field->new(
value => $_,
size => $attrs->{$attr}{'size'},
required => $attrs->{$attr}{'required'},
) };
has $attr => (
is => 'rw',
isa => $subtype,
coerce => 1,
writer => "set_$attr",
handles => { "get_$attr" => 'value' },
default => sub {
MyApp::Type::Field->new(
size => $attrs->{$attr}{'size'},
required => $attrs->{$attr}{'required'},
)
},
);
}
}
__PACKAGE__->meta->make_immutable;
1;
package MyApp::Test;
sub print_person {
my $person = shift;
printf "Title: %s [%d][%d]\n" .
"First name: %s [%d][%d]\n" .
"Last name: %s [%d][%d]\n",
$person->title->value || '[undef]',
$person->title->size,
$person->title->required,
$person->get_first_name || '[undef]',
$person->first_name->size,
$person->first_name->required,
$person->get_last_name || '[undef]',
$person->last_name->size,
$person->last_name->required;
}
my $person;
$person = MyApp::Person->new(
title => 'Mr',
first_name => 'Joe',
last_name => 'Bloggs',
);
print_person($person);
$person = MyApp::Person->new();
$person->set_first_name('Joe');
$person->set_last_name('Bloggs');
print_person($person);
1;
Lienzo:
Title: Mr [5][0]
First name: Joe [45][6]
Last name: Bloggs [45][7]
Title: [undef] [5][0]
First name: Joe [45][8]
Last name: Bloggs [45][9]
El campo es más como un MooseX :: Types :: Structured que un atributo con meta-atributos. Un ejemplo de uso es un formulario web donde cada campo necesita un valor, una longitud máxima (tamaño) y un indicador requerido. El modelo (la clase Person, en este ejemplo), establece el tamaño y el indicador requerido. 'Campo', por lo tanto, debe ser bastante genérico, mientras que la clase' Persona' es más específica. Miré meta-atributos antes, pero son un poco incómodos de acceder ('$ persona-> meta-> get_attribute ('nombre') -> tamaño()'), por ejemplo. Un subtipo puede ser una opción. Examinaré esto ... – Mike
Acabo de experimentar con la creación de un subtipo, y creo que podría proporcionar una buena solución. Haré algunas pruebas más mañana ... Gracias. – Mike
He actualizado mi respuesta con una solución propuesta que utiliza su sugerencia de subtipo. Gracias por su consejo. – Mike