Why separate routing and controller actions in mojolicious?
Asked Answered
L

1

7

I am reading the section of Mojolicious::Guides::Growing where it tells you how to grow a Mojolicious::Lite into a "well organized" cpan-uploadable application. First, it tells you to split the M::L app into a launch script and an application class.

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

use MyUsers;

sub startup {
  my $self = shift;

  # ...auth stuff omitted...

  my $r = $self->routes;
  $r->any('/' => sub {
    my $self = shift;

    my $user = $self->param('user') || '';
    my $pass = $self->param('pass') || '';
    return $self->render unless $self->users->check($user, $pass);

    $self->session(user => $user);
    $self->flash(message => 'Thanks for logging in.');
    $self->redirect_to('protected');
  } => 'index');

  $r->get('/protected' => sub {
    my $self = shift;
    return $self->redirect_to('index') unless $self->session('user');
  });

  $r->get('/logout' => sub {
    my $self = shift;
    $self->session(expires => 1);
    $self->redirect_to('index');
  });
}

1;

This makes sense to me. But then it goes on to say that this application class can further be refactored into a controller class with the actions, and the application class itself can be reduced to the routing information:

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

sub index {
  my $self = shift;

  my $user = $self->param('user') || '';
  my $pass = $self->param('pass') || '';
  return $self->render unless $self->users->check($user, $pass);

  $self->session(user => $user);
  $self->flash(message => 'Thanks for logging in.');
  $self->redirect_to('protected');
}

sub protected {
  my $self = shift;
  return $self->redirect_to('index') unless $self->session('user');
}

sub logout {
  my $self = shift;
  $self->session(expires => 1);
  $self->redirect_to('index');
}

1;

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

use MyUsers;

sub startup {
  my $self = shift;

  # ...auth stuff omitted...

  my $r = $self->routes;
  $r->any('/')->to('login#index')->name('index');
  $r->get('/protected')->to('login#protected')->name('protected');
  $r->get('/logout')->to('login#logout')->name('logout');
}

1;

I don't see why this is superior to the "hybrid" version where routes and actions are intermingled, because now in order to redirect between the actions with redirect_to() in the controller, you need to look at the routing infomration in a different file, and if you want to change a url, you have to do it in two different files instead of one. This:

  $r->get('/protected' => sub {
    my $self = shift;
    return $self->redirect_to('index') unless $self->session('user');
  });

turns into:

sub protected {
  my $self = shift;
  return $self->redirect_to('index') unless $self->session('user');
}

$r->get('/protected')->to('login#protected')->name('protected');

Which has the word "protected" 4 times in two different files (although I'm not sure what the name("protected") does yet).

I'm a complete novice when it comes to web development, by the way.

Lashing answered 12/9, 2012 at 22:21 Comment(0)
S
9

It's not superior; rather, it's different.

As soon as you move beyond one developer, having your app in one file is no longer a benefit; you'll end up stepping on each others toes. Even if you're the only dev, it's never easy to keep track of locations in files of 1000+ lines. In addition, being able to look at one file and determine all your routes at a glance is quite useful when you have more than just a few routes, not to mention 100+.

Also, you don't have to change a redirect url in a controller action when the route changes. Mojolicious will do the work for you if you're making use of named routes.

Seamark answered 12/9, 2012 at 22:55 Comment(1)
OOhhhh, I didn't realize that redirect_to('foo') interpreted 'foo' as a name instead of as the path '/foo', and that the M::L get/post/any/etc functions automatically created names. It all makes sense now, thanks.Lashing

© 2022 - 2024 — McMap. All rights reserved.