MojoForum - A Forum in Mojolicious (Part-2)

Welcome back. This article is in continuation of learning the Mojolicious by developing a project from scratch. The name of the Series is MojoForum and, this is the second article of the series. For the previous write-up, visit here.

Ashutosh Kukreti

Welcome back. This article is in continuation of learning the Mojolicious by developing a project from scratch. The name of the Series is MojoForum and, this is the second article of the series. For the previous write-up, visit here.

The project repository on git is MojoForum.

In the Part-1, we accomplished:

  1. Installation of Perl
  2. Installation of MySQL
  3. Installation of Mojolicious
  4. Create and Launch the Application
  5. Create the Database Tables and their relationships.

We have the database and the tables. However, we do not have the data in the columns. Let's add some data in the database.

Visit the git repository and go to the src_data and download all the CSV files.

wget https://raw.githubusercontent.com/akuks/MojoForum/master/src_data/Post.csv
wget https://raw.githubusercontent.com/akuks/MojoForum/master/src_data/Thread.csv
wget https://raw.githubusercontent.com/akuks/MojoForum/master/src_data/User.csv

The above files are generated by 'generate_fake_data.pl'. I need to modify the script to generate and insert the data into the MySQL. So that we have the painless data generation process. I think this is where the Mojolicious lags a bit from other frameworks like Django/Laravel where the fake data generation is integrated within the framework. If Mojolicious already has this functionality, Please do let me know.

But it does other things like async and IOLoop much better than the other frameworks so we can live with manual fake data generation. Probably by the end of this series, we'll have the way to generate the fake data directly in to the MySQL

Alternatively, you can clone the git repository, using:

git clone https://github.com/akuks/MojoForum.git -b YOUR_BRANCH_NAME

NOTE: Please don't clone the master. Create a new branch and then clone it.

If you are using SQL Pro or MySQL Workbench, then import the CSV file using the "Table Data Import Wizard" in the following order:

  1. User.csv
  2. Thread.csv
  3. Post.csv

You can also use command line to import the CSV.

What we have done so far:

  1. We generate fake data for the user, thread, and replies tables.
  2. We import the data into MySQL.

The next step is to quickly go through the files structure generated by the Mojolicous framework.

Package mojoForum.pm is the entry point of the application. All the routes and basic configuration defined in this Perl package. And then the request is transferred to the controller under the lib/mojoForum folder. From the controller, the information carried to the templates, and then the user views it on the browsers. Please refer the image for more details.

We'll understand the more concepts as we get along with forum development.

Setting up Mojolicious

In this section of the course, we'll set up the Mojolicious with initial configurations required for the development of MojoForum. We set up the database in the earlier section. However, we don't have any method to pull it up from the database. Let's set up the database connection.

Setup DBIx with Mojolicious

For this course, we use DBIx to fetch the data from the MySQL/MariaDB database.

Create a new file dbschema.conf and update the file with the following content

schema_class mojoForum::Schema

# connection string
<connect_info>
    dsn     dbi:mysql:MojoForum
    user    your_db_user
    pass    your_db_password
</connect_info>

# dbic loader options
<loader_options>
    dump_directory ./lib/
    components     InflateColumn::DateTime
    components     TimeStamp
    components     EncodedColumn
</loader_options>
dbicdump dbschema.conf

Go to the root of the application directory and run the above command. Please install dbicdump if not installed already.

Meanwhile, the above commands execute successfully. It-dumps the database structure under the Schema folder. And Schema folder is under the lib/mojoForum directory.

Dumping the schema is one of the essential steps in our forum. Yet there is one more steps i.e. link the database with our application.

Create the DB.pm file under the lib/mojoForum directory.

cd lib/mojoForum
touch DB.pm

Open the DB.pm file and add the following content:

package mojoForum::Model::DB;

use mojoForum::Schema;
use DBIx::Class ();

use strict;

my ($schema_class, $connect_info);

BEGIN {
    $schema_class = 'mojoForum::Schema';
    $connect_info = {
        dsn      => 'dbi:mysql:mojoForum',
        user     => 'root',
        password => '',
    };
}

sub new {
    return __PACKAGE__->config( $schema_class, $connect_info );
}

sub config {
    my $class = shift;

    my $self = {
        schema       => shift,
        connect_info => shift,
    };

    my $dbh = $self->{schema}->connect(
        $self->{connect_info}->{dsn}, 
        $self->{connect_info}->{user}, 
        $self->{connect_info}->{password}
    );

    return $dbh;
}

1;

Add use mojoForum::Model::DB to mojoForum.pm just before the startup subroutine. Also add _db_handler() subroutine assign the database handler to our application.

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

use mojoForum::Model::DB;           # Add here

