Doing Rails-style routes with Catalyst
People often ask about using Rails-style routes, and why Catalyst doesn't "support" them. Well, actually, it does - through the magic of config.
Routes via the config file
The key is this: action attributes are actually just defaults - when you do
sub foo :Local {
that makes the default attribute hash for foo { Local =
[ '' ] }
>. Well, it doesn't, because when Local is parsed - what you actually
get for the following:
package MyApp::Controller::Name; sub foo :Local { # ...
is { Path =
[ 'name/foo' ] } >. Now, this can be overriden from
the config file by doing something like:
<Controller Name> <action foo> Path somewhere/else </action> </Controller>
Which is perhaps a little bit verbose, but then you only use config for per-deployment stuff, right? Right?
Routes via code, and controller reuse
So doing it from MyApp.pm is maybe a bit cleaner -
__PACKAGE__->config( Controller => { Name => { actions => { foo => { Path => 'somewhere/else' } } } } );
although this is still quite verbose, of course. Generally, this approach is only ever used to allow URL localisations. For example, if you sell a white label app to a foreign client you can change the URL parts to their language. I'm aware of people having done this in production.
Of course, the other thing you can do is to use this to make reusable controllers -
package MyApp::ControllerBase::List; sub list :Args(0) { my ($self, $c) = @_; my $rs = $c->stash->{rs}->page($c->req->query_params->{page}||1); $c->stash( pager => $rs->pager, results => [ $rs->all ] ); } package MyApp::Controller::Thingies; use base qw(MyApp::ControllerBase::List); __PACKAGE__->config( actions => { list => { Chained => 'load_thingies' } } ); sub load_thingies :Chained('/') :CaptureArgs(0) :PathPart('thingies') { my ($self, $c) = @_: $c->stash( rs => $c->model('DB::Thingy') ); }
and now Thingies->list will be chained off Thingies->load_thingies.
Syntactic sugar
But we were talking about routes, weren't we? Though I would observe that controllers' self-contained-ness is what makes this sort of subclass-to-reuse stuff possible; the Rails guys say "re-use in the large is overrated", we say "well, actually, it's bloody hard, but if you're careful ...". Anyway.
Let's see if we can't make routes a bit prettier.
{ my %config; sub routes (&) { my $cr = $_[0]; %config = (); $cr->(); __PACKAGE__->config(\%config); } sub route { my ($path, $to) = @_; my ($controller, $action) = split(/->/, $to); $config{"Controller::$controller"}{actions}{$action}{Path} = [ $path ]; } sub to { @_ } } use namespace::clean; # this will get rid of the subs on EOF routes { route 'some/path' to 'Name->foo'; };
and the end result will be (provided we've marked foo as an action via
sub foo :Action {
) that /some/path will dispatch to that method
on MyApp::Controller::Name.
Of course, I still think that Catalyst's self-contained controller approach is better, but if you really want routes, please consider the code above as under the same license as Perl and send CatalystX::Routes to the CPAN :)
-- mst
AUTHOR
Matt S Trout <mst@shadowcat.co.uk>
( http://www.shadowcat.co.uk/ )