newbie question about routing

81 views
Skip to first unread message

hwmbo...@gmail.com

unread,
Jan 2, 2019, 5:34:20 AM1/2/19
to Mojolicious
Hi. I want to route a request to a controller that's dynamically constructed, base on a place holder in the url. To illustrate, I want do something like the following:

use Mojo::Base 'Mojolicious';

sub startup {

my $r = $self->routes;
           my $route = $r->get('/:customer/boxs/:id');
 
[ somehow, extract the :customer holder into a variable $customer, then ]
           $route->(
controller => "$customer::boxs',
action     => 'get_list',
);
}


I tried but can't seem to find way to this, and I'm not sure if this is possible?
Some pointer is appreciated. Thanks. 
 

Stefan Adams

unread,
Jan 2, 2019, 10:34:06 AM1/2/19
to mojolicious
Hi!  Welcome to the group and to Mojolicious!

For your specific question, see Mojolicious::Routes::Route->to and the bottom part of the Optional Placeholders section of the Routing Guide, specifically:
# /           -> {controller => 'foo',   action => 'bar'}
# /users      -> {controller => 'users', action => 'bar'}
# /users/list -> {controller => 'users', action => 'list'}
$r->get('/:controller/:action')->to('foo#bar');

Special stash values like controller and action can also be placeholders, which is very convenient especially during development, but should only be used very carefully, because every controller method becomes a potential route. All uppercase methods as well as those starting with an underscore are automatically hidden from the router and you can use "hide" in Mojolicious::Routes to add additional ones.

To know more, please definitely read the Tutorial and all of the Guides.  But if you have any more questions, please don't hesitate to ask for Support.


--
You received this message because you are subscribed to the Google Groups "Mojolicious" group.
To unsubscribe from this group and stop receiving emails from it, send an email to mojolicious...@googlegroups.com.
To post to this group, send email to mojol...@googlegroups.com.
Visit this group at https://groups.google.com/group/mojolicious.
For more options, visit https://groups.google.com/d/optout.
Message has been deleted

hwmbo...@gmail.com

unread,
Jan 2, 2019, 4:22:51 PM1/2/19
to Mojolicious

Thanks Stefan. 


So, trying a different tack, I was able to parse the url of a request with a 'before_depatch' hook, and figure out what Customer Class should be used, before routing happens.

Assuming the request url is /kwikemart/liquor/22, I figure out the customer name (kwikemart) like this:


sub startup {
    my $self = shift;
    $self->hook( before_dispatch => sub {
            my $c= shift;
            $c->req->url->path =~ m#^/(\w+)/.+#;
            $customer = $1;
    }
}


The question is, where to store this $customer, so I can use it to specify the correct route later? I still don't quite get how Mojo::Base Class work. Not sure how to correctly create custom attribute, that's local to each request, so it can be used within a $r->to() call.

Stefan Adams

unread,
Jan 2, 2019, 5:27:44 PM1/2/19
to mojolicious
Hi!  I'm glad you were able to figure that out and even using the more advanced hook feature, but if I'm understanding your objective correctly, I would advise that you follow the references I shared.  In essence, if I'm reading it correctly, you are using a framework and then solving problems on your own which are already solved by the framework.

I just want to make sure you get off to a good start with Mojolicious so that you can reap the full benefits of the framework and not become irritated that it's not doing things that you want easily -- essentially questioning why you're using this framework in the first place.  :)

I'd recommend starting with a Mojolicious::Lite app.

Start with the Hello World in the Tutorial.  And then expand it for your needs, perhaps like so:
#!/usr/bin/env perl
use Mojolicious::Lite;

get '/:customer' => sub {
  my $c = shift;
  $c->render(inline => 'Hello, <%= param "customer" %>!');
};

app->start;

Also, just to show some other equivalent ways to do the same thing:
#!/usr/bin/env perl
use Mojolicious::Lite;

get '/:customer' => sub {
  my $c = shift;
  my $customer = $c->param('customer');
  $c->render(inline => 'Hello, <%= $customer %>!', customer => $customer);
};

app->start;

Also:
#!/usr/bin/env perl
use Mojolicious::Lite;

get '/:customer' => sub {
  my $c = shift;
  $c->render(inline => 'Hello, <%= $c->param("customer") %>!');
};

app->start;

