The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

CatalystX::Declare::Keyword::Action - Declare Catalyst Actions

SYNOPSIS

    use CatalystX::Declare;

    controller MyApp::Web::Controller::Example {

        # chain base action with path part setting of ''
        # body-less actions don't do anything by themselves
        action base as '' under '/';

        # simple end-point action
        action controller_class is final under base {
            $ctx->response->body( 'controller: ' . ref $self );
        }

        # chain part actions can have arguments
        action str (Str $string) under base {

            $ctx->stash(chars => [split //, $string]);
        }

        # and end point actions too, of course
        action uc_chars (Int $count) under str is final {
    
            my $chars = $ctx->stash->{chars};
            ...
        }


        # you can use a shortcut for multiple actions with
        # a common base
        under base {

            # this is an endpoint after base
            action normal is final;

            # the final keyword can be used to be more 
            # visually explicit about end-points
            final action some_action { ... }

            # type dispatching works
            final action with_str (Str $x) as via_type;
            final action with_int (Int $x) as via_type;
        }

        # of course you can also chain to external actions
        final action some_end under '/some/controller/some/action';
    }

DESCRIPTION

This handler class provides the user with action, final and under keywords. There are multiple ways to define actions to allow for greater freedom of expression. While the parts of the action declaration itself do not care about their order, their syntax is rather strict.

You can choose to separate syntax elements via , if you think it is more readable. The action declaration

    action foo is final under base;

is parsed in exactly the same way if you write it as

    action foo, is final, under base;

Basic Action Declaration

The simplest possible declaration is

    action foo;

This would define a chain-part action chained to nothing with the name foo and no arguments. Since it isn't followed by a block, the body of the action will be empty.

You will automatically be provided with two variables: $self is, as you might expect, your controller instance. $ctx will be the Catalyst context object. Thus, the following code would stash the value returned by the get_item method:

    action foo {
        $ctx->stash(item => $self->get_item);
    }

Why $ctx instead of $c

Some might ask why the context object is called $ctx instead of the usual $c. The reason is simple: It's an opinionated best practice, since $ctx stands out more.

Setting a Path Part

As usual with Catalyst actions, the path part (the public name of this part of the URI, if you're not familiar with the term yet) will default to the name of the action itself (or more correctly: to whatever Catalyst defaults).

To change that, use the as option:

    under something {
        action base      as '';             # <empty>
        action something as 'foo/bar';      # foo/bar
        action barely    as bareword;       # bareword
    }

Chaining Actions

Currently, CatalystX::Declare is completely based on the concept of chained actions. Every action you declare is chained or private. You can specify the action you want to chain to with the under option:

    action foo;                     # chained to nothing
    action foo under '/';           # also chained to /
    action foo under bar;           # chained to the local bar action
    action foo under '/bar/baz';    # chained to baz in /bar

under is also provided as a grouping keyword. Every action inside the block will be chained to the specified action:

    under base {
        action foo { ... }
        action bar { ... }
    }

You can also use the under keyword for a single action. This is useful if you want to highlight a single action with a significant diversion from what is to be expected:

    action base under '/';

    under '/the/sink' is final action foo;

    final action bar under base;

    final action baz under base;

Instead of the under option declaration, you can also use a more english variant named chains to. While under might be nice and concise, some people might prefer this if they confuse under with the specification of a public path part. The argument to chains to is the same as to under:

    action foo chains to bar;
    action foo under bar;

By default all actions are chain-parts, not end-points. If you want an action to be picked up as end-point and available via a public path, you have to say so explicitely by using the is final option:

    action base under '/';
    action foo under base is final;   # /base/foo

You can also drop the is part of the is final option if you want:

    under base, final action foo { ... }

You can make end-points more visually distinct by using the final keyword instead of the option:

    action base under '/';
    final action foo under base;      # /base/foo

And of course, the final, under and action keywords can be used in combination whenever needed:

    action base as '' under '/';

    under base {

        final action list;          # /list

        action load;

        under load {

            final action view;      # /list/load/view
            final action edit;      # /list/load/edit
        }
    }

There is also one shorthand alternative for declaring chain targets. You can specify an action after a <- following the action name:

    action base under '/';
    final action foo <- base;       # /base/foo

Arguments

You can use signatures like you are use to from MooseX::Method::Signatures to declare action parameters. The number of positinoal arguments will be used during dispatching as well as their types.

The signature follows the action name:

    # /foo/*/*/*
    final action foo (Int $year, Int $month, Int $day);

If you are using the shorthand definition, the signature follows the chain target:

    # /foo/*
    final action foo <- base ($x) under '/' { ... }

Parameters may be specified on chain-parts and end-points:

    # /base/*/foo/*
    action base (Str $lang) under '/';
    final action page (Int $page_num) under base;

Named parameters will be populated with the values in the query parameters:

    # /view/17/?page=3
    final action view (Int $id, Int :$page = 1) under '/';

If you specify a query parameter to be an ArrayRef, it will be specially handled. For one, it will match even if there is no such value in the parameters. Second, it will always be wrapped as an array reference.

Your end-points can also take an unspecified amount of arguments by specifying an array as a variable:

    # /find/some/deep/path/spec
    final action find (@path) under '/';

Validation

The signatures are now validated during dispatching-time, and an action with a non-matching signature (number of positional arguments and their types) will not be dispatched to. This means that

    action base under '/' as '';

    under base {

        final as double, action double_integer (Int $x) {
            $ctx->response->body( $x * 2 );
        }

        final as double, action double_string (Str $x) {
            $ctx->response->body( $x x 2 );
        }
    }

will return foofoo when called as /double/foo and 46 when called as /double/23.

Actions and Method Modifiers

Method modifiers can not only be applied to methods, but also to actions. There is no way yet to override the attributes of an already established action via modifiers. However, you can modify the method underlying the action.

The following code is an example role modifying the consuming controller's base action:

    use CatalystX::Declare;

    controller_role MyApp::Web::ControllerRole::RichBase {

        before base (Object $ctx) {
            $ctx->stash(something => $ctx->model('Item'));
        }
    }

Note that you have to specify the $ctx argument yourself, since you are modifying a method, not an action.

Any controller having a base action (or method, for this purpose), can now consume the RichBase role declared above:

    use CatalystX::Declare;

    controller MyApp::Web::Controller::Foo
        with   MyApp::Web::Controller::RichBase {

        action base as '' under '/';

        action show, final under base { 
            $ctx->response->body(
                $ctx->stash->{something}->render,
            );
        }
    }

You can consume multiple action roles similarly to the way you do with the class or role keyword:

    action user
    with LoggedIn
    with isSuperUser {}

Or

    action User
    with (LoggedIn, isSuperUser) {}

Lastly, you can pass parameters to the underlying Catalyst::Action using a syntax that is similar to method traits:

    action myaction with hasRole(opt1=>'val1', opt2=>'val2')

Where %opts is a hash that is used to populate $action->attributes in the same way you might have done the following in classic Catalyst

    sub myaction :Action :Does(hasRole) :opt1(val1) :opt2(val2)

Here's a more detailed example:

    action User
    with hasLogger(log_engine=>'STDOUT')
    with hasPermissions(
        role=>['Administrator', 'Member'],
    ) {}

Think of these are classic catalyst subroutine attributes on steriods. Unlike subroutine attributes, you can split and format your code across multiple lines and you can use deep and complex data structures such as HashRefs or ArrayRefs. Also, since the parameters are grouped syntactically within the with keyword this should improve readability of your code, since it will be more clear which parameters belong to with roles. This should give CatalystX::Declare greater compatibility with legacy Catalyst code but offer us a way forward from needing subroutine attributes, which suffer from significant drawbacks.

A few caveats and differences from method traits. First of all, unlike method traits, parameters are not passed to the Catalyst::Action constructor, but instead used to populate the attributes attribute, which is to preserve compatibility with how subroutine attributes work in classic Catalyst.

Additionally, since subroutines attributes supported a very limited syntax for supplying values, we follow the convention where parameter values are pushed onto an arrayref. In other words the following:

    action User with hasLogger(engine=>'STDOUT')

would create the following data structure:

    $action->attributes->{engine} = ['STDOUT']

The one exception is that if the value is an arrayref, those will be merged:

    action User with Permissions(roles=>[qw/admin member/]) {}
    ## Creates: $action->attributes->{roles} = ['admin','member']

My feeling is that this gives better backward compatibility with classic sub attributes:

    sub User :Action :Does(Permissions) :roles(admin) :roles(member)

However, I realize this method could lead to namespace collisions within the $action-attributes> attribute. For now this is an avoidable issue. In the future we may add a $action-trait_attributes> or similar attribute to the Catalyst::Action class in order to resolve this issue.

Action Classes

This option is even more experimental

You might want to create an action with a different class than the usual Catalyst::Action. A usual suspect here is Catalyst::Action::RenderView. You can use the isa option (did I mention it's experimental?) to specify what class to use:

    controller MyApp::Web::Controller::Root {

        $CLASS->config(namespace => '');

        action end isa RenderView;
    }

The loaded class will be Mooseified, so we are able to apply essential roles.

Private Actions

This option is a bit less, but still pretty experimental

You can declare private actions with the is private trait:

    action end is private isa RenderView;

ROLES

MooseX::Declare::Syntax::KeywordHandling

METHODS

These methods are implementation details. Unless you are extending or developing CatalystX::Declare, you should not be concerned with them.

parse

    Object->parse (Object $ctx, Str :$modifier?, Int :$skipped_declarator = 0)

A hook that will be invoked by MooseX::Declare when this instance is called to handle syntax. It will parse the action declaration, prepare attributes and add the actions to the controller.

SEE ALSO

CatalystX::Declare
CatalystX::Declare::Keyword::Controller
MooseX::Method::Signatures

AUTHOR

See "AUTHOR" in CatalystX::Declare for author information.

LICENSE

This program is free software; you can redistribute it and/or modify it under the same terms as perl itself.