Last Updated: February 25, 2016
·
2.729K
· 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.