Catalyst Tip: Easy paging using uri_with()

LTjake on 2006-09-20T13:03:20

Just about every app needs to do paging. Using DBIx::Class, grabbing a Data::Page object from a resultset is as easy as calling $rs->pager. Constructing the next/previous page urls can sometimes been an odd challenge. Enter uri_with.

uri_with lets you add new (or replace existing) key-value pairs in the current request's URL. The following Template Toolkit code shows you how to take a URL (eg. http://example.com/search/?q=foo) and append next and previous page numbers (eg. http://example.com/search/?q=foo&page=2). It assumes that you have a Data::Page object in a stash variable called "pager".

[% IF pager.previous_page %]
Previous Page
[% END %]
[% IF pager.next_page %]
Next Page
[% END %]


uri_with

bart on 2006-09-21T07:18:22

Where is that function stored? I can imagine it to be useful for people who are not using Template Toolkit, and just use Data::Pager in their CGI scripts.

Re:uri_with

LTjake on 2006-09-21T10:39:17

The function resides in Catalyst::Request. Its utility is fairly dependent on the fact that we use a URI object for the request URL.

Here's the relevant snippet:

use utf8;
use Carp;
use URI::QueryParam;

sub uri_with {
    my( $self, $args ) = @_;

    carp( 'No arguments passed to uri_with()' ) unless $args;

    for my $value ( values %$args ) {
        for ( ref $value eq 'ARRAY' ? @$value : $value ) {
            $_ = "$_";
            utf8::encode( $_ );
        }
    };

    my $uri = $self->uri->clone;

    $uri->query_form( {
        %{ $uri->query_form_hash },
        %$args
    } );
    return $uri;
}

Re:uri_with

bart on 2006-09-22T08:46:59

OK, silly question perhaps... Why do you do the hard work manually, why not make use of the method query_param_append?

Re:uri_with

LTjake on 2006-09-22T11:20:11

Unless i'm misunderstanding your point, I don't believe that method does what we're looking for. Example: say we have the following URL

http://www.example.com/?q=test&page=2

...and we're generating the "next page" url. The following test script should show the two different methods:

use strict;
use warnings;

use Test::More tests => 2;

use URI;
use URI::QueryParam;

my $uri      = URI->new( 'http://example.com/?q=test&page=2' );
my $expected = {
    q    => 'test',
    page => 3
};

# method used in Catalyst::Request
{
    my $next = $uri->clone;
    $next->query_form( {
        %{ $next->query_form_hash },
        ( page => 3 )
    } );
    is_deeply( $next->query_form_hash, $expected, 'method used in Catalyst::Request' );
}

# use query_param_append()
{
    my $next = $uri->clone;
    $next->query_param_append( page => 3 );
    is_deeply( $next->query_form_hash, $expected, 'use query_param_append()' );
}

And the results...

1..2
ok 1 - method used in Catalyst::Request
not ok 2 - use query_param_append()
#   Failed test 'use query_param_append()'
#   in uri.pl at line 29.
#     Structures begin differing at:
#          $got->{page} = ARRAY(0x82aa794)
#     $expected->{page} = '3'
# Looks like you failed 1 test of 2.

As you can see, it made the page param into page => [2, 3] rather than just page => 3.

Re:uri_with

bart on 2006-09-22T17:55:23

Oh, if replacing instead of adding is what you want, then there are other methods that this module provides, such as query_param:
If additional arguments are given, they are used to update successive parameters with the given key. If any of the values provided are array references, then the array is dereferenced to get the actual values.

Re:uri_with

Aristotle on 2006-09-23T04:05:59

The point is you don’t have to construct a URI object. You just call uri_with and everything except the bits you want to override is already set up for you. Buf if you haven’t used an MVCish web app framework (even an ad-hoc one of your own device), you probably won’t appreciate why that’s so very useful.

Re:uri_with

bart on 2006-09-23T10:22:26

The point is you don’t have to construct a URI object.
I'm not disputing that. I'm talking about the actual implementation of uri_with, which does create an object, and takes the long road to do it. It recreates every form parameter, instead of just adding new ones, and manually prepares every passed parameter for addition. This is a job the used module should have done.

quotes

zby on 2006-10-20T08:19:50

Shouldn't the 'page' param be quoted? Like: c.request.uri_with( 'page' => pager.next_page

Re:quotes

LTjake on 2006-10-20T18:25:45

Nope -- quotes are not strictly required. So says the examples in the docs, at least :)