let us use CCAR instead of doing our own thing
[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
accfac7d 661If you specify a query parameter to be an C<ArrayRef>, it will be specially
662handled. For one, it will match even if there is no such value in the
663parameters. Second, it will always be wrapped as an array reference.
664
856ac9a7 665Your end-points can also take an unspecified amount of arguments by specifying
666an array as a variable:
667
668 # /find/some/deep/path/spec
6e2492a4 669 final action find (@path) under '/';
856ac9a7 670
5fb5cef1 671=head2 Validation
672
ed4a2203 673The signatures are now validated during dispatching-time, and an action with
674a non-matching signature (number of positional arguments and their types) will
675not be dispatched to. This means that
676
677 action base under '/' as '';
678
679 under base {
680
ed4a2203 681 final as double, action double_integer (Int $x) {
682 $ctx->response->body( $x * 2 );
683 }
aee1c364 684
685 final as double, action double_string (Str $x) {
686 $ctx->response->body( $x x 2 );
687 }
ed4a2203 688 }
689
690will return C<foofoo> when called as C</double/foo> and C<46> when called as
691C</double/23>.
5fb5cef1 692
856ac9a7 693=head2 Actions and Method Modifiers
694
695Method modifiers can not only be applied to methods, but also to actions. There
696is no way yet to override the attributes of an already established action via
697modifiers. However, you can modify the method underlying the action.
698
699The following code is an example role modifying the consuming controller's
700C<base> action:
701
702 use CatalystX::Declare;
703
205323ac 704 controller_role MyApp::Web::ControllerRole::RichBase {
856ac9a7 705
706 before base (Object $ctx) {
707 $ctx->stash(something => $ctx->model('Item'));
708 }
709 }
710
711Note that you have to specify the C<$ctx> argument yourself, since you are
712modifying a method, not an action.
713
714Any controller having a C<base> action (or method, for this purpose), can now
715consume the C<RichBase> role declared above:
716
717 use CatalystX::Declare;
718
719 controller MyApp::Web::Controller::Foo
720 with MyApp::Web::Controller::RichBase {
721
6e2492a4 722 action base as '' under '/';
856ac9a7 723
724 action show, final under base {
725 $ctx->response->body(
726 $ctx->stash->{something}->render,
727 );
728 }
729 }
730
2bb54af3 731=head2 Action Classes
732
733B<This option is even more experimental>
734
735You might want to create an action with a different class than the usual
736L<Catalyst::Action>. A usual suspect here is L<Catalyst::Action::RenderView>.
737You can use the C<isa> option (did I mention it's experimental?) to specify
738what class to use:
739
740 controller MyApp::Web::Controller::Root {
741
742 $CLASS->config(namespace => '');
743
744 action end isa RenderView;
745 }
746
747The loaded class will be L<Moose>ified, so we are able to apply essential
748roles.
749
b79aeec3 750=head2 Private Actions
751
752B<This option is a bit less, but still pretty experimental>
753
754You can declare private actions with the C<is private> trait:
755
756 action end is private isa RenderView;
757
856ac9a7 758=head1 ROLES
759
760=over
761
762=item L<MooseX::Declare::Syntax::KeywordHandling>
763
764=back
765
766=head1 METHODS
767
768These methods are implementation details. Unless you are extending or
769developing L<CatalystX::Declare>, you should not be concerned with them.
770
771=head2 parse
772
773 Object->parse (Object $ctx, Str :$modifier?, Int :$skipped_declarator = 0)
774
775A hook that will be invoked by L<MooseX::Declare> when this instance is called
776to handle syntax. It will parse the action declaration, prepare attributes and
777add the actions to the controller.
778
779=head1 SEE ALSO
780
781=over
782
783=item L<CatalystX::Declare>
784
785=item L<CatalystX::Declare::Keyword::Controller>
786
787=item L<MooseX::Method::Signatures>
788
789=back
790
791=head1 AUTHOR
792
793See L<CatalystX::Declare/AUTHOR> for author information.
794
795=head1 LICENSE
796
797This program is free software; you can redistribute it and/or modify it under
798the same terms as perl itself.
918fb36e 799
856ac9a7 800=cut
918fb36e 801