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

Oh great makers of Heroic Perl,

I am trying to build a system, and as I sit here in my dark corner all by myself contemplating the possiblities, I am struck that I need some guidence.

As part of the exercise in making this program/system, I am using Object Oriented Perl modules. This is an effort to deepen my understanding of the subject. I am also using a database to keep the data persistent.

Sorry for the vagueness, but the specifics have little to do with my question.

What would be the best way to create an object which in part would have member data based on the data from a table. I want to create this so that as columns are added or deleted from the table the code in the object need not be changed.

Let me give a generic example, if I may. My table has columns:

a b c d

I retrieve data from the table into a hash, and use this hash to create the new object. The new object has this data, plus addition data from other tables or specific settings. I want to leave this open-ended so it can all be driven by the db.

I know how to create objects and all that, but I have never done it in this way. Is it possible, and if so, can anyone make suggestions on the best approach?

I lay down at the alter of the monks awaiting their wisdom.

(Ya think, I grovelled enough?)

Edit ar0n -- removed excessive <code> tags

Replies are listed 'Best First'.
(Ovid) Re: OO Perl and Design Issues
by Ovid (Cardinal) on Jan 01, 2002 at 02:19 UTC

    I don't know enough about what you're trying to do, but it sounds like you want to use a database to model your objects. If so, check out http://poop.sourceforge.net/. "poop" (lovely acronym, eh?) stands for Perl Object-Oriented Persistence. This allows you to closely match the database and your objects. I've heard great things about Alzobo and Class::DBI for instance, though I've been looking at Tangram and think that it's very interesting.

    Cheers,
    Ovid

    Join the Perlmonks Setiathome Group or just click on the the link and check out our stats.

      This does indeed sound in part what I would like to do, and I will look further into that. However, I am having additional problems with trying to create the object from a hash outside of the object. Let me explain: In addition to creating the object from the db I also would like to create it from inputted parameters. I am used to blessing a hash created inside the class, but this time I want to create the hash outside the class and pass it to the constructor. If I do this, then I may not know exactly what the keys are in the hash and in this way it can be generic. Does that make a little more sense? I know this is unusal. Maybe, I shouldn't be doing it this way at all, but it seems a good way to deal with a number of other issues I would have to handle such as dynamic databases (ie. columns changing in the input forms or in the databases).

        Although you can mix database fields and other parameters into a single hash, my first impression would be to avoid this and simply have an attribute in your object that contains a reference to the hash that contains the database fields.

        Though, exactly how to do this all depends on how you are getting the external hash into your constructor. The two common methods are "pass by value" and "pass by reference":

        # Pass by value: $obj= My::Class->new( %hash ); $obj= My::Class=>new( key=>"value", key2=>$value2 ); # vs. pass by reference: $obj= My::Class->new( \%hash ); $obj= My::Class=>new( { key=>"value", key2=>$value2 } );
        I prefer "pass by reference" because it leaves more room for API adjustments down the road, makes miscalling easier to detect, and few other reasons.

        So you could write a constructor something like this:

        sub new { my( $class, $hashRef )= @_; my $self= bless $hashRef, $class; return $self; }
        but that could cause problems because it makes an object out of the calling code's hash directly. So, for example,
        my %hash= ( key=>"value", key2=>$value2 ); my $obj1= My::Class->new( \%hash ); $hash{key}= "cheap"; # This changes $obj1! my $obj2= My::Class->new( \%hash );
        would modify $obj1 and then make $obj2 an alias to $obj1 (so you'd really only have one object).

        So you'd want to copy the values (usually doing some validation, but it doesn't sound like you want to any of that):

        sub new { my( $class, $hashRef )= @_; my $self= bless {%$hashRef}, $class; return $self; } # or sub new { my( $class, $hashRef )= @_; my $self= bless {}, $class; @{$self}{keys %$hashRef}= values %$hashRef; return $self; } # or sub new { my( $class, $hashRef )= @_; my $self= bless {}, $class; %$self= %$hashRef; return $self; } # etc.
        To show just a couple of the ways to do it.

                - tye (but my friends call me "Tye")
        I apologize. I was being dumb. I knew I could do that. I started to doubt myself, and then two seconds after I made that post, I figured out that the reason it was not working was a stupid typo. My bad.
Re: OO Perl and Design Issues
by chip (Curate) on Jan 01, 2002 at 02:40 UTC
    Perhaps your object &new could use DBI to populate itself, without having to know which particular database driver was being used.

    Specifically: You could pass a DBI statement handle containing the "select" statement as a parameter to &new, which would call one of the DBI fetch methods in whatever manner it might find convenient.

    Think of a statement handle as a freeze-dried selection.

        -- Chip Salzenberg, Free-Floating Agent of Chaos

