Yesterday we looked at Class::DBI, a module that lets us create
objects that represent the rows stored in a database. Today we're
going to look at Pixie, a module that allows us to store data in
rows of the database to represent objects. The distinction between
the two is a subtle, but important one.

A relational database is a tightly constrained system, where the
database knows exactly what is being stored in it and how it relates to
other data in it. Using this knowledge it can do powerful searches for
you and produce. On the other hand setting up all this takes quite
a bit of effort on the programmers behalf as he must try and map each
object from the database to one or more rows in the table.

This is in direct contrast to the way Pixie works. Pixie allows
you, with no fuss, to simply store complex objects in the database and
pull them out again. You don't have to worry about setting up mapping
code like you do with Class::DBI, and you don't have to worry about
creating tables for each different object you want to store. Pixie
will simply serialise up the object, object by object and stores it in
the database as binary data.

This does mean that Pixie can't perform complex searches like a true
relational database, but what it does mean instead is that objects stored
in Pixie don't have any artificial hierarchy instilled in them through their
trip through the database. Suddenly any relationship that makes sense
to you in code can instantly be stored in the database.
This sudden escape from the constraints is truly liberating. I find
this freeform storage system it particularly useful for storing session
data for people between pages on a website. Because the session
object isn't constrained by the database I can add new sections to it
and store ad hoc data in it depending on the need of any particular
page on my site.

Before we begin storing objects, we must first get Pixie to
configure the database so that it has all the correct tables to store
the Pixie records in it.

#!/usr/bin/perl

# turn on Perl's safety features
use strict;
use warnings;

# setup the database in the file 'pixie.db'
# with no username or password
use Pixie::Store::DBI;
Pixie::Store::DBI->deploy("dbi:SQLite:pixie.db", '', '');

Obviously the user that you connect to the database must have sufficient
privileges to create tables and so forth (which you always will have with
a SQLite database at least.)

Once the database is created then inserting objects into it couldn't be
simpler. All you need to do is create a pixie object and use it to
insert any objects that you want to store:

# create a pixie that's connected to the database
use Pixie;
my $pixie = Pixie->new();
$pixie->connect("dbi:SQLite:pixie.db", '', '');
# create an object, any old object, it doesn't need to be
# anything special or know how to store itself or anything

If we make any changes an object that we've stored in the database then
all we need to do is reinsert it. Pixie will be intelligent enough to
realise that this object was stored before and that it should change the
object in there not create a second instance of it.

I normally store the $cookie string for each person's session in
their browser cookies so that whenever they show up at my site I can
look up their session object. Sometimes however my users decide to
user another computer or a different browser and I can't get this
cookie back again. What I need to do then is offer them a way of
getting at their data without their cookie.

Pixie can bind a name to object which can be used instead of the
cookie to retrieve the object:

Data can be extracted with the get_object_named method that's pretty
much identical to get, but it uses a bound name rather than a cookie
id

# get me back again
$pixie->get_object_named("2shortplanks");

As way of a demonstration, here's a sample CGI script that will get the
session of a person for a person based on either their cookie or their
username and password, and will create a new session for them if they
don't supply either.

# try and get the session with either the username or
# cookie
if ($username)
{
# get the object named the same as their username
$session = $pixie->get_object_named($username);
# don't let them get access to the session if they
# got the password wrong
if (defined($session) && $session->password ne $password)
{
# just print the invalid login page and exit
print $cgi->header;
print invalid_login();
exit;
}
}
else
{
# get with the cookie
$session = $pixie->get($cookie);
}

# check we got a session out in the end
unless ($session)
{
# No session recreated? Create a new session then!
my $person = Person->new();

Proxy objects

One of the cleverest things about Pixie is the way that it returns
object trees from the database. Consider the situation where you have
an object in the database and that object has references to another
few objects, and each of those objects have a few more objects
themselves and so on and so on. Suddenly pulling the top object of
the database requires a few hundred objects to be pulled out of the
database. This is pretty wasteful if you only needed to check a
simple attribute of the topmost object.

To avoid this situation Pixie creates what are called "Proxy" objects.
Whenever you pull an object out of the database the objects it
references in turn are not pulled out of the database, but instead in
the place in the object where they would have been stored one or more
"Proxy" object are created. As soon as you call any method on these
objects they are magically upgrade themselves to the real object by
extracting themselves from the database (in a similar fashion to the
way Object::Realize::Later objects upgrade themselves.) What this
means is that Pixie is very efficient with database access, but you
can't access the data structures of objects that haven't been
extracted yet. For example:

my $person = $pixie->get($cookie);

# wont work, as shopping_basket is only a proxy object
# you really should be using accessors methods!
print $person->shopping_basket->{type};

# works, as causes shopping_basket to be sucked in
print $person->shopping_basket->value();

# now works as shopping basket has been loaded and is
# the real object now.
print $person->shopping_basket->{type};

Complicity

Although Pixie can store an awful lot of objects nicely and
automatically, it does have problems when the data that is part of an
object is inaccessible to Perl itself. One such case is when Perl is
interfacing with a C library. Some of the data Pixie needs to store
for that object may be squirrled away by the library in some place
that Perl can't find it.

For this reason you shouldn't expect Pixie to be infallible when it
comes to storing objects, and you should see if a module is
implemented in C before storing it with Pixie. However, even if it
is all is not lost. Pixie has a system called "Complicity" that
allows you to override the way it stores and retrieves objects.

Pixie declares a few methods in the UNIVERSAL class that all
classes inherit from. These methods tell pixie how to store and
retrieve normal objects, but any class can override these methods to
provide a custom way of storing these objects.

As way of an example consider a GD Image. As the GD module is a
wrapper to a C library Perl has no way of accessing the image data.
However, we can instruct GD to dump out the image in the gd2 file
format (the format that GD uses to store temporary copies of image on
disk,) so we could store that in the database and recreate the
original image object from that instead.

Firstly we need to convince Pixie that we can save this object to the
database. To do this we create a fully qualified method subroutine.
This declares the subroutine right in the GD::Image namespace
meaning that when GD::Image->px_storable is called it will be
executed.

Now we need to create the px_freeze method that must return a plain
object that Pixie is capable of storing whenever $image->px_freeze is called. The resulting object needs to be in a
separate class to GD::Image, so we choose the name
Memento::GD::Image.

sub GD::Image::px_freeze
{
my $gd = shift;

# get the image as binary data
my $bin = $gd->gd2;

# return that data in a data structure blessed into a new class
return bless [ $bin ], "Memento::GD::Image";
}

Finally we must write a px_thaw method for Memento::GD::Image
objects that will will recreate the original GD::Image for us.

sub Memento::GD::Image::px_thaw
{
my $memento = shift;

# get the binary data out of the object
my $bin = $memento->[0];

# create a new GD image from it and return it
return GD::Image->newFromGD2Data($bin);
}

Conclusion

Pixie presents a powerful and robust solution to storing objects
that can easily be adapted to store any possible object. By choosing
to use a relational and object database for the right tasks you can
solve your data storage needs in a very much more efficient manner than
with either solution alone.