4

Background:

I'm using a CRUD framework in Catalyst that auto-generates forms and lists for all tables in a given database. For example: /admin/list/person or /admin/add/person or /admin/edit/person/3 all dynamically generate pages or forms as appropriate for the table 'person'. (In other words, Admin.pm has actions edit, list, add, delete and so on that expect a table argument and possibly a row-identifying argument.)

Question:

In the particular application I'm building, the database will be used by multiple customers, so I want to introduce a URI scheme where the first element is the customer's identifier, followed by the administrative action/table etc:

  • /cust1/admin/list/person
  • /cust2/admin/add/person
  • /cust2/admin/edit/person/3

This is for "branding" purposes, and also to ensure that bookmarks or URLs passed from one user to another do the expected thing.

But I'm having a lot of trouble getting this to work. I would prefer not to have to modify the subs in the existing framework, so I've been trying variations on the following:

sub customer : Regex('^(\w+)/(admin)$') {
    my ($self, $c, @args) = @_;
    #validation of captured arg snipped..
    my $path = join('/', 'admin', @args);
    $c->request->path($path);
    $c->dispatcher->prepare_action($c);
    $c->forward($c->action, $c->req->args);
}

But it just will not behave. I've used regex matching actions many times, but putting one in the very first 'barrel' of a URI seems unusually traumatic. Any suggestions gratefully received.

RET
  • 9,100
  • 1
  • 28
  • 33
  • This is not a question about regular expressions per se, hence I've removed the regex tag added by an Editor. – RET Jun 25 '11 at 04:51
  • In your example, the regex will not match because of the first `/` in the uris -- the `^\w` will not match in that case. `sub customer : Regex('^(\w+)/(admin)$') {` – agent-j Jun 25 '11 at 04:52
  • That is not how Catalyst Regex actions work. The example above is in the Root (empty namespace) controller, so the leading slash is not relevant. – RET Jun 25 '11 at 05:37
  • Have you tried escaping the `\w` as `\\w`? That is also in my answer? If not, I'll delete my answer. – agent-j Jun 25 '11 at 12:18
  • This is not a problem with regular expressions, or backslash-itis. It's specifically about Catalyst behaviour. The actual regex is not really relevant, which is why I didn't include the regex tag when I posted the question. – RET Jun 26 '11 at 01:35

2 Answers2

3

Make the customer action :Chained and then chain the admin action to that one. For example:

sub customer :Chained('/') :PathPart('') :CaptureArgs(1) {
    # load current customer into the stash
}

sub admin :Chained('customer') :PathPart('admin') :Args(0) {
}

For more information see the Catalyst::DispatchType::Chained

Dimitar Petrov
  • 667
  • 1
  • 5
  • 16
  • Thanks Dimitar. Unfortunately it's not quite that simple, as admin is not a sub, but another controller containing a variety of actions. In fact it's not even that simple, because most of its actions it inherits from the original CRUD framework controller. ie: Admin.pm contains a sub auto, but otherwise inherits the list, add, edit subs from Framework::Controller.pm. So I don't really have any subs I can hang the :Chained onto... – RET Jun 28 '11 at 08:45
  • I got this solved using a variation on your suggestion (basically, sub-classing all the inherited subs with the :attributes defined). See my answer. – RET Jul 02 '11 at 07:36
1

I've marked Dimitar's answer as the correct one, because it put me on the right track to solving this problem. (I've never really had a need for - or grokked FTM - Chained actions until now.)

Here's what I've got in the various controllers, for anyone who's interested.

=== Root.pm ===

sub customer_home : Path: Args(1) { # eg /cust1
    my ($self, $c, $custarg) = @_;
    ...
}

sub ourclub :Chained('/') :PathPart('') :CaptureArgs(1) { # eg /cust1/<admin-function>
    my ($self, $c, $custarg) = @_;
    ...
}

=== Admin.pm ===

use base 'Framework::Controller';

# create chained action versions of the framework actions
sub admin       :Chained('/customer') :PathPart('admin')     { shift->SUPER::admin(@_) }
sub list        :Chained('/customer') :PathPart('list')      { shift->SUPER::list(@_) }
sub view        :Chained('/customer') :PathPart('view')      { shift->SUPER::view(@_) }
sub add         :Chained('/customer') :PathPart('add')       { shift->SUPER::add(@_) }
sub edit        :Chained('/customer') :PathPart('edit')      { shift->SUPER::edit(@_) }
sub delete      :Chained('/customer') :PathPart('delete')    { shift->SUPER::delete(@_) }
sub search      :Chained('/customer') :PathPart('search')    { shift->SUPER::search(@_) }
sub model       :Chained('/customer') :PathPart('model')     { shift->SUPER::model(@_) }

I had a red-hot go at dynamically generating all those subs via *{$action} = eval "sub ..." and related ideas, but eventually had to admit defeat on that.

RET
  • 9,100
  • 1
  • 28
  • 33