2009-11-18 13 views
7

Sto utilizzando Mooseroles per applicare un comportamento del wrapper su alcuni metodi di accesso in una classe. Voglio applicare questo ruolo a un numero di moduli, ognuno dei quali ha un diverso insieme di attributi i cui accessor si desidera avvolgere. Esiste un modo per accedere alla meta classe del modulo applicato, all'interno del ruolo? vale a dire una cosa del genere:Come posso accedere alla meta classe del modulo al quale viene applicato il mio ruolo 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; 

risposta

8

Sembra MooseX::Role::Parameterized farà il trucco:

ruoli ordinari possono richiedere che i suoi consumatori hanno una particolare lista dei nomi dei metodi. Poiché i ruoli parametrizzati hanno accesso diretto al proprio consumatore, è possibile ispezionarlo e generare errori se il consumatore non soddisfa le proprie esigenze. (link)

I dettagli della specializzazione del ruolo vengono mantenuti dalla classe che viene aumentata; non ha nemmeno bisogno di passare alcun parametro tutto ciò che serve sapere quali sono i parametri (l'elenco dei campi da avvolgere) da passare al ruolo. L'unica chiave è che il ruolo deve essere utilizzato dopo gli attributi rilevanti sono stati definiti sulla classe.

Pertanto, la classe consumato e il ruolo diventano definiti in questo modo:

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; 

Addendum: ho scoperto che se un ruolo parametrizzato consuma un altro ruolo con parametri, quindi $target_meta nel ruolo nidificato sarà in realtà la meta-classe del ruolo padre (isa MooseX::Role::Parameterized::Meta::Role::Parameterized), piuttosto che la meta-classe della classe di consumo (isa Moose::Meta::Class). Per poter derivare la corretta meta-classe, è necessario passarla esplicitamente come parametro. Ho aggiunto questo a tutti i miei ruoli parametrizzate come una "best practice" Template:

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: Ho inoltre scoperto che se il ruolo viene applicato a un'istanza oggetto, in contrapposizione a una classe, poi $target_meta nel ruolo sarà effettivamente la classe dell'oggetto facendo il 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 

Pertanto, questo codice è necessario quando l'estrazione del meta-classe all'inizio del ruolo parametrizzato:

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

Questa è una delle cose per cui è stato scritto il modulo. – perigrin

+2

Nota: non considero più la precedente "best practice", e in effetti ho rifattorizzato tutto questo (ab) uso di MXRP. IMHO se hai bisogno di accedere a '$ meta' da un ruolo, hai qualcosa di puzzolente nel tuo progetto. – Ether

Problemi correlati