NAME
Class::Tables - Lightweight, zero-configuration object-DB abstraction
SYNOPSIS
The only thing you need to do is give it a database handle to look at, and it Just Works, right out of the box, provided your database follows some very basic rules:
use Class::Tables;
Class::Tables->dbh( DBI->connect($dsn, $user, $passwd) );
my $new_guy = Employee->new( name => "Bilbo Baggins" );
my $old_guy = Employee->fetch( $id );
my $john = Employee->search( name => "John Doe" );
$john->name( "Jonathan Doe" ); ## simple accessors/mutators
print $john->age, $/;
print "Stringification to the object's 'name' attribute: $john\n";
my $dept = $john->department; ## because we also have a table named
print $dept->description, $/; ## "department", it returns an object
$john->department( $other_dept ); ## assign a Department object
$john->department( 15 ); ## .. or just the ID of one
my @coworkers = $dept->employee; ## get all Employee objects that
## reference this Department
## this is also equivalent:
my @coworkers = Employee->search( department => $dept );
DESCRIPTION
The goal of this module is not an all-encompassing object abstraction for relational data. If you want that, see Class::DBI or Alzabo and the like. Instead, Class::Tables aims to be a zero-configuration object abstraction. Using simple rules about the metadata -- the names of tables, columns, and their types -- Class::Tables automatically generates appropriate classes, with object relationships intact. These rules are so simple that you may find you are already following them.
Meta-Data
- Primary Key
-
All tables must have an
id
column, which is the primary key of the table, and set toAUTO_INCREMENT
. - Foreign Key Inflating
-
A column that shares a name with a table is treated as a foreign key reference to items of that table.
If the
employee
table has a column calleddepartment
, and there is a table in the database also nameddepartment
, then thedepartment
accessor for Employee objects will return the object referred to by the ID in that column. The mutator will also accept an appropriate object (or ID).Conversely, an
employee
accessor (read-only) would be available to all Department objects that returns all Employee objects referencing the Department object in question. - Lazy Loading
-
All
*blob
and*text
columns will be lazy-loaded: not queried or stored into memory until requested. - Automatic Sort Order
-
The first column in the table which is not the
id
column is the default sort order. All result sets returning multiple objects from a table will be sorted in this order (ascending). - Stringification
-
If the table has a
name
column, then its value will be used as the stringification value of an object. Otherwise, the object will stringify toCLASS:ID
. - Class Names
-
Each table must be associated with a package. The default package name for a table in
underscore_separated
style is the corresponding name translated toStudlyCaps
. However, foreign-key accessors are still named according to the column name. So calling$obj->foo_widget
returns aFooWidget
object.
INTERFACE
Public Interface
Class::Tables->dbh( $dbh )
-
You must pass Class::Tables an active database handle before you can use any generated object classes.
Data Class Methods
Every class that Class::Tables generates gets the following class methods:
SomeClass->new( [ field => value, ... ] )
-
Creates a new object in the database with the given values set. If successful, returns the object, otherwise returns undef. You can pass an object or an ID as the value if the field is a foreign key.
SomeClass->search( [ field => value, ... ] )
-
Searches the appropriate table for objects matching the given restrictions. In list context, returns all objects that matched (or an empty list if no objects matched). In scalar context returns only the first object returned by the query (or undef if no objects matched). The scalar context query is slightly optimized. If no arguments are passed to
search
, every object in the class is returned. SomeClass->fetch( $id )
-
Semantically equivalent to
SomeClass->search( id => $id )
, but slightly optimized internally. Unlikesearch
, will never return multiple items. Returns undef if no object with the given ID exists in the database.
Object Methods
Every object in a Class::Tables-generated class has the following methods:
$obj->delete()
-
Removes the object from the database.
- Accessor/Mutators:
$obj->foo( [ $new_val ] )
-
For each column foo in the table, an accessor/mutator is provided by the same name. It returns the current value of that column for the object. If foo is also the name of another table in the database, then the accessor will return the corresponding Foo object with that ID, or undef if there is no such object. You may pass either a Foo object or an integer ID as the new value,
Alternately, foo can also be the name of a table that has a foreign key pointing to objects of the same type as
$obj
. If$obj
is a Bar object,$obj->foo
is exactly equivalent toFoo->search( bar => $obj )
. $obj->field( $field [, $new_val ] )
-
This is an alternative syntax to accessors/mutators. If you aren't a fan of variable method names, you can use the
field
method:for my $thing (qw/name age favorite_color/) { ## these two are equivalent: print $obj->$thing, $/; print $obj->field($thing), $/; }
$obj->dump
-
Returns a hashref containing the object's attribute data. Recursively inflates foreign keys, too. Reverse foreign keys are mapped to an array ref. You may find this useful with HTML::Template!
OTHER STUFF
You can still override/augment object methods with SUPER:
package Employee;
sub ssn {
my $self = shift;
my $ssn = $self->SUPER::ssn;
$ssn =~ s/(\d{3})(\d{2})(\d{4})/$1-$2-$3/;
return $ssn;
}
But since the objects are blessed scalars, you have to use something like this to store extra (non-persistent) subclass attributes with the objects:
## if you want to do something like this:
for my $emp ( grep { not $_->seen_already } @employees ) {
## do something to $emp that you only want to do once..
## maybe give $emp a raise?
$emp->see;
}
## in the subclass, you can do this:
package Employee;
my %seen;
sub seen_already { $seen{+shift}; }
sub see { $seen{+shift}++; }