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