2010-12-13 11 views
5

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::Fieldsize 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] 

Respuesta

3

Es cada persona va a tener diferentes requisitos para el campo name? Esto parece poco probable.

Parece más probable que tenga un conjunto de parámetros para cada Field en la aplicación. Así que defina un tipo PersonName como un subtipo de Field. Su coerción sería de cadena a Nombre de persona. Luego, el código de coerción y puede aplicar los valores apropiados a required y length cuando llame al Field->new().

Además, esto realmente parece que está creando un objeto de atributo para un objeto Moose, que se basa en un sistema de metaobjetos que ya proporciona objetos de atributo. ¿Por qué no extiendes tu objeto de atributo en lugar de crear el tuyo propio?

Consulte el Moose Cookbook Meta Recipes para obtener más información sobre este enfoque.

+1

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

+0

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

+0

He actualizado mi respuesta con una solución propuesta que utiliza su sugerencia de subtipo. Gracias por su consejo. – Mike