Model Mojolicious
I have recently noticed that there are few documented examples of Model use in Mojolcious so I thought I'd add one.
The application name I'm using is PersonalTracking instead of the standard MyApp that seems to be used in most documentation.
So let's start with the directory structure I'm using ...
.
|-- db
| |-- tracking.sql
| `-- tracking.db
|-- lib
| |-- PersonalTracking
| | |-- Controller
| | |-- Model
| | |-- Plugin
| | |-- Schema
| | `-- Schema.pm
| `-- PersonalTracking.pm
|-- public
|-- script
|-- t
|-- templates
|-- layouts
`-- main
... it's the standard directory structure provided by mojo generate with the addition of ...
- db - for the database
- lib/PersonalTracking/Model - for the models
- lib/PersonalTracking/Plugin - for the model helpers
- lib/PersonalTracking/Schema - generated by DBIx::Class dbicdump after the creation of the database
... as an aside here is the command used to generate the Schema directory ...
dbicdump -o dump_directory=./lib -o components='["InflateColumn::DateTime"]' PersonalTracking::Schema dbi:SQLite:db/tracking.db
... now setup a Model class to provide the schema ...
package PersonalTracking::Model::Store;
use Mojo::Base -base, -signatures;
use Mojo::Home;
use PersonalTracking::Schema;
state $schema;
# -------------------------------------------------------------------
# Provide schema
# -------------------------------------------------------------------
sub schema
{
state $home = Mojo::Home->new;
state $db_path = $home->rel_file('db');
$schema = PersonalTracking::Schema->connect("dbi:SQLite:$db_path/tracking.db");
return $schema;
}
1;
... which uses Mojo::Home to locate the db directory containing the database.
Now add a Model class that uses that schema ...
package PersonalTracking::Model::Weight;
use Mojo::Base -base, -signatures;
use Carp ();
has 'schema' => sub { Carp::croak 'schema is required' };
# -------------------------------------------------------------------
# Get limited number of recent weights by reverse date
# -------------------------------------------------------------------
sub recent ($self, $limit)
{
my $rs = $self->schema->resultset('Weight')->search(
{},
{
rows => $limit,
order_by => { '-desc' => ['date'] },
}
);
my @rows = map { my %h = $_->get_columns; \%h } $rs->all();
return \@rows;
}
1;
... which as you'd expect requires a schema to be instantiated correctly.
Now to be able to use the models we need to be able to access them easily throughout the application. An application plugin can be used to collect together the setup of the helpers needed to ease access ...
package PersonalTracking::Plugin::Organise;
use Mojo::Base 'Mojolicious::Plugin', -signatures;
use PersonalTracking::Model::Store;
use PersonalTracking::Model::Weight;
sub register ($self, $app, $conf) {
# database schema
my $store = PersonalTracking::Model::Store->new();
# individual models setup as helpers
my $weight = PersonalTracking::Model::Weight->new(schema => $store->schema());
$app->helper('organise.weight' => sub { return $weight });
return;
}
1;
... this creates a new database schema and passes it along to the models, just the one in this case, and provides helpers to access the models.
On to the application start up ...
package PersonalTracking;
use Mojo::Base 'Mojolicious', -signatures;
use PersonalTracking::Model::Weight;
use PersonalTracking::Schema;
sub startup ($c)
{
# Application plugin to provide model helpers
$c->plugin('PersonalTracking::Plugin::Organise');
# Router
my $r = $c->routes;
# Routes
$r->get('/')->to(controller => 'Main', action => 'index');
}
1;
... uses the Schema, Models and Organise Plugin to get everything initialised.
Now to use all that in a Controller to provide the main page ...
package PersonalTracking::Controller::Main;
use Mojo::Base 'Mojolicious::Controller', -signatures;
use PersonalTracking::Plugin::Organise;
use Mojo::JSON qw(encode_json);
sub index ($self)
{
my $days = $self->param('days') || 10;
my $weights = $self->organise->weight->recent($days);
$self->stash(weights => encode_json($weights));
}
1;
... uses the application plugin to access the Weight model via the helper ...
$self->organise->weight->recent($days)
... this also shows passing url parameters into the model.
So finally a very simple front page to show the data being used ...
% layout 'default';
% title 'Welcome';
<h2>Test</h2>
<script type="application/json" id="weight_data">
%== $weights
</script>
... in this case the data is output for later use by a javascript charting library but it demonstrates one of the many ways the data can be used on a page.
I hope that this helps you connect together the various elements required to do things like pass arguments into models and use them easily in controllers. It took me a while to connect the dots and so I hope this helps speed things up for you.