# This method will run once at server start
sub startup {
  my $self = shift;
  ....
  ....
  $self->_db_handler();
 }

 # Add Database Handler
 sub _db_handler {
    my $self = shift;

    $self->{ dbh } = mojoForum::Model::DB->new();

    return $self;;
}

 1;

In the main application file, by default there is only route added to the application. We want our home page to load all the forums.

$r->get('/')->to('example#welcome');

The above is the default route present in the main file. We'll go through it later. For now, replace the line with

$r->get('/')->to(controller => 'Thread', action => 'show');

It states, whenever we have a request for '/' (the root of the application), go to the Thread controller and look out for the show subroutine in the Thread controller package.

Before creating the thread controller, please delete the example folder under the templates and Example.pm under the controller.

We have not created the Thread controller yet. Please create Thread.pm, under the Controller folder and write the following code.

package mojoForum::Controller::Thread;
use Mojo::Base 'Mojolicious::Controller';

# This action will render a template
sub show {
    my $self = shift;

    my $dbh = $self->app->{dbh};            # Database handler

    # Fetch all the threads from the thread table;
    my @threads = $dbh->resultset('Thread')->search({});

    @threads = map { { 
        title => $_->title,
        body  => $_->body,
        user  => $_->user->first_name. ' ' . $_->user->last_name
    } } @threads;

    $self->render(
        template => 'thread',               # Template name, thread.html.ep under the templates folder.
        threads  => \@threads               # Pass thread array ref to the template
    );
}

1;

Above, we retrieved all the threads from the thread table. And store it in an array. We want to render all the data in the template thread. We also pass the reference of array threads to the template threads => \@threads.

% foreach my $thread (@$threads) {
    <li> <%= $thread->{title} %> </li>
%}

Refresh the webpage.

All the title of the threads listed in the page.

It doesn't looks nice. We'll add it some styles to it.

Adding Style to our Forum

We are going to add some styles in mojoForum to improve the UI look and feel. For our forums, we are going to use CSS only framework and if required, then we add our javascript of our own.

For CSS only framework, we use Tailwind CSS. It is a utility-first CSS framework for quickly building the designs. At present, we directly include the CSS using the CDN, however in the later parts, we limit the size of Tailwind to include the relevant classes to make our application lighter and faster.

Open default.html.ep and update the content with:

<!DOCTYPE html>
<html>

<head>
    <title><%= title %></title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <meta name="description" content="">
    <meta name="author" content="">
    <link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet">

</head>

<body>
    <div class="container mx-auto">

        <nav class="flex justify-center p-4"><a class="text-xl text-indigo-500 font-semibold" href="#"></a></nav>

        <nav class="flex flex-wrap bg-indigo-500 items-center justify-between p-4">

            <div class="flex flex-shrink-0 mr-10">
                <a class="text-xl text-white font-bold" href="/"> MojoForum </a>
            </div>

            <div class="block lg:hidden">

                <button
                    class="navbar-burger flex items-center py-2 px-3 text-indigo-500 rounded border border-indigo-500">

                    <svg class="fill-current h-3 w-3" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
                        <title>Menu</title>
                        <path d="M0 3h20v2H0V3zm0 6h20v2H0V9zm0 6h20v2H0v-2z" />
                    </svg>

                </button>

            </div>

            <div class="navbar-menu hidden lg:flex lg:flex-grow lg:items-center w-full lg:w-auto">

                <div class="lg:mr-auto">

                    <a class="block lg:inline-block mt-4 lg:mt-0 mr-10 text-white font-bold hover:text-blue-700" href="#">Threads </a>

                </div>

                <div>

                    <a class="block lg:inline-block mt-4 lg:mt-0 lg:mr-8 text-white hover:text-red-500 " href="#">Sign in </a>

                    <a class="inline-block py-3 px-6 mt-4 lg:mt-0 leading-none text-indigo-500 bg-white hover:bg-indigo-600 hover:text-white rounded shadow"
                        href="#">Sign up
                    </a>

                </div>

            </div>

        </nav>

        <%= content %>

    </div>

</body>

</html>

Refresh the webpage now. :)

We observe that our forum is getting into the shape. Still the list of threads is not looking good. We need to modify it.

Go to the thread.html.ep and modify the content as:

% layout 'default';
% title 'MojoForum - A Forum with Mojolicious';

% foreach my $thread (@$threads) {
    <div class="w-full border rounded py-3 mt-5">  

        <div class="font-bold text-blue-700 ml-5">
            <%= $thread->{title} %> 
        </div> 

    </div>

%}

Refresh the web page now. We can see that threads are looking much nicer than before.

Still our forums are far from over. We noticed the following problem:

  1. Threads are not clickable.
  2. We cannot see the comments on the thread.
  3. We cannot see at what time it was posted.
  4. User who posted the threads/comments.

We address the above issues in our next article.

All the code pushed to git repository MojoForum.

'Have fun learning Mojolicious'.

Mojolicious