On Wed, Jan 2, 2019 at 3:39 PM <hwmbo...@gmail.com> wrote:

Thanks Stefan. 


So, trying a different tack, I was able to parse the url of a request with a 'before_depatch' hook, and figure out what Customer Class should be used, before routing happens.

Assuming the request url is /kwikemart/liquor/22, I figure out the customer name (kwikemart) like this:


sub startup {
    my $self = shift;

    $self->req->url->path =~ m#^/(\w+)/.+#;
    $customer = $1;
}


The question is, where to store this $customer, so I can use it to specify the correct route later? I still don't quite get how Mojo::Base Class work. Not sure how to correctly create custom attribute, that's local to each request, so it can be used within a $r->to() call.





On Wednesday, January 2, 2019 at 4:34:20 AM UTC-6, hwmbo...@gmail.com wrote:

--

hwmbo...@gmail.com

unread,
Jan 2, 2019, 10:04:20 PM1/2/19
to Mojolicious
Hey, no problem. Mojolicious so far, can do just about anything I can think of, without much difficulty, as most stuff just work out of the box, which is a pleasant surprise.

If you don't mind, I'll take a step back, and describe my design and reason behind it, and see if you can maybe spot something that's wrong with my approach. Here's the hypothetical scenario:

1. Two customers, one Kwikemart, and one Ezymart, they both handle boxes, but in different ways, one open it with a cutter, say, and the other one insist on using bare hands.
2. So I build one Customer class, and extend it, to implement actual customer. Both Kwikemart and Ezymart are children of Customer class, and each has a open_box() method, but within it, they each do their own thing.
3. In order to expose the boxes' content as RESTful resource, the url to access them, will be something like /api/v1/:customer/box/:boxid

Of course, I could just code the route explicitly, since there's only two customer, by having:

     $r->get('/api/v1/kwikemart/box/:id')->to('Customer::Kwikemart#open_box');
     $r->get('/api/v1/ezymart/box/:id')->to('Customer::Ezymart#open_box');

However, it just feels not elegant. The name of the class, is the only difference between the two different routes, and it'll be great to generalize it within the dispatching process, so if I have hundreds customers to do tomorrow, each of their own variants of 'open_box()' action, can be routed with just one call, instead of hundreds, as long as the url conforms to the '/api/v1/:customer/box/:id' format.

Maybe I am just trying to be too clever for my own good, but I really think there must be an easy and elegant way to do this?

Stefan Adams

unread,
Jan 5, 2019, 9:33:08 PM1/5/19
to mojolicious
Seems like you understand your problem and have a well-thought out design pattern!  I think what you are wanting to accomplish elegantly is exactly what Mojolicious was built for!

Your two sample routes can be condensed into one to handle any number of customers, subclassed from a base class:

  $r->get('/api/v1/:controller/box/:id')->to('#open_box');

$ mojo generate app
   :
$ cat my_app/lib/MyApp.pm my_app/lib/MyApp/Controller/Customer.pm my_app/lib/MyApp/Controller/Kwikemart.pm my_app/lib/MyApp/Controller/Ezymart.pm 
package MyApp;
use Mojo::Base 'Mojolicious';
sub startup {
  my $self = shift;
  my $r = $self->routes;
  $r->get('/api/v1/:controller/box/:id')->to('#open_box');
}
1;
package MyApp::Controller::Customer;
use Mojo::Base 'Mojolicious::Controller';
sub open_box { die "open_box method not implemented by subclass" }
1;
package MyApp::Controller::Kwikemart;
use Mojo::Base 'MyApp::Controller::Customer';
sub open_box {
  my $self = shift;
  $self->render(text => 'Hello, Kwikemart Customer!');
}
1;
package MyApp::Controller::Ezymart;
use Mojo::Base 'MyApp::Controller::Customer';
sub open_box {
  my $self = shift;
  $self->render(text => 'Welcome to Ezymart!');
}
1;
$ perl my_app/script/my_app routes
/api/v1/:controller/box/:id  GET  apiv1controllerboxid
$ perl my_app/script/my_app get /api/v1/kwikemart/box/1
Hello, Kwikemart Customer!
$ perl my_app/script/my_app get /api/v1/ezymart/box/1
Welcome to Ezymart!
Reply all
Reply to author
Forward
0 new messages