Wednesday, May 27, 2009

How (not) to do inherited tables in DBIx::Class

When writing DBIx::Class schemas for a database that includes several similar tables, it would appear to make sense to use object-oriented programming, and make one table inherit from another.
(Now, this actually makes me think that your database tables aren't correctly normalised, however sometimes you don't have a choice over the DB layout, or you have some reason for using the denormalised layout.)

When using DBIx::Class, do not be tempted to make a Result class that inherits from another Result class. It ends up with a big mess occuring in the internal structures of dbic, and although it seems to work at first, you'll get some weirdness down the track.

Let me give you an example *of what not to do* so you understand.

package My::Schema::People;
use base 'DBIx::Class';
__PACKAGE__->load_components('Core');
__PACKAGE__->table('people'); # Or persons?
__PACKAGE__->add_columns(qw(id age gender));
1;

package My::Schema::Teenagers;
use base 'My::Schema::People';
__PACKAGE__->table('teenagers');
__PACKAGE__->add_columns(qw(allowance angst phone_bill));
1;

package My::Schema::Adults;
use base 'My::Schema::People';
__PACKAGE__->table('adults');
__PACKAGE__->add_columns(qw(salary stress childcare_centre));
1;


The problem is that when you "use base People" you cause ->table() to get called with it set to "people".. Now later you re-call table() in your own class and set it to teenagers, but it's too late - that first call to table() triggers a lot of code inside DBIC which ends up associating the wrong things to your class and to that original table.

I'll now show you a way which does work correctly.


package My::Base::People;
use base 'DBIx::Class';
sub foo {
my ($class, %args) = @_;
$class->load_components('Core');
$class->table($args{table});
$class->add_columns(qw(id age gender));
}
1;

package My::Schema::People;
use base 'My::Base::People';
__PACKAGE__->foo(table => 'people');
1;

package My::Schema::Teenagers;
use base 'My::Base::People';
__PACKAGE__->foo(table => 'teenagers');
__PACKAGE__->add_columns(qw(allowance angst phone_bill));
1;

package My::Schema::Adults;
use base 'My::Schema::People';
__PACKAGE__->foo(table => 'adults');
__PACKAGE__->add_columns(qw(salary stress childcare_centre));
1;


I have also thought of creating the base class as a DBIC Component instead, and hooking it into the ->table() call. It'd look slightly neater in the result classes, but wouldn't be as clear for my example and understanding.

ie. your result classes would look like:

package My::Schema:People;
use base 'DBIx::Class';
__PACKAGE__->load_components(qw(+My::Component::People Core));
__PACKAGE__->table('people')
1;
and your component class would look a bit like
package My::Component::People;
use base 'DBIx::Class::Component';
sub table {
my ($class, $table) = @_;
$class->next::method($table);
$class->add_columns(qw(id age gender));
}
1;

Ironman

I have so many blogs, or microblogs, of one sort or another already..
Livejournal, Flickr's photostream, Facebook's news page, Twitter, Dreamwidth, Identi.ca.
Why am I starting another one?

I'm entering the Enlightened Perl Ironman challenge. (See link from title)
It seems like a good idea, and about time I wrote more about what I do, rather than the inanity of facebook and twitter, or the boring details of my life on livejournal.