fixed problem with deep trait parameters
[catagits/CatalystX-Declare.git] / lib / CatalystX / Declare / Keyword / Action.pm
CommitLineData
918fb36e 1use MooseX::Declare;
c2a8165b 2use MooseX::Role::Parameterized ();
918fb36e 3
8d66ec34 4class CatalystX::Declare::Keyword::Action {
918fb36e 5
6
a1dd1788 7 use Carp qw( croak );
8 use Perl6::Junction qw( any );
9 use Data::Dump qw( pp );
10 use MooseX::Types::Util qw( has_available_type_export );
c2a8165b 11 use Moose::Util qw( add_method_modifier ensure_all_roles );
a82dd4d2 12 use Data::Pond qw(pond_write_datum);
a1dd1788 13 use Class::Inspector;
14 use Class::MOP;
15
918fb36e 16
e10b92dd 17 use constant STOP_PARSING => '__MXDECLARE_STOP_PARSING__';
9c11a562 18 use constant UNDER_VAR => '$CatalystX::Declare::SCOPE::UNDER';
fe864e80 19 use constant UNDER_STACK => '@CatalystX::Declare::SCOPE::UNDER_STACK';
e10b92dd 20
5fb5cef1 21 use aliased 'CatalystX::Declare::Action::CatchValidationError';
8d66ec34 22 use aliased 'CatalystX::Declare::Context::StringParsing';
918fb36e 23 use aliased 'MooseX::Method::Signatures::Meta::Method';
24 use aliased 'MooseX::MethodAttributes::Role::Meta::Method', 'AttributeRole';
c2a8165b 25 use aliased 'MooseX::MethodAttributes::Role::Meta::Role', 'AttributeMetaRole';
918fb36e 26
27
856ac9a7 28 method parse (Object $ctx, Str :$modifier?, Int :$skipped_declarator = 0) {
918fb36e 29
30 # somewhere to put the attributes
31 my %attributes;
32 my @populators;
918fb36e 33
34 # parse declarations
35 until (do { $ctx->skipspace; $ctx->peek_next_char } eq any qw( ; { } )) {
918fb36e 36
37 $ctx->skipspace;
38
39 # optional commas
40 if ($ctx->peek_next_char eq ',') {
41
42 my $linestr = $ctx->get_linestr;
43 substr($linestr, $ctx->offset, 1) = '';
44 $ctx->set_linestr($linestr);
45
46 next;
47 }
48
49 # next thing should be an option name
50 my $option = (
51 $skipped_declarator
52 ? $ctx->strip_name
53 : do {
54 $ctx->skip_declarator;
55 $skipped_declarator++;
56 $ctx->declarator;
57 })
58 or croak "Expected option token, not " . substr($ctx->get_linestr, $ctx->offset);
59
60 # we need to be able to handle the rest
61 my $handler = $self->can("_handle_${option}_option")
62 or croak "Unknown action option: $option";
63
64 # call the handler
e10b92dd 65 my $populator = $self->$handler($ctx, \%attributes);
66
67 if ($populator and $populator eq STOP_PARSING) {
68
69 return $ctx->shadow(sub (&) {
70 my ($body) = @_;
71 return $body->();
72 });
73 }
74
75 push @populators, $populator
76 if defined $populator;
918fb36e 77 }
78
79 croak "Need an action specification"
80 unless exists $attributes{Signature};
81
82 my $name = $attributes{Subname};
856ac9a7 83
17a275f5 84 if ($attributes{Private}) {
85 $attributes{Signature} ||= '@';
86 }
87
918fb36e 88 my $method = Method->wrap(
89 signature => qq{($attributes{Signature})},
90 package_name => $ctx->get_curstash_name,
91 name => $name,
92 );
93
a1dd1788 94 AttributeRole->meta->apply($method);
95
eb97acbb 96 my $count = $self->_count_positional_arguments($method);
97 $attributes{CaptureArgs} = $count
98 if defined $count;
99
918fb36e 100 $_->($method)
101 for @populators;
102
aae7ad1f 103 unless ($attributes{Private}) {
dd2759b0 104 $attributes{PathPart} ||= $name;
918fb36e 105
aae7ad1f 106 delete $attributes{CaptureArgs}
107 if exists $attributes{Args};
918fb36e 108
aae7ad1f 109 $attributes{CaptureArgs} = 0
110 unless exists $attributes{Args}
111 or exists $attributes{CaptureArgs};
112 }
113
114 if ($attributes{Private}) {
aae7ad1f 115 delete $attributes{ $_ }
ed4a2203 116 for qw( Args CaptureArgs Chained Signature Private );
aae7ad1f 117 }
918fb36e 118
4960c7ec 119 # inject a hashref for resolving runtime attribute values
8d66ec34 120 $self->_inject_attributes($ctx, \%attributes);
121
4960c7ec 122 # our declaration is followed by a block
918fb36e 123 if ($ctx->peek_next_char eq '{') {
124 $ctx->inject_if_block($ctx->scope_injector_call . $method->injectable_code);
125 }
4960c7ec 126
127 # there is no block, so we insert one.
918fb36e 128 else {
129 $ctx->inject_code_parts_here(
130 sprintf '{ %s%s }',
131 $ctx->scope_injector_call,
132 $method->injectable_code,
133 );
134 }
135
8d66ec34 136 my $compile_attrs = sub {
137 my $attributes = shift;;
138 my @attributes;
139
140 for my $attr (keys %$attributes) {
141 my $value = $attributes->{ $attr };
142
4960c7ec 143 # the compiletime chained attr might contain the under global var name
8d66ec34 144 next if $attr eq 'Chained' and $value eq UNDER_VAR;
145
8d66ec34 146 push @attributes,
a82dd4d2 147 map {
148 my $value = ref $_ ? pond_write_datum($_) : $_;
149 sprintf '%s%s', $attr, defined($value) ? sprintf('(%s)', $value) : ''
150 } (
151 ref($value) eq 'ARRAY'
152 ) ? @$value : $value;
8d66ec34 153 }
154
155 return \@attributes;
156 };
918fb36e 157
8d66ec34 158 return $ctx->shadow(sub {
918fb36e 159 my $class = caller;
8d66ec34 160 my $attrs = shift;
856ac9a7 161 my $body = shift;
4960c7ec 162
163 # the runtime-resolved name
dd2759b0 164 my $name = $attrs->{Subname};
918fb36e 165
4960c7ec 166 # in case no hashref was specified
8d66ec34 167 $body = $attrs and $attrs = {}
168 if ref $attrs eq 'CODE';
169
4960c7ec 170 # default path part to runtime-resolved name
dd2759b0 171 unless ($attrs->{Private}) {
172
173 $attrs->{PathPart} = $attrs->{Subname}
174 unless defined $attrs->{PathPart};
175 }
176
4960c7ec 177 # in CXD we are explicit about chained values, an undefined
178 # value means we defaulted to the outer-scope under and there
179 # was none.
8d66ec34 180 delete $attrs->{Chained}
181 unless defined $attrs->{Chained};
182
4960c7ec 183 # some attrs need to be single quoted in their stringified forms
8d66ec34 184 defined($attrs->{ $_ }) and $attrs->{ $_ } = sprintf "'%s'", $attrs->{ $_ }
185 for qw( Chained PathPart );
186
4960c7ec 187 # merge runtime and compiletime attrs
8d66ec34 188 my %full_attrs = (%attributes, %$attrs);
8d66ec34 189 my $compiled_attrs = $compile_attrs->(\%full_attrs);
24a5fc45 190 my $real_method = $method->reify(
191 actual_body => $body,
8d66ec34 192 attributes => $compiled_attrs,
dd2759b0 193 name => $name,
24a5fc45 194 );
856ac9a7 195
4960c7ec 196 # NYI
856ac9a7 197 if ($modifier) {
24a5fc45 198 add_method_modifier $class, $modifier, [$name, $real_method];
a82dd4d2 199 } else {
856ac9a7 200
c2a8165b 201 my $prepare_meta = sub {
202 my ($meta) = @_;
203
24a5fc45 204 $meta->add_method($name, $real_method);
8d66ec34 205 $meta->register_method_attributes($meta->name->can($real_method->name), $compiled_attrs);
c2a8165b 206 };
207
208 if ($ctx->stack->[-1] and $ctx->stack->[-1]->is_parameterized) {
209 my $real_meta = MooseX::Role::Parameterized->current_metaclass;
210
211 $real_meta->meta->make_mutable
212 if $real_meta->meta->is_immutable;
213 ensure_all_roles $real_meta->meta, AttributeMetaRole
214 if $real_meta->isa('Moose::Meta::Role');
215
216 $real_meta->$prepare_meta;
217 }
1754e3e7 218 else {
c2a8165b 219
1754e3e7 220 $class->meta->$prepare_meta;
221 }
856ac9a7 222 }
918fb36e 223 });
224 }
225
a1dd1788 226 method _handle_with_option (Object $ctx, HashRef $attrs) {
227
284b2994 228 my @roles_with_args = ();
229 push @roles_with_args, @{ $ctx->strip_names_and_args };
a1dd1788 230
231 # we need to fish for aliases here since we are still unclean
284b2994 232 my @roles = ();
233 for my $role_with_arg(@roles_with_args) {
d14acb87 234 my ($role, $params) = @{$role_with_arg};
235 if($params) {
236 my ($first, @rest) = eval $params;
34a0a1ff 237 my %params = ref $first eq 'HASH' ? %$first : ($first, @rest); # both (%opts) and {%opts}
d14acb87 238 for my $key (keys %params) {
a82dd4d2 239 my $value = ref $params{$key} eq 'ARRAY' ? $params{$key} : [$params{$key}];
240 push @{ $attrs->{$key} ||=[] }, @$value;
241 ##push @{ $attrs->{$key} ||=[] }, $params;
242
d14acb87 243 }
244 }
245
284b2994 246 if (defined(my $alias = $self->_check_for_available_import($ctx, $role))) {
247 $role = $alias;
248 }
249 push @roles, $role;
a1dd1788 250 }
251
284b2994 252 push @{ $attrs->{CatalystX_Declarative_ActionRoles} ||= [] }, @roles;
a1dd1788 253 return;
254 }
255
256 method _handle_isa_option (Object $ctx, HashRef $attrs) {
257
258 my $class = $ctx->strip_name
259 or croak "Expected bareword action class specification for action after isa";
260
261 if (defined(my $alias = $self->_check_for_available_import($ctx, $class))) {
262 $class = $alias;
263 }
264
265 $attrs->{CatalystX_Declarative_ActionClass} = $class;
266
267 return;
268 }
269
270 method _check_for_available_import (Object $ctx, Str $name) {
271
272 if (my $code = $ctx->get_curstash_name->can($name)) {
273 return $code->();
274 }
275
276 return undef;
277 }
278
918fb36e 279 method _handle_action_option (Object $ctx, HashRef $attrs) {
280
281 # action name
dd2759b0 282 my $name = $self->_strip_actionpath($ctx, interpolate => 1)
918fb36e 283 or croak "Anonymous actions not yet supported";
284
64baeca0 285 $ctx->skipspace;
286 my $populator;
287
4960c7ec 288 # shortcut under base option is basically handled by the under handler
64baeca0 289 if (substr($ctx->get_linestr, $ctx->offset, 2) eq '<-') {
290 my $linestr = $ctx->get_linestr;
291 substr($linestr, $ctx->offset, 2) = '';
292 $ctx->set_linestr($linestr);
293 $populator = $self->_handle_under_option($ctx, $attrs);
294 }
295
918fb36e 296 # signature
297 my $proto = $ctx->strip_proto || '';
298 $proto = join(', ', 'Object $self: Object $ctx', $proto || ());
299
300 $attrs->{Subname} = $name;
301 $attrs->{Signature} = $proto;
aae7ad1f 302 $attrs->{Action} = [];
918fb36e 303
12b853ff 304 push @{ $attrs->{CatalystX_Declarative_DefaultActionRoles} ||= [] }, CatchValidationError;
4960c7ec 305
306 # default chained base to the global under var, to be resolved at runtime
8d66ec34 307 $attrs->{Chained} ||= UNDER_VAR;
e10b92dd 308
64baeca0 309 return unless $populator;
310 return $populator;
918fb36e 311 }
312
2dde75e7 313 method _handle_final_option (Object $ctx, HashRef $attrs) {
314
315 return $self->_build_flag_populator($ctx, $attrs, 'final');
316 }
317
918fb36e 318 method _handle_is_option (Object $ctx, HashRef $attrs) {
319
320 my $what = $ctx->strip_name
321 or croak "Expected symbol token after is symbol, not " . substr($ctx->get_linestr, $ctx->offset);
322
2dde75e7 323 return $self->_build_flag_populator($ctx, $attrs, $what);
324 }
325
326 method _build_flag_populator (Object $ctx, HashRef $attrs, Str $what) {
327
17a275f5 328 $attrs->{Private} = []
329 if $what eq 'private';
330
918fb36e 331 return sub {
332 my $method = shift;
333
334 if ($what eq any qw( end endpoint final )) {
eb97acbb 335 $attrs->{Args} = delete $attrs->{CaptureArgs};
918fb36e 336 }
337 elsif ($what eq 'private') {
aae7ad1f 338 $attrs->{Private} = [];
918fb36e 339 }
340 };
341 }
342
343 method _handle_under_option (Object $ctx, HashRef $attrs) {
344
8d66ec34 345 my $target = $self->_strip_actionpath($ctx, interpolate => 1);
e10b92dd 346 $ctx->skipspace;
347
348 if ($ctx->peek_next_char eq '{' and $self->identifier eq 'under') {
349 $ctx->inject_if_block(
8d66ec34 350 $ctx->scope_injector_call .
351 sprintf ';local %s = %s;',
e10b92dd 352 UNDER_VAR,
353 $target,
354 );
355 return STOP_PARSING;
356 }
357
8d66ec34 358 $attrs->{Chained} = $target;
918fb36e 359
360 return sub {
361 my $method = shift;
918fb36e 362 };
363 }
364
365 method _handle_chains_option (Object $ctx, HashRef $attrs) {
366
367 $ctx->skipspace;
368 $ctx->strip_name eq 'to'
369 or croak "Expected to token after chains symbol, not " . substr($ctx->get_linestr, $ctx->offset);
370
371 return $self->_handle_under_option($ctx, $attrs);
372 }
373
374 method _handle_as_option (Object $ctx, HashRef $attrs) {
375
376 $ctx->skipspace;
377
8d66ec34 378 my $path = $self->_strip_actionpath($ctx, interpolate => 1);
379 $attrs->{PathPart} = $path;
918fb36e 380
381 return;
382 }
383
384 method _count_positional_arguments (Object $method) {
c2a8165b 385 my $signature = $method->parsed_signature;
918fb36e 386
387 if ($signature->has_positional_params) {
388 my $count = @{ scalar($signature->positional_params) };
389
390 if ($count and ($signature->positional_params)[-1]->sigil eq '@') {
391 return undef;
392 }
393
394 return $count - 1;
395 }
396
397 return 0;
398 }
399
8d66ec34 400 method _inject_attributes (Object $ctx, HashRef $attrs) {
401
4960c7ec 402 # attrs that need to be runtime-resolved
dd2759b0 403 my @inject = qw( Chained PathPart Subname );
8d66ec34 404
4960c7ec 405 # turn specific attributes into a hashref
406 my $code = sprintf ' +{ %s }, sub ', # the ', sub ' turns method +{ ... } { ... } into
407 join ', ', # method +{ ... }, sub { ... }
8d66ec34 408 map { (@$_) }
8d66ec34 409 map { defined( $_->[1] ) ? $_ : [$_->[0], 'undef'] }
410 map { [pp($_), $attrs->{ $_ }] }
411 grep { defined $attrs->{ $_ } }
412 @inject;
413
4960c7ec 414 # inject the hashref code before the action body
8d66ec34 415 $ctx->inject_code_parts_here($code);
416 $ctx->inc_offset(length $code);
417 }
418
419 method _strip_actionpath (Object $ctx, :$interpolate?) {
918fb36e 420
421 $ctx->skipspace;
422 my $linestr = $ctx->get_linestr;
423 my $rest = substr($linestr, $ctx->offset);
8d66ec34 424 my $interp = sub { $interpolate ? "'$_[0]'" : $_[0] };
918fb36e 425
4960c7ec 426 # find simple barewords
918fb36e 427 if ($rest =~ /^ ( [_a-z] [_a-z0-9]* ) \b/ix) {
428 substr($linestr, $ctx->offset, length($1)) = '';
429 $ctx->set_linestr($linestr);
8d66ec34 430 return $interp->($1);
918fb36e 431 }
4960c7ec 432
433 # allow single quoted more complex barewords
a0ebba1d 434 elsif ($rest =~ /^ ' ( (?:[.:;,_a-z0-9]|\/)* ) ' /ix) {
918fb36e 435 substr($linestr, $ctx->offset, length($1) + 2) = '';
436 $ctx->set_linestr($linestr);
8d66ec34 437 return $interp->($1);
438 }
4960c7ec 439
440 # double quoted strings and variables
8d66ec34 441 elsif ($interpolate and my $str = $ctx->get_string) {
442 return $str;
918fb36e 443 }
4960c7ec 444
445 # not suitable as action path
918fb36e 446 else {
447 croak "Invalid syntax for action path: $rest";
448 }
449 }
8d66ec34 450
4960c7ec 451 # down here because it requires the parse method
8d66ec34 452 with 'MooseX::Declare::Syntax::KeywordHandling';
453
454 around context_traits { $self->$orig, StringParsing }
918fb36e 455}
456
856ac9a7 457__END__
458
459=head1 NAME
460
461CatalystX::Declare::Keyword::Action - Declare Catalyst Actions
462
463=head1 SYNOPSIS
464
465 use CatalystX::Declare;
466
467 controller MyApp::Web::Controller::Example {
468
469 # chain base action with path part setting of ''
470 # body-less actions don't do anything by themselves
6e2492a4 471 action base as '' under '/';
856ac9a7 472
473 # simple end-point action
474 action controller_class is final under base {
475 $ctx->response->body( 'controller: ' . ref $self );
476 }
477
478 # chain part actions can have arguments
479 action str (Str $string) under base {
480
481 $ctx->stash(chars => [split //, $string]);
482 }
483
484 # and end point actions too, of course
485 action uc_chars (Int $count) under str is final {
486
487 my $chars = $ctx->stash->{chars};
488 ...
489 }
490
491
492 # you can use a shortcut for multiple actions with
493 # a common base
494 under base {
495
496 # this is an endpoint after base
497 action normal is final;
498
499 # the final keyword can be used to be more
500 # visually explicit about end-points
501 final action some_action { ... }
ed4a2203 502
503 # type dispatching works
504 final action with_str (Str $x) as via_type;
505 final action with_int (Int $x) as via_type;
856ac9a7 506 }
507
508 # of course you can also chain to external actions
509 final action some_end under '/some/controller/some/action';
510 }
511
512=head1 DESCRIPTION
513
514This handler class provides the user with C<action>, C<final> and C<under>
515keywords. There are multiple ways to define actions to allow for greater
516freedom of expression. While the parts of the action declaration itself do
517not care about their order, their syntax is rather strict.
518
519You can choose to separate syntax elements via C<,> if you think it is more
520readable. The action declaration
521
522 action foo is final under base;
523
524is parsed in exactly the same way if you write it as
525
526 action foo, is final, under base;
527
528=head2 Basic Action Declaration
529
530The simplest possible declaration is
531
532 action foo;
533
6e2492a4 534This would define a chain-part action chained to nothing with the name C<foo>
856ac9a7 535and no arguments. Since it isn't followed by a block, the body of the action
536will be empty.
537
538You will automatically be provided with two variables: C<$self> is, as you
539might expect, your controller instance. C<$ctx> will be the Catalyst context
540object. Thus, the following code would stash the value returned by the
541C<get_item> method:
542
543 action foo {
544 $ctx->stash(item => $self->get_item);
545 }
546
32663314 547=head2 Why $ctx instead of $c
548
549Some might ask why the context object is called C<$ctx> instead of the usual
550C<$c>. The reason is simple: It's an opinionated best practice, since C<$ctx>
551stands out more.
552
856ac9a7 553=head2 Setting a Path Part
554
555As usual with Catalyst actions, the path part (the public name of this part of
556the URI, if you're not familiar with the term yet) will default to the name of
557the action itself (or more correctly: to whatever Catalyst defaults).
558
559To change that, use the C<as> option:
560
6e2492a4 561 under something {
562 action base as ''; # <empty>
563 action something as 'foo/bar'; # foo/bar
564 action barely as bareword; # bareword
565 }
856ac9a7 566
567=head2 Chaining Actions
568
569Currently, L<CatalystX::Declare> is completely based on the concept of
570L<chained actions|Catalyst::DispatchType::Chained>. Every action you declare is
6e2492a4 571chained or private. You can specify the action you want to chain to with the
572C<under> option:
856ac9a7 573
6e2492a4 574 action foo; # chained to nothing
856ac9a7 575 action foo under '/'; # also chained to /
576 action foo under bar; # chained to the local bar action
577 action foo under '/bar/baz'; # chained to baz in /bar
578
579C<under> is also provided as a grouping keyword. Every action inside the block
580will be chained to the specified action:
581
582 under base {
583 action foo { ... }
584 action bar { ... }
585 }
586
587You can also use the C<under> keyword for a single action. This is useful if
588you want to highlight a single action with a significant diversion from what
589is to be expected:
590
6e2492a4 591 action base under '/';
856ac9a7 592
593 under '/the/sink' is final action foo;
594
595 final action bar under base;
596
597 final action baz under base;
598
599Instead of the C<under> option declaration, you can also use a more english
600variant named C<chains to>. While C<under> might be nice and concise, some
601people might prefer this if they confuse C<under> with the specification of
602a public path part. The argument to C<chains to> is the same as to C<under>:
603
604 action foo chains to bar;
605 action foo under bar;
606
607By default all actions are chain-parts, not end-points. If you want an action
608to be picked up as end-point and available via a public path, you have to say
609so explicitely by using the C<is final> option:
610
6e2492a4 611 action base under '/';
856ac9a7 612 action foo under base is final; # /base/foo
613
614You can also drop the C<is> part of the C<is final> option if you want:
615
616 under base, final action foo { ... }
617
618You can make end-points more visually distinct by using the C<final> keyword
619instead of the option:
620
6e2492a4 621 action base under '/';
856ac9a7 622 final action foo under base; # /base/foo
623
624And of course, the C<final>, C<under> and C<action> keywords can be used in
625combination whenever needed:
626
6e2492a4 627 action base as '' under '/';
856ac9a7 628
629 under base {
630
631 final action list; # /list
632
633 action load;
634
635 under load {
636
637 final action view; # /list/load/view
638 final action edit; # /list/load/edit
639 }
640 }
641
642There is also one shorthand alternative for declaring chain targets. You can
643specify an action after a C<E<lt>-> following the action name:
644
6e2492a4 645 action base under '/';
856ac9a7 646 final action foo <- base; # /base/foo
647
648=head2 Arguments
649
650You can use signatures like you are use to from L<MooseX::Method::Signatures>
ed4a2203 651to declare action parameters. The number of positinoal arguments will be used
652during dispatching as well as their types.
856ac9a7 653
654The signature follows the action name:
655
656 # /foo/*/*/*
657 final action foo (Int $year, Int $month, Int $day);
658
659If you are using the shorthand definition, the signature follows the chain
660target:
661
662 # /foo/*
6e2492a4 663 final action foo <- base ($x) under '/' { ... }
856ac9a7 664
665Parameters may be specified on chain-parts and end-points:
666
667 # /base/*/foo/*
6e2492a4 668 action base (Str $lang) under '/';
856ac9a7 669 final action page (Int $page_num) under base;
670
671Named parameters will be populated with the values in the query parameters:
672
673 # /view/17/?page=3
6e2492a4 674 final action view (Int $id, Int :$page = 1) under '/';
856ac9a7 675
accfac7d 676If you specify a query parameter to be an C<ArrayRef>, it will be specially
677handled. For one, it will match even if there is no such value in the
678parameters. Second, it will always be wrapped as an array reference.
679
856ac9a7 680Your end-points can also take an unspecified amount of arguments by specifying
681an array as a variable:
682
683 # /find/some/deep/path/spec
6e2492a4 684 final action find (@path) under '/';
856ac9a7 685
5fb5cef1 686=head2 Validation
687
ed4a2203 688The signatures are now validated during dispatching-time, and an action with
689a non-matching signature (number of positional arguments and their types) will
690not be dispatched to. This means that
691
692 action base under '/' as '';
693
694 under base {
695
ed4a2203 696 final as double, action double_integer (Int $x) {
697 $ctx->response->body( $x * 2 );
698 }
aee1c364 699
700 final as double, action double_string (Str $x) {
701 $ctx->response->body( $x x 2 );
702 }
ed4a2203 703 }
704
705will return C<foofoo> when called as C</double/foo> and C<46> when called as
706C</double/23>.
5fb5cef1 707
856ac9a7 708=head2 Actions and Method Modifiers
709
710Method modifiers can not only be applied to methods, but also to actions. There
711is no way yet to override the attributes of an already established action via
712modifiers. However, you can modify the method underlying the action.
713
714The following code is an example role modifying the consuming controller's
715C<base> action:
716
717 use CatalystX::Declare;
718
205323ac 719 controller_role MyApp::Web::ControllerRole::RichBase {
856ac9a7 720
721 before base (Object $ctx) {
722 $ctx->stash(something => $ctx->model('Item'));
723 }
724 }
725
726Note that you have to specify the C<$ctx> argument yourself, since you are
727modifying a method, not an action.
728
729Any controller having a C<base> action (or method, for this purpose), can now
730consume the C<RichBase> role declared above:
731
732 use CatalystX::Declare;
733
734 controller MyApp::Web::Controller::Foo
735 with MyApp::Web::Controller::RichBase {
736
6e2492a4 737 action base as '' under '/';
856ac9a7 738
739 action show, final under base {
740 $ctx->response->body(
741 $ctx->stash->{something}->render,
742 );
743 }
744 }
745
72a81644 746You can consume multiple action roles similarly to the way you do with the
747class or role keyword:
748
749 action user
750 with LoggedIn
751 with isSuperUser {}
752
753Or
754
755 action User
756 with (LoggedIn, isSuperUser) {}
757
758Lastly, you can pass parameters to the underlying L<Catalyst::Action> using
759a syntax that is similar to method traits:
760
761 action myaction with hasRole(opt1=>'val1', opt2=>'val2')
762
763Where C<%opts> is a hash that is used to populate $action->attributes in the
764same way you might have done the following in classic L<Catalyst>
765
766 sub myaction :Action :Does(hasRole) :opt1(val1) :opt2(val2)
767
768Here's a more detailed example:
769
770 action User
771 with hasLogger(log_engine=>'STDOUT')
772 with hasPermissions(
773 role=>['Administrator', 'Member'],
774 ) {}
775
776Think of these are classic catalyst subroutine attributes on steriods. Unlike
777subroutine attributes, you can split and format your code across multiple lines
778and you can use deep and complex data structures such as HashRefs or ArrayRefs.
779Also, since the parameters are grouped syntactically within the C<with> keyword
780this should improve readability of your code, since it will be more clear which
781parameters belong to with roles. This should give L<CatalystX::Declare> greater
782compatibility with legacy L<Catalyst> code but offer us a way forward from
783needing subroutine attributes, which suffer from significant drawbacks.
784
785A few caveats and differences from method traits. First of all, unlike method
786traits, parameters are not passed to the L<Catalyst::Action> constructor, but
787instead used to populate the C<attributes> attribute, which is to preserve
788compatibility with how subroutine attributes work in classic L<Catalyst>.
789
790Additionally, since subroutines attributes supported a very limited syntax for
791supplying values, we follow the convention where parameter values are pushed
792onto an arrayref. In other words the following:
793
794 action User with hasLogger(engine=>'STDOUT')
795
796would create the following data structure:
797
798 $action->attributes->{engine} = ['STDOUT']
799
800The one exception is that if the value is an arrayref, those will be merged:
801
802 action User with Permissions(roles=>[qw/admin member/]) {}
803 ## Creates: $action->attributes->{roles} = ['admin','member']
804
805My feeling is that this gives better backward compatibility with classic sub
806attributes:
807
808 sub User :Action :Does(Permissions) :roles(admin) :roles(member)
809
b09fbf67 810However, I realize this method could lead to namespace collisions within the
811C<$action->attributes> attribute. For now this is an avoidable issue. In the
812future we may add a C<$action->trait_attributes> or similar attribute to the
813L<Catalyst::Action> class in order to resolve this issue.
72a81644 814
2bb54af3 815=head2 Action Classes
816
817B<This option is even more experimental>
818
819You might want to create an action with a different class than the usual
820L<Catalyst::Action>. A usual suspect here is L<Catalyst::Action::RenderView>.
821You can use the C<isa> option (did I mention it's experimental?) to specify
822what class to use:
823
824 controller MyApp::Web::Controller::Root {
825
826 $CLASS->config(namespace => '');
827
828 action end isa RenderView;
829 }
830
831The loaded class will be L<Moose>ified, so we are able to apply essential
832roles.
833
b79aeec3 834=head2 Private Actions
835
836B<This option is a bit less, but still pretty experimental>
837
838You can declare private actions with the C<is private> trait:
839
840 action end is private isa RenderView;
841
856ac9a7 842=head1 ROLES
843
844=over
845
846=item L<MooseX::Declare::Syntax::KeywordHandling>
847
848=back
849
850=head1 METHODS
851
852These methods are implementation details. Unless you are extending or
853developing L<CatalystX::Declare>, you should not be concerned with them.
854
855=head2 parse
856
857 Object->parse (Object $ctx, Str :$modifier?, Int :$skipped_declarator = 0)
858
859A hook that will be invoked by L<MooseX::Declare> when this instance is called
860to handle syntax. It will parse the action declaration, prepare attributes and
861add the actions to the controller.
862
863=head1 SEE ALSO
864
865=over
866
867=item L<CatalystX::Declare>
868
869=item L<CatalystX::Declare::Keyword::Controller>
870
871=item L<MooseX::Method::Signatures>
872
873=back
874
875=head1 AUTHOR
876
877See L<CatalystX::Declare/AUTHOR> for author information.
878
879=head1 LICENSE
880
881This program is free software; you can redistribute it and/or modify it under
882the same terms as perl itself.
918fb36e 883
856ac9a7 884=cut
918fb36e 885