Search code examples
mojolicious

Delegate Rerouting to Controller


Mojolicious + Hypnotoad.

I want my controllers to reroute the remaining portion of the request, so that I don't have to declare all routes at the level of the main script.

So for example, '/foo/bar/baz' should route to Controller 'FOO', which will then decide routing for 'bar/baz', internal to itself.

Main script :

package MyApp;
use Mojo::Base 'Mojolicious';
use Mojolicious::Plugin::Config;

sub startup {
    my $self = shift;

    $self->moniker('MyApp');
    $self->plugin('Config');

    my $r = $self->routes;
    $r->any('/foo/*remainder')->to('FOO#rerouter')->name('TEST_NAME');
}

I've tried one way, by dynamically adding routes, but running it many times shows inconsistency - probably caused by a race condition or something :

package MyApp::Controller::FOO;
use Mojo::Base 'Mojolicious::Controller';

sub rerouter {
    my $self = shift;

    my $r = Mojolicious::Routes->new;
    $r->get('bar/:baz')->to('myInternalControllerAction'); 

    my $current_route = $self->current_route; # 'TEST_NAME'

    $self->app->routes->find($current_route)->add_child($r);
}

sub myInternalControllerAction { #stuff }

That seems consistent with this other answer that :

"The router is dynamic until the first request has been served, after that, the router cannot change routes"

https://stackoverflow.com/a/22291864/2304437

Even if that did work though, the execution would exit the controller FOO before re-entering it on a different action via the dynamically added route(s).

The other way I can do it is just create a dispatch table in the "re-router" of the controller, since I have access to the remainder of the request anyway :

sub rerouter {
    my $self = shift;
    my $remainder = $self->stash->{remainder};
    # ...
}

But then I lose benefits of Mojolicious' dispatcher - I would have to parse the remaining request URL / path myself. Feels dirty. Hooks don't seem appropriate here either.

How can I elegantly let the controller route its own child portion ?


Solution

  • I solved this as follows :

    use MyApp::Controller::FOO; 
    my $r = $self->routes;
    my $r_root = $r->under('/' => sub { return 1; });
    
    my $baz = $r_root->under('/baz' => sub { return 1; });
    $baz->add_child($MyApp::Controller::FOO::routes);
    

    Then in the module :

    package MyApp::Controller::FOO;
    use Mojo::Base 'Mojolicious::Controller';
    
    our $routes = Mojolicious::Routes->new;
    my $r = $routes->under('/')->to('FOO#bar');
    
    sub bar { ... }
    

    Maybe not as elegant as it could be, but it works very well for me.