in reply to Re: Catalyst - chained actions with empty PathPart
in thread Catalyst - chained actions with empty PathPart

Because I can not do it with Chained dispatch type I created my own dispatch type as a subclass of Catalyst::DispatchType::Chained.

I tried to modify recurse_match so that intermediate action in chain can refuse their captures. I replaced:

push(@captures, splice(@parts, 0, $capture_attr->[0]));

by:

my @action_captures = splice(@parts, 0, $capture_attr->[0]); next TRY_ACTION if !$self->match_action_captures( $c, $action, \@actio +n_captures ); push(@captures, @action_captures);

For the captures matching of intermediate action I try to find other (private) action which has the same name + _match_captures suffix. If found the action is called to decide whether the captures are OK or not.

sub match_action_captures { my ( $this, $c, $action, $action_captures ) = @_; my $mc_action = $c->dispatcher->get_action_by_path( $action->private_path . '_match_captures' ); return $mc_action ? $c->forward( $mc_action, $action_captures ) : +1; }

In controller it looks like:

sub load_category_match_captures : Private { my ( $this, $c, $category ) = @_; return scalar grep { $category eq $_ } qw(cosmetics entertainment) +; }

The Chained dispatch type replacement is a bit cumbersome:

package TestApp; use Catalyst; # qw(-Debug); sub setup_dispatcher { my $this = shift; my $dispatcher = $this->next::method(@_); $dispatcher->preload_dispatch_types( ['+TestApp::DispatchType::Chained'] ); return $dispatcher; } __PACKAGE__->setup; my $dp = __PACKAGE__->dispatcher->dispatch_types; @$dp = grep { ( ref($_) || $_ ) ne 'Catalyst::DispatchType::Chained'; +} @$dp; 1;

I tried a few tests and so far it works.

One of the reasons why I try to stick with Chained are language based URLs I need to implement soon. I decided to put the language at the beginning of the chain without a distinguishing path part.

/cs/something .... /en/something ....

To have the language (and region/category) in $c->req->captures has some advantage for uri construction. I have my own version of $c->uri_for_action for chained actions with the same signature $c->my_uri_for_action( $action, $captures?, @arguments, \%params?). The idea is that for a chained $action, I know how many captures it needs. So if I provide less (or none) of them, the captures are supplied from left by those from $c->req->captures. Thus I will almost never needs to add $c->language into $c->uri_for since it is copied from current action.

The code of new dispatch type class:

package TestApp::DispatchType::Chained; use Moose; extends 'Catalyst::DispatchType::Chained'; sub recurse_match { my ( $self, $c, $parent, $path_parts ) = @_; my $children = $self->_children_of->{$parent}; return () unless $children; my $best_action; my @captures; TRY: foreach my $try_part (sort { length($b) <=> length($a) } keys %$children) { # $b then $a to try longest part first my @parts = @$path_parts; if (length $try_part) { # test and strip PathPart next TRY unless ($try_part eq join('/', # assemble equal number of parts splice( # and strip them off @parts as w +ell @parts, 0, scalar(@{[split('/', $try_p +art)]}) ))); # @{[]} to avoid split to @_ } my @try_actions = @{$children->{$try_part}}; TRY_ACTION: foreach my $action (@try_actions) { if (my $capture_attr = $action->attributes->{CaptureArgs}) + { # Short-circuit if not enough remaining parts next TRY_ACTION unless @parts >= $capture_attr->[0]; my @captures; my @parts = @parts; # localise # original Catalyst::DispatchType::Chained # push(@captures, splice(@parts, 0, $capture_attr->[0] +)); # /original # modification my @action_captures = splice(@parts, 0, $capture_attr- +>[0]); next TRY_ACTION if !$self->match_action_captures( $c, +$action, \@action_captures ); # strip CaptureArgs into list push(@captures, @action_captures); # /modification # try the remaining parts against children of this act +ion my ($actions, $captures, $action_parts) = $self->recur +se_match( $c, '/'.$action->reverse, + \@parts ); # No best action currently # OR The action has less parts # OR The action has equal parts but less captured data + (ergo more defined) if ($actions && (!$best_action || $#$action_parts < $#{$best_action->{parts}} || ($#$action_parts == $#{$best_action->{parts}} && $#$captures < $#{$best_action->{captures}}))){ $best_action = { actions => [ $action, @$actions ], captures=> [ @captures, @$captures ], parts => $action_parts }; } } else { { local $c->req->{arguments} = [ @{$c->req->args}, @ +parts ]; next TRY_ACTION unless $action->match($c); } my $args_attr = $action->attributes->{Args}->[0]; # No best action currently # OR This one matches with fewer parts left than the c +urrent best action, # And therefore is a better match # OR No parts and this expects 0 # The current best action might also be Args(0), # but we couldn't chose between then anyway so we'l +l take the last seen if (!$best_action || @parts < @{$best_action->{parts}} || (!@parts && $args_attr eq 0)){ $best_action = { actions => [ $action ], captures=> [], parts => \@parts } } } } } return @$best_action{qw/actions captures parts/} if $best_action; return (); } sub match_action_captures { my ( $this, $c, $action, $action_captures ) = @_; my $mc_action = $c->dispatcher->get_action_by_path( $action->private_path . '_match_captures' ); return $mc_action ? $c->forward( $mc_action, $action_captures ) : +1; }