[Catalyst] PathPart help

Matt S Trout dbix-class at trout.me.uk
Sat Nov 17 11:40:27 GMT 2007


On Fri, Nov 16, 2007 at 01:50:03PM -0500, Jason Kohles wrote:
> On Nov 16, 2007, at 11:11 AM, Christopher H. Laco wrote:
> 
> >Interesting twist. Reminds me of the RHOX stuff..
> >
> >users/id/<id>
> >users/name/<name>
> >
> >etc. Damnit. Now you have me thinking again. That setup is a great
> >reason to keep REST and web controllers seperated. :/
> >
> 
> 
> The only problem with it is that I would love to be able to do this,  
> but so far I haven't come up with a reasonable way to say 'chain from  
> any of these controllers'.   i.e. it would be really nice to be able  
> to do this:
> 
> sub base : Chained('/') PathPart('users') CaptureArgs(0) { }
> 
> sub id : Chained('base') PathPart('id') CaptureArgs(1) { }
> sub name : Chained('base') PathPart('name') CaptureArgs(1) { }
> sub email : Chained('base') PathPart('email') CaptureArgs(1) { }
> 
> And then be able to base later controllers on any of these, something  
> like:
> 
> sub edit : Chained('id','name','email') PathPart('edit') Args(0) { }
> 
> I would also be nice to be able to have chain elements that didn't  
> correspond to URLs, so you could do something like this:
> 
> sub has_user : Chained('id|name|email') NoPathPart CaptureArgs(0) { }
> sub edit : Chained('has_user') PathPart('edit') Args(0) { }
> 
> 
> I keep planning to dig into the dispatcher and figure out how to  
> implement this (and multiple PathParts), but never enough TUITs...

I intentionally -didn't- implement this when I wrote Chained; as soon as
you do that the $c->uri_for($action, ...) syntax becomes ambiguous and a
whole host of other problems crop up.

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 ...

-- 
      Matt S Trout       Need help with your Catalyst or DBIx::Class project?
   Technical Director                    http://www.shadowcat.co.uk/catalyst/
 Shadowcat Systems Ltd.  Want a managed development or deployment platform?
http://chainsawblues.vox.com/            http://www.shadowcat.co.uk/servers/



More information about the Catalyst mailing list