Catalyst dispatcher for arbitrary tree-structure

377 Views Asked by At

Greetings,

I'm new to Catalyst and I am attempting to implement some dispatch logic.

My database has a table of items, each with a unique url_part field, and every item has a parent in the same table, making a tree structure. If baz is a child of bar which is a child of foo which is a child of the root, I want the URL /foo/bar/baz to map to this object. The tree can be any depth, and users will need to be able to access any node whether branch or leaf.

I have been looking through the documentation for Chained dispatchers, but I'm not sure if this can do what I want. It seems like each step in a chained dispatcher must have a defined name for the PathPart attribute, but I want my URLs to be determined solely by the database structure.

Is this easy to implement with the existing Catalyst dispatcher, or will I need to write my own dispatch class?

Thanks! :)

ETA:

I figured out that I can use an empty Args attribute to catch an arbitrary number of arguments. The following seems to successfully catch every request under the root:

sub default :Path :Args() {
    my ( $self, $c ) = @_;

    my $path = $c->request->path;

    $c->response->status( 200 );
    $c->response->body( "Your path is $path" );
}

From there I can manually parse the path and get what I need, however, I don't know if this is the best way to accomplish what I'm after.

2

There are 2 best solutions below

0
On BEST ANSWER

After playing around with various options, I believe I've arrived at an acceptable solution. Unfortunately, I couldn't get a recursive dispatch going with :Chained (Catalyst complains if you try to chain a handler to itself. That's no fun.)

So I ended up using a single handler with a large CaptureArgs, like this:

sub default : CaptureArgs(10) PathInfo('') { 
    my ( $self, $c, @args ) = @_;

    foreach my $i( 0 .. $#args ) { 
        my $sub_path = join '/', @args[ 0 .. $i ];

        if ( my $ent = $self->_lookup_entity( $c, $sub_path ) ) { 
            push @{ $c->stash->{ent_chain} }, $ent;
            next;
        }

        $c->detach( 'error_not_found' );
    }

    my $chain = join "\n", map { $_->entity_id } @{ $c->stash->{ent_chain} };
    $c->response->content_type( 'text/plain' );
    $c->response->body( $chain );
}

If I do a GET on /foo/bar/baz I get

foo
foo/bar
foo/bar/baz

which is what I want. If any part of the URL doesn't correspond to an object in the DB, I get a 404.

This works fine for my application, which will never have things ten-levels deep, but I wish I could find a more general solution that could support an arbitrary-depth tree.

1
On

It depends on the structure of your data, which I'm not completely clear on from your question.

If there is a fixed number of levels (or at least a limited range of numbers of levels) with each level corresponding to a specific sort of thing, then Chained can do what you want -- it's valid (and downright common) to have a chained action with :CaptureArgs(1) PathPart('') which will create a /*/ segment in the path -- that is, it gobbles up one segment of the path without requiring any particular fixed string to show up.

If there's not any such thing -- e.g. you're chasing an unlimited number of levels down an arbitrary tree, then a variadic :Args action is probably exactly what you want, and there's nothing dirty in using it. But you don't need to be decoding $c->req->path yourself -- you can get the left-over path segments from $c->req->args, or simply do my ($self, $c, @args) = @_; in your action. You can write a new DispatchType, but it's just not likely to be worth the payoff.