[Catalyst] RFC: Multiple :Chained and/or :PathPart attributes?

John Napiorkowski jjn1056 at yahoo.com
Mon Aug 20 15:33:56 GMT 2007


--- Jason Kohles <email at jasonkohles.com> wrote:

> Currently, attempting to pass multiple arguments to
> either Chained or  
> PathPart attributes in a controller simply results
> in an exception  
> being thrown indicating that you can't do that, I'm
> thinking it might  
> be useful to change that behavior to allow for
> multiple arguments to  
> simply build multiple chains that take the same
> path.  As an example,  
> here is the code that led me to this thinking...
> 
> I was working on an application that includes page
> revisioning, and  
> ended up with this code, which seems excessively
> verbose to me...
> 
> The requirements were:
> 	* Wiki-like page structure
> 	* /page/SomePage displays the current revision of
> SomePage
> 	* /page/SomePage/revision/3 displays revision
> number 3 of SomePage
> 	* /page/SomePage/rev/3 also displays revision
> number 3 of SomePage
> 	* /page/SomePage/date/2007-01-01 displays whatever
> revision was  
> current on 2007-01-01 (or show a selector if there
> was more than one  
> change that day)
> 	* /page/SomePage/datetime/2007-01-01T12:10:00
> displays whatever  
> revision was current at that time
> 	* For ALL of the above URLs, the page should be
> displayed if there  
> are no other path parts, or if the URL ends with
> /view
> 
> These requirements led to this code...
> 
> sub page : Chained('/') PathPart('page')
> CaptureArgs(1) {
> 	my ( $self, $c, $page ) = @_;
> 	# load page data for the indicated page into the
> stash
> }
> 
> sub revision : Chained('page') PathPart('revision')
> CaptureArgs(1) {
> 	my ( $self, $c, $rev ) = @_;
> 
> 	$c->forward( 'handle_revision', [ $rev ] );
> }
> 
> sub rev : Chained('page') PathPart('rev')
> CaptureArgs(1) {
> 	my ( $self, $c, $rev ) = @_;
> 
> 	$c->forward( 'handle_revision', [ $rev ] );
> }
> 
> sub handle_revision : Private {
> 	my ( $self, $c ) = @_;
> 	# load the revision indicated in
> $c->request->args[0] into the stash
> }
> 
> sub date : Chained('page') PathPart('date')
> CaptureArgs(1) {
> 	my ( $self, $c, $date ) = @_;
> 
> 	$c->forward( 'handle_datetime', [ $date ] );
> }
> 
> sub datetime : Chained('page') PathPart('datetime')
> CaptureArgs(1) {
> 	my ( $self, $c, $date ) = @_;
> 
> 	$c->forward( 'handle_datetime', [ $date ] );
> }
> 
> sub handle_datetime : Private {
> 	my ( $self, $c ) = @_;
> 	# create a datetime object from
> $c->request->args->[0]
> 	# load the appropriate revision into the stash
> }
> 
> sub view : Chained('page') PathPart('view') Args(0)
> {
> 	my ( $self, $c ) = @_;
> 
> 	$c->forward( 'process_view' );
> }
> 
> # All of these subs have the exact same body as
> 'view', which does  
> nothing but forward to 'process_view'
> sub view_default : Chained('page') PathPart('')
> Args(0) {
> sub view_revision : Chained('revision')
> PathPart('view') Args(0) {}
> sub view_revision_default : Chained('revision')
> PathPart('') Args(0) {}
> sub view_rev : Chained('rev') PathPart('view')
> Args(0) {}
> sub view_rev_default : Chained('rev') PathPart('')
> Args(0) {}
> sub view_date : Chained('date') PathPart('view')
> Args(0) {}
> sub view_date_default : Chained('date') PathPart('')
> Args(0) {}
> sub view_datetime : Chained('datetime')
> PathPart('view') Args(0) {}
> sub view_datetime_default : Chained('datetime')
> PathPart('') Args(0) {}
> 
> sub process_view : Private {
> 	my ( $self, $c ) = @_;
> 
> 	if ( $c->stash->{ 'matches' } ) {
> 		$c->stash->{ 'template' } = 'select_revision.tt';
> 	} elsif ( $c->stash->{ 'page' } ) {
> 		$c->stash->{ 'template' } = 'display_page.tt';
> 	} else {
> 		$c->stash->{ 'template' } = 'not_found.tt';
> 	}
> }
> 
> If the Chained and PathPart attributes could take
> multiple arguments  
> that would simply build multiple chains, then I
> could have  
> significantly reduced the amount of repetition, by
> doing something  
> along these lines instead...
> 
> sub page : Chained('/') PathPart('page')
> CaptureArgs(1) {
> 	my ( $self, $c, $page ) = @_;
> 	# load page data for the indicated page into the
> stash
> }
> 
> sub revision : Chained('page')
> PathPart('revision','rev') CaptureArgs 
> (1) {
> 	my ( $self, $c, $rev ) = @_;
> 
> 	# load the indicated revision into the stash
> }
> 
> sub date : Chained('page')
> PathPart('date','datetime') CaptureArgs(1) {
> 	my ( $self, $c, $date ) = @_;
> 
> 	# load the appropriate revision into the stash
> }
> 
> sub view : Chained('page','revision','date')
> PathPart('','view') Args 
> (0) {
> 	my ( $self, $c ) = @_;
> 
> 	if ( $c->stash->{ 'matches' } ) {
> 		$c->stash->{ 'template' } = 'select_revision.tt';
> 	} elsif ( $c->stash->{ 'page' } ) {
> 		$c->stash->{ 'template' } = 'display_page.tt';
> 	} else {
> 		$c->stash->{ 'template' } = 'not_found.tt';
> 	}
> }
> 
> I tried to put together a patch that could do this,
> but this is my  
> first trip into the innards of the dispatcher, and
> it looks like it's  
> going to take a while to wrap my brain around it, so
> if anyone has  
> suggestions on how to implement this (or reasons
> that it's a very bad  
> idea) I'd love to hear them...

I don't have any thoughts on this particular idea, but
I solved a similar solution by creating a common
action class to handle the action stuff that was
similar across a few of my controllers.

Since you can declare actions in a controller's
configuration I am finding that good use of this
feature and action classes is making my controllers
less redundant and more like the glue we expect.

For example, you could create an action class called
Forwards, something like (code might need a little
work, off the top of my head)

package  myapp::Action::Forwards;

use Class::C3;
use base 'Catalyst::Action';

sub execute
{
    my $self = shift;
    my ( $controller, $c ) = @_;
    my $fwd = $c->action->attributes->{Forwards}->[0];
    
    ## Might want some logic to make sure $fwd is good
    $c->forward($fwd);
   
    ## In case there's more to do...
    $c->next::method(@_);
}


In your controller:

package myapp::Controller::Forwarding;

__PACKAGE__->config(
  action => {
    can_forward = {
      Chained=>'mychain',
      ActionClass=>"+myapp::Action::Forwards",
      Forwards=>'View',
    },
  },
);


Something like that.  I'll dig up a real example when
I get to the office.

Hope it gave you something to consider, or at least
didn't add to the confusion.  --john


       
____________________________________________________________________________________
Looking for a deal? Find great prices on flights and hotels with Yahoo! FareChase.
http://farechase.yahoo.com/



More information about the Catalyst mailing list