File Coverage

blib/lib/SPOPSx/Ginsu.pm
Criterion Covered Total %
statement 11 11 100.0
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 15 15 100.0


line stmt bran cond sub pod time code
1             package SPOPSx::Ginsu;
2              
3 7     7   736180 use strict;
  7         16  
  7         336  
4 7     7   42 use vars qw($VERSION $Revision);
  7         14  
  7         642  
5              
6             BEGIN {
7 7     7   68 $Revision = sprintf "%d.%03d", q$Revision: 1.60 $ =~ /: (\d+)\.(\d+)/;
8 7         176 $VERSION = '0.58';
9             }
10              
11 7     7   39 use base qw( SPOPSx::Ginsu::DBI );
  7         16  
  7         4763  
12             use SPOPSx::Ginsu::DBI;
13             use SPOPS::ClassFactory;
14             use SPOPS::DBI;
15             use DBI qw( :sql_types );
16             use Log::Log4perl qw( get_logger );
17              
18             my $log = get_logger();
19              
20             sub ROOT_OBJ_CLASS { die "Must be overridden by a root base class."; }
21              
22             sub e_has_a { return { }; }
23              
24             ##----- Public Class Methods -----
25             sub new {
26             my $class = shift;
27             my $p = shift;
28             # my $self = $class->SUPER::new($p);
29            
30             ## Since SUPER::new($p) ignores keys in $p that are defined as fields
31             ## in the CONFIG of a parent object, we have to do the assigning of
32             ## these parameters manually (or fix SPOPS to handle this internally).
33             my $self = $class->SUPER::new;
34            
35             foreach my $field ( @{$class->all_fields} ) {
36             $self->{$field} = defined $p->{$field} ? $p->{$field} : undef;
37             }
38            
39             $self->{class} = ref($self);
40              
41             return $self;
42             }
43              
44             sub isa_classes {
45             my $self = shift;
46             my $isa = $self->_isa_classes;
47            
48             return [ sort { $isa->{$a} <=> $isa->{$b} } keys %$isa ];
49             }
50              
51             sub inherited_fields {
52             my $class = shift;
53             $class = ref($class) if ref($class); ## get class if passed an object
54            
55             my $fields = [];
56             foreach my $c ( @{$class->isa_classes} ) {
57             next if $c eq $class;
58             foreach my $field ( @{$c->field_list} ) {
59             push @$fields, $field unless $field eq $c->id_field;
60             }
61             }
62              
63             return $fields;
64             }
65              
66             sub all_fields { return [ @{$_[0]->field_list}, @{$_[0]->inherited_fields} ]; }
67              
68             sub all_field_types {
69             my $class = shift;
70             my $p = shift;
71             $class = ref($class) if ref($class); ## get class if passed an object
72            
73             my $type_info = {};
74             foreach my $c ( @{$class->isa_classes} ) {
75             my $c_types = { $c->db_discover_types( $c->base_table, $p )->as_hash };
76             foreach my $field ( @{$c->field_list} ) {
77             $type_info->{$field} = $c_types->{$field}
78             unless $field eq $c->id_field && $c ne $class; ## skip parent table ids
79             }
80             }
81              
82             return $type_info;
83             }
84              
85             sub config_and_init {
86             my $class = shift;
87            
88             SPOPS::ClassFactory->create( $class->_build_conf )
89             unless $class->_config_processed;
90              
91             $class->class_initialize;
92             }
93              
94             ## copied straight from SPOPS::DBI, with the modifications as noted
95             sub fetch {
96             my ( $class, $id, $p ) = @_;
97             $p ||= {};
98              
99             $log->is_debug &&
100             $log->debug( "Trying to fetch an item of $class with ID $id and params ",
101             join( " // ", map { sprintf( "%s -> %s", $_, defined $p->{$_} ? $p->{$_} : '' ) }
102             grep { defined $_ } keys %{ $p } ) );
103              
104             # No ID, no object
105              
106             return undef unless ( defined( $id ) and $id ne '' and $id !~ /^tmp/ );
107              
108             # Security violations bubble up to caller
109              
110             my $level = $p->{security_level};
111             unless ( $p->{skip_security} ) {
112             $level ||= $class->check_action_security({ id => $id,
113             required => SPOPS::Secure::SEC_LEVEL_READ });
114             }
115              
116             # Do any actions the class wants before fetching -- note that if
117             # any of the actions returns undef (false), we bail.
118              
119             return undef unless ( $class->pre_fetch_action( { %{ $p }, id => $id } ) );
120              
121             my $obj = undef;
122              
123             # If we were passed the data for an object, go ahead and create
124             # it; if not, check to see if we can whip up a cached object
125              
126             if ( ref $p->{data} eq 'HASH' ) {
127             $obj = $class->new({ %{ $p->{data} }, skip_default_values => 1 });
128             }
129             else {
130             $obj = $class->get_cached_object({ %{ $p }, id => $id });
131             $p->{skip_cache}++; # Set so we don't re-cache it later
132             }
133              
134             unless ( ref $obj eq $class ) {
135             ##----- REPLACE THIS ORIGINAL CODE -----
136             # my ( $raw_fields, $select_fields ) = $class->_fetch_select_fields( $p );
137             ##----- WITH THIS OVERRIDING CODE -----
138             ## Note: this code skips the column group and alter field stuff
139             my $table_name = $class->base_table;
140             my $my_id_field = $class->id_field;
141             my $raw_fields = [];
142             my $select_fields = [];
143             my $sqltables = [];
144             my $sqlwhere = [];
145             foreach my $parent_class ( @{$class->isa_classes} ) {
146             my $table = $parent_class->table_name;
147             my $id_field = $parent_class->id_field;
148              
149             push @$sqltables, $table; ## list of tables for "FROM" clause
150            
151             ## join tables by id field (set all equal to id field of this class)
152             push @$sqlwhere, $table . '.' . $id_field . ' = ' .
153             $table_name . '.' . $my_id_field
154             unless($table_name eq $table);
155            
156             ## all fields, except id of inherited tables
157             foreach my $field ( keys %{$parent_class->field} ) {
158             next if $parent_class ne $class && $field eq $id_field;
159             push @$select_fields, $table . '.' . $field;
160             push @$raw_fields, $field;
161             }
162             }
163             push @$sqlwhere, $class->id_clause( $id, undef, $p );
164             ##----- END OVERRIDING CODE -----
165             $log->is_info &&
166             $log->info( "SELECTing: ", join( "//", @{ $select_fields } ) );
167              
168             # Put all the arguments into a hash (so we can reuse them simply
169             # later) and grab the record
170              
171             ##----- REPLACE THIS ORIGINAL CODE -----
172             # my %args = (
173             # from => [ $class->table_name ],
174             # select => $select_fields,
175             # where => $class->id_clause( $id, undef, $p ),
176             ##----- WITH THIS OVERRIDING CODE -----
177             my %args = (
178             from => $sqltables,
179             select => $select_fields,
180             where => join(' AND ', @$sqlwhere),
181             ##----- END OVERRIDING CODE -----
182             db => $p->{db},
183             return => 'single',
184             );
185             my $row = eval { $class->db_select( \%args ) };
186             if ( $@ ) {
187             $class->fail_fetch( \%args );
188             die $@;
189             }
190              
191             # If the row isn't found, return nothing; just as if an incorrect
192             # (or nonexistent) ID were passed in
193              
194             return undef unless ( $row );
195              
196             # Note that we pass $p along to the ->new() method, in case
197             # other information was passed in needed by it -- however, we
198             # need to be careful that certain parameters used by this
199             # method (e.g., the optional 'field_alter') is not the same as
200             # a parameter of an object -- THAT would be fun to debug...
201              
202             $obj = $class->new({ id => $id, skip_default_values => 1, %{ $p } });
203             $obj->_fetch_assign_row( $raw_fields, $row, $p );
204             }
205             return $obj->_fetch_post_process( $p, $level );
206             }
207              
208             ## copied straight from SPOPS::DBI, with the modifications as noted
209             sub fetch_group {
210             my ( $class, $p ) = @_;
211             ##----- REPLACE THIS ORIGINAL CODE -----
212             # ( $p->{raw_fields}, $p->{select} ) = $class->_construct_group_select( $p );
213             ##----- WITH THIS OVERRIDING CODE -----
214             ## Note: this code skips the column group and alter field stuff
215             my $table_name = $class->table_name;
216             my $p_original = $p ? { %$p } : {};
217             my $my_id_field = $class->id_field;
218             my $raw_fields = [];
219             my $select_fields = [];
220             my $sqltables = [];
221             my $sqlwhere = [];
222              
223             foreach my $parent_class ( @{$class->isa_classes} ) {
224             my $table = $parent_class->table_name;
225             my $id_field = $parent_class->id_field;
226              
227             push @$sqltables, $table; ## list of tables for "FROM" clause
228              
229             ## join tables by id field (set all equal to id field of this class)
230             push @$sqlwhere, $table . '.' . $id_field . ' = ' .
231             $table_name . '.' . $my_id_field
232             unless($table_name eq $table);
233              
234             ## all fields, except id of inherited tables
235             foreach my $field ( keys %{$parent_class->field} ) {
236             next if $parent_class ne $class && $field eq $id_field;
237             push @$select_fields, $table . '.' . $field;
238             push @$raw_fields, $field;
239             }
240             }
241              
242             ## original table list and WHERE clause
243             push @$sqltables, @{ $p->{from} } if $p->{from};
244             push @$sqlwhere, $p->{where} if $p->{where};
245              
246             $p->{where} = join(' AND ', @$sqlwhere);
247             $p->{from} = $sqltables;
248             $p->{select} = $select_fields;
249             $p->{raw_fields} = $raw_fields;
250              
251             ## get indices into rows of class name and object id
252             my ($classname_idx) = grep $raw_fields->[$_] eq 'class', (0..$#{$raw_fields});
253             my ($id_field_idx) = grep $raw_fields->[$_] eq $class->id_field, (0..$#{$raw_fields});
254             ##----- END OVERRIDING CODE -----
255             my $sth = $class->_execute_multiple_record_query( $p );
256             my ( $offset, $max ) = SPOPS::Utility->determine_limit( $p->{limit} );
257             my @obj_list = ();
258              
259             my $row_count = 0;
260             ROW:
261             while ( my $row = $sth->fetchrow_arrayref ) {
262             ##----- BEGIN ADDITIONAL CODE -----
263             my $newclass = $row->[ $classname_idx ];
264             if ($newclass eq $class) {
265             ##----- END ADDITIONAL CODE -----
266             my $obj = $class->new({ skip_default_values => 1 });
267             $obj->_fetch_assign_row( $p->{raw_fields}, $row, $p );
268              
269             next ROW unless ( $obj ); # How could this ever be true?
270              
271             # Check security on the row unless overridden by
272             # 'skip_security'. If the security check fails that's ok, just
273             # skip the row and move on
274              
275             my $sec_level = SPOPS::Secure::SEC_LEVEL_WRITE;
276             unless ( $p->{skip_security} ) {
277             $log->is_debug &&
278             $log->debug( "Checking security for [", ref( $obj ), ": ", $obj->id, "]" );
279             $sec_level = eval {
280             $obj->check_action_security({ required => SPOPS::Secure::SEC_LEVEL_READ })
281             };
282             if ( $@ ) {
283             $log->is_info &&
284             $log->info( "Security check for object in ",
285             "fetch_group() failed, skipping." );
286             next ROW;
287             }
288             }
289              
290             # Not to the offset yet, so go to the next row but still increment
291             # the counter so we calculate limits properly
292              
293             if ( $offset and ( $row_count < $offset ) ) {
294             $row_count++;
295             next ROW;
296             }
297             last ROW if ( $max and ( $row_count >= $max ) );
298             $row_count++;
299              
300             # If we've made it down to here, we're home free; just call the
301             # post_fetch callback
302              
303             next ROW unless ( $obj->_fetch_post_process( $p, $sec_level ) );
304             push @obj_list, $obj;
305             ##----- BEGIN ADDITIONAL CODE -----
306             } else {
307             next ROW unless UNIVERSAL::isa($newclass, $class);
308             my $obj = $newclass->fetch( $row->[ $id_field_idx ], $p_original);
309             next ROW unless ( $obj );
310              
311             ## deleted security check (done by fetch)
312              
313             # Not to the offset yet, so go to the next row but still increment
314             # the counter so we calculate limits properly
315              
316             if ( $offset and ( $row_count < $offset ) ) {
317             $row_count++;
318             next ROW;
319             }
320             last ROW if ( $max and ( $row_count >= $max ) );
321             $row_count++;
322              
323             ## deleted _post_fetch_process (done by fetch)
324             push @obj_list, $obj;
325             }
326             ##----- END ADDITIONAL CODE -----
327             }
328             $sth->finish;
329             return \@obj_list;
330             }
331              
332             ## copied straight from SPOPS::DBI, with the modifications as noted
333             sub fetch_count {
334             my ( $class, $p ) = @_;
335             my $row_count = 0;
336             ##----- REPLACE THIS ORIGINAL CODE -----
337             # if ( $p->{skip_security} ) {
338             # $p->{select} = [ 'COUNT(*)' ];
339             # my $db = $p->{db}
340             # || $class->global_datasource_handle( $p->{connect_key} );
341             # my $row_count_rec = eval {
342             # $class->db_select({ select => [ 'COUNT(*)' ],
343             # where => $p->{where},
344             # value => $p->{value},
345             # from => $class->table_name,
346             # return => 'single',
347             # db => $db })
348             # };
349             # $row_count = $row_count_rec->[0];
350             # if ( $@ ) {
351             # $log->warn( "Caught error running SELECT COUNT(*): $@" );
352             # }
353             # }
354             # else {
355             # $p->{select} = [ $class->id_field_select( $p ) ];
356             ##----- WITH THIS OVERRIDING CODE -----
357             ## should be fine if the class has a table,
358             ## except we can't use inherited fields in WHERE clause
359             ## without doing explicit join
360             return $class->SUPER::fetch_count($p) if $class->_config_processed;
361            
362             my $obj_table = $class->ROOT_OBJ_CLASS->table_name;
363             my $my_table = $class->table_name;
364             $p->{select} = [ $class->id_field_select( $p ), "$obj_table.class" ];
365             if ($my_table ne $obj_table) {
366             push @{$p->{from}}, $obj_table;
367             my @where = $obj_table . '.' . $class->ROOT_OBJ_CLASS->id_field . ' = ' .
368             $my_table . '.' . $class->id_field;
369             push @where, $p->{where} if $p->{where};
370             $p->{where} = join(' AND ', @where);
371             }
372             ##----- END OVERRIDING CODE -----
373             my $sth = $class->_execute_multiple_record_query( $p );
374             while ( my $row = $sth->fetch ) {
375             eval {
376             $class->check_action_security({ id => $row->[0],
377             required => SPOPS::Secure::SEC_LEVEL_READ })
378             };
379             next if ( $@ );
380             ##----- BEGIN ADDITIONAL CODE -----
381             next unless UNIVERSAL::isa($row->[1], $class);
382             ##----- END ADDITIONAL CODE -----
383             $row_count++;
384             }
385             ##----- BEGIN REMOVE CODE -----
386             # }
387             ##----- END REMOVE CODE -----
388             return $row_count;
389             }
390              
391             sub pm_fetch {
392             my ( $class, $id, $p ) = @_;
393              
394             $p->{where} = $class->id_clause( $id );
395             my $obj = $class->fetch_group( $p )->[0];
396              
397             return $obj;
398             }
399              
400             sub fetch_group_by_field {
401             my ( $class, $field, $vals, $p ) = @_;
402             return [] unless @$vals;
403              
404             my $where = $class->base_table . ".$field" .
405             ' IN (' . join(',', map('?', @$vals)) . ')';
406             $where .= ' AND (' . $p->{where} . ')' if $p->{where};
407             $p->{where} = $where;
408             unshift @{$p->{value}}, @$vals;
409              
410             my $objs = $class->fetch_group( $p );
411              
412             return $objs;
413             }
414              
415             sub fetch_group_by_ids {
416             my ( $class, $ids, $p ) = @_;
417              
418             my $unordered = $class->fetch_group_by_field( $class->id_field, $ids, $p );
419            
420             ## order by id list
421             my %obj_by_id = map { $_->id => $_ } @$unordered;
422             my @ordered = grep { $_ } map { $obj_by_id{$_} } @$ids;
423              
424             return \@ordered;
425             }
426              
427             ##----- Public Object Methods -----
428             ## copied straight from SPOPS::DBI, with the modifications as noted
429             sub save {
430             my ( $self, $p ) = @_;
431             $log->is_info &&
432             $log->info( "Trying to save a (", ref $self, ")" );
433              
434             # We can force save() to be an INSERT by passing in a true value
435             # for the is_add parameter; otherwise, we rely on the flag within
436             # SPOPS::Tie to reflect whether an object has been saved or not.
437              
438             my $is_add = ( $p->{is_add} or ! $self->saved );
439              
440             # If this is an update and it hasn't changed, we don't need to do
441             # anything.
442              
443             unless ( $is_add or $self->changed ) {
444             $log->is_info &&
445             $log->info( "This object exists and has not changed. Exiting." );
446             return $self;
447             }
448              
449             # Check security for create/update
450              
451             my $action = ( $is_add ) ? 'create' : 'update';
452             my ( $level );
453             unless ( $p->{skip_security} ) {
454             $level = $self->check_action_security({ required => SPOPS::Secure::SEC_LEVEL_WRITE,
455             is_add => $is_add });
456             }
457             $log->is_info &&
458             $log->info( "Security check passed ok. Continuing." );
459              
460             # Callback for objects to do something before they're saved
461              
462             return undef unless ( $self->pre_save_action({ %{ $p },
463             is_add => $is_add }) );
464              
465             ##----- BEGIN ADDITIONAL CODE -----
466             ## get list of classes which need to be saved
467             ## (put ROOT_OBJ_CLASS first, and this class last)
468             my $no_insert = $p->{no_insert}; ## this gets converted to an empty hash
469             ## which causes problems the 2nd time through
470             my @classes = reverse @{ $self->isa_classes };
471             foreach my $class (@classes) {
472             bless $self, $class;
473             $p->{field} = [];
474             $p->{value} = [];
475             $p->{no_insert} = $no_insert;
476             ##----- END ADDITIONAL CODE -----
477             # Do not include these fields in the insert/update at all. Allow
478             # user to override even with an empty arrayref.
479              
480             my ( %not_included );
481             if ( $is_add ) {
482             my ( @no_insert_items );
483             if ( $p->{no_insert} ) {
484             @no_insert_items = ( ref $p->{no_insert} eq 'ARRAY' )
485             ? @{ $p->{no_insert} } : ( $p->{no_insert} );
486             }
487             elsif ( my $no_insert_config = $self->no_insert ) {
488             @no_insert_items = keys %{ $no_insert_config };
489             }
490             %not_included = map { $_ => 1 } @no_insert_items;
491             }
492             else {
493             my ( @no_update_items );
494             if ( $p->{no_update} ) {
495             @no_update_items = ( ref $p->{no_update} eq 'ARRAY' )
496             ? @{ $p->{no_update} } : ( $p->{no_update} );
497             }
498             elsif ( my $no_update_config = $self->no_update ) {
499             @no_update_items = keys %{ $no_update_config };
500             }
501             %not_included = map { $_ => 1 } @no_update_items;
502             }
503              
504             # Do not include these fields in the insert/update if they're not defined
505             # (note that this includes blank/empty)
506              
507             $p->{skip_undef} ||= [];
508             my $skip_undef = $self->skip_undef || {};
509             $skip_undef->{ $_ }++ for ( @{ $p->{skip_undef} } );
510              
511             $p->{field} = [];
512             $p->{value} = [];
513              
514             FIELD:
515             foreach my $field ( keys %{ $self->field } ) {
516             next FIELD if ( $not_included{ $field } );
517             my $value = $self->{ $field };
518             next FIELD if ( ! defined $value and $skip_undef->{ $field } );
519             push @{ $p->{field} }, $field;
520             push @{ $p->{value} }, $value;
521             }
522              
523             # Do the insert/update based on whether the object is new; don't
524             # catch the die() that might be thrown -- let that percolate
525              
526             ##----- REPLACE THIS ORIGINAL CODE -----
527             # if ( $is_add ) { $self->_save_insert( $p, \%not_included ) }
528             ##----- WITH THIS OVERRIDING CODE -----
529             if ( $is_add ) {
530             eval { $self->_save_insert( $p, \%not_included ) };
531             ## clean up partial saves if there is a duplicate entry error
532             if (my $error = $@) { ## save $@ from getting overwritten
533             ## in remove_from_parent_tables()
534             $self->_remove_from_parent_tables if $error =~ /Duplicate entry/;
535             die $error;
536             }
537             }
538             ##----- END OVERRIDING CODE -----
539             else { $self->_save_update( $p, \%not_included ) }
540             ##----- BEGIN ADDITIONAL CODE -----
541             }
542             ##----- END ADDITIONAL CODE -----
543              
544             # Set the 'has_save' flag so that any saved changes to the object
545             # in the post_save will be an update rather than another insert;
546             # clear the changed fields for the same reason
547              
548             $self->has_save;
549             $self->clear_change;
550              
551             # Do any actions that need to happen after you save the object
552              
553             return undef unless ( $self->post_save_action({ %{ $p },
554             is_add => $is_add }) );
555              
556             # Save the newly-created/updated object to the cache
557              
558             $self->set_cached_object( $p );
559              
560             # Note the action that we've just taken (opportunity for subclasses)
561              
562             unless ( $p->{skip_log} ) {
563             $self->log_action( $action, scalar $self->id );
564             }
565              
566             return $self;
567             }
568              
569             sub compare {
570             my $self = shift;
571             my $twin = shift;
572             my $p = shift;
573              
574             ## must be objects of the same type
575             return 0 unless ref($self) eq ref($twin);
576            
577             ## and their fields must all have the same values
578             my $type_info = $self->all_field_types($p);
579             foreach my $field ( @{$self->all_fields} ) {
580             next if $field eq $self->id_field;
581             next unless defined $self->{$field} || defined $twin->{$field};
582             return 0 unless defined $self->{$field} && $twin->{$field};
583             if ( ref($self->{$field}) ) {
584             return 0 unless $self->{$field}->compare($twin->{$field});
585             } else {
586             if ( $self->_is_numeric_type($type_info->{$field}) ) {
587             return 0 unless $self->{$field} == $twin->{$field};
588             } else {
589             return 0 unless $self->{$field} eq $twin->{$field};
590             }
591             }
592             }
593              
594             return 1;
595             }
596              
597             sub as_string {
598             my $self = shift;
599             my $tab = shift || '';
600             my $fields = $self->CONFIG->{as_string_order} || $self->all_fields;
601             my $msg = '';
602             foreach my $field (@$fields){
603             $msg .= $tab.sprintf( "%-20s: %s\n", $field, defined $self->{$field} ? $self->{$field} : '');
604             my $ref = ref $self->{$field};
605             next unless ($ref && $ref ne 'HASH' && $ref ne 'ARRAY');
606             $msg .= $self->{$field}->as_string("\t");
607             }
608             return $msg;
609             }
610              
611             ##----- Private Class Methods -----
612             ## overrides method in SPOPS
613             sub _get_definitive_fields { return $_[0]->all_fields; }
614              
615             sub _build_conf {
616             my $class = shift;
617             my $conf = shift || {};
618              
619             # get the conf variable for the class.
620             my $class_conf = $class->_get_CONF;
621             # get the alias for the class
622             my $alias = $class->_get_main_alias;
623              
624             unless (exists ($conf->{$alias})) {
625             $conf->{$alias} = $class_conf->{$alias};
626             }
627             my $class_links = $class->_get_links_to || '';
628             if ($class_links) {
629             foreach my $key (keys %$class_links) {
630             next if ($key->_config_processed ||
631             exists $conf->{$key->_get_main_alias});
632             $conf = $key->_build_conf($conf);
633             }
634             }#end if
635             return $conf;
636             }
637              
638             sub _get_main_alias {
639             ## This method can be used to find the main alias even
640             ## before the class's configuration has been processed.
641             my $class = shift;
642             my $conf = eval '$' . $class . '::CONF';
643             my ($alias) = grep $conf->{$_}->{class} eq $class, keys %$conf;
644              
645             return $alias;
646             }
647              
648             sub _get_CONF { return eval '$' . $_[0] . '::CONF'; }
649             sub _get_links_to { return $_[0]->_get_CONF->{$_[0]->_get_main_alias}->{links_to}; }
650              
651             sub _config_processed {
652             no strict 'refs';
653             my $CONFIG_method = *{$_[0]."::CONFIG"}{CODE};
654             return ref($CONFIG_method) eq 'CODE';
655             }
656              
657             sub _is_numeric_type {
658             my $self = shift;
659             my $type_info = shift;
660            
661             return grep $type_info == $_, ( SQL_NUMERIC,
662             SQL_DECIMAL,
663             SQL_INTEGER,
664             SQL_SMALLINT,
665             SQL_FLOAT,
666             SQL_REAL,
667             SQL_DOUBLE,
668             # (no longer in DBI) SQL_BIGINT,
669             SQL_TINYINT );
670             }
671              
672             sub _isa_classes {
673             my $class = shift;
674             my $href = shift || {};
675             my $depth = shift || 1;
676              
677             $class = ref($class) if ref($class); ## get class if passed an object
678             $href->{$class} = $depth; ## stick it as a key in the hash
679            
680             foreach my $parent ( @{$class->CONFIG->{isa}} ) {
681             next unless $parent->isa($class->ROOT_OBJ_CLASS);
682             $href = $parent->_isa_classes($href, $depth+1)
683             unless $href->{$parent} && $href->{$parent} > $depth+1;
684             }
685             return $href;
686             }
687              
688             ##----- Private Object Methods -----
689             sub _remove_from_parent_tables {
690             my $self = shift;
691             my $p = shift || {};
692            
693             foreach my $class (@{ $self->isa_classes }) {
694             next if $class eq ref($self);
695             eval {
696             $class->db_delete({
697             table => $class->table_name,
698             where => $class->id_clause($self->id, 'noqualify', {%$p}),
699             db => $p->{db},
700             })
701             };
702             warn "Unable to remove row from ". $class->table_name if $@;
703             }
704              
705             return 1;
706             }
707              
708             ##----- Callback Methods -----
709             sub post_fetch_action {
710             my $self = shift;
711             my $orig_p = shift || {};
712              
713             ## call the overridden post_fetch_action to handle rulesets
714             $self->SUPER::post_fetch_action( $orig_p );
715              
716             foreach my $field ( keys %{ $self->e_has_a } ) {
717             my $h = $self->e_has_a->{$field};
718             if ( $h->{fetch} && $h->{fetch}{type} eq 'auto' ) {
719             if ( my $val = $self->{$field} ) {
720             my %p;
721             $p{db} = $orig_p->{db} if defined $orig_p->{db};
722             $self->{$field} = $h->{class}->pm_fetch($val, \%p ) ||
723             die "Could not auto-fetch '$field' ($h->{class}) id: $val";
724             }
725             }
726             }
727              
728             return $self;
729             }
730              
731             sub pre_save_action {
732             my $self = shift;
733             my $orig_p = shift || {};
734            
735             ## call the overridden pre_save_action to handle rulesets
736             $self->SUPER::pre_save_action( $orig_p );
737              
738             foreach my $field ( keys %{ $self->e_has_a } ) {
739             my $h = $self->e_has_a->{ $field };
740             my $val = $self->{$field};
741             if ( $val && ref $val ) {
742             ## save if indicated
743             my %p;
744             $p{db} = $orig_p->{db} if defined $orig_p->{db};
745             $val->save( \%p ) if $h->{fetch} && !$h->{fetch}{nosave};
746              
747             ## move object to a temp field during save
748             $self->{'tmp_' . $field . '_'} = $val;
749             $self->{$field} = $self->{$field}->id;
750             }
751             }
752              
753             return $self;
754             }
755              
756             sub post_save_action {
757             my $self = shift;
758             my $orig_p = shift || {};
759              
760             ## call the overridden post_save_action to handle rulesets
761             $self->SUPER::post_save_action( $orig_p );;
762              
763             foreach my $field ( keys %{ $self->e_has_a } ) {
764             my $h = $self->e_has_a->{$field};
765             my $val = $self->{'tmp_' . $field . '_'};
766             if ( $val && ref $val ) {
767             $self->{$field} = $val;
768             $self->{'tmp_' . $field . '_'} = undef;
769             } elsif ( $h->{fetch} && $h->{fetch}{type} eq 'auto' ) {
770             if ( my $val = $self->{$field} ) {
771             my %p;
772             $p{db} = $orig_p->{db} if defined $orig_p->{db};
773             $self->{$field} = $h->{class}->pm_fetch($val, \%p ) ||
774             die "Could not auto-fetch '$field' ($h->{class}) id: $val";
775             }
776             }
777             }
778              
779             return $self;
780             }
781              
782             sub pre_remove_action {
783             my $self = shift;
784             my $orig_p = shift || {};
785             my $class = ref($self);
786              
787             ## call the overridden pre_remove_action to handle rulesets
788             $self->SUPER::pre_remove_action( $orig_p );
789              
790             ## auto-remove specified secondary objects
791             foreach my $field ( keys %{ $self->e_has_a } ) {
792             my $h = $self->e_has_a->{$field};
793             if ( $h->{remove} && $h->{remove}{type} eq 'auto' && $self->{$field} ) {
794             my %p;
795             $p{db} = $orig_p->{db} if defined $orig_p->{db};
796             $self->{$field} = $h->{class}->pm_fetch($self->{$field}, \%p )
797             unless ref $self->{$field};
798             $self->{$field}->remove( $orig_p ) if $self->{$field};
799             }
800             }
801              
802             ## remove all corresponding rows in 'links_to' tables
803             my ($table, $where);
804             foreach $table ( values %{$self->CONFIG->{links_to}} ) {
805             $where = $self->id_clause(undef, 'noqualify', $orig_p);
806             eval { $self->db_delete( { table => $table,
807             where => $where,
808             db => $orig_p->{db} }) };
809             if ( $@ ) {
810             warn "Unable to remove links.";
811             }
812             }
813            
814             ## remove corresponding row in each parent table
815             $self->_remove_from_parent_tables;
816              
817             return $self;
818             }
819              
820             1;
821             __END__