Recently I’ve been working on MiniMVC, a very tiny MVC framework based on Mason. When I’ve mentioned it to people, I’ve had a surprisingly high number say that they’ve heard of MVC — the Model-View-Controller pattern — but aren’t quite sure what it means in practice.
So here’s a demonstration, in AMAZING TECHNICOLOR!
First up, some CGI
This is a simple CGI script, very much like what you would’ve been writing in the late 1990s. It takes a web request including the ID of a book in a library and displays that book’s details. You’d call it by going to a URL like http://example.com/show_book.cgi?id=42.
This code is untested — I haven’t actually run it — but you’ll get the general idea. I’ve also left out a certain amount of boilerplate, which I hope you’ll excuse.
Without further ado, I present: Web programming circa 1998!
#!/usr/bin/perl use strict; use warnings; use CGI::Simple; use DBI; my $cgi = CGI::Simple->new(); my $dbh = ... # connect to database here my $book_id = $cgi->param('id'); print $cgi->header(); unless ($book_id) { print qq(
Error
You need to provide the ID of a book to display. ); exit; } my $sth = $dbh->prepare(qq( SELECT * FROM Books WHERE ID = ? )) or die "Can't prepare SQL statement: " . $dbh->errstr(); my $book; if ($sth->execute($book_id)) { $book = $sth->fetchrow_arrayref; } unless ($book) { print qq(
Error
Couldn't find that book in the database. ); exit; } print qq(
$book->{title}
Author: $book->{author}
Published $book->{year} by $book->{publisher}. );
Three main activities
There are three main types of code going on here.
1. Talking to the database 2. Printing out HTML
3. Everything else.
Let’s do some colouring-in and you’ll see what I mean
Green for database, red for HTML, blue for everything else (in this case, the logical flow and error handling).
As you can see, it’s all mixed up: there are chunks of colour all over the place. This is only a tiny example, but imagine if you wanted to write a whole library catalogue system like this. Now imagine you want to tweak the HTML slightly. You’d have to go digging through all that CGI Perl code to get at it. Now imagine a change to the database structure, which would mean you have to go through a whole heap of CGI scripts and make sure that all the SQL embedded in there is updated. It gets old really fast.
Splitting it out into M, V, and C
By using a Model-View-Controller layout, you keep these three types of code (database, display, and other) quite separate.
Here’s a demo using MiniMVC, but the principle holds true for any web-based MVC framework, whether it’s Ruby on Rails, Catalyst, or whatever.
Put your database code in a “Model” class
In MiniMVC, and assuming this application is called “Bookshelf”, that’d be `lib/Bookshelf/Model/Book.pm`.
Ordinarily you’d use an ORM (Object Relational Mapping) library for this, to avoid having to write any SQL whatsoever. Since I don’t want to have to explain all that as well, I’m going to more or less just use the old-fashioned DBI techniques you saw above.
Put your display code in a “View”
In MiniMVC, views are HTML::Mason templates and live in the `view/` subdirectory. We’ll need two of them: one to display the book, and one to display error messages.
Here’s book display one, aka `view/book/display.mhtml`, first:
And here’s the error one, `view/error.mhtml`:
Everything else goes in a Controller class
Let’s call it `lib/Bookshelf/Controller/Book.pm`.
Bring it all together
Finally, we need to make sure there’s a mapping between a URL like http://example.com/book/display/42 and the controller we just wrote. In MiniMVC we do that by editing the Dispatcher class, `lib/Bookshelf/Dispatcher.pm`:
package Bookshelf::Dispatcher; use base MasonX::MiniMVC::Dispatcher; sub new { my ($class) = @_; my $self = $class->SUPER::new({ 'book' => 'Bookshelf::Controller::Book', } } 1;
Now, when you point your browser at http://example.com/book/display/42, the dispatcher knows to use the controller `Bookshelf::Controller::Book` for any URL with `book` at the start. It then calls the subroutine `display()` passing it the argument `42`, which gets used to fetch the book by ID from the model class.
Et voila!
Having separated out the model, view, and controller code, you end up with something that’s much easier to develop and maintain. Need to change the way the website is displayed? All you touch is the view. Need to swap out your current database and replace it with a new one? No need to touch anything but the model. New business requirements mean you need to add some extra control-flow logic? The controller’s where it’s at.
Read more: Model-view-controller on Wikipedia.