Catalyst dispatcher for arbitrary tree-structure
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.
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.
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.
精彩评论