package Catalyst::Controller::ActionRole; our $VERSION = '0.07'; # ABSTRACT: Apply roles to action instances use Moose; use Class::MOP; use Catalyst::Utils; use Moose::Meta::Class; use String::RewritePrefix; use MooseX::Types::Moose qw/ArrayRef Str RoleName/; use List::Util qw(first); use namespace::clean -except => 'meta'; extends 'Catalyst::Controller'; __PACKAGE__->mk_classdata(qw/_action_role_prefix/); __PACKAGE__->_action_role_prefix([ 'Catalyst::Action::Role::' ]); has _action_role_args => ( is => 'ro', isa => ArrayRef[Str], init_arg => 'action_roles', auto_deref => 1, ); has _action_roles => ( is => 'ro', isa => ArrayRef[RoleName], init_arg => undef, lazy_build => 1, auto_deref => 1, ); sub _build__action_roles { my $self = shift; my @roles = $self->_expand_role_shortname($self->_action_role_args); Class::MOP::load_class($_) for @roles; return \@roles; } around new => sub { my $next = shift; my $self = $next->(@_); # force this to run at object creation time $self->_action_roles; return $self; }; sub create_action { my ($self, %args) = @_; my $class = exists $args{attributes}->{ActionClass} ? $args{attributes}->{ActionClass}->[0] : $self->_action_class; Class::MOP::load_class($class); my @roles = ($self->_action_roles, @{ $args{attributes}->{Does} || [] }); if (@roles) { Class::MOP::load_class($_) for @roles; my $meta = Moose::Meta::Class->initialize($class)->create_anon_class( superclasses => [$class], roles => \@roles, cache => 1, ); $meta->add_method(meta => sub { $meta }); $class = $meta->name; } return $class->new(\%args); } sub _expand_role_shortname { my ($self, @shortnames) = @_; my $app = Catalyst::Utils::class2appclass(blessed($self) || $self); my @prefixes = (qq{${app}::Action::Role::}, @{$self->_action_role_prefix}); return String::RewritePrefix->rewrite( { '' => sub { my $loaded = Class::MOP::load_first_existing_class( map { "$_$_[0]" } @prefixes ); return first { $loaded =~ /^$_/ } sort { length $b <=> length $a } @prefixes; }, '~' => $prefixes[0], '+' => '' }, @shortnames, ); } sub _parse_Does_attr { my ($self, $app, $name, $value) = @_; return Does => $self->_expand_role_shortname($value); } 1; __END__ =head1 NAME Catalyst::Controller::ActionRole - Apply roles to action instances =head1 VERSION version 0.07 =head1 SYNOPSIS package MyApp::Controller::Foo; use parent qw/Catalyst::Controller::ActionRole/; sub bar : Local Does('Moo') { ... } =head1 DESCRIPTION This module allows to apply roles to the Cs for different controller methods. For that a C attribute is provided. That attribute takes an argument, that determines the role, which is going to be applied. If that argument is prefixed with C<+>, it is assumed to be the full name of the role. If it's prefixed with C<~>, the name of your application followed by C<::Action::Role::> is prepended. If it isn't prefixed with C<+> or C<~>, the role name will be searched for in C<@INC> according to the rules for L. Additionally it's possible to to apply roles to B actions of a controller without specifying the C keyword in every action definition: package MyApp::Controller::Bar use parent qw/Catalyst::Controller::ActionRole/; __PACKAGE__->config( action_roles => ['Foo', '~Bar'], ); # has Catalyst::Action::Role::Foo and MyApp::Action::Role::Bar applied # if MyApp::Action::Role::Foo exists and is loadable, it will take # precedence over Catalyst::Action::Role::Foo sub moo : Local { ... } =head1 ATTRIBUTES =head2 _action_role_prefix This class attribute stores an array reference of role prefixes to search for role names in if they aren't prefixed with C<+> or C<~>. It defaults to C<[ 'Catalyst::Action::Role::' ]>. See L. =head2 _action_roles This attribute stores an array reference of role names that will be applied to every action of this controller. It can be set by passing a C argument to the constructor. The same expansions as for C will be performed. =head1 AUTHORS Florian Ragwitz Hans Dieter Pearcey =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2009 by Florian Ragwitz. This is free software; you can redistribute it and/or modify it under the same terms as perl itself. =head1 ROLE PREFIX SEARCHING Roles specified with no prefix are looked up under a set of role prefixes. The first prefix is always C (with C replaced as appropriate for your application); the following prefixes are taken from the C<_action_role_prefix> attribute.