[prev] [thread] [next] [Date index for 2006/01/27]
All, I've been using Class::DBI for a bit now and came up with what I think are a couple of nifty extensions I thought I might share, if only to ask if I've simply reinvented someone else's wheel. The first trick I did was to add a method called "as_graph" to my base CDBI class so that I could get the whole schema as a Graph::Directed object. It looks essentially like this: sub as_graph { my $g = Graph::Directed->new; for my $table ( represented_tables() ) { my $class = table_name_to_class( $table ); my $has_a = $class->meta_info('has_a') || {}; my %valid = map { $_, 1 } $class->columns('All'); my $pk_name = $class->columns('Primary'); # Check foreign key references. if ( %$has_a ) { while ( my ( $fk, $fk_info ) = each %$has_a ) { my $fk_class = $fk_info->{'foreign_class'}; $g->add_edge( $table, $fk_class->table ); } } if ( !$g->has_vertex( $table ) ) { $g->add_vertex( $table ); } } return $g; } You'll notice that I had to come up with a way to get a listing of all the tables, so I hacked in the "represented_tables" method. Then I have a simple algorithm in my "table_name_to_class" for converting table names to class names (e.g., table_name => "My::CDBI::Prefix::TableName"). It turns out there are many times when it's nice to have a graph representation of your schema, such as when I'm generating my classes from my schema[1]. I like to put all the CDBI classes into one file, but the class definition needs to be just right to satisfy CDBI's "has_a" and "has_many" relationships. It turns out using Graph::Traversal::DFS for a depth-first search is just the way to handle this. Additionally, I've added a method called "get_related" that allows any inherited CDBI object to use the graph to query for any related object in any table, not just the ones adjacent and defined by "has_a" or "has_many." E.g., say you have a Brewery that makes Beers that are served in Pubs, you could ask a Brewery object to return all the Pubs in which its Beers are sold like so: my @pubs = $brewery->get_related('pub'); The code looks like this: sub get_related { my $self = shift; my $dest_table = shift or croak 'No destination table'; my $opts_ref = shift || {}; my $this_table = $self->table; my $graph = $self->as_graph; if ( !$graph->has_vertex( $dest_table ) ) { croak "Invalid destination table ($dest_table)"; } # make edges bidirectional my @edges = $graph->edges; for my $edge ( @edges ) { $graph->add_edge( reverse @$edge ); } my @path = $graph->SP_Dijkstra( $this_table, $dest_table ); if ( @path ) { my $start = shift @path; my @data = _uniq( grep { $_->table eq $dest_table } _extract( $self, \@path ) ); if ( @data ) { return wantarray ? @data : $data[0]; } else { return wantarray ? () : undef; } } else { my $this_table = $self->table; croak "No path from $this_table to $dest_table"; } return; } This uses a little "_uniq" method (which I hacked from List::MoreUtils): sub _uniq { my %h; map { $h{ $_->id() }++ == 0 ? $_ : () } @_; } And this little recursive "_extract" sub: sub _extract { my ( $from, $path ) = @_; return if !defined $from; my $next = shift @$path or return; return if !$from->can( $next ); my @return; for my $object ( $from->$next() ) { next if !defined $object; if ( @$path ) { push @return, _extract( $object, $path ); } else { push @return, $object; }; } return @return; } I'm very interested to see if anyone would comment on this because I'm not very confident that it's as well-written as it could be. I had troubles initially when there wasn't data along the path to get to the destination table, so it would return at whatever furthest object it would reach. Additionally, I'm trying to figure out a way for the "get_related" to sort the found objects, so I'd love to hear any suggestions there. I hope some of you find this helpful or interesting. Humbly, ky [1] - Using a template and SQL::Translator. I'm happy to post that code if anyone is interested in autogenerating code from schemas. _______________________________________________ ClassDBI mailing list ClassDBI@xxxxx.xxxxxxxxxxxxxxxx.xxx http://lists.digitalcraftsmen.net/mailman/listinfo/classdbi
[CDBI] Graph methods with CDBI to retrieve non-adjacent related data
|
Re: [CDBI] Graph methods with CDBI to retrieve non-adjacent related data
|
Generated at 17:59 on 03 Feb 2006 by mariachi v0.52