Chained Examples

Quick introduction to Chained

Assume you have URLs like: /things/1/edit, /things/1/display etc. With Catalyst::DispatchType::Chained, you can separate the processing of the things/1/ bit into its own method. In this example, the method that handles the /things/1/ bit would probably do something like fetch a record from a database and put it in the stash.


Using chained actions, you can separate out parts of your application based on the accessed path. For example you may want:

  • Access to everything under /account requiring login
  • Access to /account/org will display all "org" records
  • Access to /account/org/* will display one "org" record
  • Access to /account/org/*/edit will edit one "org" record

Across two controllers, the definitions needed would be:

package MyApp::Controller::Account;
use parent 'Catalyst::Controller';

# match /account
sub base :Chained("/") :PathPart("account") :CaptureArgs(0) {}

# match /account (end of chain)
sub root :Chained("base") :PathPart("") :Args(0) {}

package MyApp::Controller::Account::Org;
use parent 'Catalyst::Controller';

# match /account/org
sub base :Chained("/account/base") :PathPart("org") :CaptureArgs(0) {}

# match /account/org (end of chain)
sub list :Chained("base") :PathPart("") :Args(0) {}

# match /account/org/*
sub id :Chained("base") :PathPart("") :CaptureArgs(1) {}

# match /account/org/* (end of chain)
sub view :Chained("id") :PathPart("") :Args(0) {}

# match /account/org/*/edit (end of chain)
sub edit :Chained("id") :PathPart("edit") :Args(0) {}

Filling out the code a little more, you'd get:

package MyApp::Controller::Account;

# base sub for matching public path /account
# -- this verifies logged-in-ness for everything chained onto it
sub base :Chained("/") :PathPart("account") :CaptureArgs(0) {
  my($self, $c) = @_;

  if (!$c->user_exists) {

# display /account page
sub root :Chained("base") :PathPart("") :Args(0) {
  $c->stash->{template} = "account/";

package MyApp::Controller::Account::Org;

# nothing to do here except match the public path /account/org
# "/account" in the :Chained attribute refers to the path_prefix/namespace
# that is implicit in MyApp::Controller::Account
# the URL is "/account" because of the :PathPart("account"), which is separate
sub base :Chained("/account/base") :PathPart("org") :CaptureArgs(0) { }

# match public path /account/org and display all orgs
sub list :Chained("base") :PathPart("") :Args(0) {
  my($self, $c) = @_;

  $c->stash->{orgs} = $c->model("DB::Org")->search();
  $c->stash->{template} = "";

# match public path /account/org/* and load the org record
sub id :Chained("base") :PathPart("") :CaptureArgs(1) {
  my($self, $c, $org_id) = @_;

  $c->stash->{org} = $c->model("DB::Org")->find($org_id);

# match public path /account/org/*
# display the org record, already loaded for us
sub view :Chained("id") :PathPart("") :Args(0) {
  my($self, $c) = @_;

  $c->stash->{template} = "";

sub edit :Chained("id") :PathPart("edit") :Args(0) :FormConfig("org.yml") {
  my($self, $c) = @_;

  my $form = $c->stash->{form};

  if ($form->submitted_and_valid) {
    $c->res->redirect($c->uri_for("/account/org", $c->stash->{org}->id));

  $c->stash->{template} = "";