Skip to content

Latest commit

 

History

History
352 lines (277 loc) · 12.9 KB

tools.md

File metadata and controls

352 lines (277 loc) · 12.9 KB

Underlying Tools

All of the web development mechanisms that we will cover use many of the same underlying tools. To avoid repetition, we will introduce some of the most important of these tools in this chapter.

  • Perl is the programming language that we will use for the web-server side code in all of our examples.

  • Moose (and its cut-down cousin, Moo) gives a simple and powerful way to write Object-Oriented code in Perl.

  • The Template Toolkit is a great way to separate the parts of our code that display data to the users from the parts which work out what we need to show to the users.

  • DBIx::Class is used to communicate with a database.

  • PSGI is a specification that defines the interaction between a web server and a web application. It's like a super-charged version of CGI. Plack is a toolkit for working with that specification.

Perl

Moose

When Perl 5 was released in 1994, one of the major new features it added was the ability to write Object Oriented code. Sometimes, people complain that Perl's OO functionality feels a bit "bolted-on" to the existing language. This is a completely fair criticism, as this feature was bolted on to the existing language.

When the Perl 6 project started, people started to think about how they really wanted Perl's Object Oriented features to work. Some years later, when Perl 6 still hadn't appeared, other people started to wonder whether it was possible to support a Perl 6-style OO system in Perl 5. The results of these experiments were released to CPAN as Moose - The Modern Object Oriented System. Since its original release, Moose has become incredibly popular among Perl programmers and it is now the de-facto standard approach for OO programming in Perl.

Moose works by adding a number of new keywords to the Perl language. In fact, these "keywords" are mostly just subroutines, but they are usually called in a way that disguises that fact. These keywords allow you to define classes, attributes and methods in a far more easy way than "classic" Perl 5 OO. Here is a simple example of a Moose class that models a person.

package Person;
use Moose;

has name => (
    isa      => 'Str',
    is       => 'rw',
    required => 1,
);

has dob => (
    isa      => 'DateTime',
    is       => 'ro',
    required => 1,
);

has gender => (
    isa      => 'Str',
    is       => 'rw',
    required => 1,
);

This defines a class called "Person" which has three attributes - a name, a date of birth and a gender. Classes are defined with the package keyword (because classes are just like any other Perl package) and attributes are defined with the has keyword. Attributes have a number of properties which are used to define the attribute. In this example I have used isa (to define the type of the attribute), is (to say whether the attribute is read-only or read-write) and required (to say whether the attribute is mandatory or optional) but there are a number of other options available.

Even with this simple class we can start to write programs. We can create objects of our class and access their attributes. We can also change any attributes that are marked as read-write.

#!/usr/bin/perl
use strict;
use warnings;
use 5.010;
use DateTime;
use Person;

my $newborn = Person->new(
    name   => 'Miles',
    dob    => DateTime->now,
    gender => 'M'
);

say $newborn->name; # displays 'Miles'
say $newborn->dob;  # displays current date
$newborn->name('Max'); # Parents change their minds
say $newborn->name; # displays 'Max'

Generated methods

You'll see from the previous example that Moose has generated some methods for us. Firstly we get a new() method that constructs a new instance of the class (an object). Moose's new() method checks that all mandatory attributes have been given (using the required property) and also checks that the attributes are all of the correct types (using the isa property). If any of these checks fail then your program will die with an error message. But if the checks all pass then you will get back an object that contains all of the data that you passed to new().

Moose will also give you methods for each of the attributes that you have defined. By default, these methods have the same name as the attributes. If you define an attribute as read-only then you can call an attribute method to get the value of the attribute. If an attribute is defined as read-write then you can also use the method to change the value of the attribute by passing a new attribute to the method.

A common name for these methods is "getters and setters" as they allow you to get and set the values of attributes. You will also see them called "accessors and mutators" as you use them to access or mutate (change) attribute values.

Types

One very useful property is isa. This defines the type of the attribute. We can draw from Moose's set of built-in types (like the 'Str' type that we have used for name and gender) or we can declare that the attribute is an object of a particular class (as I have done for the dob attribute). Defining the dob attribute as a DateTime object is very powerful as we can easily get access to extra information about the date. For example, we can add something like this to our previous program

say $newborn->dob->day_name;

There are, however downsides to this. In my current sample code I have avoided the issue by setting the date of birth to DateTime->now which gives the current date (and time), but often we will need to create objects containing details of people who aren't being born right now. Currently, we would need to create a DateTime object for the correct date.

use DateTime;
use Person;

my $dob = DateTime->new(year => 1962, month => 9, day => 7);
my $dave = Person->new(
    name   => 'Dave',
    dob    => $dob,
    gender => 'M'm
);

say $dave->dob->day_name; # Friday

Another approach might be to construct a DateTime object from a string using DateTime::Format::Strptime.

use DateTime::Format::Strptime;
use Person;

my $date_parser = DateTime::Format::Strptime->new(
    pattern => '%Y-%m-%d',
);

my $dob = $date_parser->parse_datetime('1962-09-07');

my $dave = Person->new(
    name   => 'Dave',
    dob    => $dob,
   gender => 'M'
);

