When I parse html response body I want to find route names for all links found in the body. I use next code snippet:
my $url = Mojo::URL->new( $got );
my $method = uc( $url->query->clone->param( '_method' ) || 'GET' );
my $c = $t->app->build_controller;
my $m = Mojolicious::Routes::Match->new( root => $t->app->routes );
$m->find( $c => { method => $method, path => $url->path } );
Then $m->endpoint->name gives me the name of route.
But is there more simple way to find route name by given path?
I am looking for something like: $app->routes->find( '/api/v/users/146/link/7QRgs' ) which should return user_hash_check because I have next route:
$guest->get( '/users/:id/link/:hash', 'user_hash_check' )->to( 'user#hash_check' );
I have found only one place where we can find route by path. That is Mojolicious::Routes::Match and there is no other way to do this
The one ugly thing here to my mind is requirement to supply Mojolicious::Controller object. But controller is only required to make decision: dispatch or not dispatch Because it has extra info to make this decision: this is data to check conditions
The problem as I think is because of here are mixed two things:
Request
Path
And find should just return all routes matched against given arguments: path and method. Like selectors does The array result maybe cached (now routes with conditions are not cached)
Then dispatcher should check conditions against each route, not matcher. Here each condition may be called in context of right controller and not default one. And this will fix this issue. The routes have in most cases their own controller class have not?
Until this behavior will be fixed the example in the question is the best way to find routes
Related
I have a Mojolicious controller that fires in response to different URL routes. For example, given the URL path:
/v1/users/:someid
and a controller that fires:
sub handle_request ($self) {
my $place_holder_name = $self->route->?????? # how can I get 'someid'?
is($place_holder_name, 'someid', 'can access the placeholder name');
}
How can I find out the name of the placeholder?
Param
These are not currently documented under Mojolicious::Routes, so I can see why that's confusing. They're documented under Mojolicious::Controller#param,
What you have there is a Route param, so you can retreive that value with,
$c->param('someid');
Getting All Params Provided to a Controller
Though undocumneted, you can find the names of the captures in the internal hashref like this,
$self->stash->{'mojo.captures'};
Like this;
my $params = $self->stash->{'mojo.captures'};
warn for keys %$params;
I have the following code in our webapp written using Mojolicious, and it doesn't work as expected: the bridge handler doesn't get the correct stash data derived from routes (gets undef), so the rest of code fails, however, debug output of $self->stash('city') in any of route handlers is as expected.
...
# Router.
my $r = $self->routes->bridge->to('Main#common');
$r->route('/')->to('Main#index')->name('start');
$r->route('/:region/:city/category/:id')->to('Main#list_category')->name('list_category');
$r->route('/:region/:city/part/:id/:name')->to('Main#show_part')->name('show_part');
...
# Controller.
sub common
{
my $self=shift;
my $db=$self->db;
my $city=$self->stash('city');
my $region=$self->db->selectrow_hashref('select * from region where LOWER(translit)=? ORDER BY region_id LIMIT 1',undef,$city);
say "City=$city.";
if(!$region)
{
$region={};
}
$self->stash(region=>$region);
return 1;
}
...
I think it's correct behavior.
Look at this code.
Placeholder is added when the appropriate route is taken to the processing, i.e., step by step.
Really, look at you routes.
my $r = $self->routes->bridge->to('Main#common');
$r->route('/')->to('Main#index')->name('start');
$r->route('/:region/:city/category/:id')->to('Main#list_category')->name('list_category');
$r->route('/:region/:city/part/:id/:name')->to('Main#show_part')->name('show_part');
I can't understand what behavior you expect when go to route /.
Sub common will be invoked in this case. There are no value for placeholder city!
So, correct solution for your routes must look like this:
my $r = $self->routes;
$r->route('/')->to('Main#index')->name('start');
my $r_city = $r->bridge('/:region/:city/')->to('Main#common');
$r_city->route('/category/:id')->to('Main#list_category')->name('list_category');
$r_city->route('/part/:id/:name')->to('Main#show_part')->name('show_part');
By the way,
starting from Mojolicious version 6.0 bridge was deprecated to favor under. So, you need to replace bridge on under.
But, if you very-very want to have value of placeholder city in common function, you may look at this two line.
You need to write this BAD code in common sub:
sub common {
my $self = shift;
my $stack = $self->match->stack;
warn $self->dumper($stack);
...
}
Print $stack and you understand how to get value of placeholder city.
I would like to interpret, for example a request like this:
GET /my/path?foo=bar
just as if it was actually rewritten to e.g.
GET /?path=/my/path&foo=bar
Now I thought I'll be able to achieve this using following route, and use param('path') along with param('foo') and the likes, e.g.:
get '/:path' => sub {
return printf "...so you want %s, thinking that best foo is %s...",
param('path'),
param('foo');
}
but I get 404 -- It seems that the :path part cannot contain slashes.
Can I achieve this with routes at all? Or I'm looking at the wrong direction (I'm fresh new to Dancer)?
You might want to match route by regular expression instead of the token. Matches are then stored in a special array that can be returned by keyword splat. Your path will not be accessible by param('path'), though.
Code:
get qr{/([^?]*)} => sub {
my ($path) = splat;
return printf "...so you want %s, thinking that best foo is %s...",
$path,
param('foo');
}
Imagine situation, when the url should looks like
/catalog/sectionIdent?page=1
where page param is optional.
Of course, custom route should be defined. Consider the following code:
$route = new Zend_Controller_Router_Route_Regex(
'catalog/([-a-z]+)(?:\?page=([0-9]*))?',
array('controller'=>'catalog','action'=>'list','page'=>''),
array(1=>'section',2=>'page'),
'catalog/%s?page=%d'
);
$router->addRoute('catalog-section-page',$route);
But this route won't be triggered with '?' symbol in url.
Without '?' (for example, by adding escaped '!' symbol to pattern) everything works as it should.
Is there any way to achieve '?' presence in custom defined regex route? Maybe I'm doing something wrong in pattern?
P.S.: Don't offer to use '/' instead of '?', question is exactly about pattern restrictions in Zend_Controller_Router_Route_Regex implementation.
The ZF routing classes operate on the REQUEST_URI with the query string stripped off, so you may have a hard time get this working in the way you are expecting. However, I believe GET parameters are put into the request object by default, so you shouldn't need to cater for them in your routes. I'd suggest changing your route to remove the query string parts:
$route = new Zend_Controller_Router_Route_Regex(
'catalog/([-a-z]+)',
array('controller'=>'catalog','action'=>'list'),
array(1=>'section'),
'catalog/%s'
);
$router->addRoute('catalog-section-page',$route);
You should still be able to access the params in your controller as if they had been populated by the routes:
public function listAction()
{
echo $this->_getParam('page');
}
and you can use the same method to set a default:
public function listAction()
{
$page = $this->_getParam('page', 1); // defaults to 1 if no page in URL
}
You just may need to sanitise them there (make sure they are numeric).
Edit:
Example of URL helper with this route:
echo $this->url(array('section' => 'foo', 'page' => 2), 'catalog-section-page')
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.