Catalyst Idioms I'm Missing

CromeDome on 2009-03-09T12:51:25

So I am trying to get a URL that looks like this:

http://localhost:3000/admin/itemtype/32/edit

Instead, I am getting URLs that look like:

http://localhost:3000/admin/itemtype/edit/32

In my template, I tried this:

[% c.uri_for( 'edit', [ item_type.type_id ]) %]

which gave me the latter URL. If I do this:

[% c.uri_for(c.controller('Admin::ItemType').action_for('edit'), [item_type.type_id] ) %]

I get what I want, but it seems too clunky to be doing in my view. Is there a simpler way to write that and have it still work? It seems like too common a thing to not have there be a simpler way, but I am just not finding it.


Chained actions

phaylon on 2009-03-09T17:40:20

You might want to look at chained actions: Catalyst::DispatchType::Chained.

That gives you an easy way to have URIs like

/user/$userid/entries/$year/$month

And you can set the URI via:

my $action = $ctx->controller('Foo')->action_for('edit');
my $uri = $ctx->uri_for($action, \@captures, @arguments);

What goes into captures and what into arguments depends on your application design.

While the version of uri_for() that uses an action object instead of a string seems a bit more work, it gives you one huge advantage: If you change your application's URIs later on (imagine wanting to move /st/$id to /stories/$id for SEO purposes), you can do that via the applications configuration file, and if you use action objects instead of giving it an explicit string path, Catalyst will from then on generate the new, correct, URIs without you needing to touch one link.

If you just want to type less, it might be the simplest thing to build yourself a utility method. One possibility would be to do this in a controller subclass:

sub uri_closure_for {
    my ($self, $ctx, $action) = @_;

    my $action_obj = $self->action_for($action);

    return sub {
        $ctx->uri_for($action_obj, @_);
    };
}

and in the actual controller you could do:

sub foo : ... {
    my ($self, $ctx) = @_;

    $ctx->stash(make_edit_uri => $self->uri_closure_for('edit'));
}

and then in the template

[% make_edit_uri([2, 3], 4) %]

where [2, 3] would be the captures and the 4 would be the argument.

HTH, phaylon

PS: Note that the code is just quickly hacked up and untested, of course.

Excerpt from $work

Aristotle on 2009-03-19T20:44:38

From MyApp.pm:

sub uri_for {
    my $c = shift;
    my ( $path ) = @_;

    if ( not Scalar::Util::blessed( $path ) and 'ARRAY' eq ref $path ) {
        my $action_path = shift @$path;
        unshift @_, $action_cache->{ $action_path } ||= $c->dispatcher->get_action_by_path( $action_path );
        $path = $_[0];
    }

    return URI::WithBase::Rel->new( $c->NEXT::uri_for( @_ ), $c->req->uri );
}

With that, you can write your second example like this:

[% c.uri_for(['/admin/itemtype/edit, item_type.type_id]) %]

Re:Excerpt from $work

Aristotle on 2009-03-19T20:46:57

Woops, that would be:

sub uri_for {
    my $c = shift;
    my ( $path ) = @_;

    if ( not Scalar::Util::blessed( $path ) and 'ARRAY' eq ref $path ) {
        my $action_path = shift @$path;
        unshift @_, $c->dispatcher->get_action_by_path( $action_path );
        $path = $_[0];
    }

    return URI::WithBase::Rel->new( $c->NEXT::uri_for( @_ ), $c->req->uri );
}

(The actual method in our source is much more complicated since it also caches a bunch of things and papers over a few misbehaviours in the Cat dispatcher.)