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

Jason Kohles email at jasonkohles.com
Mon Aug 20 14:07:31 GMT 2007


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

-- 
Jason Kohles
email at jasonkohles.com
http://www.jasonkohles.com/
"A witty saying proves nothing."  -- Voltaire





More information about the Catalyst mailing list