Anonymous Monk has asked for the wisdom of the Perl Monks concerning the following question:

Can you use inheritance in Class::DBI to make classes which use other Class::DBI classes to hold part of their data and implementation?

Consider the classic example of the classes for Person, Employee, and Boss. Suppose that Person holds the name and address, Employee holds the pay rate and department the employee works in, and Boss holds the department being bossed around and the bonus that boss will get at the end of the year if her department works hard enough. And suppose that for inheritance, a Boss is an employee, and an employee is a person.

Is it possible to use Class::DBI to represent the class hierarchy of Person / Employee / Boss? I've tried it out, and I can make it so that an Employee is a Person, by using use base 'Person'; and I can create a Person object by inserting an Employee object. That lets me store base class Person stuff for an employee.

However, if I change the table for the Employee to the employee table, Employee->table('employee'); then the base class data for Person, name and address, is no longer in the employee table. That means that Class::DBI thinks that the columns for name and address don't exist in the Employee class's table, because of course they don't.

So that's where I'm stuck, how can I use Class::DBI to make a class hierarchy using inheritance? Is it possible for a derived class to get data from its base class? Or is doing this impossible because of the inherent limitations of the underlying SQL layer?

As a workaround, I think I could say that an Employee "has a" person, such that in the employee table there would be a column for 'pid' which references which row in the person table is the person for this employee: Employee->has_a(pid => 'Person'); Then if I had an object of that class I could do something like this: $employee->pid->name = 'homer'; This seems like an appropriate SQL way to do it, but in OOP terms, it seems like a dirty hack.

Has anybody worked out how to represent inherited class hierarchies in Class::DBI? I'd really like to see an example please if somebody has!

Replies are listed 'Best First'.
Re: Inheritance and class hierarchies in Class::DBI
by perrin (Chancellor) on Apr 12, 2007 at 03:51 UTC
Re: Inheritance and class hierarchies in Class::DBI
by GrandFather (Saint) on Apr 12, 2007 at 02:16 UTC

    Recently I've been playing around with a class wrapper for tables that is pretty close to what you want. At present it goes something like this:

    package DBManager; use strict; use warnings; use DBI; use Date::Manip; use Class::Singleton; our @ISA = qw(Class::Singleton); 1; sub _new_instance { my $type = shift; my $class = ref $type || $type; die "DBManager::instance must be invoked as DBManager->instance" i +f ! defined $class; my $dbh = DBI->connect("dbi:SQLite:databasefilename", "", ""); die "Cannot connect to database: $DBI::errstr\n" unless defined $dbh; $dbh->{AutoCommit} = 0; $dbh->{RaiseError} = 1; return bless {dbh => $dbh}, $class; } sub DESTROY { my $self = shift; $self->{dbh}->disconnect () if defined $self->{dbh}; } package DBObject; require Exporter; our @ISA = ('Exporter'); our @EXPORT = qw(new); sub new { my ($class, %params) = @_; die "DBObject::new must be invoked as DBObject->new" unless defined $class; die "A table definition (tableDef => ['name', 'column info'] is re +quired by $class->new ()" unless 'ARRAY' eq ref $params{tableDef}; die "database handle must be from a DBManager derived object" if exists $params{db} and ! $params{db}->isa ('DBManager'); my $self = bless {%params}, $class; my %cols = map {s/^\s*//; s/\s*$//; /(\w+)\s*(.*)/; $1 => $2} split /,/, $params{tableDef}[1]; $self->{cols} = \%cols; $self->setDbh (); return $self; } sub DESTROY { my $self = shift; $self->{db} = undef; }
    1; package Group; use base 'DBObject'; require Exporter; our @ISA = ('DBObject'); our @EXPORT = qw( ); my $groupTableDef = ['groups', 'id INTEGER PRIMARY KEY NOT NULL, groupid INTEGER NOT NULL, user INTEGER NOT NULL' ]; sub new { my ($class, %params) = @_; $params{tableDef} = $groupTableDef unless exists $params {tableDef +}; return $class->SUPER::new (%params); } 1;

    It would be pretty easy to add columns to the table definition in derived classes rather than expecting the most derived class to supply the whole thing, although I've not had a need for that yet so it's not done that way.

    Given that Task is derived from DBObject with a 'starts' column you can do stuff like:

    my $task = Task->new; my $now = Date::Manip::ParseDate ('today'); my $tasks = $task->fetch_all (starts => ["<= '$now'"]); my @readyTasks = sort {$a->{starts} cmp $b->{starts}} values %$tasks;

    DWIM is Perl's answer to Gödel