How can I access the meta class of the module to which my moose role applies?

I use Moose roles to apply some wrapper behavior around some accessor methods in the class. I want to apply this role to a number of modules, each of which has a different set of attributes whose accessors I want to wrap. Is there a way to access the metaclass of the module to which it is applied from within this role? i.e. something like this:

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; 
+7
perl moose roles
source share
1 answer

It looks like MooseX :: Role :: Parameterized will do the trick:

Ordinary roles may require its consumers to have a specific list of method names. Since parameterized roles have direct access to your consumer, you can check it and throw errors if the consumer does not meet your needs. (link)

Role specialization details are retained from the extended class; he doesn’t even need to pass any parameters, all he needs to know is which parameters (list of fields to wrap) go to the role. The only clue is that the role should be used after the corresponding attributes have been defined in the class.

Therefore, the consumed class and role are defined as follows:

 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; 

Addition . I found that if the parameterized role uses a different parameterized role, then $target_meta in the nested role will actually be a meta class of the parent role (isa MooseX::Role::Parameterized::Meta::Role::Parameterized ), and not a metaclass of the consuming class (isa Moose::Meta::Class ). To get the proper metaclass, you need to explicitly pass it as a parameter. I added this to all parameterized roles as a "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; 

Appendix 2 . I also found that if the role is applied to the instance of the object, and not to the class, then $target_meta in the role will actually be the class of the object making consumption:

 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 

Therefore, this code is required when retrieving a metaclass at the beginning of a parameterized role:

 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! 
+8
source share

All Articles