Beefy Boxes and Bandwidth Generously Provided by pair Networks
Do you know where your variables are?

Tied hashes and arrays, yeah. But tied typeglobs?

by Phaysis (Pilgrim)
on Sep 29, 2003 at 05:48 UTC ( [id://294889] : perlquestion . print w/replies, xml ) Need Help??

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

Hey folks-

I have a set of database classes I'm writing that are customized for each table in my database (allowing for business logic, foreign-key stuff, etc). I've implemented these as tied hashes for the sake of user-code simplicity. Each hash represents one and only one row from the database. When I want to create a new user in the user database table, I do something like this:

use My::DB::User; my (%th,$tho); my %nu = ( # new user data username => 'foobar', password => 'raboof', groupid => 1, url => '', email => 'fake@fakeolameo.como', active => 1, ); # tie it here $tho = tie %th, 'My::DB::User', {-data=>\%nu}; print "username is: $th{'username'}"; #prints "username is: foobar" # save the data to the database $tho->saveData();

This creates a new user and does the SQL INSERT into the proper table. I can load a user from the database in a similar way:

$tho = tie %th, 'My::DB::User', {-userid=>'25'};

This will return a single row (userid of 25) which I can then manipulate as a hash. But what if I wanted to select a set of rows from a table that matched a certain search condition? This would result in a number of rows being returned, not just one, hence my single-row tied hash paradigm breaks down. I'd need an array to handle the tied hashes of rows. It'd be nice if my classes could sniff out the need for a single row or a collection of rows. That I can do.

But what I'm wondering is this: since the variable type in the first position of tie()'s parameters defines which of TIEHASH or TIEARRAY gets called, is it possible, or prudent, to include both of those methods in my parent class? Would this be a good situation where tied typeglobs would work? A typeglob could be sent to the tie() statement and return the correct datatype. Is it possible to tie typeglobs? I don't think so, but I'm not so sure.

Any input would be helpful. Thanks!

(Ph) Phaysis (Shawn)
If idle hands are the tools of the devil, are idol tools the hands of god?

Replies are listed 'Best First'.
Re: Tied hashes and arrays, yeah. But tied typeglobs?
by bart (Canon) on Sep 29, 2003 at 07:28 UTC
    Tied typeglobs exist. But AFAIK, their only purpose is for tying filehandles, nothing else. See the subsection "Tying FileHandles" in perltie. I think you're out of luck.

    You can use a blessed typeglob as an object, or at least a reference to one, so you might just try a different interface, not the tied one. See Symbol, and the function gensym().

Re: Tied hashes and arrays, yeah. But tied typeglobs?
by Jasper (Chaplain) on Sep 29, 2003 at 14:03 UTC
    If you can't do it any other way, perhaps all 'create' methods should return an arrayref of tied hashes (one or more). I'm sure you've considered this, and probably rejected it on the basis of too much rewriting.

      I've considered something like that, actually. My problem with that is having to rewrite the code that uses these classes to put yet-another-step (and many extra keystrokes) into each usage of the code.

      What I'm thinking about, though, is having each row-hash autoconvert to a row-collection when the SQL select returns many rows. The main My::DB code will create an arrayref, then iteratively call itself to load hashrefs into that arrayref to store each row. During usage in that situation, each access to the tied hash object will get/set data relative to a "cursor" which points to any given row. To manipulate the cursor, I may either access a method using the statement $tho->setCursor(25) to increment, decrement, set or get the current cursor/row position. Or, an easier way is to simply write to a specially-named (and hopefully unique and unclobberable) hash key, something like this:

      $th{'_CURSORPOS_'} = $newRowSelection;

      This, of course, may be frought with potential problems should I have a table, down the road, with a column named '_CURSORPOS_'. As well, I'd have to use a read-only hash key named something like '_TOTALROWS_' to return the total number of rows in the collection for usage in loops. I hate flattening things like this, but it might be my way out of this problem.

      (Ph) Phaysis (Shawn)
      If idle hands are the tools of the devil, are idol tools the hands of god?

Re: Tied hashes and arrays, yeah. But tied typeglobs?
by tilly (Archbishop) on Oct 04, 2003 at 15:27 UTC
    You could give the $tho object a method that advances by one row. Then your sample usage is something like this..
    my $tho = tie %th, 'My::DB::User', {-data=>\%nu}; while (exists $th{'username'}) { print "username is: $th{'username'}\n"; $tho->nextRow; }
    If you want to consider more arbitrary APIs and tie is not doing what you want in terms of overloading behaviour, you can play games with overload instead.

    However my suggestion is that your API will quickly become so arbitrary that would-be users will have a hard time figuring out how it could possibly do what it does under the cover, and will get very confused. Were I going from scratch, KISS tells me to seriously consider not setting any tie anywhere because I don't see how it is clarifying my API.

      Thanks for the overload suggestion. I can see where I could use it, carefully of course, for autoincrementing or autodecrementing the current row.

      I am, though, plowing ahead with a variation of what I suggested above. The My::DB::TIEHASH method tests for the need to load one row or many rows based on a few cases: if there's a "WHERE" clause defined in the parameter -where=>'active = 1' in the tie constructor (any "WHERE" existing assumes multiple rows), or if data passed in through -data has an array of hashes in it instead of a plain hash (many rows).

      Right now, my sample code usage goes as follows:

      ... tie %th, 'My::DB::User', {-where=>'active=1'}; for (my $i=0; $i < $th{'_ROWCOUNT_'}; $i++) { $th{'_CURRENTROW_'} = $i; print "$th{'username'} has been an active member since $th{'create +d'}\n"; }

      I do see a situation where overload could be useful, though. The whole for loop could be avoided by overloading ++ and -- to manipulate $th{'_CURRENTROW_'} like so:

      ... while ($th{'_CURRENTROW_'} < $th{'_ROWCOUNT_'}) { #do something, then: $th{'_CURRENTROW_'}++; }

      I should look into this. I may not need to overload depending on how autoincrement acts on the tied hash already ; I need to test this. As far as my API is concerned, I think it's working out rather well in this case. I can see where it can become a ball of mud, but I'm fighting to keep it simple. Of course, I've not gotten into making the variant subclasses of My::DB for all the remaining tables yet....

      (Ph) Phaysis (Shawn)
      If idle hands are the tools of the devil, are idol tools the hands of god?