ClearPress Framework

Object Relational Mapping

ClearPress comes with a built-in, basic ORM layer in the ClearPress::model superclass. Like views, data model objects should be systematically named to avoid adding complexity (though it is possible to override everything if you really want to!). Usually a class maps to a table and an instance of a class (yes, an object) usually maps to an individual row. Tables are usually named in the singular form (e.g. 'child' rather than 'children') and the class name-suffix has the same name as the table, e.g. application::model::child.

Table fields / data members

Data model objects at the bare minimum carry the following information - the package name and an array of fields which map to the database table the class represents.

Fields can be reflected in automatic get/set accessors courtesy of Class::Accessor. Most data models come with this near their head:

__PACKAGE__->mk_accessors(fields())
which uses the array from sub fields() { } to construct the accessors.

The ClearPress ORM does not (currently) perform database inspection to build attributes and relationships automatically (at run- or compile- time). It relies on the list of fields always reflecting what's in the database. For example:

sub fields {
 return qw(id_child name dob id_family);
}

Creating stuff

Here's our data model:

package Grandkids::model::kid;
use strict;
use warnings;
use base qw(ClearPress::model);

__PACKAGE__->mk_accessors(fields());

sub fields {
 return qw(id_kid name dob id_family);
}

1;
which represents a database table:
create table kid(
 id_kid bigint unsigned primary key auto_increment not null,
 name char(128) default '' not null,
 dob datetime not null,
 id_family bigint unsigned
);
and here's how we could use it to add a new entry in the database:
use Grandkids::model::kid;
my $oKid = Grandkids::model::kid->new();
$oKid->name('Bob');
$oKid->id_family(1);
$oKid->dob('2009-02-10 10:00:00');

$oKid->create or die;
Using accessors to set all the values is unwieldy so we can also do it during construction:
my $oKid = Grandkids::model::kid->new({
 name      => 'Bob',
 id_family => 1,
 dob       => '2009-02-10 10:00:00',
});

$oKid->create or die;
Notice that we didn't add an id_kid to either of these, because we want our database to allocate them for us. id_kid is the first field in sub fields and as such it's recognised by ClearPress::model as the primary key for this class. If we did add an id_kid, the new entry would be created with that id unless it already exists, in which case an error would be raised.

Reading stuff

Read access from the database comes in a couple of flavours - lists and individuals. The latter (read) requires an entity's id. Listings are fractionally simpler as the basics are implemented in the superclass.

List

To activate the basic list method (list everything) we add this to the top of the class

__PACKAGE__->has_all()
which adds a pluralised accessor (in this case kids) returning an array reference of all kid objects from the database. This can now be used like so:
 my $oRootKid = Grandkids::model::kid->new(); # note no id required
 my $arKids   = $oRootKid->kids();
 for my $oKid (@{$arKids}) {
  printf q[%d %s %s %d\n],
         $oKid->id_kid,
         $oKid->name,
         $oKid->dob,
         $oKid->id_family;
 }

Read

To read a specific entity given its id (primary key) we can specify it in the constructor:

my $oKid = Grandkids::model::kid->new({id_kid=>1});
 printf q[%d %s %s %d\n],
        $oKid->id_kid,
        $oKid->name,
        $oKid->dob,
        $oKid->id_family;

Updating stuff

To modify an existing entity we load it with its primary key, add the value to change then ask it to update:

my $oKid = Grandkids::model::kid->new({id_kid=>1});
$oKid->name('Billy');
$oKid->update or die;
If the row in the database is heavy (e.g. contains a large text field or blob) you don't need to worry. ClearPress::model tries to be lazy by default - as we don't ask it for anything we didn't give it, it won't try to fetch the row from the database. It will just save the values it has.

Deleting stuff

Deleting entries from the database is easy. Read with an entity's primary key, then call delete:

my $oKid = Grandkids::model::kid->new({id_kid=>1});
$oKid->delete or die;

Secondary Keys

ClearPress::model supports the use of a unique secondary key (not compound). If, say, a user has a unique username, the primary id would be id_user or similar, and the secondary key would be username. ClearPress::model::init will check for a non-numeric scalar value in the constructor, and attempt to load on that when the primary key is not present. Similarly it will also attempt to load against a named field.

  package app::model::user;
  use strict;
  use warnings;
  __PACKAGE__->mk_accessors(fields());
  sub fields {
    return qw(id_user username);
  }

  sub secondary_key {
    return q[username];
  }
  1;
  
  INSERT INTO user VALUES(1,'bob123');
  
  use app::model::user;
  my $u = app::model::user->new(1);
  my $u = app::model::user->new('bob123');
  my $u = app::model::user->new({ id_user => 1 });
  my $u = app::model::user->new({ username => 'bob123' });
  
The named-field method is particularly useful when you wish to load based on a numeric secondary key.