parse actionrole params and pass them to attributes
[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;
237 my %params = ref $first eq 'HASH' ? %$first : ($first, @rest);
238 for my $key (keys %params) {
239 $attrs->{$key} = [$params{$key}];
240 }
241 }
242
284b2994 243 if (defined(my $alias = $self->_check_for_available_import($ctx, $role))) {
244 $role = $alias;
245 }
246 push @roles, $role;
a1dd1788 247 }
248
284b2994 249 push @{ $attrs->{CatalystX_Declarative_ActionRoles} ||= [] }, @roles;
a1dd1788 250
251 return;
252 }
253
254 method _handle_isa_option (Object $ctx, HashRef $attrs) {
255
256 my $class = $ctx->strip_name
257 or croak "Expected bareword action class specification for action after isa";
258
259 if (defined(my $alias = $self->_check_for_available_import($ctx, $class))) {
260 $class = $alias;
261 }
262
263 $attrs->{CatalystX_Declarative_ActionClass} = $class;
264
265 return;
266 }
267
268 method _check_for_available_import (Object $ctx, Str $name) {
269
270 if (my $code = $ctx->get_curstash_name->can($name)) {
271 return $code->();
272 }
273
274 return undef;
275 }
276
918fb36e 277 method _handle_action_option (Object $ctx, HashRef $attrs) {
278
279 # action name
dd2759b0 280 my $name = $self->_strip_actionpath($ctx, interpolate => 1)
918fb36e 281 or croak "Anonymous actions not yet supported";
282
64baeca0 283 $ctx->skipspace;
284 my $populator;
285
4960c7ec 286 # shortcut under base option is basically handled by the under handler
64baeca0 287 if (substr($ctx->get_linestr, $ctx->offset, 2) eq '<-') {
288 my $linestr = $ctx->get_linestr;
289 substr($linestr, $ctx->offset, 2) = '';
290 $ctx->set_linestr($linestr);
291 $populator = $self->_handle_under_option($ctx, $attrs);
292 }
293
918fb36e 294 # signature
295 my $proto = $ctx->strip_proto || '';
296 $proto = join(', ', 'Object $self: Object $ctx', $proto || ());
297
298 $attrs->{Subname} = $name;
299 $attrs->{Signature} = $proto;
aae7ad1f 300 $attrs->{Action} = [];
918fb36e 301
5fb5cef1 302 push @{ $attrs->{CatalystX_Declarative_ActionRoles} ||= [] }, CatchValidationError;
4960c7ec 303
304 # default chained base to the global under var, to be resolved at runtime
8d66ec34 305 $attrs->{Chained} ||= UNDER_VAR;
e10b92dd 306
64baeca0 307 return unless $populator;
308 return $populator;
918fb36e 309 }
310
2dde75e7 311 method _handle_final_option (Object $ctx, HashRef $attrs) {
312
313 return $self->_build_flag_populator($ctx, $attrs, 'final');
314 }
315
918fb36e 316 method _handle_is_option (Object $ctx, HashRef $attrs) {
317
318 my $what = $ctx->strip_name
319 or croak "Expected symbol token after is symbol, not " . substr($ctx->get_linestr, $ctx->offset);
320
2dde75e7 321 return $self->_build_flag_populator($ctx, $attrs, $what);
322 }
323
324 method _build_flag_populator (Object $ctx, HashRef $attrs, Str $what) {
325
17a275f5 326 $attrs->{Private} = []
327 if $what eq 'private';
328
918fb36e 329 return sub {
330 my $method = shift;
331
332 if ($what eq any qw( end endpoint final )) {
eb97acbb 333 $attrs->{Args} = delete $attrs->{CaptureArgs};
918fb36e 334 }
335 elsif ($what eq 'private') {
aae7ad1f 336 $attrs->{Private} = [];
918fb36e 337 }
338 };
339 }
340
341 method _handle_under_option (Object $ctx, HashRef $attrs) {
342
8d66ec34 343 my $target = $self->_strip_actionpath($ctx, interpolate => 1);
e10b92dd 344 $ctx->skipspace;
345
346 if ($ctx->peek_next_char eq '{' and $self->identifier eq 'under') {
347 $ctx->inject_if_block(
8d66ec34 348 $ctx->scope_injector_call .
349 sprintf ';local %s = %s;',
e10b92dd 350 UNDER_VAR,
351 $target,
352 );
353 return STOP_PARSING;
354 }
355
8d66ec34 356 $attrs->{Chained} = $target;
918fb36e 357
358 return sub {
359 my $method = shift;
918fb36e 360 };
361 }
362
363 method _handle_chains_option (Object $ctx, HashRef $attrs) {
364
365 $ctx->skipspace;
366 $ctx->strip_name eq 'to'
367 or croak "Expected to token after chains symbol, not " . substr($ctx->get_linestr, $ctx->offset);
368
369 return $self->_handle_under_option($ctx, $attrs);
370 }
371
372 method _handle_as_option (Object $ctx, HashRef $attrs) {
373
374 $ctx->skipspace;
375
8d66ec34 376 my $path = $self->_strip_actionpath($ctx, interpolate => 1);
377 $attrs->{PathPart} = $path;
918fb36e 378
379 return;
380 }
381
382 method _count_positional_arguments (Object $method) {
c2a8165b 383 my $signature = $method->parsed_signature;
918fb36e 384
385 if ($signature->has_positional_params) {
386 my $count = @{ scalar($signature->positional_params) };
387
388 if ($count and ($signature->positional_params)[-1]->sigil eq '@') {
389 return undef;
390 }
391
392 return $count - 1;
393 }
394
395 return 0;
396 }
397
8d66ec34 398 method _inject_attributes (Object $ctx, HashRef $attrs) {
399
4960c7ec 400 # attrs that need to be runtime-resolved
dd2759b0 401 my @inject = qw( Chained PathPart Subname );
8d66ec34 402
4960c7ec 403 # turn specific attributes into a hashref
404 my $code = sprintf ' +{ %s }, sub ', # the ', sub ' turns method +{ ... } { ... } into
405 join ', ', # method +{ ... }, sub { ... }
8d66ec34 406 map { (@$_) }
8d66ec34 407 map { defined( $_->[1] ) ? $_ : [$_->[0], 'undef'] }
408 map { [pp($_), $attrs->{ $_ }] }
409 grep { defined $attrs->{ $_ } }
410 @inject;
411
4960c7ec 412 # inject the hashref code before the action body
8d66ec34 413 $ctx->inject_code_parts_here($code);
414 $ctx->inc_offset(length $code);
415 }
416
417 method _strip_actionpath (Object $ctx, :$interpolate?) {
918fb36e 418
419 $ctx->skipspace;
420 my $linestr = $ctx->get_linestr;
421 my $rest = substr($linestr, $ctx->offset);
8d66ec34 422 my $interp = sub { $interpolate ? "'$_[0]'" : $_[0] };
918fb36e 423
4960c7ec 424 # find simple barewords
918fb36e 425 if ($rest =~ /^ ( [_a-z] [_a-z0-9]* ) \b/ix) {
426 substr($linestr, $ctx->offset, length($1)) = '';
427 $ctx->set_linestr($linestr);
8d66ec34 428 return $interp->($1);
918fb36e 429 }
4960c7ec 430
431 # allow single quoted more complex barewords
a0ebba1d 432 elsif ($rest =~ /^ ' ( (?:[.:;,_a-z0-9]|\/)* ) ' /ix) {
918fb36e 433 substr($linestr, $ctx->offset, length($1) + 2) = '';
434 $ctx->set_linestr($linestr);
8d66ec34 435 return $interp->($1);
436 }
4960c7ec 437
438 # double quoted strings and variables
8d66ec34 439 elsif ($interpolate and my $str = $ctx->get_string) {
440 return $str;
918fb36e 441 }
4960c7ec 442
443 # not suitable as action path
918fb36e 444 else {
445 croak "Invalid syntax for action path: $rest";
446 }
447 }
8d66ec34 448
4960c7ec 449 # down here because it requires the parse method
8d66ec34 450 with 'MooseX::Declare::Syntax::KeywordHandling';
451
452 around context_traits { $self->$orig, StringParsing }
918fb36e 453}
454
856ac9a7 455__END__
456
457=head1 NAME
458
459CatalystX::Declare::Keyword::Action - Declare Catalyst Actions
460
461=head1 SYNOPSIS
462
463 use CatalystX::Declare;
464
465 controller MyApp::Web::Controller::Example {
466
467 # chain base action with path part setting of ''
468 # body-less actions don't do anything by themselves
6e2492a4 469 action base as '' under '/';
856ac9a7 470
471 # simple end-point action
472 action controller_class is final under base {
473 $ctx->response->body( 'controller: ' . ref $self );
474 }
475
476 # chain part actions can have arguments
477 action str (Str $string) under base {
478
479 $ctx->stash(chars => [split //, $string]);
480 }
481
482 # and end point actions too, of course
483 action uc_chars (Int $count) under str is final {
484
485 my $chars = $ctx->stash->{chars};
486 ...
487 }
488
489
490 # you can use a shortcut for multiple actions with
491 # a common base
492 under base {
493
494 # this is an endpoint after base
495 action normal is final;
496
497 # the final keyword can be used to be more
498 # visually explicit about end-points
499 final action some_action { ... }
ed4a2203 500
501 # type dispatching works
502 final action with_str (Str $x) as via_type;
503 final action with_int (Int $x) as via_type;
856ac9a7 504 }
505
506 # of course you can also chain to external actions
507 final action some_end under '/some/controller/some/action';
508 }
509
510=head1 DESCRIPTION
511
512This handler class provides the user with C<action>, C<final> and C<under>
513keywords. There are multiple ways to define actions to allow for greater
514freedom of expression. While the parts of the action declaration itself do
515not care about their order, their syntax is rather strict.
516
517You can choose to separate syntax elements via C<,> if you think it is more
518readable. The action declaration
519
520 action foo is final under base;
521
522is parsed in exactly the same way if you write it as
523
524 action foo, is final, under base;
525
526=head2 Basic Action Declaration
527
528The simplest possible declaration is
529
530 action foo;
531
6e2492a4 532This would define a chain-part action chained to nothing with the name C<foo>
856ac9a7 533and no arguments. Since it isn't followed by a block, the body of the action
534will be empty.
535
536You will automatically be provided with two variables: C<$self> is, as you
537might expect, your controller instance. C<$ctx> will be the Catalyst context
538object. Thus, the following code would stash the value returned by the
539C<get_item> method:
540
541 action foo {
542 $ctx->stash(item => $self->get_item);
543 }
544
32663314 545=head2 Why $ctx instead of $c
546
547Some might ask why the context object is called C<$ctx> instead of the usual
548C<$c>. The reason is simple: It's an opinionated best practice, since C<$ctx>
549stands out more.
550
856ac9a7 551=head2 Setting a Path Part
552
553As usual with Catalyst actions, the path part (the public name of this part of
554the URI, if you're not familiar with the term yet) will default to the name of
555the action itself (or more correctly: to whatever Catalyst defaults).
556
557To change that, use the C<as> option:
558
6e2492a4 559 under something {
560 action base as ''; # <empty>
561 action something as 'foo/bar'; # foo/bar
562 action barely as bareword; # bareword
563 }
856ac9a7 564
565=head2 Chaining Actions
566
567Currently, L<CatalystX::Declare> is completely based on the concept of
568L<chained actions|Catalyst::DispatchType::Chained>. Every action you declare is
6e2492a4 569chained or private. You can specify the action you want to chain to with the
570C<under> option:
856ac9a7 571
6e2492a4 572 action foo; # chained to nothing
856ac9a7 573 action foo under '/'; # also chained to /
574 action foo under bar; # chained to the local bar action
575 action foo under '/bar/baz'; # chained to baz in /bar
576
577C<under> is also provided as a grouping keyword. Every action inside the block
578will be chained to the specified action:
579
580 under base {
581 action foo { ... }
582 action bar { ... }
583 }
584
585You can also use the C<under> keyword for a single action. This is useful if
586you want to highlight a single action with a significant diversion from what
587is to be expected:
588
6e2492a4 589 action base under '/';
856ac9a7 590
591 under '/the/sink' is final action foo;
592
593 final action bar under base;
594
595 final action baz under base;
596
597Instead of the C<under> option declaration, you can also use a more english
598variant named C<chains to>. While C<under> might be nice and concise, some
599people might prefer this if they confuse C<under> with the specification of
600a public path part. The argument to C<chains to> is the same as to C<under>:
601
602 action foo chains to bar;
603 action foo under bar;
604
605By default all actions are chain-parts, not end-points. If you want an action
606to be picked up as end-point and available via a public path, you have to say
607so explicitely by using the C<is final> option:
608
6e2492a4 609 action base under '/';
856ac9a7 610 action foo under base is final; # /base/foo
611
612You can also drop the C<is> part of the C<is final> option if you want:
613
614 under base, final action foo { ... }
615
616You can make end-points more visually distinct by using the C<final> keyword
617instead of the option:
618
6e2492a4 619 action base under '/';
856ac9a7 620 final action foo under base; # /base/foo
621
622And of course, the C<final>, C<under> and C<action> keywords can be used in
623combination whenever needed:
624
6e2492a4 625 action base as '' under '/';
856ac9a7 626
627 under base {
628
629 final action list; # /list
630
631 action load;
632
633 under load {
634
635 final action view; # /list/load/view
636 final action edit; # /list/load/edit
637 }
638 }
639
640There is also one shorthand alternative for declaring chain targets. You can
641specify an action after a C<E<lt>-> following the action name:
642
6e2492a4 643 action base under '/';
856ac9a7 644 final action foo <- base; # /base/foo
645
646=head2 Arguments
647
648You can use signatures like you are use to from L<MooseX::Method::Signatures>
ed4a2203 649to declare action parameters. The number of positinoal arguments will be used
650during dispatching as well as their types.
856ac9a7 651
652The signature follows the action name:
653
654 # /foo/*/*/*
655 final action foo (Int $year, Int $month, Int $day);
656
657If you are using the shorthand definition, the signature follows the chain
658target:
659
660 # /foo/*
6e2492a4 661 final action foo <- base ($x) under '/' { ... }
856ac9a7 662
663Parameters may be specified on chain-parts and end-points:
664
665 # /base/*/foo/*
6e2492a4 666 action base (Str $lang) under '/';
856ac9a7 667 final action page (Int $page_num) under base;
668
669Named parameters will be populated with the values in the query parameters:
670
671 # /view/17/?page=3
6e2492a4 672 final action view (Int $id, Int :$page = 1) under '/';
856ac9a7 673
accfac7d 674If you specify a query parameter to be an C<ArrayRef>, it will be specially
675handled. For one, it will match even if there is no such value in the
676parameters. Second, it will always be wrapped as an array reference.
677
856ac9a7 678Your end-points can also take an unspecified amount of arguments by specifying
679an array as a variable:
680
681 # /find/some/deep/path/spec
6e2492a4 682 final action find (@path) under '/';
856ac9a7 683
5fb5cef1 684=head2 Validation
685
ed4a2203 686The signatures are now validated during dispatching-time, and an action with
687a non-matching signature (number of positional arguments and their types) will
688not be dispatched to. This means that
689
690 action base under '/' as '';
691
692 under base {
693
ed4a2203 694 final as double, action double_integer (Int $x) {
695 $ctx->response->body( $x * 2 );
696 }
aee1c364 697
698 final as double, action double_string (Str $x) {
699 $ctx->response->body( $x x 2 );
700 }
ed4a2203 701 }
702
703will return C<foofoo> when called as C</double/foo> and C<46> when called as
704C</double/23>.
5fb5cef1 705
856ac9a7 706=head2 Actions and Method Modifiers
707
708Method modifiers can not only be applied to methods, but also to actions. There
709is no way yet to override the attributes of an already established action via
710modifiers. However, you can modify the method underlying the action.
711
712The following code is an example role modifying the consuming controller's
713C<base> action:
714
715 use CatalystX::Declare;
716
205323ac 717 controller_role MyApp::Web::ControllerRole::RichBase {
856ac9a7 718
719 before base (Object $ctx) {
720 $ctx->stash(something => $ctx->model('Item'));
721 }
722 }
723
724Note that you have to specify the C<$ctx> argument yourself, since you are
725modifying a method, not an action.
726
727Any controller having a C<base> action (or method, for this purpose), can now
728consume the C<RichBase> role declared above:
729
730 use CatalystX::Declare;
731
732 controller MyApp::Web::Controller::Foo
733 with MyApp::Web::Controller::RichBase {
734
6e2492a4 735 action base as '' under '/';
856ac9a7 736
737 action show, final under base {
738 $ctx->response->body(
739 $ctx->stash->{something}->render,
740 );
741 }
742 }
743
2bb54af3 744=head2 Action Classes
745
746B<This option is even more experimental>
747
748You might want to create an action with a different class than the usual
749L<Catalyst::Action>. A usual suspect here is L<Catalyst::Action::RenderView>.
750You can use the C<isa> option (did I mention it's experimental?) to specify
751what class to use:
752
753 controller MyApp::Web::Controller::Root {
754
755 $CLASS->config(namespace => '');
756
757 action end isa RenderView;
758 }
759
760The loaded class will be L<Moose>ified, so we are able to apply essential
761roles.
762
b79aeec3 763=head2 Private Actions
764
765B<This option is a bit less, but still pretty experimental>
766
767You can declare private actions with the C<is private> trait:
768
769 action end is private isa RenderView;
770
856ac9a7 771=head1 ROLES
772
773=over
774
775=item L<MooseX::Declare::Syntax::KeywordHandling>
776
777=back
778
779=head1 METHODS
780
781These methods are implementation details. Unless you are extending or
782developing L<CatalystX::Declare>, you should not be concerned with them.
783
784=head2 parse
785
786 Object->parse (Object $ctx, Str :$modifier?, Int :$skipped_declarator = 0)
787
788A hook that will be invoked by L<MooseX::Declare> when this instance is called
789to handle syntax. It will parse the action declaration, prepare attributes and
790add the actions to the controller.
791
792=head1 SEE ALSO
793
794=over
795
796=item L<CatalystX::Declare>
797
798=item L<CatalystX::Declare::Keyword::Controller>
799
800=item L<MooseX::Method::Signatures>
801
802=back
803
804=head1 AUTHOR
805
806See L<CatalystX::Declare/AUTHOR> for author information.
807
808=head1 LICENSE
809
810This program is free software; you can redistribute it and/or modify it under
811the same terms as perl itself.
918fb36e 812
856ac9a7 813=cut
918fb36e 814