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!
Questa è una delle cose per cui è stato scritto il modulo. – perigrin
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