Beefy Boxes and Bandwidth Generously Provided by pair Networks
Think about Loose Coupling
 
PerlMonks  

Inheritance, Class::DBI and POOP

by TGI (Parson)
on Oct 02, 2002 at 03:36 UTC ( [id://202180]=perlquestion: print w/replies, xml ) Need Help??

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

I've been thinking about POOP a lot lately.

I'm looking to pick a RDBMS-OO mapper to use as a standard tool for (most) of my projects, and I am pretty sure that Class::DBI is the way I want to go. But one thing bothers me, how do you handle inheritance in Class::DBI (or other POOPy modules, if you have one that you really like)?

Consider a class hierarchy with three classes: Host, MailServer and WebServer.

Class: Host
Properties: ID, name, IPAddress, location, OS

Class: MailServer ISA Host
Properties: MTA, version

Class: WebServer ISA Host
Properties: httpd, version, CGI

How do I go about structuring my database for this? What if I want to have a method in Host that can return a list of all hosts (including WebServers, MailServers, and other subclasses of Host) that match some criteria? Must I keep each subclass in a separate table? Is there some obvious solution that I am missing?


TGI says moo

Replies are listed 'Best First'.
Re: Inheritance, Class::DBI and POOP
by abell (Chaplain) on Oct 02, 2002 at 10:41 UTC

    I don't know much about Class::DBI, but I have spent some time over the OO <-> relational DBMS questions.

    The three classical solutions for subclassing are:

    1. One common table for all subclasses, containing all attributes that appear in any of the subclasses;
    2. One table for the common attributes and one for each class. The class-table contains the class-specific attributes, plus a pointer-field to the record in the common class (this is my favourite);
    3. One table for each class, each table having columns for both common and class-specific attributes.

    In the first and second case, a query over the common attributes will probably map to a single QUERY statement. In the third case, you have to repeat the query for all subclasses.

    In case 1/2, you will probably have to deal with casting issues, since after getting the common attributes you should determine what class they belong to and retrieve the remaining attributes. This can be transparent and often avoid one more query if the mapping layer implements lazy attributes. In case 3, you would not face this problem, because each query would only deal with one class.

    Best regards

    Antonio Bellezza

    N.B. Throughout the post, "probably" is short for "in most conditions, depending on implementation of the mapping layer".

    The stupider the astronaut, the easier it is to win the trip to Vega - A. Tucket
Re: Inheritance, Class::DBI and POOP
by thpfft (Chaplain) on Oct 02, 2002 at 12:49 UTC

    Class::DBI is more a mapper from database to object set than the other way around. It's not an intrinsic limitation - it can work either way - but its assumptions and documentation don't fit nearly as well if your starting point is a class hierarchy and you want to express it in database form.

    however, there are several ways that it could handle your example. You could do almost exactly what you describe, though i don't think it the best approach. something like this should work:

    Package My::Hosts; use base qw(My::Class::DBI::Subclass); __PACKAGE__->table('hosts'); __PACKAGE__->columns(Primary => qw(ID)); __PACKAGE__->columns(Essential => qw(name IPAddress location OS)); Package My::MailServers; use base qw(My::Hosts); __PACKAGE__->columns(mailserver => qw(MTA version)); Package My::WebServers; use base qw(My::Hosts); __PACKAGE__->columns(webserver => qw(httpd version CGI));

    which would allow you to use the same table for each class, and webservers would inherit host methods as i think you want. As a child of Schwern, its encapsulation is very sound.

    however, that isn't the best way to use the module, nor - imho - a very good way to use the database. I'd probably use three tables and define many to one relationships linking webservers to hosts. You may after all have more than one webserver to describe for each host:

    Package My::Hosts; use base qw(My::Class::DBI::Subclass); __PACKAGE__->table('hosts'); __PACKAGE__->columns(Primary => qw(ID)); __PACKAGE__->columns(Essential => qw(name IPAddress location OS)); __PACKAGE__->has_many('mailservers', 'My::MailServers', 'host'); __PACKAGE__->has_many('webservers', 'My::WebServers', 'host'); # has_many also defines a column in the foreign class: # in this case webserver->host # which returns the relevant Hosts object when called. Package My::MailServers; use base qw(My::Class::DBI::Subclass); __PACKAGE__->table('mailservers'); __PACKAGE__->columns(Primary => qw(ID)); __PACKAGE__->columns(Essential => qw(MTA version)); Package My::WebServers; use base qw(My::Class::DBI::Subclass); __PACKAGE__->table('webservers'); __PACKAGE__->columns(Primary => qw(ID)); __PACKAGE__->columns(Essential => qw(httpd version CGI mod_perl port h +ttps));

    which is what Class::DBI does best. It will leave you with possibilities like this:

    my $webserver = My::WebServers->retrieve($id); my $version = $webserver->version; my $ip = $webserver->host->IPAddress; my @mailservers_on_this_host = $webserver->host->mailservers; my @hosts_with_webservers = map { $_->host } My::WebServer->retrieve_a +ll; my @same thing = grep { $_->webservers } My::Hosts->retrieve_all; # ...

    If defining relationships is going to be a pain on the input side - it sometimes is, if you're pulling data in from flat files, for example - then I think the official approach would be something like:

    Package My::Hosts; use base qw(My::Class::DBI::Subclass); __PACKAGE__->table('hosts'); __PACKAGE__->columns(Primary => qw(ID)); __PACKAGE__->columns(Essential => qw(name IPAddress location OS)); __PACKAGE__->columns(mailserver => qw(MTA version)); __PACKAGE__->columns(webserver => qw(httpd version CGI)); __PACKAGE__->make_filter(mailservers => 'not MTA is NULL'); __PACKAGE__->make_filter(webservers => 'not httpd is NULL');

    Which gives you a single table and reasonably efficient access to the various subsets of your data. I haven't really used it this way, though, and I'm not sure about using 'is NULL' in the filters - think it might be database dependent - but it does serve to demonstrate that there are many ways to do what you require.

    The other thing I'd suggest is that you run a factory class in front of the set of data-mapping classes. it's not essential if you're working in a controlled setting, but it greatly adds to the flexibility of the app and makes it easier to grow, and becomes almost essential when you start to use templating systems for output. I've put together a Class::DBI::Factory and associated handler and pager modules that might make their way onto CPAN in a few days if they meet with the approval of the poop-group, but it has all gone a bit over the top in its efforts to be all things to all people, and something much simpler would suffice for the sort of example you've described.

Re: Inheritance, Class::DBI and POOP
by perrin (Chancellor) on Oct 02, 2002 at 20:13 UTC
    I think you should take a look at Tangram.

      Can I use Tangram with HTML::Mason and mod_perl? One post in the mailing list archives claims that it won't work. Is this correct?

      It looks pretty cool, especially when you add Class::Tangram to the picture.

      Thanks for the suggestion! ++


      TGI says moo

Re: Inheritance, Class::DBI and POOP
by mp (Deacon) on Oct 02, 2002 at 16:20 UTC
    Martin Fowler has a book coming out that discusses object relational mapping. As of today, there is still a version available on the web. See this node for more information.
Re: Inheritance, Class::DBI and POOP
by hakkr (Chaplain) on Oct 02, 2002 at 13:53 UTC
    You could use an object orientated database instead of a relational one.

    Does anyone know of a pure perl Object orientated database system?

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://202180]
Approved by diotalevi
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others goofing around in the Monastery: (2)
As of 2024-04-25 21:24 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found