Как я могу получить доступ к метаклассу модуля, к которому применяется моя роль Moose? - PullRequest
7 голосов
/ 18 ноября 2009

Я использую Moose role , чтобы применить некоторое поведение оболочки к некоторым методам доступа в классе. Я хочу применить эту роль к ряду модулей, каждый из которых имеет свой набор атрибутов, чьи методы доступа я хочу обернуть. Есть ли способ доступа к метаклассу применяемого модуля изнутри роли? то есть что-то вроде этого:

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;

1 Ответ

9 голосов
/ 19 ноября 2009

Похоже, что MooseX :: Role :: Parameterized добьется цели:

Обычные роли могут требовать, чтобы у их потребителей был определенный список имен методов. Поскольку параметризованные роли имеют прямой доступ к своему потребителю, вы можете проверить его и выдать ошибки, если потребитель не соответствует вашим потребностям. (ссылка)

Детали специализации ролей хранятся в классе, который расширяется; ему даже не нужно передавать какие-либо параметры, все, что ему нужно знать, это то, какие параметры (список полей для переноса) передать роли. Единственный ключ заключается в том, что роль должна использоваться после соответствующие атрибуты были определены в классе.

Следовательно, потребляемый класс и роль определяются следующим образом:

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;

Добавление : я обнаружил, что если параметризованная роль потребляет другую параметризованную роль, то $target_meta во вложенной роли фактически будет мета-классом родительской роли (isa MooseX::Role::Parameterized::Meta::Role::Parameterized), а не мета-класс потребляющего класса (isa Moose::Meta::Class). Чтобы получить правильный метакласс, вам необходимо явно передать его в качестве параметра. Я добавил это ко всем моим параметризованным ролям в качестве шаблона «передового опыта»:

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;

Приложение 2 : Я также обнаружил, что если роль применяется к экземпляру объекта , а не к классу, то $target_meta в роли фактически будет Класс объекта, потребляющий:

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

Следовательно, этот код необходим при извлечении метакласса в начале параметризованной роли:

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!
...