Safe Dispatch Using a Path

A Catalyst controller can dispatch processing to another action using $c->forward, $c->detach, $c->visit and $c->go.   (This example will use $c->go but the general approach should work for the others as well.)   One way to invoke those methods is by passing them the private path to a Catalyst action.

The Problem

Suppose you try to:

$c->go('/foo/bar/baz');

This will work fine if that path maps to sub Baz() in the Foo::Bar controller, or sub Bar() in the Foo controller (in which case 'baz' would get passed as an argument).   However, if that path maps to the default() action in e.g. the Foo controller, you will get:

Couldn't go("/foo/bar/baz"): Couldn't go to command "/foo/bar/baz": Invalid action or component.

To make that work correctly, you would instead have to modify the path like so:

$c->go('/foo/default/bar/baz');

but it is not obvious from looking at the original path how it might need to be modified.

A Solution

Instead of passing the path directly to $c->go, you can have the dispatcher create a Catalyst::Action from the path.   The dispatcher will take into account whether the path maps to a default() action at some depth.   Then pass that action to $c->go:

my $path = "/foo/bar/baz";    # some path to an action

# Stuff the path into the current request:
#
$c->request->path($path);

# Tell the dispatcher to create a new action.
#
# It will use $c->request->path to update
# both $c->action and $c->request->args.
#
$c->dispatcher->prepare_action($c);

$c->go($c->action, $c->request->args);    # make sure to include the request args!

This should work fine if you are using $c->detach or $c->go, which do not return to the caller upon completion.   If you are using $c->forward or $c->visit, you may want to save the state of $c->action and $c->request so they can be restored afterward.