Re: OO Perl and Design Issues
by lachoy (Parson) on Jan 02, 2002 at 06:43 UTC

    One possibility is using another module from the POOP group Ovid mentioned earlier: SPOPS. Here's some sample code for you:

    #!/usr/bin/perl use strict; use DBI; use SPOPS::Initialize; my ( $DB ); sub My::Sample::global_datasource_handle { unless ( $DB ) { $DB = DBI->connect( 'DBI:mysql:sample', 'me', 'pass' ) || die "Cannot connect: $DBI::errstr"; $DB->{RaiseError} = 1; } return $DB; } sub dump_objects { my ( $object_list ) = @_; foreach my $object ( @{ $object_list } ) { print <<DUMP; a: $object->{a} b: $object->{b} c: $object->{c} d: $object->{d} DUMP } } { my %config = ( sample => { class => 'My::Sample', isa => [ 'SPOPS::DBI::MySQL', 'SPOPS::DBI' ], field => [ 'a', 'b', 'c', 'd' ], id_field => 'a', base_table => 'sampletable', }, ); SPOPS::Initialize->process({ config => \%config }); # Create a new object and save it: my $new_object = My::Sample->new({ a => 15, b => 'Smithson', c => 0.389, d => 'Arf!' }); $new_object->save; # Grab an existing object and modify a field my $saved_object = My::Sample->fetch( 15 ); $saved_object->{d} = 'Quack!'; $saved_object->save; # Grab all the objects my $all_objects = My::Sample->fetch_group({ order => 'a' }); dump_objects( $all_objects ); # Grab some of the objects my $some_objects = My::Sample->fetch_group({where=>'b LIKE ?', value=>['smith%']}); dump_objects( $some_objects ); }

    There are tons of other things you can do -- relating objects to one another, giving your object custom behaviors, setting rules so that you can ensure the data going into the database conforms to your business logic, just to name a couple -- but this gives you a brief idea.

    Chris
    M-x auto-bs-mode

Re: OO Perl and Design Issues
by mstone (Deacon) on Jan 02, 2002 at 03:05 UTC

    Tye already mentioned 'demoting' the data from the table.. giving the class a single attribute (a hash) that holds all the tuples from that table instead of trying to make every key-value tuple from the table a full-fledged attribute. I agree with that suggestion, and what follows is a discussion of why I agree:

    Good OOP tends to lean toward consistent interfaces.. every object in a class having exactly the same set of attributes and methods, with the instances only differing in their attribute values. That gives you the freedom to assume that any object from the same class will work exactly the same way, which in turn lets you write simpler client code (code that uses the object).

    Polymorphism.. creating an object that inherits interfaces from more than one base class.. makes it easier to shuffle interfaces among several classes, but still keeps the interfaces themselves consistent. Every object in a polymorphic class still has the same set of methods and attributes, and it's still safe to assume that all objects from that class will work the same way.

    You're asking how to put different interfaces on two objects from the same class. Yes, it's possible, but it tends to be a bad idea.

    For one thing, changing interfaces on a per-object basis tends to make your client code insanely complicated. Instead of being able to write one-size-fits-all routines, you're forced to inspect each object you get to see what it can do. Then you have to work out the logical gymnastics necessary to reach the right handling routine for each permutation of attributes and methods. In the vocabulary I know, that's called 'floating' complexity up to the higher, more abstract parts of your program, instead of 'sinking' it into the lower, more concrete regions. Historical best practices suggest that it's better to go the opposite way.

    For another thing, making objects that flexible tends to create a breeding ground for runtime errors that are a bitch to find and fix. Typos become especially nasty, because there's no reason for the object to think you don't want one attribute named 'thing1' and another named 'thing 1'. Likewise, it becomes easy to break your code by changing it in one place, but not propagating the same change to everywhere else that it might be necessary. And since your client code has to inspect the object a lot, you end up with more places to search.

    And since you did specify design issues, those are things you should probably keep in mind. ;-)

      Maybe I need to think about this deeper (in fact I am sure I do, you always do), but I didn't mean to suggest that I wanted two interfaces. When I said that the data may come from the table or it may come from input, I meant that it was arbitrary not distinct. So that, I want it to be generic such that it is one interface able to handle any kind of data. Thus, the constuctor is set to handle an inputed hash. That hash is checked against a "permitted" hash which makes sure that no extra hash values are added to the object. The permitted hash does much of what you suggest. It grabs the columns from the table which are allowable. I simply put this in a seperate private method which I figured would make it easier to maintain. I think this in part addresses Tye' +s concerns which I agree are something to be careful of. Here is my current constructor: sub new { my ($caller, $args) = @_; my %memberData; my $class = ref($caller) || $caller; # grab the fields that are allowed to be member data $memberData{'_permitted'} = &_permitted(); for (keys %$args) { if ($memberData{'_permitted'}->{$_}) { $memberData{$_} = $args->{$_}; } } $memberData{'errors'} = 0; my $self= bless { %memberData }, $class; # $self->error now contains errors, check before proceeding..... $self->_validate(); return $self; } Does this take care of those issues or am I way off base? As I said, I have made these classes before but usually much more straight-forward. This time I want to try and the class so that I have the handy OO interface with a class that takes the data as a parameter because the member data fields will be dynamic (or at least I want to be prepared if they are).

        I think I see what you're getting at. Personally, I'd write the constructor like so:

        package Base_class; ## new (args:hashref) : Object_ref # # create a new object, then initialize it from class-specific # defaults and/or argument values. # sub new { my $O = bless {}, shift; my $args = shift; my $defaults = $O->_defaults; ## iterate over the keys in %$defaults, because those are all ## we really care about: for $k (keys %$defaults) { $O->{ $k } = (defined $args->{$k}) ? $args->{ $k } : $defaults->{ $k }; } return ($O); } package Subclass_1; @ISA = qw( Base_class ); ## _defaults (nil) : hashref # # return a hash of default values for objects of this class # sub _defaults { ## this is basically a static variable for the class. i ## define such things at runtime, but that's just a personal ## taste. if ( ! defined $DEFAULTS ) { $DEFAULTS = { 'attr1' => 'val1', 'attr2' => 'val2', 'attr3' => 'val3', }; } return ($DEFAULTS); }

        That gives you the freedom to create as many subclasses as you want, each with its own signature of attributes. Each subclass will only accept values from its own signature, and every object will be fully initialized to its own signature by the time the ref leaves new().