Why not adopt me?
NAME
CatalystX::RouteMaster - role for components providing Catalyst actions
VERSION
version 1.06
SYNOPSIS
package MyApp::Base::Stuff;
use Moose;
extends 'Catalyst::Component';
with 'CatalystX::RouteMaster';
sub _kind_name {'Stuff'}
sub _wrap_code {
my ($self,$c,$url_prefix,$action_name,$route) = @_;
my $code = $route->{code};
my $extra_config = $route->{extra_config};
return sub {
my ($controller,$ctx) = @_;
$self->$code($ctx->req,$extra_config);
}
}
Then:
package MyApp::Stuff::One;
use Moose;
extends 'MyApp::Base::Stuff';
sub routes {
return {
'/some/url' => {
action_name => {
code => \&my_action_method,
extra_config => $whatever,
},
...
},
...
}
}
sub my_action_method {
my ($self,$request) = @_;
# do something
}
Also, remember to tell Catalyst to load your Stuff
components:
<setup_components>
search_extra [ ::Stuff ]
</setup_components>
DESCRIPTION
First of all, this role does not give you anything that you could not do with Catalyst directly. It does not add any functionality. What it does is allow you to write some simple controllers in a different style.
This was originally part of CatalystX::ConsumesJMS, then I realised it could easily be generalised, so here is the "general" part.
Rationale
So, since this module is quite complicated, and does not actually provide anything that Catalyst doesn't already do, why does it exist? When I wrote CatalystX::ConsumesJMS and Plack::Handler::Stomp, I was (ab)using Catalyst as a combination dependency injection container + dispatcher, to write an application to consume ActiveMQ messages. One of the design goals was to hide Catalyst from the actual application code, since in the end there was no need to expose the framework. If you don't need to hide Catalyst from your code, or you need non-trivial dispatching, you're probably better off without this module.
Usage
This role is to be used to define base classes for components in your Catalyst applications. It's not to be consumed directly by application components. Classes inheriting from those base classes will, when loaded, create controllers and register their actions inside them.
Routing
Subclasses of your component base specify which URLs they are interested in, by writing a routes
method, see the synopsis for an example.
They can specify as many URLs and action names as they want / need, and they can re-use the code
values as many times as needed.
The main limitation is that you can't have two components using the exact same URL / action name pair (even if they derive from different base classes!). If you do, the results are undefined. This is the same "limitation" as not having two actions for the same request in regular Catalyst.
It is possible to alter the URL via configuration, like:
<Stuff::One>
<routes_map>
logical_url /the/actual/url
</routes_map>
</Stuff::One>
You can also do this:
<Stuff::One>
<routes_map>
logical_url /the/actual/url
logical_url /another/url
</routes_map>
</Stuff::One>
to get your class to respond to two different URLs without altering the code.
You can even alter the action name via the configuration:
<Stuff::One>
<routes_map>
<logical_url /the/actual/url>
action_name local_action
</local_url>
<logical_url /another/url>
action_name local_action1
action_name local_action2
</local_url>
</routes_map>
</Stuff::One>
That would (with the default "_action_extra_params" method) install 3 identical actions for the following URLs:
(Again, nothing that you couldn't do with normally-written controllers, it's just a different way to do it).
The "code"
The hashref specified by each URL / action name pair will be passed to the "_wrap_code" function (that the consuming class has to provide), and the coderef returned will be installed as the action to invoke for that name under that URL.
Required methods
_kind_name
As in the synopsis, this should return a string that, in the names of the classes deriving from the consuming class, separates the "application name" from the "component name".
These names are mostly used to access the configuration.
_wrap_code
This method is called with:
the Catalyst application as passed to
register_actions
the URL (mapped via "routing")
the action name (mapped via "routing")
the value from the
routes
corresponding to the URL and action name slot (see "Routing" above)
You can do whatever you need in this method, see the synopsis for an idea.
The coderef returned will be invoked as a Catalyst action for each matching request, which means it will get:
the controller instance (you should rarely need this)
the Catalyst application context
Implementation Details
HERE BE DRAGONS.
This role should be consumed by sub-classes of Catalyst::Component
.
The consuming class is supposed to be used as a base class for application components (see the "SYNOPSIS", and make sure to tell Catalyst to load them!).
Since these components won't be in the normal Model/View/Controller namespaces, we need to modify the COMPONENT
method to pick up the correct configuration options. This is were "_kind_name" is used.
We hijack the expand_modules
method to generate various bits of Catalyst code on the fly.
If the component has a configuration entry enabled
with a false value, it is ignored, thus disabling it completely.
We generate a controller package for each (mapped) URL, by calling "_generate_controller_package", and we add an after
method modifier to its register_actions
method inherited from Catalyst::Controller
, to create all the (mapped) actions. The modifier is generated by calling "_generate_register_action_modifier".
_generate_controller_package
my $pkg = $self->_generate_controller_package(
$appname,$url,
$config,$route);
Generates a controller package, inheriting from whatever "_controller_base_classes" returns, called ${appname}::Controller::${url}
(invalid characters are replaced with _
). Any roles returned by "_controller_roles" are applied to the controller.
Inside the controller, we set the namespace
config slot to the $url
.
_controller_base_classes
List (not arrayref!) of class names that the controllers generated by "_generate_controller_package" should inherit from. Defaults to 'Catalyst::Controller'
.
_controller_roles
List (not arrayref!) of role names that should be applied to the controllers created by "_generate_controller_package". Defaults to the empty list.
_generate_register_action_modifier
my $modifier = $self->_generate_register_action_modifier(
$appname,$url,
$controller_pkg,
$config,$route);
Returns a coderef to be installed as an after
method modifier to the register_actions
method. The coderef will register each (mapped) action with the Catalyst dispatcher. You can pass additional parameters to the controller's create_action
method by overriding "_action_extra_params".
Each action's code is obtained by calling "_wrap_code".
_action_extra_params
my %extra_params = $self->_action_extra_params(
$c,$url,
$action_name,$route->{$action_name},
);
You can override this method to provide additional arguments for the create_action
call inside "_generate_register_action_modifier". For example you could return:
attributes => { MySpecialAttr => [ 'foo' ] }
to set that attribute for all generated actions. Defaults to:
attributes => { 'Path' => ["$url/$action_name"] }
to make all the action "local" to the generated controller (i.e. they will be invoked for requests to $url/$action_name
).
AUTHOR
Gianni Ceccarelli <gianni.ceccarelli@net-a-porter.com>
CONTRIBUTORS
Thanks to Peter Sergeant (SARGIE) for the name.
COPYRIGHT AND LICENSE
This software is copyright (c) 2012 by Net-a-porter.com.
This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.