2009-11-18 5 views
7

J'utilise Mooseroles pour appliquer un comportement d'encapsulation autour de certaines méthodes d'accès dans une classe. Je souhaite appliquer ce rôle à un certain nombre de modules, chacun d'entre eux ayant un ensemble d'attributs différent dont je souhaite boucler les accesseurs. Existe-t-il un moyen d'accéder à la méta-classe du module appliqué, à partir du rôle? à-dire quelque chose comme ceci:Comment puis-je accéder à la méta-classe du module auquel mon rôle Moose est appliqué?

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; 

Répondre

8

Il ressemble à MooseX::Role::Parameterized fera l'affaire:

Les rôles ordinaires peuvent exiger que ses consommateurs ont une liste particulière des noms de méthode. Puisque les rôles paramétrés ont un accès direct à son consommateur, vous pouvez l'inspecter et y jeter des erreurs si le consommateur ne répond pas à vos besoins. (link)

Les détails de la spécialisation de rôle sont conservés de la classe en cours d'augmentation; il n'a même pas besoin de passer des paramètres tout ce dont il a besoin de savoir, c'est ce que les paramètres (la liste des champs à emballer) à transmettre au rôle. La seule clé est que le rôle doit être utilisé après les attributs pertinents ont été définis sur la classe.

Par conséquent, la consommation de classe et le rôle se définit comme ceci:

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: Je l'ai découvert que si un rôle paramétrés consomme un autre rôle paramétrées, puis $target_meta dans le rôle imbriqué sera en fait la méta-classe du rôle parent (isa MooseX::Role::Parameterized::Meta::Role::Parameterized), plutôt que la méta-classe de la classe consommatrice (isa Moose::Meta::Class). Pour que la méta-classe appropriée soit dérivée, vous devez la passer explicitement en paramètre. Je l'ai ajouté à tous mes rôles paramétrés comme modèle de « meilleures pratiques »:

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: Je l'ai découvert en outre que si le rôle est appliqué à une instance d'objet , par opposition à une classe, puis $target_meta dans le rôle sera effectivement la classe de l'objet à faire la consommatrice:

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 

par conséquent, ce code est nécessaire lors de l'extraction du méta-classe au début du rôle paramétré:

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

Ceci est l'une des choses pour lesquelles le module a été écrit. – perigrin

+2

Note: Je ne considère plus ce qui précède comme une "meilleure pratique", et j'ai en effet refacturé toute cette utilisation (ab) de MXRP. À mon humble avis si vous avez besoin d'accéder à $ meta à partir d'un rôle, vous avez quelque chose de puant dans votre conception. – Ether

Questions connexes