say $dave->dob->day_name;

We can build on this approach to automate this conversion. Moose types support a feature called coercion, where values of one type can be automatically converted to values of another type. And all of the complexity can be hidden away in the Person class. We need to add the following code to Person.pm.

use Moose::Util::TypeConstraints;
use DateTime::Format::Strptime;

subtype 'BirthDate',
    as 'DateTime';

coerce 'BirthDate',
    from 'Str',
    via {
        return DateTime::Format::Strptime->new(
            pattern => '%Y-%m-%d',
        )->parse_datetime($_);
    };

We start by loading a Moose utility class that we need along with the DateTime parsing class. We then define a subtype of the DateTime type (because it's a really bad idea to start messing aroung with base types). Next we set up the coercion, saying that you can convert a Str value to a BirthDate value using DateTime::Format::Strptime in the same way as we used it before. The via clause in the coerce definition requires a block of code which takes the input value (which is stored in $_ and returns the coerced value.

We also need to make some changes to the definition of the dob attribute - changing the type to our new subtype and setting a flag which tells Moose that this attribute can be coerced.

has dob => (
    isa      => 'BirthDate',
    is       => 'ro',
    required => 1,
    coerce   => 1,
);

Once we have done all of this, we can write some much simpler code to create our object.

use Person;

my $dave = Person->new(
    name   => 'Dave',
    dob    => '1962-09-07',
    gender => 'M'
);

say $dave->dob->day_name;

The conversion from a string to a DateTime object happens automatically and we no longer need to care about it.

Our new BirthDate type is a subtype of DateTime, but any DateTime object is also a valid DateTime. But we can also define subtypes that are specialisations of their base type. Take, for example, our gender attribute. We have defined that as a string, and we expect it to take a value of either 'F' or 'M'. But currently we have nothing in place to enforce that restriction. Let's fix that.

Again, we'll define a subtype of an existing type. This time we'll create Gender as a subtype of Str. But this time we will use the where and message clauses to define valid values and provide an error to be displayed when validation fails.

subtype 'Gender',
    as 'Str',
    where { /^[FM]$/ },
    message { "$_ is not a valid gender. It should be F or M" };

We also need to change the type of the gender attribute.

has gender => (
    isa      => 'Gender',
    is       => 'rw',
    required => 1,
);

All of our existing code will continue to work, but if we try to create a new object with an invalid gender we will get an error message.

my $dave = Person->new(
    name   => 'Dave',
    dob    => '1962-09-07',
    gender => 'X'
);

This generates the error "Attribute (gender) does not pass the type constraint because: X is not a valid gender. It should be F or M".

Adding Methods

Our current class doesn't do much. It is just a collection of attributes. Classes become much more useful if you can do other things with them. For example, our class knows when a person was born, but it can't tell you how old the person is. To do that we need to add a method to our class.

sub age {
    my $self = shift;

    my $duration = DateTime->now - $self->dob;

    return $duration->years;
}

A method in a Perl class is just a subroutine that is passed one special parameter - a reference to the object that the method has been called on. Moose objects are implemented as hash references, but you generally don't need to care about that as you access the data stored inside the object using the attribute methods that Moose has given you.

Inside the method, we copy this special parameter into a variable called $self (that's not a hard and fast rule, but it's a tradition amongst Perl programmers and you would need a pretty good reason to give the variable a different name). We then get the date of birth from the object and subtract that from the current date. This gives us a DateTime::Duration object, and one of the methods on that object gives us the number of years that the duration covers.

Template Toolkit

DBIx::Class

If you're writing an application of any reasonable level of size of complexity then you will almost certainly have a requirement to store some persistent data. And the most common way to store persistent data is to put it into a database. It's pretty likely, therefore, that your application will need to interact with a database in some way. Perl has standard tools that make this as simple as possible.

The standard way to communicate with a database using Perl is through the Database Interface (DBI) module. This module gives you a standardised interface for talking to a database. This means that talking to an Oracle database is done using exactly the same interface that you use when talking a MySQL database (or any of the many other database engines that DBI supports. In order to be supported in DBI, a database engine requires the existence of a Database Driver (DBD) module. These driver modules already exist for a large number of database engines. For example, the database driver for MySQL is called DBD::mysql and the one for Oracle is called DBD::Sybase.

Using DBI and the correct DBD, allows you to connect to a database and run SQL queries to insert, update, select and delete data. But the programmer needs to write raw SQL to carry out these operations. We can do better than that.

We can do better than that using a tool called an Object Relational Mapper (ORM). An ORM takes data from a database and converts it into classes and objects that are usually easier to work with than the arrays and hashes you get from DBI. This conversion relies on some obvious parallels between the relational database model and the world of object oriented programming.

  • Classes, like tables, describe a type of data that is stored in your system.
  • Objects, like rows, each contain the details of a single instance of a class.
  • Attributes, like columns, each contain a single piece of information about a single instance of a class.

We can therefore take a row from a table and convert it into a object of a class. This is how ORMs work. They convert (or "map") between the data in your database and the objects in your program.

Currently, the most popular Perl ORM is called DBIx::Class and that is what we will use in the following examples.

PSGI/Plack