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