English 中文(简体)
How can I access the meta class of the module my Moose role is being applied to?
原标题:

I m using Moose roles to apply some wrapper behaviour around some accessor methods in a class. I want to apply this role to a number of modules, each of which have a different set of attributes whose accessors I want to wrap. Is there a way to access the meta class of the module being applied to, from within the 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;
最佳回答

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

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

The details of the role specialization is kept from the class being augmented; it doesn t even need to pass any parameters all it needs to know is what parameters (the list of fields to wrap) to pass to the role. The only key is that the role must be used after the relevant attributes have been defined on the class.

Therefore, the consumed class and the role become defined like so:

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: I have discovered that if a parameterized role consumes another parameterized role, then $target_meta in the nested role will actually be the meta-class of the parent role (isa MooseX::Role::Parameterized::Meta::Role::Parameterized), rather than the meta-class of the consuming class (isa Moose::Meta::Class). In order for the proper meta-class to be derived, you need to explicitly pass it as a parameter. I have added this to all my 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;

Addendum 2: I have further discovered that if the role is being applied to an object instance, as opposed to a class, then $target_meta in the role will actually be the class of the object doing the consuming:

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 necessary when extracting the meta-class at the start of the 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!
问题回答

暂无回答




相关问题
Why does my chdir to a filehandle not work in Perl?

When I try a "chdir" with a filehandle as argument, "chdir" returns 0 and a pwd returns still the same directory. Should that be so? I tried this, because in the documentation to chdir I found: "...

How do I use GetOptions to get the default argument?

I ve read the doc for GetOptions but I can t seem to find what I need... (maybe I am blind) What I want to do is to parse command line like this myperlscript.pl -mode [sth] [inputfile] I can use ...

Object-Oriented Perl constructor syntax and named parameters

I m a little confused about what is going on in Perl constructors. I found these two examples perldoc perlbot. package Foo; #In Perl, the constructor is just a subroutine called new. sub new { #I ...

Where can I find object-oriented Perl tutorials? [closed]

A Google search yields a number of results - but which ones are the best? The Perl site appears to contain two - perlboot and perltoot. I m reading these now, but what else is out there? Note: I ve ...

热门标签