in reply to object method question

In general, I think I would subclass from the other object, and add in the functions I want to the subclass. In your specific example with DBI, I think that will be royally difficult to do. You may be better off just telling the users to call $foo->dbq rather than $foo->get_dbh->dbq.

It is possible in perl to affect other packages' inheritance (that's what base does). It's a bad idea in general unless you're asking someone else to affect your tree (DBI won't be asking you to affect its tree). And with DBI, it's even worse since there aren't any actual DBI objects floating around - DBI->connect is just a factory function for finding the right DBD driver, loading it, and instantiating it with the connection string.

I'm curious, however, as to why you feel you need to be able to call dbq from the dbh object rather than from the foo object. Currently existing code won't know about the new dbq function they could ask for from the db handle, only new code could be aware of it, so if you just pass in $foo instead of $dbh, then you're all set - call $foo->dbq for your queries, and $foo->get_dbh for anything else.

The alternate design that may work takes up a fair bit more time, IMO. Not runtime time, but developer time, which is a much more precious commodity most of the time. You could write a wrapper object that HASA $dbh handle (or HASA $foo handle), and an AUTOLOAD which would redirect unknown functions (such as execute or prepare) to the $dbh (or $foo->dbh). Then you could have get_dbh return this object instead, and most users wouldn't be able to tell the difference. e.g.,

package Foo::DBIWrapper; use strict; sub new { my $class = shift; my $dbh = shift; my $self = { _dbh => $dbh }; bless $self, $class; } sub dbq { my $self = shift; my $sql = shift; my $sth = $self->prepare($sql); # don't use the _dbh! $sth->execute(@_); # added @_ for any binding return $sth; } our $AUTOLOAD; sub AUTOLOAD { my $self = shift; (my $func = $AUTOLOAD) =~ s/.*:://; if (my $coderef = $self->{_dbh}->can($func)) { unshift @_, $self; goto &$coderef; } die "Can't $AUTOLOAD in $self"; } 1;
Then, instead of returning $self->{_dbh}, you return Foo::DBIWrapper->new($self->{_dbh}). A bit more work, and it does make things just a wee bit more entertaining to debug, and there are lots of AUTOLOAD horror stories to be concerned with (throws UNIVERSAL::can for loops, for example, doesn't inherit very nicely, etc.), but if you're careful, and you know what you're doing, it should work. Perl does make this a bit easier than other languages ;-)

Replies are listed 'Best First'.
Re^2: object method question
by jZed (Prior) on Oct 11, 2005 at 03:03 UTC
    ...there aren't any actual DBI objects floating around - DBI->connect is just a factory function for finding the right DBD driver, loading it, and instantiating it with the connection string.
    That's not really how it works. You're right thatDBI->connect() is a factory method and that it passes much of the work over to the DBD but connect() also creates DBI driver and database handles which work in conjunction with the DBD's driver ::dr and ::db (and ::st classes for prepare). It's those clases of DBI that you need to override if you want to subclass DBI.
      > ::dr and ::db (and ::st classes for prepare). It's those clases of DBI that you need to override if you want to subclass DBI.

      That just might be what I was missing.

      I had been trying single-mindedly to override DBI itself ( in various creative and ridiculous ways! )

      Many thanks for the heads-up, I'll try the ::db instead.

Re^2: object method question
by Zarathustra (Beadle) on Oct 11, 2005 at 03:09 UTC

    Thanks very much for the information. I'll have to take a moment to think about it all; you're absolutely right about the time/work/effort ratio, but you've provided me with plenty of food for thought.

    As far as your question regarding why I need the '$foo->{_dbh}->dbq' behavior rather than the more straight forward '$foo->dbq'. This is all due to some refactoring of old code that's in really ugly shape with some newer stuff that's better designed. A transition phase, we're going to have alot of the old stuff and new stuff forced to work together.

    To keep a very long story short, that '_dbh' needs to get handed around, from class to class - and alot of the old code expects a 'dbq' method attached to it. I specifically would prefer to pass just the '_dbh' object around, rather than the whole '$foo' object ( which'll contain way too much extraneous stuff, unrelated and unwanted by the various recipient class's ).

    Not only that, but ( actually, more importantly ) I've found that I tend to want to do similar things when I'm designing my libraries; and it's something I've simply never been able to implement.

    I do plenty of of base classing and super classing and whatnot, but I'd really like to better understand how to insert methods into existing objects.

    Thanks again!

Re^2: object method question
by BUU (Prior) on Oct 11, 2005 at 21:00 UTC
    sub _setup_dbh { my $self = shift; my $dbh = DBI->connect($foo); *{ref($dbh)."dbq"} = sub { print "I am dbq" }; }
    Not that I'm really advocating such a beast, merely mentioning it =]

      BUU, you're right, of course, that you could do this. And I knew that when I posted. And I deliberately left it out. Figured it was best not to confuse the poor OP with dangerous things like this ;-)

      Oh, and I think you need a "::" in front of "dbq":

      *{ref($dbh)."::dbq"} = sub { print "I am dbq" };
      ;-)

        Yeah, you're right. Confusing people is fun!