How can I use Catalyst and uri chaining with a REST interface?
Asked Answered
G

3

5

I'm expecting to receive uri's like

/user/*/account/*

I've got a user function defined as

sub user  :Path('/user') :PathPart('') :ActionClass('REST' ) {}

then

sub user_GET :PathPart('user') Chained('/') CaptureArgs(1) {
    #do stuff

}

For accounts I'm defining them similarly.

 sub account :Path('/account') :PathPart('') :ActionClass('REST') {}

 sub account_GET :PathPart('account') Chained('user_GET') Args(1) {
     #do stuff
 }

So, the problem is when I set Chained in account_GET to 'user_GET' the server debug show that the path is set:

[debug] Loaded Chained actions:
.-----------------------------+--------------------------------------.
| Path Spec                   | Private                              |
+-----------------------------+--------------------------------------+
| /user/*/account/*           | /mcp/user_GET (1)                    |
|                             | => /mcp/account_GET                  |
'-----------------------------+--------------------------------------'

When I set Chained in account_GET to 'user' the server debug shows:

[debug] Unattached Chained actions:

[debug] Unattached Chained actions:

.-------------------------------------+--------------------------------------.
| Private                             | Missing parent                       |
+-------------------------------------+--------------------------------------+
| /mcp/account_GET                    | /mcp/user                            |
'-------------------------------------+--------------------------------------'

The problem is that clearly that latter isn't being set up and the former is returning that it wasn't found.

So the problem is if I'm calling /user/12345/account/23456 how do I get that path set correctly when what appears to be the obvious path, Chained('user'), isn't being set and the less obvious path, Chained('user_GET'), simply isn't working?

Grenville answered 13/12, 2011 at 17:43 Comment(1)
Did you ever figure this out?Syringe
D
4

Personally, I'd go for something like the following in the user controller:

package MyApp::Controller::User;
...
# root of the chain
sub object: Chained PathPart('user') CaptureArgs(1) { ... }

The object action above would load the user object into the stash. Now I'd have the user controller chained off the above like the following:

package MyApp::Controller::User::Account;
...
# chains to the action loading the user object and dispatches RESTy
sub account: Chained('/user/object') ActionClass('REST') Args(1) { ... }

# handle individual request methods
sub account_GET { ... }
sub account_POST { ... }

Here the account action provides common data for the account_* methods, which perform the actual operations.

Having method specific actions as parts of the chain (like having user react to a POST request to the account action) seems kind of counter-intuitive from a design standpoint. That might work, but I've never tried it.

The above examples are of course simplified. I usually have a base action in every controller setting the namespace and a common parent action, and all other actions in the controller will chain off that one. Then I'll have an object like above for loading single resources, and a root for a root action of the controller. Since you can build any kind of tree structure, it is rather flexible. So the best solution is often depending on what your constraints are.

Derry answered 15/12, 2011 at 22:8 Comment(1)
This does seem awkward though because account creation would be done with a POST to /user/object/account where that would be a stub (account should not accept :Args(1).. I'm just saying this would work, but this isn't the traditional REST convention as defined by catalyst and backboneZebra
R
3

mst from #catalyst says:

the _GET/_POST methods don't need dispatch attributes

Recrudescence answered 13/12, 2011 at 19:37 Comment(3)
that's nice, but how do you then do /user/*/account/*Scales
This worked. Just setting sub user :Path('/user') :PathPart('') :ActionClass('REST' ) {} and then having sub user_GET { } and the same thing for sub account is what I was looking for. Thanks for your response.Grenville
Sorry, this did not work. What it did was stop things from breaking and I mistakenly thought that it worked. So the problem that xenoterracide addressed in the comment above remains a problem: "/user/*/account/*"Grenville
S
1

I believe that doing something like the following will work, however you will have to pass the chained argument somehow, either in the stash, or possibly as an object attribute in $self.

sub base
    :Chained('/')
    :PathPart('')
    :CaptureArgs(0)
    {
    my ( $self, $c ) = @_;
}

sub user_account
    :Chained('/')
    :PathPart('user')
    :CaptureArgs(1)
    :ActionClass('REST')
    {
    my ( $self, $c, $user_id ) = @_;
}

sub user_account_GET
    :Chained('user')
    :PathPart('account')
    :Args(1)
    {
    my ( $self, $c ) = @_;
}

Here's the path spec it creates

[debug] Loaded Chained actions:
.-------------------------------------+--------------------------------------.
| Path Spec                           | Private                              |
+-------------------------------------+--------------------------------------+
| /user/*/account/*                   | /user/base (0)                       |
|                                     | -> /user/user_account (1)            |
|                                     | => /user/user_account_GET            |

:Chained('/') of course means the beggining of a chain. :Chained('user_account') basically means look for a subroutine in this controller named user_account if you put :Chained('/user_account') it would start looking in the root controller (this is a bit more complicated as you can make global chains outside of the root controller). :PathPart('foo') determines the component of the actual URI. Obviously you'd need to use :CaptureArgs(1) on a midpoint and :Args(1) at the end point.

Scales answered 13/12, 2011 at 20:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.