2009-11-18 3 views
7

Estoy usando Mooseroles para aplicar algún comportamiento de envoltura alrededor de algunos métodos de acceso en una clase. Quiero aplicar este rol a una serie de módulos, cada uno de los cuales tiene un conjunto diferente de atributos cuyos accesadores quiero ajustar. ¿Hay alguna forma de acceder a la metaclase del módulo al que se está aplicando desde dentro del rol? es decir, algo como esto:¿Cómo puedo acceder a la metaclase del módulo al que se está aplicando mi función de Moose?

package My::Foo; 
use Moose; 
with 'My::Role::X'; 

has [ qw(attr1 attr2) ] => (
    is => 'rw', # ... 
); 

has 'fields' => (
    is => 'bare', isa => 'ArrayRef[Str]', 
    default => sub { [qw(attr1 attr2) ] }, 
); 
1; 

package My::Role::X; 
use Moose::Role; 

# this should be a Moose::Meta::Class object 
my $target_meta = '????'; 

# get Class::MOP::Attribute object out of the metaclass 
my $fields_attr = $target_meta->find_attribute_by_name('fields'); 

# extract the value of this attribute - should be a coderef 
my $fields_to_modify = $fields_attr->default; 

# evaluate the coderef to get the arrayref 
$fields_to_modify = &$fields_to_modify if ref $fields_to_modify eq 'CODE'; 

around $_ => sub { 
    # ... 
} for @$fields_to_modify; 
1; 

Respuesta

8

Parece que MooseX::Role::Parameterized hará el truco:

papeles ordinarios pueden requerir que sus consumidores tienen una lista particular de los nombres de método. Como los roles parametrizados tienen acceso directo a su consumidor, puede inspeccionarlo y lanzar errores si el consumidor no satisface sus necesidades. (link)

Los detalles de la especialización del rol se mantienen de la clase que se va a aumentar; ni siquiera necesita pasar ningún parámetro, todo lo que necesita saber es qué parámetros (la lista de campos para envolver) para pasar al rol. La única clave es que el rol se debe usar después de los atributos relevantes se han definido en la clase.

Por lo tanto, la clase consumida y el papel convertido definen así:

package My::Foo; 
use Moose; 

my @fields = qw(attr1 attr2); 

has \@fields => (
    is => 'rw', # ... 
); 

has 'fields' => (
    is => 'bare', isa => 'ArrayRef[Str]', 
    default => sub { \@fields }, 
); 

with 'My::Role::X' => {}; 

1; 

package My::Role::X; 
use MooseX::Role::Parameterized; 

role { 
    my $p = shift; 

    my %args = @_; 

    # this should be a Moose::Meta::Class object 
    my $target_meta = $args{consumer}; 

    # get Class::MOP::Attribute object out of the metaclass 
    my $fields_attr = $target_meta->find_attribute_by_name('fields'); 

    # extract the value of this attribute - should be a coderef 
    my $fields_to_modify = $fields_attr->default; 

    # evaluate the coderef to get the arrayref 
    $fields_to_modify = &$fields_to_modify if ref $fields_to_modify eq 'CODE'; 

    around $_ => sub { 
     # ... 
    } for @$fields_to_modify; 
}; 

1; 

Adición: He descubierto que si una función parametrizada consume otra función parametrizada, entonces $target_meta en el rol anidado en realidad será la metaclase del rol principal (isa MooseX::Role::Parameterized::Meta::Role::Parameterized), en lugar de la metaclase de la clase consumidora (isa Moose::Meta::Class). Para que se derive la metaclase adecuada, debe pasarla explícitamente como parámetro. He añadido a todos mis papeles con parámetros como la "mejor práctica" plantilla:

package MyApp::Role::SomeRole; 

use MooseX::Role::Parameterized; 

# because we are used by an earlier role, meta is not actually the meta of the 
# consumer, but of the higher-level parameterized role. 
parameter metaclass => (
    is => 'ro', isa => 'Moose::Meta::Class', 
    required => 1, 
); 

# ... other parameters here... 

role { 
    my $params = shift; 
    my %args = @_; 

    # isa a Moose::Meta::Class 
    my $meta = $params->metaclass; 

    # class name of what is consuming us, om nom nom 
    my $consumer = $meta->name; 

    # ... code here... 

}; # end role 
no Moose::Role; 
1; 

Addendum 2: He descubierto además que si el papel está siendo aplicada a una instancia objeto, en lugar de una clase, entonces $target_meta en el papel de hecho será la clase del objeto haciendo el consumo:

package main; 
use My::Foo; 
use Moose::Util; 

my $foo = My::Foo->new; 
Moose::Util::apply_all_roles($foo, MyApp::Role::SomeRole, { parameter => 'value' }); 

package MyApp::Role::SomeRole; 
use MooseX::Role::Parameterized; 
# ... use same code as above (in addendum 1): 

role { 
    my $meta = $args{consumer}; 
    my $consumer = $meta->name;  # fail! My::Foo does not implement the 'name' method 

por lo tanto, este código es necesario cuando la extracción de la metaclase al inicio de la función parametrizada:

role { 
    my $params = shift; 
    my %args = @_; 

    # could be a Moose::Meta::Class, or the object consuming us 
    my $meta = $args{consumer}; 
    $meta = $meta->meta if not $meta->isa('Moose::Meta::Class'); # <-- important! 
+0

Esta es una de las cosas para las que se escribió el módulo. – perigrin

+2

Nota: ya no considero que lo anterior sea una "mejor práctica", y de hecho he refactorizado todo este (ab) uso de MXRP. En mi humilde opinión, si necesita acceder a '$ meta' desde una función, tiene algo apestoso en su diseño. – Ether

Cuestiones relacionadas