[Catalyst] PathPart help

Matt S Trout dbix-class at trout.me.uk
Tue Nov 20 18:04:38 GMT 2007

On Mon, Nov 19, 2007 at 09:41:06AM +0100, Dami Laurent (PJ) wrote:
> >-----Message d'origine-----
> >De : Matt S Trout [mailto:dbix-class at trout.me.uk] 
> >Envoyé : samedi, 17. novembre 2007 12:40
> [ snip ]
> >
> >What I'd do instead is -
> >
> >package MyApp::ControllerBase::HasObject;
> >
> >sub has_object :PathPart('') :CaptureArgs(0)
> >sub edit :Chained('has_user') :PathPart('edit') :Args(0)
> >
> >package MyApp::ControllerBase::ChainBase;
> >
> >__PACKAGE__->mk_accessors(qw(object_chains));
> >
> >__PACKAGE__->config(object_chains => []);
> >
> >sub COMPONENT {
> >  my $new = shift->NEXT::COMPONENT(@_);
> >  foreach my $chain (@{$new->object_chains}) { # commented 
> >using example 'id'
> >    my $action = $self->action_for($chain); # Catalyst::Action 
> >for /foo/id
> >    my $pkg = ref($new).'::'.ucfirst($chain); # 'id' -> 
> >Controller::Foo::Id
> >    {
> >      no strict 'refs';
> >      @{"${pkg}::ISA"} = 'MyApp::ControllerBase::HasObject'; # 
> >inject base class
> >    }
> >    # Set :Chained('id') on Controller::Foo::Id->has_user
> >    $pkg->config(actions => { has_object => { Chained => 
> >$action->reverse } });
> >  }
> >  return $new;
> >}
> >
> >package MyApp::Controller::Foo;
> >
> >use base qw(MyApp::ControllerBase::Chains);
> >
> >__PACKAGE__->config(object_chains = [ qw(id name email) ]);
> >
> >sub id ...
> >sub name ...
> >sub email ...
> >
> >Now, when Catalyst creates the Controller::Foo instance the 
> >stuff after the
> >component method will create Controller::Foo::Id, ::Name, 
> >::Email - Catalyst
> >will automatically pick this up (the same way it picks up the 
> >sub-models
> >created by e.g. Model::DBIC::Schema) and will load the 
> >Foo/Id.pm etc. files
> >afterwards -if- they exist.
> >
> >That way you'll get /foo/id/edit, /foo/name/edit etc. actions 
> >which can be
> >passed happily to $c->uri_for without ambiguity, and still have minimal
> >repeated code.
> >
> >(Disclaimer: code typed straight into mail. probably at least 
> >one typo or
> >thinko lurking in there)
> >
> >If people like this approach, I could write it up as an advent 
> >entry ...
> >
> Well, it may seem cute, but I am worried about long-term maintenance of this approach : if two years later somebody has to change anything in that setup, without knowing as much about Catalyst internals as you do Matt, then it could be quite hard for them to understand what is going on. 

Which is why I provided a full explanation of the technique, such as could be
put into comments in the classes involved so in two years the maintenance
programmer opens the file and finds a complete description of the mechanisms :)
> The original Catalyst design of URL-method mappings was quite obvious to understand (Local, Global, Regex, etc.). Then came the chained actions, that gave us much more flexibility and reuse, but require quite a bit of reading and experiment to really understand them. Now if we add even more indirection levels, it will make it even harder to follow.
> Two days ago at the French Perl Workshop somebody mentioned that Ruby on Rails has a notion of global, centralized dispatch table. Apache also has a convenient way to centralize the URL mappings, with <Location> or <LocationMatch> directives. As far as I know, Catalyst does not yet have any similar dispatch mechanism, but maybe that would be an interesting evolution path to investigate ?

You can already do that.

All the attributes can be assigned from controller config, as shown above.

So in MyApp.pm

  Controller::Foo => { action => { bar => { Path => '/some/path' } } }

is identical to having :Path(/some/path) on the 'sub bar'. So we -can- do
things globally if we really want to.

But having self-contained controllers was an intentional design decision in
order to allow for improved code re-use - my base class trickery can be
written once and shared between projects, and in fact Reaction will include
a number of Catalyst::Controller subclasses designed for just this sort of
usage - a general admin interface one that scaffolds a full CRUD system, a
basic one-table CRUD controller and a simpler one-table 'list and view actions
only' controller.

Most large-scale Catalyst projects that Shadowcat is currently helping clients
with are already using base classes and connect-the-dots approaches like this
to maximise code re-use across their architecture (and were doing so before
we got involved); where this stuff then reflects out to apache configs there's
invariably either a nightmare nest of Include directives or a TT setup that
generates the appropriate httpd.conf chunks in order to get round the
limitation of a single global config tree.

So, on the whole the rails/httpd.conf approach might be superficially simpler
for small projects, but really doesn't scale the way the currently available
Catalyst approaches do. Additional levels of indirection is a normal part of
refactoring for re-use; liberal use of comments and good project documentation
are the solution for maintainability, not giving up the flexibility robust
generalised architecture brings.

The rails justification for not catering to this is 're-use in the large is
overrated'. Between a number of significantly similar sites within a single
organisation, or within a site with a number of significantly similar areas
that operate on different types of data and are only mildly specialised for
the particular data type handled by each area, re-use in the large is the
best way to maintain development velocity and maintainability in the long run
even if it does require an up-front investment in understanding, careful
design and ensuring the team has a shared mental map of the project's

      Matt S Trout       Catalyst and DBIx::Class consulting and support -
   Technical Director      http://www.shadowcat.co.uk/catalyst/
 Shadowcat Systems Ltd.  Christmas fun in collectable card game form -

More information about the Catalyst mailing list