Categories
Mojolicious Perl Test::Most

BeastBB, my new project to build a Bulletin Board like webpage using Mojolicious.

https://gitea.sergiotarxz.freemyip.com/sergiotarxz/BeastBB

Just to improve with Mojolicious as Perl web developer I started a few weeks ago a project called BeastBB which I want to be just another Bulletin Board webpage.

In this project I am learning a lot about Mojo::Pg, for example I learned how to test it using DBD::Mock, and also learned to use SQL::Abstract::Pg which makes querying simple things or inserts/updates less overhead.

Also in the development of this project I found how to avoid a nasty error which required my Makefile.PL to run the install target two times before the templates being correctly installed.

I also achieved .config/beastbb/ config file installation which is great because mades the installation project dir agnostic, you can run cpanm . –installdeps && cpanm . -v and then delete the project dir and the webpage will work anyway. The database migrations files also get installed automatically and if new migrations are found after an upgrade they will be run without the need to do it manually.

I created an object called BeastBB::Response which is used to control controllable errors, you typically would use it like this:

my $response = FunctionThatMayFail();
return $self->reply->exception('Function failed because the data is wrong with message ' . $response->ErrorMessage)->rendered(400) if $response->IsError;
my $data = $response->Content;
doThings($data);

The BeastBB::Response code is something like this:

package BeastBB::Response;

use 5.32.1;

use strict;
use warnings;

use Carp qw/confess cluck/;

use Params::ValidationCompiler 'validation_for';
use Types::Standard qw/Bool Str Any/;

{
    my $validator = validation_for(
        params => {
            is_error      => { type => Bool, default  => 0 },
            content       => { type => Any,  optional => 1 },
            error_message => { type => Str,  optional => 1 },
        }
    );

    sub new {
        my $class    = shift;
        my %params   = $validator->(@_);
        my $is_error = $params{is_error};
        my $content;
        my $error_message;

        if ( exists $params{content} ) {
            $content = $params{content};
        }
        if ( exists $params{error_message} ) {
            $error_message = $params{error_message};
        }

        if ($is_error) {
            cluck 'Error should not have content, stripping it.'
              if defined $content;
            cluck 'You should pass a error message on error.'
              if !defined $error_message;
            return bless {
                is_error      => 1,
                error_message => $error_message // '',
            }, $class;
        }
        return bless { content => $content }, $class;
    }
}

sub IsError {
    my $self = shift;
    return $self->{is_error};
}

sub ErrorMessage {
    my $self = shift;
    if ( !$self->IsError ) {
        confess 'This is not an error.';
    }
    return $self->{error_message};
}

sub Content {
    my $self = shift;
    if ( $self->IsError ) {
        confess 'Attempt to get content from error.';
    }
    return $self->{content};
}
1;

I trying to test most I do, but the 100% coverage is still a challenge because testing controllers is hard and sometimes I am to lazy to test too trivial things:

I didn’t achieve yet to split the Mock objects from the real code, I suppose more Mock objects will be needed to avoid to do large objects constructions in most tests like the ones needed to build a User which is a object which takes many parameters.

Params::ValidationCompiler is making my live easier than it was with Params::Validate allowing me to define my own types like matrix_address or asking for a concrete class.

I made a class with a few utility types to reuse them everywhere called BeastBB::Types:

package BeastBB::Types;

use 5.30.3;

use strict;
use warnings;

use Exporter qw/import/;
use Scalar::Util qw/blessed/;
use Type::Tiny;

use Const::Fast;

our @EXPORT_OK = ( '&IsClassTypeGenerator', '$MATRIX_ADDRESS_REGEX', '$MATRIX_ADDRESS_TYPE' );

const our $MATRIX_ADDRESS_REGEX => qr/^@\w+:(\w|\.)+\.(\w+)$/;


const our $MATRIX_ADDRESS_TYPE => Type::Tiny->new(
    name => "MatrixAddressChecker",
    constraint => sub {
        my $matrix_address = shift;
        return 1 if $matrix_address =~ /$MATRIX_ADDRESS_REGEX/;
    }
);

my %generated_classes;

sub IsClassTypeGenerator {
    my $class = shift;
    if ( !exists $generated_classes{$class} ) {
        my $sanitized_class = $class =~ s/:://gr;
        $generated_classes{$class} = Type::Tiny->new(
            name => "Is$sanitized_class",
            constraint => sub {
                my $item_to_test = shift;
                return 1 if blessed $item_to_test && $item_to_test->isa($class);
                return 0;
            },
        );
    }
    return $generated_classes{$class};
}
1;

It’s being really fun to build BeastBB with Perl thanks to all the libraries I can use, in my cpanfile you can find all those:

requires 'Mojolicious';
requires 'Mojo::Pg';
requires 'ExtUtils::MakeMaker';
requires 'Crypt::URandom';
requires 'DBD::Pg';
requires 'DBD::Mock';
requires 'Const::Fast';
requires 'Params::ValidationCompiler';
requires 'Types::Standard';
requires 'Crypt::Bcrypt::Easy';
requires 'DateTime';
requires 'DateTime::Format::Pg';
requires 'Test::Most';
requires 'Test::MockModule';
requires 'Test::Warnings';

They may not seem to be too much, but they give me the capabilities of their dependencies like does Mojo::Pg with SQL::Abstract::Pg.

Using Perl to build a web is a fun exercise with Mojolicious.

By sergiotarxz

I am a software developer with high interest on free software.

Leave a Reply

Your email address will not be published. Required fields are marked *