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;
}
|