2. About me
● Tudor Constantin
● Perl Developer @ Evozon
● http://programming.tudorconstantin.com/
● http://stackoverflow.com/users/459233/tudor-constantin
● https://github.com/tudorconstantin
● http://www.linkedin.com/in/tudorconstantin
● twitter: @tudorconstantin
● gmail: tudorconstantin at gmail dot com
3. Content
● Sample app overview - Expense Tracker
● (Short) Intro to RESTful Web Services
● DBIx::Class
● Mojolicious
● Routing in Mojo
● Generic Mojo Controller for CRUD operations
● Sample operation - update user
● Steps for getting RESTful routes/operations for a table in DB
4. Sample app Expense
Tracker
VERY simple application - but usable (insert expenses, assign categories and
see reports)
● Five tables
○ users
○ currencies
○ categories
○ operations
○ operations_categories
● Relationships
○ 1 user has many categories
○ 1 user has many operations
○ 1 category has many sub categories
○ 1 category has many operations
○ 1 operation has many categories
○ 1 operation has a currency
1 operation belongs to a user
5. Intro to RESTful Web
Services
REST - REpresentational State Transfer
● concept introduced in 2000 by Roy Fielding in his academic dissertation,
"Architectural Styles and the Design of Network-based Software Architectures"
Main ideas for REST:
● Resources are accessible through unique URLs:
○ /user/23
○ /operations
● Use HTTP methods explicitly
○ GET - for Read
○ POST - for Create
○ PUT - for Update
○ DELETE - for Delete
● Be stateless
○ the server does not know nor cares about the state of the client
application
● Transfer Representations of resources (HTML, JSON, XML, etc)
6. DBIx::Class
● An abstraction for working with DBs
● More than an ORM (Object Relational Mapper) - It knows how to work on
Result Sets
● Components - simplified overview:
○ DBIx::Class::Schema - connection to DB
○ DBIx::Class::ResultSet - a query used for fetching a set of results
○ DBIx::Class::Row - objects returned from DBIx::Class::ResultSets
using the create, find, next and all methods
● Sample usage:
my @rows = ExpenseTracker::Models->connect(
$config->{database}->{ $mode }->{dsn},
$config->{database}->{ $mode }->{user},
$config->{database}->{ $mode }->{password},
) # DBIx::Class::Schema
->resultset( 'ExpenseTracker::Models::Result::User' ) # DBIx::Class::ResultSet
->search_rs(
{ id => 10 },
) # DBIx::Class::ResultSet - only users
with id = 10
->all(); # The collection of DBIx::Class::Row
instances
7. Mojolicious
● Microframework inspired by Sinatra (Ruby)
● Components of interest (for this app)
○ Router
○ Controller
● Not (quite) interested in:
○ Views (since we render mainly json)
● Not provided at all:
○ Models (we plug in and use DBIx::Class)
8. Routing in Mojolicious
In app context (ie - the startup routine):
my $r = $self->routes;
#sample route named 'login' for GET - executing method login from controller ExpenseTracker::Controllers::Login
$r->get('/login')->to('login#login')->name('login');
Shortcut, generic routing:
$params->{app_routes}->add_shortcut(resource => sub {
my ($r, $name ) = @_;
# Generate route for "/$name" - Controller is ExpenseTracker::Controller::camelize($name)
my $resource = $r->route( "/$name" )->to("$name#");
# Handle POST requests - will hit the create method in controller
$resource->post->to('#create')->name("create_$name");
# Handle GET requests - lists the collection of this resource - hits the list method in controller
$resource->get->to('#list')->name("list_$name");
$resource = $r->route( "/$name/:id" )->to("$name#");
$resource->get->to('#show')->name("show_$name");
$resource->delete->to('#remove')->name("delete_$name");
$resource->put->to('#update')->name("update_$name");
return $resource;
});
9. Generic Controller for CRUD
Operations
● Each resource needs a controller responsible for it
● Each of those controllers will have to implement at least 7 actions:
○ create - POST /resource_name - creates a new resource
○ update - PUT /resource_name/:id - updates a resource
○ list - GET /resource_name - show the collection of resources
○ show - GET /resource_name/:id - get the resource with id :id
○ remove - DELETE /resource_name/:id - annihilate resource :id
● Possible approaches
○ create a Moose role that will expose all those methods and use this
role (I guess)
○ implement a basic controller that will be inherited by all the other
resource controller
10. Sample op - update user
In child controller (ExpenseTracker::Controller::User)
=head update
sample of overriding a default update method
route here: PUT /user/:id
=cut
sub update{
my $self = shift;
return $self->render(status => 405, json => {message => 'You can only update your own profile!!!'} )
if ( !defined $self->param('id') or !defined $self->app->user or $self->param('id') != $self->app->user->id );
return $self->SUPER::update(@_);
}
11. Sample op - update user
In base controller ExpenseTracker::Controllers::Base - the one that
ExpenseTracker::Controllers::User inherits from:
sub update{
my $self = shift;
my $result_rs = $self->app->model
->resultset( $self->{resource} )
->search_rs(
{ id => $self->param('id') },
);
return $self->render_not_found if ( scalar( ( $result_rs->all ) ) == 0 );
$result_rs->update_all( $self->req->json );
$result_rs->result_class('DBIx::Class::ResultClass::HashRefInflator');
my @result = $result_rs->all();
return $self->render_json( [ @result ] );
}
12. Steps for getting RESTful
routes/operations
● Generate the DBIx::Class model based on the DB table, with DBIx::
Class::Schema::Loader
● Create a controller that inherits from ExpenseTracker::Controllers::Base
● Add the resource name to the conf.yml
13. The End
Fork the sample app and play with it:
https://github.com/tudorconstantin/expense-tracker