X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FCatalyst%2FController.pm;h=8cb6d6cfffbd195b9d971ac7a3da9e768d2a08a1;hb=6329bfe593d5ea3891011333f7d7984b4e5e7910;hp=9f52b3fffb75b9e565c5922fb77766e67ac36f99;hpb=4d4e5de8d391ea3351093be5a1bed02b3d29c32c;p=catagits%2FCatalyst-Runtime.git
diff --git a/lib/Catalyst/Controller.pm b/lib/Catalyst/Controller.pm
index 9f52b3f..8cb6d6c 100644
--- a/lib/Catalyst/Controller.pm
+++ b/lib/Catalyst/Controller.pm
@@ -1,7 +1,11 @@
package Catalyst::Controller;
use Moose;
+use Class::MOP;
+use Class::Load ':all';
+use String::RewritePrefix;
use Moose::Util qw/find_meta/;
+use List::Util qw/first/;
use List::MoreUtils qw/uniq/;
use namespace::clean -except => 'meta';
@@ -13,28 +17,57 @@ use Catalyst::Utils;
with 'Catalyst::Component::ApplicationAttribute';
-has path_prefix =>
- (
- is => 'rw',
- isa => 'Str',
- init_arg => 'path',
- predicate => 'has_path_prefix',
- );
-
-has action_namespace =>
- (
- is => 'rw',
- isa => 'Str',
- init_arg => 'namespace',
- predicate => 'has_action_namespace',
- );
-
-has actions =>
- (
- accessor => '_controller_actions',
- isa => 'HashRef',
- init_arg => undef,
- );
+has path_prefix => (
+ is => 'rw',
+ isa => 'Str',
+ init_arg => 'path',
+ predicate => 'has_path_prefix',
+);
+
+has action_namespace => (
+ is => 'rw',
+ isa => 'Str',
+ init_arg => 'namespace',
+ predicate => 'has_action_namespace',
+);
+
+has actions => (
+ accessor => '_controller_actions',
+ isa => 'HashRef',
+ init_arg => undef,
+);
+
+has _action_role_args => (
+ traits => [qw(Array)],
+ isa => 'ArrayRef[Str]',
+ init_arg => 'action_roles',
+ default => sub { [] },
+ handles => {
+ _action_role_args => 'elements',
+ },
+);
+
+has _action_roles => (
+ traits => [qw(Array)],
+ isa => 'ArrayRef[RoleName]',
+ init_arg => undef,
+ lazy => 1,
+ builder => '_build__action_roles',
+ handles => {
+ _action_roles => 'elements',
+ },
+);
+
+has action_args => (is => 'ro');
+
+# ->config(actions => { '*' => ...
+has _all_actions_attributes => (
+ is => 'ro',
+ isa => 'HashRef',
+ init_arg => undef,
+ lazy => 1,
+ builder => '_build__all_actions_attributes',
+);
sub BUILD {
my ($self, $args) = @_;
@@ -42,9 +75,23 @@ sub BUILD {
my $actions = delete $args->{actions} || {};
my $attr_value = $self->merge_config_hashes($actions, $action);
$self->_controller_actions($attr_value);
+
+ # trigger lazy builder
+ $self->_all_actions_attributes;
+ $self->_action_roles;
}
+sub _build__action_roles {
+ my $self = shift;
+ my @roles = $self->_expand_role_shortname($self->_action_role_args);
+ load_class($_) for @roles;
+ return \@roles;
+}
+sub _build__all_actions_attributes {
+ my ($self) = @_;
+ delete $self->_controller_actions->{'*'} || {};
+}
=head1 NAME
@@ -71,10 +118,11 @@ for more info about how Catalyst dispatches to actions.
#I think both of these could be attributes. doesn't really seem like they need
#to ble class data. i think that attributes +default would work just fine
-__PACKAGE__->mk_classdata($_) for qw/_dispatch_steps _action_class/;
+__PACKAGE__->mk_classdata($_) for qw/_dispatch_steps _action_class _action_role_prefix/;
__PACKAGE__->_dispatch_steps( [qw/_BEGIN _AUTO _ACTION/] );
__PACKAGE__->_action_class('Catalyst::Action');
+__PACKAGE__->_action_role_prefix([ 'Catalyst::ActionRole::' ]);
sub _DISPATCH : Private {
@@ -139,6 +187,12 @@ around action_namespace => sub {
my $class = ref($self) || $self;
my $appclass = ref($c) || $c;
+
+ # FIXME - catalyst_component_name is no longer a class accessor, because
+ # 'MyApp as a controller' behavior is removed. But is this call to
+ # catalyst_component_name necessary, or is it always the same as $class?
+ my $component_name = ref($self) ? $self->catalyst_component_name : $self;
+
if( ref($self) ){
return $self->$orig if $self->has_action_namespace;
} else {
@@ -160,7 +214,7 @@ around action_namespace => sub {
}
}
- my $namespace = Catalyst::Utils::class2prefix($self->catalyst_component_name, $case_s) || '';
+ my $namespace = Catalyst::Utils::class2prefix($component_name, $case_s) || '';
$self->$orig($namespace) if ref($self);
return $namespace;
};
@@ -182,11 +236,10 @@ around path_prefix => sub {
sub get_action_methods {
my $self = shift;
my $meta = find_meta($self) || confess("No metaclass setup for $self");
- confess("Metaclass "
- . ref($meta) . " for "
- . $meta->name
- . " cannot support register_actions." )
- unless $meta->can('get_nearest_methods_with_attributes');
+ confess(
+ sprintf "Metaclass %s for %s cannot support register_actions.",
+ ref $meta, $meta->name,
+ ) unless $meta->can('get_nearest_methods_with_attributes');
my @methods = $meta->get_nearest_methods_with_attributes;
# actions specified via config are also action_methods
@@ -194,11 +247,9 @@ sub get_action_methods {
@methods,
map {
$meta->find_method_by_name($_)
- || confess( 'Action "'
- . $_
- . '" is not available from controller '
- . ( ref $self ) )
- } keys %{ $self->_controller_actions }
+ || confess( sprintf 'Action "%s" is not available from controller %s',
+ $_, ref $self )
+ } keys %{ $self->_controller_actions }
) if ( ref $self );
return uniq @methods;
}
@@ -225,7 +276,9 @@ sub register_action_methods {
foreach my $method (@methods) {
my $name = $method->name;
- my $attributes = $method->attributes;
+ # Horrible hack! All method metaclasses should have an attributes
+ # method, core Moose bug - see r13354.
+ my $attributes = $method->can('attributes') ? $method->attributes : [];
my $attrs = $self->_parse_attrs( $c, $name, @{ $attributes } );
if ( $attrs->{Private} && ( keys %$attrs > 1 ) ) {
$c->log->debug( 'Bad action definition "'
@@ -248,16 +301,53 @@ sub register_action_methods {
}
}
-sub create_action {
+sub _apply_action_class_roles {
+ my ($self, $class, @roles) = @_;
+
+ 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 });
+
+ return $meta->name;
+}
+
+sub action_class {
my $self = shift;
my %args = @_;
my $class = (exists $args{attributes}{ActionClass}
- ? $args{attributes}{ActionClass}[0]
- : $self->_action_class);
+ ? $args{attributes}{ActionClass}[0]
+ : $self->_action_class);
+
Class::MOP::load_class($class);
+ return $class;
+}
+
+sub create_action {
+ my $self = shift;
+ my %args = @_;
+
+ my $class = $self->action_class(%args);
+
+ load_class($class);
+ Moose->init_meta(for_class => $class)
+ unless Class::MOP::does_metaclass_exist($class);
+
+ unless ($args{name} =~ /^_(DISPATCH|BEGIN|AUTO|ACTION|END)$/) {
+ my @roles = $self->gather_action_roles(%args);
+ $class = $self->_apply_action_class_roles($class, @roles) if @roles;
+ }
+
+ my $action_args = (
+ ref($self)
+ ? $self->action_args
+ : $self->config->{action_args}
+ );
- my $action_args = $self->config->{action_args};
my %extra_args = (
%{ $action_args->{'*'} || {} },
%{ $action_args->{ $args{name} } || {} },
@@ -266,6 +356,15 @@ sub create_action {
return $class->new({ %extra_args, %args });
}
+sub gather_action_roles {
+ my ($self, %args) = @_;
+
+ return (
+ (blessed $self ? $self->_action_roles : ()),
+ @{ $args{attributes}->{Does} || [] },
+ );
+}
+
sub _parse_attrs {
my ( $self, $c, $name, @attrs ) = @_;
@@ -285,23 +384,32 @@ sub _parse_attrs {
}
}
- #I know that the original behavior was to ignore action if actions was set
- # but i actually think this may be a little more sane? we can always remove
- # the merge behavior quite easily and go back to having actions have
- # presedence over action by modifying the keys. i honestly think this is
- # superior while mantaining really high degree of compat
- my $actions;
+ my ($actions_config, $all_actions_config);
if( ref($self) ) {
- $actions = $self->_controller_actions;
+ $actions_config = $self->_controller_actions;
+ # No, you're not getting actions => { '*' => ... } with actions in MyApp.
+ $all_actions_config = $self->_all_actions_attributes;
} else {
my $cfg = $self->config;
- $actions = $self->merge_config_hashes($cfg->{actions}, $cfg->{action});
+ $actions_config = $self->merge_config_hashes($cfg->{actions}, $cfg->{action});
+ $all_actions_config = {};
}
- %raw_attributes = ((exists $actions->{'*'} ? %{$actions->{'*'}} : ()),
- %raw_attributes,
- (exists $actions->{$name} ? %{$actions->{$name}} : ()));
+ %raw_attributes = (
+ %raw_attributes,
+ # Note we deep copy array refs here to stop crapping on config
+ # when attributes are parsed. RT#65463
+ exists $actions_config->{$name} ? map { ref($_) eq 'ARRAY' ? [ @$_ ] : $_ } %{ $actions_config->{$name } } : (),
+ );
+ # Private actions with additional attributes will raise a warning and then
+ # be ignored. Adding '*' arguments to the default _DISPATCH / etc. methods,
+ # which are Private, will prevent those from being registered. They should
+ # probably be turned into :Actions instead, or we might want to otherwise
+ # disambiguate between those built-in internal actions and user-level
+ # Private ones.
+ %raw_attributes = (%{ $all_actions_config }, %raw_attributes)
+ unless $raw_attributes{Private};
my %final_attributes;
@@ -422,6 +530,32 @@ sub _parse_MyAction_attr {
return ( 'ActionClass', $value );
}
+sub _parse_Does_attr {
+ my ($self, $app, $name, $value) = @_;
+ return Does => $self->_expand_role_shortname($value);
+}
+
+sub _expand_role_shortname {
+ my ($self, @shortnames) = @_;
+ my $app = $self->_application;
+
+ my $prefix = $self->can('_action_role_prefix') ? $self->_action_role_prefix : ['Catalyst::ActionRole::'];
+ my @prefixes = (qq{${app}::ActionRole::}, @$prefix);
+
+ return String::RewritePrefix->rewrite(
+ { '' => sub {
+ my $loaded = load_first_existing_class(
+ map { "$_$_[0]" } @prefixes
+ );
+ return first { $loaded =~ /^$_/ }
+ sort { length $b <=> length $a } @prefixes;
+ },
+ '~' => $prefixes[0],
+ '+' => '' },
+ @shortnames,
+ );
+}
+
__PACKAGE__->meta->make_immutable;
1;
@@ -446,11 +580,31 @@ of setting namespace to '' (the null string).
Sets 'path_prefix', as described below.
+=head2 action
+
+Allows you to set the attributes that the dispatcher creates actions out of.
+This allows you to do 'rails style routes', or override some of the
+attribute definitions of actions composed from Roles.
+You can set arguments globally (for all actions of the controller) and
+specifically (for a single action).
+
+ __PACKAGE__->config(
+ action => {
+ '*' => { Chained => 'base', Args => 0 },
+ base => { Chained => '/', PathPart => '', CaptureArgs => 0 },
+ },
+ );
+
+In the case above every sub in the package would be made into a Chain
+endpoint with a URI the same as the sub name for each sub, chained
+to the sub named C. Ergo dispatch to C would call the
+C method, then the C method.
+
=head2 action_args
Allows you to set constructor arguments on your actions. You can set arguments
-globally (for all actions of the controller) and specifically (for a single
-action). This is particularly useful when using Cs
+globally and specifically (as above).
+This is particularly useful when using Cs
(L) and custom Ces.
__PACKAGE__->config(
@@ -509,11 +663,20 @@ action methods for this package.
Creates action objects for a set of action methods using C< create_action >,
and registers them with the dispatcher.
+=head2 $self->action_class(%args)
+
+Used when a controller is creating an action to determine the correct base
+action class to use.
+
=head2 $self->create_action(%args)
Called with a hash of data to be use for construction of a new
Catalyst::Action (or appropriate sub/alternative class) object.
+=head2 $self->gather_action_roles(\%action_args)
+
+Gathers the list of roles to apply to an action with the given %action_args.
+
=head2 $self->_application
=head2 $self->_app