Last Updated: February 25, 2016
·
3.034K
· codeholic

2 nice techniques to refactor Mojolicious controllers

Let's start with this ugly piece of code.

sub grow {
 my ($self) = @_;

 my $moustache = $self->app->moustaches->create({});

 $self->res->code(303);
 $self->redirect_to( 'show_moustache', id => $moustache->id );
}

sub show {
 my ($self) = @_;

 my $moustache = $self->app->moustaches->find( $self->param('id') );
 if (!$moustache) {
 $self->render_not_found;
 }

 $self->stash( moustache => $moustache );
}

sub trim {
 my ($self) = @_;

 my $moustache = $self->app->moustaches->find( $self->param('id') );
 if (!$moustache) {
 $self->render_not_found;
 }

 $moustache->trim;

 $self->res->code(204);
}

sub shave {
 my ($self) = @_;

 my $moustache = $self->app->moustaches->find( $self->param('id') );
 if (!$moustache) {
 $self->render_not_found;
 }

 $moustache->delete;

 $self->res->code(204);
}

The first nice refactoring we can do here is to define an attribute with a lazy builder callback.

has 'moustache' => sub {
 my ($self) = @_;
 return $self->app->moustaches->find( $self->param('id') );
};

sub grow {
 my ($self) = @_;

 $self->moustache( $self->app->moustaches->create({}) );

 $self->res->code(303);
 $self->redirect_to( 'show_moustache', id => $self->moustache->id );
}

sub show {
 my ($self) = @_;

 if ( !$self->moustache ) {
 $self->render_not_found;
 }

 $self->stash( moustache => $self->moustache );
}

sub trim {
 my ($self) = @_;

 if ( !$self->moustache ) {
 $self->render_not_found;
 }

 $self->moustache->trim;

 $self->res->code(204);
}

sub shave {
 my ($self) = @_;

 if ( !$self->moustache ) {
 $self->render_not_found;
 }

 $self->moustache->delete;

 $self->res->code(204);
}

The next obvious candidate for refactoring is the existence check, but there's no analogue of Rails' before_filter in Mojolious. Fortunately there's a nice module Class::Method::Modifiers, which is used in Moo, by the way.

use Class::Method::Modifiers;

has 'moustache' => sub {
 my ($self) = @_;
 return $self->app->moustaches->find( $self->param('id') );
};

around [ qw{ show trim shave } ] => \&_assert_moustache;

sub grow {
 my ($self) = @_;

 $self->moustache( $self->app->moustaches->create({}) );

 $self->res->code(303);
 $self->redirect_to( 'show_moustache', id => $self->moustache->id );
}

sub show {
 my ($self) = @_;
 $self->stash( moustache => $self->moustache );
}

sub trim {
 my ($self) = @_;

 $self->moustache->trim;
 $self->res->code(204);
}

sub shave {
 my ($self) = @_;

 $self->moustache->delete;
 $self->res->code(204);
}

sub _assert_moustache {
 my $orig = shift;
 my $self = shift;

 if ( !$self->moustache ) {
 $self->render_not_found;
 }

 return $orig->($self);
}

Note, that in order to make it possible to prevent execution of the method, that we apply a filter to, we need to use around, because before doesn't allow it.