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!
Esta es una de las cosas para las que se escribió el módulo. – perigrin
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