(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;
Brilliant, after much searching I found this article which answer exactly what I'm trying to do (I think), thanks for posting it.
ReplyDeleteI should post this again, and show how to do it with Moose. The concept is essentially the same though.
ReplyDeleteYou can easily do this in Moose by using 'builder' functions which can be overridden by the derived classes. The other way to do is to use the inner/augment functionality as well, but that is over kill if all you're doing is avoiding the table() call. To do private stuff you can use the init_arg option in Attributes. Refer Moose::Manual::Attributes
ReplyDeleteCheers for this, I could have spent hours trying to work this out for myself ;)
ReplyDeleteThis dealt with exactly the problem I was facing. I found that implementing it as a DBIC component is a very neat way of doing it. I tweaked it to make it clearer by overriding the add_columns() method instead of table().
ReplyDeleteGreat post. Additional points for a Moose version :)
ReplyDelete