File Coverage

blib/lib/Basset/Object/Persistent.pm
Criterion Covered Total %
statement 60 646 9.2
branch 19 434 4.3
condition 7 129 5.4
subroutine 12 58 20.6
pod 41 45 91.1
total 139 1312 10.5


line stmt bran cond sub pod time code
1             package Basset::Object::Persistent;
2              
3             #Basset::Object::Persistent Copyright and (c) 2000, 2002, 2003, 2004, 2005, 2006 James A Thomason III
4             #Basset::Object::Persistent is distributed under the terms of the Perl Artistic License.
5              
6             our $VERSION = '1.03';
7              
8             =pod
9              
10             =head1 NAME
11              
12             Basset::Object::Persistent - subclass of Basset::Object that allows objects to be easily stored into a relational database.
13             Presently only supports MySQL, but that may change in the future.
14              
15             =head1 AUTHOR
16              
17             Jim Thomason, jim@jimandkoka.com
18              
19             =head1 SYNOPSIS
20              
21             (no synopsis, this is an abstract super class that should never be instantiated directly, it should be subclassed for all
22             persistent objects and used through them)
23              
24             =head1 DESCRIPTION
25              
26             Basset::Object is the uber module in my Perl world. All objects should decend from Basset::Object. It handles defining attributes,
27             error handling, construction, destruction, and generic initialization. It also talks to Basset::Object::Conf to allow conf file use.
28              
29             But, some objects cannot simply be recreated constantly every time a script runs. Sometimes you need to store the data in an object
30             between uses so that you can recreate an object in the same form the last time you left it. Storing user information, for instance.
31              
32             Basset::Object::Persistent allows you to do that transparently and easily. Persistent objects need to define several pieces of additional
33             information to allow them to commit to the database, including their table definitions. Once these items are defined, you'll have access
34             to the load and commit methods to allow you to load and store the objects in a database.
35              
36             It is assumed that an object is stored in the database in a primary table. The primary table
37             contains a set of columns named the same as object attributes. The attributes are stored in those columns.
38              
39             Some::Package->add_attr('foo');
40             my $obj = Some::Package->new();
41             $obj->foo('bar');
42             $obj->commit();
43              
44             in the database, the 'foo' column will be set to 'bar'.
45              
46             =cut
47              
48 2     2   40019 use Scalar::Util qw(weaken isweak);
  2         5  
  2         189  
49              
50 2     2   1390 use Basset::Object;
  2         5  
  2         104  
51             our @ISA = Basset::Object->pkg_for_type('object');
52              
53 2     2   14 use strict;
  2         5  
  2         87  
54 2     2   43 use warnings;
  2         4  
  2         3092  
55              
56             =pod
57              
58             =head1 ATTRIBUTES
59              
60             =over
61              
62             =item loaded
63              
64             boolean flag 1/0.
65              
66             This flag tells you whether or not the objects you are operating on has been loaded from a database or initially created
67             at this time and not loaded. This flag is set internally, and you should only read it.
68              
69             =cut
70              
71             =pod
72              
73             =begin btest(loaded)
74              
75             my $o = __PACKAGE__->new();
76             $test->ok($o, "Got object");
77             $test->is(scalar(__PACKAGE__->loaded), undef, "could not call object method as class method");
78             $test->is(__PACKAGE__->errcode, "BO-08", "proper error code");
79             $test->is(scalar($o->loaded), 0, 'loaded is 0');
80             $test->is($o->loaded('abc'), 'abc', 'set loaded to abc');
81             $test->is($o->loaded(), 'abc', 'read value of loaded - abc');
82             my $h = {};
83             $test->ok($h, 'got hashref');
84             $test->is($o->loaded($h), $h, 'set loaded to hashref');
85             $test->is($o->loaded(), $h, 'read value of loaded - hashref');
86             my $a = [];
87             $test->ok($a, 'got arrayref');
88             $test->is($o->loaded($a), $a, 'set loaded to arrayref');
89             $test->is($o->loaded(), $a, 'read value of loaded - arrayref');
90              
91             =end btest(loaded)
92              
93             =cut
94              
95             __PACKAGE__->add_attr('loaded');
96              
97             =pod
98              
99             =item loading
100              
101             read only boolean flag 1/0.
102              
103             This flag is usually used internally, it keeps track of whether or not the object is currently in the process of loading
104             from the database. It will always be zero unless the object is loading. This flag is set internally, and you should only read it.
105              
106             =cut
107              
108             =pod
109              
110             =begin btest(loading)
111              
112             my $o = __PACKAGE__->new();
113             $test->ok($o, "Got object");
114             $test->is(scalar(__PACKAGE__->loading), undef, "could not call object method as class method");
115             $test->is(__PACKAGE__->errcode, "BO-08", "proper error code");
116             $test->is(scalar($o->loading), 0, 'loading is 0');
117             $test->is($o->loading('abc'), 'abc', 'set loading to abc');
118             $test->is($o->loading(), 'abc', 'read value of loading - abc');
119             my $h = {};
120             $test->ok($h, 'got hashref');
121             $test->is($o->loading($h), $h, 'set loading to hashref');
122             $test->is($o->loading(), $h, 'read value of loading - hashref');
123             my $a = [];
124             $test->ok($a, 'got arrayref');
125             $test->is($o->loading($a), $a, 'set loading to arrayref');
126             $test->is($o->loading(), $a, 'read value of loading - arrayref');
127              
128             =end btest(loading)
129              
130             =cut
131              
132             __PACKAGE__->add_attr('loading');
133              
134              
135             =item committing
136              
137             read only boolean flag 1/0.
138              
139             This flag is usually used internally, it keeps track of whether or not the object is currently in the process of committing
140             to the database. It will always be zero unless the object is committing. This flag is set internally, and you should only read it.
141              
142             =cut
143              
144             =pod
145              
146             =begin btest(committing)
147              
148             my $o = __PACKAGE__->new();
149             $test->ok($o, "Got object");
150             $test->is(scalar(__PACKAGE__->committing), undef, "could not call object method as class method");
151             $test->is(__PACKAGE__->errcode, "BO-08", "proper error code");
152             $test->is(scalar($o->committing), 0, 'committing is 0');
153             $test->is($o->committing('abc'), 'abc', 'set committing to abc');
154             $test->is($o->committing(), 'abc', 'read value of committing - abc');
155             my $h = {};
156             $test->ok($h, 'got hashref');
157             $test->is($o->committing($h), $h, 'set committing to hashref');
158             $test->is($o->committing(), $h, 'read value of committing - hashref');
159             my $a = [];
160             $test->ok($a, 'got arrayref');
161             $test->is($o->committing($a), $a, 'set committing to arrayref');
162             $test->is($o->committing(), $a, 'read value of committing - arrayref');
163              
164             =end btest(committing)
165              
166             =cut
167              
168             __PACKAGE__->add_attr('committing');
169              
170             =item committed
171              
172             Flag, N/0.
173              
174             This flag tells you whether this object has been committed during this instantiation. It will not keep track of whether an object has
175             been committed before this instantiation. The value is either 0 (no commits during this instantiation) or N, where N is a positive integer
176             number containing the number of times that this object has been committed during this instantiation. This flag is set internally, and
177             you should only read it.
178              
179             $object->commit();
180             if ($object->committed){
181             print "Yay, committed!";
182             }
183             else {
184             print "Could not commit : " . $object->errstring . "\n";
185             };
186              
187             =cut
188              
189             =pod
190              
191             =begin btest(committed)
192              
193             my $o = __PACKAGE__->new();
194             $test->ok($o, "Got object");
195             $test->is(scalar(__PACKAGE__->committed), undef, "could not call object method as class method");
196             $test->is(__PACKAGE__->errcode, "BO-08", "proper error code");
197             $test->is(scalar($o->committed), 0, 'committed is 0');
198             $test->is($o->committed('abc'), 'abc', 'set committed to abc');
199             $test->is($o->committed(), 'abc', 'read value of committed - abc');
200             my $h = {};
201             $test->ok($h, 'got hashref');
202             $test->is($o->committed($h), $h, 'set committed to hashref');
203             $test->is($o->committed(), $h, 'read value of committed - hashref');
204             my $a = [];
205             $test->ok($a, 'got arrayref');
206             $test->is($o->committed($a), $a, 'set committed to arrayref');
207             $test->is($o->committed(), $a, 'read value of committed - arrayref');
208              
209             =end btest(committed)
210              
211             =cut
212              
213             __PACKAGE__->add_attr('committed');
214             __PACKAGE__->add_attr('in_db');
215              
216             =item deleting
217              
218             read only boolean flag 1/0.
219              
220             This flag is usually used internally, it keeps track of whether or not the object is currently in the process of being deleted
221             from the database. It will always be zero unless the object is deleting. This flag is set internally, and you should only read it.
222              
223             =cut
224              
225             =pod
226              
227             =begin btest(deleting)
228              
229             my $o = __PACKAGE__->new();
230             $test->ok($o, "Got object");
231             $test->is(scalar(__PACKAGE__->deleting), undef, "could not call object method as class method");
232             $test->is(__PACKAGE__->errcode, "BO-08", "proper error code");
233             $test->is(scalar($o->deleting), 0, 'deleting is 0');
234             $test->is($o->deleting('abc'), 'abc', 'set deleting to abc');
235             $test->is($o->deleting(), 'abc', 'read value of deleting - abc');
236             my $h = {};
237             $test->ok($h, 'got hashref');
238             $test->is($o->deleting($h), $h, 'set deleting to hashref');
239             $test->is($o->deleting(), $h, 'read value of deleting - hashref');
240             my $a = [];
241             $test->ok($a, 'got arrayref');
242             $test->is($o->deleting($a), $a, 'set deleting to arrayref');
243             $test->is($o->deleting(), $a, 'read value of deleting - arrayref');
244              
245             =end btest(deleting)
246              
247             =cut
248              
249             __PACKAGE__->add_attr('deleting');
250              
251             =pod
252              
253             =item deleted
254              
255             Boolean flag, 1/0.
256              
257             When an object is deleted via the ->delete method, this flag is set to 1. Otherwise, it is 0. This is the only change that is made
258             to an object when it is deleted, so this is the way to determine if your delete was successful. This flag is set internally, and
259             you should only read it.
260              
261             $object->delete();
262             if ($object->deleted){
263             print "Yay, deleted!";
264             }
265             else {
266             print "Could not delete : " . $object->errstring . "\n";
267             };
268              
269             =cut
270              
271             =pod
272              
273             =begin btest(deleted)
274              
275             my $o = __PACKAGE__->new();
276             $test->ok($o, "Got object");
277             $test->is(scalar(__PACKAGE__->deleted), undef, "could not call object method as class method");
278             $test->is(__PACKAGE__->errcode, "BO-08", "proper error code");
279             $test->is(scalar($o->deleted), 0, 'deleted is 0');
280             $test->is($o->deleted('abc'), 'abc', 'set deleted to abc');
281             $test->is($o->deleted(), 'abc', 'read value of deleted - abc');
282             my $h = {};
283             $test->ok($h, 'got hashref');
284             $test->is($o->deleted($h), $h, 'set deleted to hashref');
285             $test->is($o->deleted(), $h, 'read value of deleted - hashref');
286             my $a = [];
287             $test->ok($a, 'got arrayref');
288             $test->is($o->deleted($a), $a, 'set deleted to arrayref');
289             $test->is($o->deleted(), $a, 'read value of deleted - arrayref');
290              
291             =end btest(deleted)
292              
293             =cut
294              
295             __PACKAGE__->add_attr('deleted');
296              
297             # tables is a class attribute that internally stores the tables associated with this object
298             __PACKAGE__->add_trickle_class_attr('tables', []);
299              
300             =pod
301              
302             =item arbitrary_selectables
303              
304             This should be set in the conf file. This is a regular expression that specifies which queries arbitary_sql
305             should expect to return data. A good value for MySQL is: (show|select|desc|set)
306              
307             =cut
308              
309             =pod
310              
311             =begin btest(arbitrary_selectables)
312              
313             =end btest(arbitrary_selectables)
314              
315             =cut
316              
317             __PACKAGE__->add_class_attr('arbitrary_selectables', '(show|select|desc|set)');
318              
319             =pod
320              
321             =item force_insert
322              
323             Boolean flag. 1/0. Trickles to subclasses.
324              
325             Your objects may be transactional in nature such that you always want to keep a record of them
326             no matter how often they've changed. In that case, you can specify the force_insert flag.
327              
328             Care must be taken with this flag to ensure you never violate primary key constraints. Also, you
329             may not use auto generated ids, for obvious reasons.
330              
331             =cut
332              
333             =pod
334              
335             =begin btest(force_insert)
336              
337             =end btest(force_insert)
338              
339             =cut
340              
341             __PACKAGE__->add_trickle_class_attr('force_insert');
342              
343             #=pod
344             #
345             #=item iterator
346             #
347             #Internally manages the iterator used by load_next
348             #
349             #=cut
350             #
351             #=pod
352              
353             =begin btest(iterator)
354              
355             my $o = __PACKAGE__->new();
356             $test->ok($o, "Got object");
357             $test->is(scalar($o->iterator), undef, 'iterator is undefined');
358             $test->is($o->iterator('abc'), 'abc', 'set iterator to abc');
359             $test->is($o->iterator(), 'abc', 'read value of iterator - abc');
360             my $h = {};
361             $test->ok($h, 'got hashref');
362             $test->is($o->iterator($h), $h, 'set iterator to hashref');
363             $test->is($o->iterator(), $h, 'read value of iterator - hashref');
364             my $a = [];
365             $test->ok($a, 'got arrayref');
366             $test->is($o->iterator($a), $a, 'set iterator to arrayref');
367             $test->is($o->iterator(), $a, 'read value of iterator - arrayref');
368              
369             =end btest(iterator)
370              
371             =cut
372              
373             __PACKAGE__->add_trickle_class_attr('iterator');
374              
375             =pod
376              
377             =back
378              
379             =head1 METHODS
380              
381             =over
382              
383             =cut
384              
385             sub add_primary_attr {
386 0     0 0 0 my $pkg = shift;
387            
388 0         0 foreach my $record (@_) {
389 0 0       0 my $attribute = ref $record eq 'ARRAY' ? $record->[0] : $record;
390            
391 0         0 $pkg->add_attr($record);
392            
393 0 0       0 $pkg->_primary_attributes->{$attribute}++
394             unless $pkg->is_attribute($attribute, 'non_primary');
395             }
396              
397             }
398              
399             sub add_non_primary_attr {
400 0     0 0 0 my $pkg = shift;
401            
402 0         0 foreach my $record (@_) {
403 0 0       0 my $attribute = ref $record eq 'ARRAY' ? $record->[0] : $record;
404            
405 0         0 $pkg->add_attr($record);
406            
407 0 0       0 $pkg->_non_primary_attributes->{$attribute}++
408             unless $pkg->is_attribute($attribute, 'primary');
409             }
410            
411             }
412              
413             sub attributes {
414 0     0 1 0 my $class = shift->pkg;
415 0         0 my $type = shift;
416            
417 0         0 my @attributes = ();
418            
419 0 0 0     0 if (defined $type && $type eq 'primary') {
    0 0        
420 0         0 @attributes = keys %{$class->_primary_attributes};
  0         0  
421             }
422             elsif (defined $type && $type eq 'non_primary') {
423 0         0 @attributes = keys %{$class->_non_primary_attributes};
  0         0  
424             }
425             else {
426 0         0 return $class->SUPER::attributes($type, @_);
427             }
428            
429 0         0 return [sort grep {! /^_/} @attributes];
  0         0  
430             }
431              
432              
433             sub is_attribute {
434 0     0 1 0 my $class = shift->pkg;
435 0         0 my $attribute = shift;
436 0   0     0 my $type = shift || 'instance';
437              
438 0 0       0 if (defined $type) {
439 0 0       0 if ($type eq 'primary') {
    0          
440 0         0 return $class->_primary_attributes->{$attribute};
441             }
442             elsif ($type eq 'non_primary') {
443 0         0 return $class->_non_primary_attributes->{$attribute};
444             }
445             }
446            
447 0         0 return $class->SUPER::is_attribute($attribute, $type, @_);
448             }
449              
450             __PACKAGE__->add_trickle_class_attr('_primary_attributes', {});
451             __PACKAGE__->add_trickle_class_attr('_non_primary_attributes', {});
452              
453             #=item init
454             #
455             #Nothing you need to worry about, Basset::Object::Persistent just intercepts init and makes sure that loaded and committed are specified first,
456             #so that objects may rely upon them being set before the start of the initialization process. Then end up getting re-specified by the
457             #super method, but that's of no consequence.
458             #
459             #=cut
460              
461             #=pod
462              
463             #=cut
464              
465             sub init {
466 14     14 1 28 my $self = shift;
467              
468 14         116 return $self->SUPER::init(
469             'loading' => 0,
470             'loaded' => 0,
471             'committing' => 0,
472             'committed' => 0,
473             'deleting' => 0,
474             'deleted' => 0,
475             'in_db' => 0,
476             'instantiated_relationships' => {},
477             'tied_to_parent' => 0,
478             'should_be_committed' => 0,
479             'should_be_deleted' => 0,
480             '_deleted_relationships' => [],
481             @_,
482             );
483              
484             };
485              
486             =pod
487              
488             =begin btest(init)
489              
490             my $o = __PACKAGE__->new();
491             $test->ok($o, "got object for init");
492              
493             $test->is($o->loading, 0, "loading is 0");
494             $test->is($o->loaded, 0, "loaded is 0");
495             $test->is($o->committing, 0, "committing is 0");
496             $test->is($o->committed, 0, "committed is 0");
497             $test->is($o->deleting, 0, "deleting is 0");
498             $test->is($o->deleted, 0, "deleted is 0");
499             $test->is(ref($o->instantiated_relationships), 'HASH', 'instantiated_relationships is hashref');
500             $test->is($o->tied_to_parent, 0, 'tied_to_parent is 0');
501             $test->is($o->should_be_committed, 0, 'should_be_committed is 0');
502             $test->is($o->should_be_deleted, 0, 'should_be_committed is 0');
503             $test->is(ref($o->_deleted_relationships), 'ARRAY', '_deleted_relationships is arrayref');
504              
505             =end btest(init)
506              
507             =cut
508              
509             =pod
510              
511             =over
512              
513             =item _keyed_accessor
514              
515             This is an accessor designed to be specified with add_attr. For example,
516              
517             Basset::User->add_attr(['user_group', '_keyed_accessor'], 'Basset::Group');
518              
519             That would specify that if you have a user object, you can only specify values to your user_group
520             attribute that would successfully load into a Basset::Group object.
521              
522             You can shut off the key validation if you're positive your value is valid
523              
524             $user->user_group($group_id); #validates
525             $user->user_group($group_id, 'valid'); #does not validate
526              
527             Also note that the validation does not occur when the object is loading. It is assumed that if the key made it
528             into the database, it's valid.
529              
530             =cut
531              
532             =pod
533              
534             =begin btest(_keyed_accessor)
535              
536             =end btest(_keyed_accessor)
537              
538             =cut
539              
540             sub _isa_keyed_accessor {
541 0     0   0 my $pkg = shift;
542 0         0 my $attr = shift;
543 0         0 my $prop = shift;
544 0         0 my $class = shift;
545            
546             return sub {
547 0     0   0 my $self = shift;
548 0 0       0 if (@_) {
549 0         0 my $val = shift;
550 0   0     0 my $valid = shift || 0;
551 0 0 0     0 if (defined $val && ! $valid && ! $self->loading) {
      0        
552 0 0       0 $self->load_pkg($class) or return;
553 0 0       0 unless ($class->exists($val) ) {
554 0         0 return $self->error("Cannot store value $val - object does not exist for $class", "BOP-48");
555             }
556             }
557 0         0 return $self->$prop($val);
558             }
559             else {
560 0         0 return $self->$prop();
561             }
562             }
563 0         0 }
564              
565             sub _isa_committing_accessor {
566 0     0   0 my $pkg = shift;
567 0         0 my $attr = shift;
568 0         0 my $prop = shift;
569 0 0       0 my $interceptor = shift or return $pkg->error("Cannot make committing accessor w/o interceptor", "XXX");
570            
571             return sub {
572 0     0   0 my $self = shift;
573 0 0       0 if ($self->committing) {
574 0         0 return $self->$interceptor($prop, @_);
575             }
576             else {
577 0         0 return $self->$prop(@_);
578             }
579             }
580 0         0 }
581              
582             =pod
583              
584             =item add_primarytable
585              
586             add_primarytable is a class method that takes a hash as an argument, which is used as a constructor
587             call for a Basset::DB::Table object (or whatever you've specified as your table type object)
588              
589             __PACKAGE__->add_primarytable(
590             'name' => 'transaction',
591             'primary_column' => 'id',
592             'autogenerated' => 1,
593             'definition' => {
594             'id' => 'SQL_INTEGER',
595             'account' => 'SQL_INTEGER',
596             'paidby' => 'SQL_INTEGER',
597             'category' => 'SQL_INTEGER',
598             'day' => 'SQL_DATE',
599             'amount' => 'SQL_DECIMAL',
600             'description' => 'SQL_VARCHAR',
601             }
602             );
603              
604             See Basset::DB::Table for more information. This table is the primary table where the object's data is stored.
605              
606             This method is a wrapper around add_tables with a single table ->factory call on the 'table' type, but it also
607             explicitly wipes out the tables list before setting the primary table.
608              
609             =cut
610              
611             =pod
612              
613             =begin btest(add_primarytable)
614              
615             =end btest(add_primarytable)
616              
617             =cut
618              
619             sub add_primarytable {
620 3     3 1 7 my $class = shift;
621              
622 3         6 my $table;
623              
624 3         6 my $create_attributes = 0;
625              
626 3 50       12 if (@_ == 1) {
627 0         0 $table = $_[0];
628             } else {
629              
630 3         13 my %init = @_;
631              
632 3 50       26 $table = $class->factory('type' => 'table', @_) or return;
633              
634             }
635              
636 3         29 $class->tables([]);
637 3         21 $class->add_tables($table);
638              
639 3         7 return $table;
640             };
641              
642             sub auto_create_attributes {
643 0     0 0 0 my $class = shift;
644              
645 0 0 0     0 my $tables = shift || $class->tables
646             or return $class->error("Cannot auto-create attributes w/o tables", "BOP-86");
647              
648 0         0 foreach my $table (@$tables) {
649 0         0 my @attributes = keys %{$table->definition};
  0         0  
650 0         0 foreach my $column (@attributes) {
651 0         0 my $attribute = $table->alias_column($column);
652 0 0       0 $class->add_attr($attribute) or return;
653             };
654             };
655              
656 0         0 return 1;
657              
658             };
659              
660             sub add_tables {
661 3     3 1 6 my $class = shift;
662              
663 3 50       21 return $class->error("Cannot add table w/o tables", "BOP-85") unless @_;
664              
665 3         4 my @tables = @{$class->tables};
  3         13  
666 3         7 my %existing_table = map {$_->name, 1} @tables;
  0         0  
667              
668 3         10 while (my $table = shift @_) {
669 3 50       12 next if $existing_table{$table->name};
670 3         7 push @tables, $table;
671              
672 3 50       8 if ($table->create_attributes) {
673              
674 2     2   24 no strict 'refs';
  2         3  
  2         4275  
675              
676 0         0 my @attributes_to_create = $table->attributes_to_create;
677 0         0 foreach my $attribute (@attributes_to_create) {
678              
679 0         0 $class->add_attr($attribute);
680              
681             }
682             } #end if create_attributes
683              
684             } #end while tables
685              
686 3         17 $class->tables(\@tables);
687              
688 3         8 return 1;
689             }
690              
691             #####
692             #
693             # XXX THIS IS EXTREMELY TEMPORARY AND A PROTOTYPE
694             #
695             # If you're looking in here, you shouldn't be. For the record, I'm debating a major overhaul of
696             # Basset's concept of "persistence" and abstracting it royally out the ass into Basset::Storage.
697             # But it's a huge undertaking, and I haven't figured out quite what needs to be done, how to do it,
698             # or if I want to. But enjoy pondering the magical little method you're spying on here. It may
699             # come to naught.
700             #
701             #####
702              
703             sub add_storage {
704 0     0 0 0 my $class = shift;
705            
706 0 0       0 return $class->error("Cannot add storage w/o storage", "XXX") unless @_;
707            
708 0         0 while (my $storage = shift @_) {
709              
710 0 0       0 my $table = $class->factory(
711             'type' => 'table',
712             'primary_column' => $class->attributes('primary'),
713             'non_primary_columns' => $class->attributes('non_primary'),
714             %$storage
715             ) or return;
716              
717 0         0 $class->add_tables($table);
718             }
719            
720 0         0 return 1;
721             }
722              
723             # XXX END TEMPORARY HACK
724              
725             =pod
726              
727             =item add_tables
728              
729             add_tables is a class method that takes a list of tables as its arguments, which are the tables
730             associated with this object when it is stored to the database.
731              
732             __PACKAGE__->add_primarytable(
733             __PACKAGE__->factory(
734             'type' => 'table',
735             'name' => 'transaction',
736             'primary_column' => 'id',
737             'autogenerated' => 1,
738             'definition' => {
739             'id' => 'SQL_INTEGER',
740             'account' => 'SQL_INTEGER',
741             'paidby' => 'SQL_INTEGER',
742             'category' => 'SQL_INTEGER',
743             'day' => 'SQL_DATE',
744             'amount' => 'SQL_DECIMAL',
745             'description' => 'SQL_VARCHAR',
746             }
747             )
748             );
749              
750             See Basset::DB::Table for more information.
751              
752             =cut
753              
754             =pod
755              
756             =begin btest(add_tables)
757              
758             =end btest(add_tables)
759              
760             =cut
761              
762             =pod
763              
764             =item primary_table
765              
766             Returns the first table associated with the given object.
767              
768             =cut
769              
770             sub primary_table {
771 36     36 1 108 return shift->tables->[0];
772             };
773              
774             =pod
775              
776             =begin btest(primary_table)
777              
778             =end btest(primary_table)
779              
780             =cut
781              
782             =pod
783              
784             =item relationships
785              
786             This is a class attribute that internally stores the relationships used by this class. Specify new relationships with has_a
787             or has_many.
788              
789             =cut
790              
791             =begin btest(relationships)
792              
793             =end btest(relationships)
794              
795             =cut
796              
797             __PACKAGE__->add_trickle_class_attr('relationships', {});
798              
799             =pod
800              
801             =item should_be_deleted
802              
803             This is used to flag an object that has been auto-vivified and is tied to a parent object. You should rarely need
804             to set, access, or worry about this flag directly.
805              
806             =cut
807              
808             =pod
809              
810             =begin btest(should_be_deleted)
811              
812             my $o = __PACKAGE__->new();
813             $test->ok($o, "Got object");
814             $test->is(scalar(__PACKAGE__->should_be_deleted), undef, "could not call object method as class method");
815             $test->is(__PACKAGE__->errcode, "BO-08", "proper error code");
816             $test->is(scalar($o->should_be_deleted), 0, 'should_be_deleted is 0');
817             $test->is($o->should_be_deleted('abc'), 'abc', 'set should_be_deleted to abc');
818             $test->is($o->should_be_deleted(), 'abc', 'read value of should_be_deleted - abc');
819             my $h = {};
820             $test->ok($h, 'got hashref');
821             $test->is($o->should_be_deleted($h), $h, 'set should_be_deleted to hashref');
822             $test->is($o->should_be_deleted(), $h, 'read value of should_be_deleted - hashref');
823             my $a = [];
824             $test->ok($a, 'got arrayref');
825             $test->is($o->should_be_deleted($a), $a, 'set should_be_deleted to arrayref');
826             $test->is($o->should_be_deleted(), $a, 'read value of should_be_deleted - arrayref');
827              
828             =end btest(should_be_deleted)
829              
830             =cut
831              
832             __PACKAGE__->add_attr('should_be_deleted');
833              
834             =pod
835              
836             =item should_be_committed
837              
838             This is used to flag an object that has been auto-vivified and is tied to a parent object. You should rarely need
839             to set, access, or worry about this flag directly.
840              
841             =cut
842              
843             =pod
844              
845             =begin btest(should_be_committed)
846              
847             my $o = __PACKAGE__->new();
848             $test->ok($o, "Got object");
849             $test->is(scalar(__PACKAGE__->should_be_committed), undef, "could not call object method as class method");
850             $test->is(__PACKAGE__->errcode, "BO-08", "proper error code");
851             $test->is(scalar($o->should_be_committed), 0, 'should_be_committed is zero');
852             $test->is($o->should_be_committed('abc'), 'abc', 'set should_be_committed to abc');
853             $test->is($o->should_be_committed(), 'abc', 'read value of should_be_committed - abc');
854             my $h = {};
855             $test->ok($h, 'got hashref');
856             $test->is($o->should_be_committed($h), $h, 'set should_be_committed to hashref');
857             $test->is($o->should_be_committed(), $h, 'read value of should_be_committed - hashref');
858             my $a = [];
859             $test->ok($a, 'got arrayref');
860             $test->is($o->should_be_committed($a), $a, 'set should_be_committed to arrayref');
861             $test->is($o->should_be_committed(), $a, 'read value of should_be_committed - arrayref');
862              
863             =end btest(should_be_committed)
864              
865             =cut
866              
867             __PACKAGE__->add_attr('should_be_committed');
868              
869             __PACKAGE__->add_attr('tied_to_parent');
870              
871             =pod
872              
873             =item instantiated_relationships
874              
875             Internal hash that keeps track of which relationships for a given object have been instantiated. Check for instantiation via the
876             is_instantiated method instead.
877              
878             =cut
879              
880             =pod
881              
882             =begin btest(instantiated_relationships)
883              
884             my $o = __PACKAGE__->new();
885             $test->ok($o, "Got object");
886             $test->is(scalar(__PACKAGE__->instantiated_relationships), undef, "could not call object method as class method");
887             $test->is(__PACKAGE__->errcode, "BO-08", "proper error code");
888             $test->is(ref(scalar($o->instantiated_relationships)), 'HASH', 'instantiated_relationships is hashref');
889             $test->is($o->instantiated_relationships('abc'), 'abc', 'set instantiated_relationships to abc');
890             $test->is($o->instantiated_relationships(), 'abc', 'read value of instantiated_relationships - abc');
891             my $h = {};
892             $test->ok($h, 'got hashref');
893             $test->is($o->instantiated_relationships($h), $h, 'set instantiated_relationships to hashref');
894             $test->is($o->instantiated_relationships(), $h, 'read value of instantiated_relationships - hashref');
895             my $a = [];
896             $test->ok($a, 'got arrayref');
897             $test->is($o->instantiated_relationships($a), $a, 'set instantiated_relationships to arrayref');
898             $test->is($o->instantiated_relationships(), $a, 'read value of instantiated_relationships - arrayref');
899              
900             =end btest(instantiated_relationships)
901              
902             =cut
903              
904             __PACKAGE__->add_attr('instantiated_relationships');
905              
906             =pod
907              
908             =item cental_load_cache
909              
910             if the use_central_load_cache parameter is set in the conf file, then objects will use a centralized loading cache, stored here.
911             This is internal only.
912              
913             =cut
914              
915             =pod
916              
917             =begin btest(cental_load_cache)
918              
919             =end btest(cental_load_cache)
920              
921             =cut
922              
923             __PACKAGE__->add_class_attr('central_load_cache', {});
924              
925             =pod
926              
927             =item _deleted_relationships
928              
929             Internal method. Keeps track of instantiated associated objects that were subsequently deleted. No looky, no touchy.
930              
931             =cut
932              
933             =pod
934              
935             =begin btest(_deleted_relationships)
936              
937             my $o = __PACKAGE__->new();
938             $test->ok($o, "Got object");
939             $test->is(scalar(__PACKAGE__->_deleted_relationships), undef, "could not call object method as class method");
940             $test->is(__PACKAGE__->errcode, "BO-08", "proper error code");
941             $test->is(ref(scalar($o->_deleted_relationships)), 'ARRAY', '_deleted_relationships is arrayref');
942             $test->is($o->_deleted_relationships('abc'), 'abc', 'set _deleted_relationships to abc');
943             $test->is($o->_deleted_relationships(), 'abc', 'read value of _deleted_relationships - abc');
944             my $h = {};
945             $test->ok($h, 'got hashref');
946             $test->is($o->_deleted_relationships($h), $h, 'set _deleted_relationships to hashref');
947             $test->is($o->_deleted_relationships(), $h, 'read value of _deleted_relationships - hashref');
948             my $a = [];
949             $test->ok($a, 'got arrayref');
950             $test->is($o->_deleted_relationships($a), $a, 'set _deleted_relationships to arrayref');
951             $test->is($o->_deleted_relationships(), $a, 'read value of _deleted_relationships - arrayref');
952              
953             =end btest(_deleted_relationships)
954              
955             =cut
956              
957             __PACKAGE__->add_attr('_deleted_relationships');
958              
959             =pod
960              
961             =item is_instantiated
962              
963             Boolean operator. Given an attribute, returns true if it is an associated attribute and has been instantiated, false if it has not been.
964              
965             =cut
966              
967             =pod
968              
969             =begin btest(is_instantiated)
970              
971             =end btest(is_instantiated)
972              
973             =cut
974              
975             sub is_instantiated {
976 0     0 1 0 my $self = shift;
977 0 0       0 my $prop = shift or return $self->error("Cannot determine if is instantiated w/o prop", "BOP-71");
978              
979 0         0 $prop = $self->deprivatize($prop);
980              
981 0   0     0 return $self->instantiated_relationships->{$prop} || 0;
982              
983              
984 0         0 my $val = $self->$prop();
985              
986 0 0       0 if (ref $val eq 'HASH') {
    0          
987 0         0 return keys %$val;
988             } elsif (ref $val eq 'ARRAY') {
989 0         0 return @$val;
990             } else {
991 0         0 return ref $val;
992             };
993             }
994              
995             =pod
996              
997             =item instantiate
998              
999             In the abstract, this is simple. Takes an attribute and an optional set of clauses, then instantiates that object.
1000              
1001             $obj->instantiate('foo');
1002              
1003             Now $obj->foo will contain whatever the instantiated list of information is, as defined when it was set up with the has_a or
1004             has_many call. Alternatively, you can pass in a set of clauses to restrict the objects loaded.
1005              
1006             $obj->instantiate('foo', {
1007             'where' => 'status_id = 1'
1008             });
1009              
1010             Will instantiate the 'foo' attribute only with the objects that have a status_id of 1, anything else will simply not be loaded. A useful
1011             clauses flag to pass is "temporary" - this will instantiate the relationship according to the clauses, but not populate the attribute.
1012              
1013             Note that you should only instantiate an attribute that is defined has having an instantiating parameter of 'manual' (as opposed
1014             to 'lazy' ) and this is due to encapsulation reasons.
1015              
1016             Lazy objects are not instantiated until the attribute holding them is accessed, but then they are instantiated automatically.
1017              
1018             Manual objects are the ones that you want to worry about. In those cases, the instantiate method is basically a shortcut to insulate you
1019             from needing to take extra steps and know the class involved.
1020              
1021             Say that a user has_many classes. You could do this:
1022              
1023             use Some::Class;
1024             use Some::User;
1025              
1026             my $user = Some::User->load(1);
1027             my $classes = Some::Class->load_where('user_id' => $user->id);
1028              
1029             or this
1030              
1031             use Some::User;
1032              
1033             my $user = Some::User->load(1);
1034             my $classes = $user->instantiate('classes');
1035              
1036             =cut
1037              
1038             sub instantiate {
1039              
1040 0     0 1 0 my $self = shift;
1041 0 0       0 my $prop = shift or return $self->error("Cannot instantiate w/o attribute", "BOP-72");
1042 0   0     0 my $clauses = shift || {};
1043 0         0 my @values = @_;
1044              
1045 0 0 0     0 if ($self->is_instantiated($prop) && ! $clauses->{'temporary'}) {
1046 0         0 $self->notify("warnings", "object already instantiated");
1047             };
1048              
1049 0         0 my $relationships = $self->relationships;
1050              
1051 0 0       0 my $relationship_data = $relationships->{$prop}
1052             or return $self->error("Cannot instantiate $prop : not relationship", "BOP-73");
1053              
1054 0         0 my $c = $relationship_data->{'clauses'};
1055              
1056 0         0 $clauses = {%$c, %$clauses};
1057              
1058 0         0 my $table = $relationship_data->{'table'};
1059 0 0       0 $table = $table->[0] if ref $table eq 'ARRAY';
1060 0         0 my $fclass = $relationship_data->{'class'};
1061              
1062 0 0       0 $self->load_pkg($fclass) or return;
1063              
1064 0 0       0 my ($referencing_cols, $foreign_cols) = $self->relationship_columns($prop) or return;
1065              
1066 0 0 0     0 return $self->error("Cannot instantiate - parent and child tables do not reference each other", "BOP-91")
1067             unless @$foreign_cols && @$referencing_cols;
1068              
1069 0         0 push @values, map {$self->$_()} $table->alias_column(@$referencing_cols);
  0         0  
1070              
1071 0         0 my $where = join(' AND ', map {"$_ = ?"} @$foreign_cols);
  0         0  
1072              
1073 0 0       0 if ($clauses->{'where'}) {
1074 0         0 $clauses->{'where'} .= " AND ($where)";
1075             } else {
1076 0         0 $clauses->{'where'} = $where;
1077             }
1078              
1079 0 0 0     0 my $instantiated = $clauses->{'value'} || $relationship_data->{'class'}->load_all(
1080             {
1081             'key' => $relationship_data->{'key'},
1082             'constructor' => {
1083             'tied_to_parent' => $relationship_data->{'tied_to_parent'}
1084             },
1085             %$clauses,
1086             },
1087             @values
1088             ) or return $self->error($relationship_data->{'class'}->errvals);
1089              
1090 0 0       0 if ($relationship_data->{'singleton'}) {
1091 0         0 $instantiated = $instantiated->[0];
1092             }
1093              
1094 0 0       0 if ($clauses->{'temporary'}) {
1095 0         0 return $instantiated;
1096             };
1097              
1098 0         0 $self->$prop($instantiated);
1099              
1100 0         0 $self->instantiated_relationships->{$prop}++;
1101              
1102 0         0 return $instantiated;
1103              
1104             };
1105              
1106             =pod
1107              
1108             =begin btest(instantiate)
1109              
1110             =end btest(instantiate)
1111              
1112             =cut
1113              
1114              
1115             =pod
1116              
1117             =item uninstantiate
1118              
1119             =cut
1120              
1121              
1122             sub uninstantiate {
1123 0     0 1 0 my $self = shift;
1124 0 0       0 my $prop = shift or return $self->error("Cannot uninstantiate w/o prop", "BOP-96");
1125              
1126 0         0 $self->$prop(undef);
1127              
1128 0         0 delete $self->instantiated_relationships->{$prop};
1129              
1130 0         0 return 1;
1131             }
1132              
1133             =pod
1134              
1135             =begin btest(uninstantiate)
1136              
1137             =end btest(uninstantiate)
1138              
1139             =cut
1140              
1141              
1142              
1143             =pod
1144              
1145             =item has_a
1146              
1147             has_a defines relationship between objects. "An object 'has_a' different object". The has_a method is simply a wrapper around
1148             has_many, passing in a key of undef and setting the singleton flag to 1.
1149              
1150             =cut
1151              
1152             =pod
1153              
1154             =begin btest(has_a)
1155              
1156             =end btest(has_a)
1157              
1158             =cut
1159              
1160             sub has_a {
1161 0     0 1 0 my $class = shift;
1162 0 0       0 my $attribute = shift or return $class->error("Cannot have many w/o attribute", "BOP-75");
1163 0 0       0 my $fclass = shift or return $class->error("Cannot have many w/o class", "BOP-76");
1164              
1165 0   0     0 my $init = shift || {};
1166              
1167 0         0 return $class->has_many(
1168             $attribute => $fclass,
1169             {
1170             %$init,
1171             'key' => undef,
1172             'singleton' => 1,
1173             }
1174             );
1175             };
1176              
1177             =pod
1178              
1179             =item _instantiating_accessor
1180              
1181             If a relationship is defined in has_many with instantiating -> lazy, then the associated objects will be populated automagically, but not
1182             until the attribute is accessed. _instantiating_accessor internally handles all of that.
1183              
1184             =cut
1185              
1186             sub _isa_instantiating_accessor {
1187 0     0   0 my $pkg = shift;
1188 0         0 my $attr = shift;
1189 0         0 my $prop = shift;
1190              
1191             return sub {
1192 0     0   0 my $self = shift;
1193            
1194             #got me. Perl 5.6 seems to require I yank this out, since it's a tied hashref.
1195 0         0 my $h = $self->relationships->{$attr};
1196            
1197             #upon mutation, we'll consider that as good as an instantiation.
1198 0 0       0 if (@_) {
    0          
    0          
1199 0         0 $self->$prop(shift);
1200            
1201 0         0 $self->instantiated_relationships->{$attr}++;
1202            
1203 0         0 return $self->$prop();
1204             }
1205             #otherwise, instantiate if we're a lazy load
1206             elsif ($h->{'instantiating'} eq 'lazy') {
1207 0 0       0 $self->instantiate($attr) unless $self->is_instantiated($attr);
1208 0         0 return $self->$prop();
1209             }
1210             #otherwise, if it's instantiated, we return it.
1211             elsif ($self->is_instantiated($attr)) {
1212 0         0 return $self->$prop();
1213             }
1214             #finally, we can't do anything, so we bomb out
1215             else {
1216 0         0 return $self->error("Cannot access $attr : not instantiated", "BOP-93");
1217             }
1218            
1219            
1220             }
1221 0         0 }
1222              
1223             =pod
1224              
1225             =begin btest(_isa_instantiating_accessor)
1226              
1227             =end btest(_isa_instantiating_accessor)
1228              
1229             =cut
1230              
1231              
1232             =pod
1233              
1234             =item has_many
1235              
1236             All right, now we finally get to define relationships. The has_many parameter needs two values, the attribute and its class.
1237              
1238             Some::Class->has_many(
1239             'wibbles' => 'Some::Other::Class'
1240             );
1241              
1242             That will create an accessor for 'wibbles' and associate it with "Some::Other::Class". You could then instantiate it from an object:
1243              
1244             $someObject->instantiate('wibbles');
1245              
1246             And populate all of your wibbles data.
1247              
1248             has_many takes an optional (but recommended!) 3rd argument, the options hash. Several options are supported.
1249              
1250             =over 8
1251              
1252             =item key
1253              
1254             If loading up multiple associated objects (a cat "has_many" paws), then they will by default appear in an arbitrarily ordered arrayref
1255             containing all of the data. But, there are times when you want to load up all of the data and quickly associate objects associated with
1256             particular attributes. In that case, pass in the key parameter.
1257              
1258             Some::Class->has_many(
1259             'wibbles' => 'Some::Other::Class',
1260             {
1261             'key' => 'foo'
1262             }
1263             );
1264              
1265             Then your data will be populated into a hashref, with the associated objects' "foo" attributes serving as their keys.
1266              
1267             =item instantiating
1268              
1269             This item should be one of 2 values - 'manual' or 'lazy'
1270              
1271             Some::Class->has_many(
1272             'wibbles' => 'Some::Other::Class',
1273             {
1274             'instantiating' => 'manual'
1275             }
1276             );
1277              
1278             =over 12
1279              
1280             =item lazy
1281              
1282             lazily instantiated objects will automatically come into being when the associated attribute of the owning object is accessed for
1283             the first time. This is the default.
1284              
1285             =item manual
1286              
1287             manually instantiated objects will never automatically come into being. you will have to explicitly call 'instantiate' yourself.
1288              
1289             =back
1290              
1291             =item singleton
1292              
1293             If the singleton flag is set, then it is known that this attribute is associated with a single other object, and consequently will
1294             just hold a reference to that object itself (not in an arrayref or hashref)
1295              
1296             Some::Class->has_many(
1297             'wibbles' => 'Some::Other::Class',
1298             {
1299             'singleton' => 1
1300             }
1301             );
1302              
1303             =item clauses
1304              
1305             the clauses hashref is the same sort of clauses hashref to be handed into the loader. in fact, it is handed into the loader when
1306             the associated objects are instantiated.
1307              
1308             Some::Class->has_many(
1309             'wibbles' => 'Some::Other::Class',
1310             {
1311             'clauses' => {
1312             'where' => 'status_id = 1'
1313             }
1314             }
1315             );
1316              
1317             =item accessibility
1318              
1319             This governs encapsulation. Associating objects with other objects is good, but you don't always want the user of the class to know
1320             that other objects are involved. You should set the accessibility flag to 'private' if the associated object will never be accessed outside
1321             of the class that defines it. These classes should probably be inlined (or at least privatedly declared inside another package)
1322              
1323             Some::Class->has_many(
1324             'wibbles' => 'Some::Other::Class',
1325             {
1326             'accessibility' => 'private'
1327             }
1328             );
1329              
1330             Default value is 'public';
1331              
1332             Making an associated object private shuts off its ability to commit or delete itself. its changes only go in when its parent object
1333             is committed or deleted.
1334              
1335             =item relationship_key
1336              
1337             Sometimes, you may have an object that references two objects in a different table. You may know
1338             that every Car has a primary_driver and a secondary_driver. So you define your relationship:
1339              
1340             Car->primary_table->references(
1341             {
1342             'primary_driver' => 'driver.id',
1343             'secondary_driver' => 'driver.id'
1344             }
1345             );
1346              
1347             But you wouldn't be able to establith relationships for those items, since instantiate would
1348             try to load an object using both of those values.
1349              
1350             Car->has_a(
1351             'primary_driver' => 'Driver'
1352             );
1353              
1354             Would try to load where driver.id = car.primary_driver_id and driver.id = car.secondary_driver_id.
1355             So it would only work in the edge case when they're the same driver, which is not your intent.
1356              
1357             The solution is to explicitly define which key you'd like to join on.
1358              
1359             Car->has_a(
1360             'primary_driver' => 'Driver',
1361             {
1362             'relationship_key' => 'primary_driver'
1363             }
1364             );
1365              
1366             Car->has_a(
1367             'secondary_driver' => 'Driver',
1368             {
1369             'relationship_key' => 'secondary_driver'
1370             }
1371             );
1372              
1373             =item transform
1374              
1375             See the 'transform' flag in the load_all method for info.
1376              
1377             =item foreign_has_a
1378              
1379             If you have a has_many relationship, then presumably your foreign class has a has_a relationship
1380             with you. You can declare that relationship here. This has two advantages.
1381              
1382             1) It allows you to autmatically populate the foreign object's has_a property with yourself
1383             upon setting the has_many.
1384              
1385             2) If the foreign class references you with multiple columns (say, obj_id_1 and obj_id_2), then
1386             the foreign has_a has defined the relationship key to use. Specifying the foreign_has_a here
1387             uses those same relationship keys.
1388              
1389             =back
1390              
1391             =cut
1392              
1393             =pod
1394              
1395             =begin btest(has_many)
1396              
1397             =end btest(has_many)
1398              
1399             =cut
1400              
1401             __PACKAGE__->add_class_attr('bridge_classes', {});
1402              
1403             sub has_many {
1404 0     0 1 0 my $class = shift;
1405              
1406 0 0       0 my $attribute = shift or return $class->error("Cannot have many w/o attribute", "BOP-77");
1407 0 0       0 my $fclass = shift or return $class->error("Cannot have many w/o class", "BOP-78");
1408              
1409 0   0     0 my $init = shift || {};
1410              
1411 0   0     0 my $table = $init->{'table'} || $class->primary_table;
1412              
1413 0 0       0 if (ref $fclass eq 'ARRAY') {
1414 0         0 my $bridgekey = join(',', @$fclass);
1415              
1416 0 0       0 if (defined $class->bridge_classes->{$bridgekey}) {
1417 0         0 $fclass = $class->bridge_classes->{$bridgekey};
1418             }
1419             else {
1420              
1421 0         0 my $loadclass = pop @$fclass;
1422              
1423 0 0       0 $class->load_pkg($loadclass) or return;
1424              
1425 0         0 my $inclass = $loadclass->inline_class;
1426              
1427 0         0 foreach my $c (@$fclass) {
1428 0 0       0 $class->load_pkg($c) or return;
1429 0         0 $inclass->add_tables(@{$c->tables});
  0         0  
1430             };
1431              
1432 0         0 $table = [$table, $fclass->[0]->primary_table];
1433              
1434 0         0 $class->bridge_classes->{$bridgekey} = $fclass = $inclass;
1435             }
1436             }
1437              
1438 0         0 $class->add_attr([$attribute, '_isa_instantiating_accessor']);
1439              
1440 0         0 $class->relationships->{$attribute} = {
1441             'class' => $fclass,
1442             'table' => $table,
1443             'singleton' => 0,
1444             'instantiating' => 'lazy',
1445             'clauses' => {},
1446             'accessibility' => 'public',
1447             %$init,
1448             };
1449              
1450 0 0       0 unless ($init->{'singleton'}) {
1451 0 0       0 $class->create_isa_to_method($attribute) or return;
1452             };
1453              
1454 0         0 return 1;
1455             }
1456              
1457             =pod
1458              
1459             =item create_isa_to_method
1460              
1461             Mainly used internally when setting up has_many relationships. When you create a has_many relationship,
1462             you automatically get an add_to* method.
1463              
1464             Some::Store->has_many(
1465             'bagels' => 'Some::Bagel::Class'
1466             );
1467              
1468             my $store->add_to_bagels(
1469             'type' => 'chocolate chip',
1470             'id' => '17738'
1471             );
1472              
1473             Is equivalent to:
1474              
1475             my $bagel = Some::Bagel::Class->new(
1476             'type' => 'chocolate chip',
1477             'id' => '17738',
1478             'store_id' => $store->id,
1479             );
1480              
1481             =cut
1482              
1483             sub create_isa_to_method {
1484 0     0 1 0 my $self = shift;
1485 0 0       0 my $attribute = shift or return $self->error("Cannot create add_to_* method w/o attribute", "BOP-87");
1486              
1487 0 0       0 my $relationship_data = $self->relationships->{$attribute}
1488             or return $self->error("Cannot create_isa_to_method for $attribute : not relationship", "BOP-88");
1489              
1490 2     2   18 no strict 'refs';
  2         5  
  2         17015  
1491              
1492 0         0 my $class = $self->pkg;
1493              
1494 0         0 *{$class . "::add_to_$attribute"} = sub {
1495 0     0   0 my $self = shift;
1496              
1497 0         0 my $obj;
1498              
1499 0 0       0 if (@_ == 1) {
1500 0         0 $obj = shift;
1501             } else {
1502 0         0 my %init = @_;
1503              
1504 0         0 my $table = $relationship_data->{'table'};
1505 0 0       0 $table = $table->[0] if ref $table eq 'ARRAY';
1506              
1507 0 0       0 my ($referencing_cols, $foreign_cols) = $self->relationship_columns($attribute) or return;
1508              
1509 0         0 foreach my $col (@$referencing_cols) {
1510 0         0 my $foreign = $table->nonqualified_name(shift @$foreign_cols);
1511 0         0 my $attr = $table->alias_column($col);
1512 0         0 $init{$foreign} = $self->$attr();
1513             }
1514              
1515 0 0       0 $obj = $relationship_data->{'class'}->new(%init) or
1516             return $self->error($relationship_data->{'class'}->errvals);
1517             }
1518              
1519 0 0       0 if ($relationship_data->{'accessibility'} eq 'private') {
1520 0         0 $obj->tied_to_parent(1);
1521             }
1522              
1523 0 0       0 if (my $key = $relationship_data->{'key'}) {
1524 0 0       0 return $self->error("Cannot add new object, missing value for $key", "BOP-90")
1525             unless defined $obj->$key();
1526 0         0 $self->$attribute()->{$obj->$key()} = $obj;
1527             } else {
1528 0         0 push @{$self->$attribute()}, $obj
  0         0  
1529             }
1530              
1531 0 0       0 if (my $foreign_method = $relationship_data->{'foreign_has_a'}) {
1532 0 0       0 $obj->$foreign_method($self) || return $self->error($obj->errvals);
1533             }
1534              
1535 0         0 return $obj;
1536 0         0 };
1537              
1538             };
1539              
1540             =pod
1541              
1542             =begin btest(create_isa_to_method)
1543              
1544             =end btest(create_isa_to_method)
1545              
1546             =cut
1547              
1548              
1549             =pod
1550              
1551             =item commit_relationships
1552              
1553             Used internally to commit all associated objects for a given object, only used for private objects
1554              
1555             $obj->commit_relationships
1556              
1557             =cut
1558              
1559             =pod
1560              
1561             =begin btest(commit_relationships)
1562              
1563             =end btest(commit_relationships)
1564              
1565             =cut
1566              
1567             sub commit_relationships {
1568 0     0 1 0 my $self = shift;
1569 0 0       0 my $singletons = $_[0] eq 'singletons' ? 1 : 0;
1570              
1571             #$self->begin() or return;
1572              
1573 0         0 my $instantiated = $self->instantiated_relationships;
1574              
1575 0 0       0 return 1 unless keys %$instantiated;
1576              
1577 0         0 my $seen = {};
1578              
1579 0         0 my $deleted_relationships = $self->_deleted_relationships;
1580              
1581 0         0 foreach my $deleted_obj (@$deleted_relationships) {
1582 0         0 $deleted_obj->should_be_deleted(2);
1583 0 0       0 $deleted_obj->delete or return $self->error($deleted_obj->errvals);
1584             }
1585              
1586 0 0       0 $self->_deleted_relationships([]) if @$deleted_relationships;
1587              
1588 0         0 my $relationships = $self->relationships;
1589              
1590 0         0 foreach my $rel (keys %$instantiated) {
1591              
1592 0 0       0 next if $seen->{$rel}++;
1593 0         0 my $relationship_data = $relationships->{$rel};
1594              
1595 0 0 0     0 next if $relationship_data->{'accessibility'} ne 'private'
1596             || $relationship_data->{'singleton'} != $singletons;
1597              
1598 0         0 my @relationships = ();
1599 0 0       0 if ($relationship_data->{'singleton'}) {
1600 0         0 @relationships = ($self->$rel());
1601             } else {
1602 0 0       0 if ($relationship_data->{'key'}) {
1603 0         0 @relationships = values %{$self->$rel};
  0         0  
1604             } else {
1605 0         0 @relationships = @{$self->$rel};
  0         0  
1606             }
1607             }
1608              
1609 0         0 foreach my $obj (@relationships) {
1610              
1611 0         0 my $table = $relationship_data->{'table'};
1612              
1613 0 0       0 my ($referencing_cols, $foreign_cols) = $self->relationship_columns($rel) or return;
1614              
1615 0         0 foreach my $col (@$referencing_cols) {
1616 0         0 my $foreign = $table->nonqualified_name(shift @$foreign_cols);
1617 0         0 my $attr = $table->alias_column($col);
1618 0         0 $obj->$foreign($self->$attr());
1619             }
1620              
1621 0         0 $obj->should_be_committed(1);
1622              
1623 0 0       0 if ($obj->commit) {
1624 0         0 $obj->should_be_committed(0);
1625             } else {
1626 0         0 $obj->should_be_committed(0);
1627 0         0 return $self->error($obj->errvals);
1628             };
1629              
1630             } #end foreach @relationships
1631             } #end foreach instantiated
1632              
1633             #$self->end() or return;
1634              
1635 0         0 return 1;
1636             }
1637              
1638             =pod
1639              
1640             =item delete_relationships
1641              
1642             Used internally to delete all associated objects for a given object.
1643              
1644             $obj->delete_relationships
1645              
1646             only used for private objects
1647              
1648             =cut
1649              
1650             =pod
1651              
1652             =begin btest(delete_relationships)
1653              
1654             =end btest(delete_relationships)
1655              
1656             =cut
1657              
1658             sub delete_relationships {
1659 0     0 1 0 my $self = shift;
1660              
1661             #$self->begin() or return;
1662              
1663 0         0 my $instantiated = $self->instantiated_relationships;
1664              
1665 0         0 my $deleted_relationships = $self->_deleted_relationships;
1666              
1667 0         0 foreach my $deleted_obj (@$deleted_relationships) {
1668 0         0 $deleted_obj->should_be_deleted(2);
1669 0 0       0 $deleted_obj->delete or return $self->error($deleted_obj->errvals);
1670             }
1671              
1672 0         0 my $seen = {};
1673              
1674 0         0 my $relationships = $self->relationships;
1675              
1676 0         0 foreach my $relationship (keys %$relationships) {
1677              
1678 0         0 my $relationship_data = $relationships->{$relationship};
1679              
1680 0 0       0 next unless $relationship_data->{'accessibility'} eq 'private';
1681              
1682 0         0 $self->instantiate($relationship);
1683              
1684 0         0 my @relationships = ();
1685              
1686 0 0       0 if ($relationship_data->{'singleton'}) {
    0          
1687 0         0 @relationships = ($self->$relationship());
1688             } elsif ($relationship_data->{'key'}) {
1689 0         0 @relationships = values %{$self->$relationship()};
  0         0  
1690             } else {
1691 0         0 @relationships = @{$self->$relationship()};
  0         0  
1692             }
1693              
1694 0         0 foreach my $obj (@relationships) {
1695 0         0 $obj->should_be_deleted(1);
1696 0 0       0 if ($obj->delete) {
1697 0         0 $obj->should_be_deleted(0);
1698             } else {
1699 0         0 $obj->should_be_deleted(0);
1700 0         0 return $self->error($obj->errvals);
1701             }
1702             }
1703             }
1704              
1705 0         0 return 1;
1706              
1707              
1708 0         0 foreach my $rel (keys %$instantiated) {
1709 0 0       0 next if $seen->{$rel}++;
1710 0         0 my $relationship_data = $relationships->{$rel};
1711              
1712 0 0       0 next unless $relationship_data->{'accessibility'} eq 'private';
1713              
1714 0         0 my @relationships = ();
1715 0 0       0 if ($relationship_data->{'singleton'}) {
1716 0         0 @relationships = ($self->$rel());
1717             } else {
1718 0 0       0 if ($relationship_data->{'key'}) {
1719 0         0 @relationships = values %{$self->$rel()};
  0         0  
1720             } else {
1721 0         0 @relationships = @{$self->$rel()};
  0         0  
1722             }
1723             }
1724              
1725 0         0 foreach my $obj (@relationships) {
1726              
1727 0         0 $obj->should_be_deleted(1);
1728 0 0       0 if ($obj->delete) {
1729 0         0 $obj->should_be_deleted(0);
1730             } else {
1731 0         0 $obj->should_be_deleted(0);
1732 0         0 return $self->error($obj->errvals);
1733             };
1734              
1735             } #end foreach @relationships
1736             } #end foreach instantiated
1737              
1738             #$self->end() or return;
1739              
1740 0         0 return 1;
1741             }
1742              
1743             =pod
1744              
1745             =item is_relationship
1746              
1747             Given an attribute, returns true if it is a relationship, false if not.
1748              
1749             if ($obj->is_relationship("some_attribute")) {
1750             #do interesting thing
1751             }
1752              
1753             =cut
1754              
1755             =pod
1756              
1757             =begin btest(is_relationship)
1758              
1759             =end btest(is_relationship)
1760              
1761             =cut
1762              
1763             sub is_relationship {
1764 0     0 1 0 my $self = shift;
1765 0 0       0 my $attribute = shift or return $self->error("Cannot determine is_relationship w/o attribute", "BOP-82");
1766              
1767 0 0       0 return $self->relationships->{$attribute} ? 1 : 0;
1768             };
1769              
1770             =pod
1771              
1772             =item relationship_columns
1773              
1774             Takes a relationship as an argument, returns a list of two arrayrefs - the referencing columns (yours)
1775             and the foreign columns (columns in the foreign table)
1776              
1777             my ($referencing, $foreign) = $self->relationship_columns($relationship);
1778              
1779             I can't think of a reason you'd ever want to call this directly.
1780              
1781             =cut
1782              
1783             sub relationship_columns {
1784 0     0 1 0 my $self = shift;
1785 0 0       0 my $prop = shift or return $self->error("Cannot get relationship_columns w/o relationship", "BOP-98");
1786              
1787 0         0 my $relationships = $self->relationships;
1788              
1789 0 0       0 my $relationship_data = $relationships->{$prop}
1790             or return $self->error("Cannot get relationship_columns for $prop : not relationship", "BOP-99");
1791              
1792 0         0 my $table = $relationship_data->{'table'};
1793 0         0 my $ftable = $relationship_data->{'class'}->primary_table;
1794              
1795 0 0       0 if (ref $table eq 'ARRAY') {
1796 0         0 ($table, $ftable) = @$table;
1797             };
1798              
1799 0         0 my ($foreign_cols, $referencing_cols);
1800              
1801             # if we have a foreign_has_a defined, then the fclass->us is a 1-many. So we can just grab the relationship
1802             # columns on the foreign table -> us and be done with it.
1803 0 0       0 if ($relationship_data->{'foreign_has_a'}) {
    0          
    0          
1804 0         0 my $fclass = $relationship_data->{'class'};
1805 0         0 my $foreign_relationship_method = $relationship_data->{'foreign_has_a'};
1806              
1807             #we flip the columns! their foreign are our referencing and vice-versa.
1808 0 0       0 ($foreign_cols, $referencing_cols) = $fclass->relationship_columns($foreign_relationship_method)
1809             or return $self->error($fclass->errvals);
1810              
1811             }
1812             #next, if we have a relationship_key, then we point to the foreign table a lot of times, but we only
1813             #keep track of the values in the key
1814             elsif ($relationship_data->{'relationship_key'}) {
1815 0         0 @$referencing_cols = ref $relationship_data->{'relationship_key'} eq 'ARRAY'
1816 0 0       0 ? @{$relationship_data->{'relationship_key'}}
1817             : ($relationship_data->{'relationship_key'});
1818 0 0       0 if (ref $referencing_cols->[0] eq 'ARRAY') {
1819 0         0 @$foreign_cols = @{$referencing_cols->[1]};
  0         0  
1820 0         0 @$referencing_cols = @{$referencing_cols->[0]};
  0         0  
1821             } else {
1822 0         0 @$foreign_cols = map {$table->referenced_column($_)} @$referencing_cols;
  0         0  
1823             }
1824             }
1825             #next, if it's a singleton, it's easy. We have a column in our table pointing to a primary key in theirs.
1826             elsif ($relationship_data->{'singleton'}) {
1827 0         0 @$referencing_cols = $table->foreign_cols($ftable);
1828 0         0 @$foreign_cols = map {$table->referenced_column($_)} @$referencing_cols;
  0         0  
1829             }
1830             #finally, still easy, it's a has_many, so they have a column in their table pointing to us.
1831             else {
1832 0         0 @$foreign_cols = $ftable->foreign_cols($table);
1833 0         0 @$referencing_cols = map {$table->nonqualified_name($ftable->referenced_column($_))} @$foreign_cols;
  0         0  
1834 0         0 @$foreign_cols = map {$ftable->qualified_name($_)} @$foreign_cols;
  0         0  
1835             }
1836              
1837 0         0 return ($referencing_cols, $foreign_cols);
1838              
1839             }
1840              
1841             =pod
1842              
1843             =begin btest(relationship_columns)
1844              
1845             =end btest(relationship_columns)
1846              
1847             =cut
1848              
1849              
1850             =pod
1851              
1852             =item primary_identifier
1853              
1854             Returns the single, unique primary identifier of the object.
1855              
1856             my $id = $obj->primary_identifier;
1857              
1858             If an object has composite keys, this method will return an error by default. You can pass the 'composite' flag to get back
1859             an arrayref of all primary keys.
1860              
1861             my $idref = $obj->primary_identifier('composite');
1862              
1863             If you simply want a string identifier to identify the object, pass in the "string" flag.
1864              
1865             my $string = $obj->primary_identifier('string');
1866              
1867             =cut
1868              
1869             =pod
1870              
1871             =begin btest(primary_identifier)
1872              
1873             =end btest(primary_identifier)
1874              
1875             =cut
1876              
1877             sub primary_identifier {
1878 0     0 1 0 my $self = shift;
1879 0   0     0 my $flag = shift || 0;
1880              
1881 0         0 my $primary_table = $self->primary_table;
1882              
1883 0         0 my @primary_cols = map {$self->$_()} $primary_table->alias_column($primary_table->primary_cols);
  0         0  
1884              
1885 0 0       0 if ($self->deleted) {
    0          
    0          
    0          
1886 0         0 return;
1887             }
1888             elsif ($flag eq 'composite') {
1889 0         0 return \@primary_cols;
1890             }
1891             elsif ($flag eq 'string') {
1892 0         0 my $tables = $self->tables;
1893 0         0 my @column_sets = ();
1894 0         0 foreach my $table (@$tables) {
1895 0         0 push @column_sets, join(';', $table->name, map {$self->$_()} $table->alias_column($table->primary_cols));
  0         0  
1896             }
1897 0         0 return join(',',
1898             $self->pkg,
1899             @column_sets,
1900             );
1901             }
1902             elsif (@primary_cols > 1) {
1903 0         0 return $self->error("Object has no unique identifier - composite key (@primary_cols)", "BOP-80");
1904             }
1905             else {
1906 0         0 return $primary_cols[0];
1907             }
1908              
1909             }
1910              
1911             =pod
1912              
1913             =item copy
1914              
1915             copy is overridden in Basset::Object::Persistent. When you copy a persistent object, it automatically wipes out
1916             the object's primary keys, and breaks all flags listing it as being in the database, so you get a fresh insert.
1917             Explicitly call Basset::Object's copy to key primary key values.
1918              
1919             my $o2 = $o->copy; #loses primary keys
1920             my $o2 = $o->Basset::Object::Copy; #keeps primary keys
1921              
1922             =cut
1923              
1924             sub copy {
1925 0     0 1 0 my $self = shift;
1926            
1927 0 0       0 my $copy = $self->SUPER::copy(@_) or return;
1928              
1929 0         0 require UNIVERSAL;
1930 0 0       0 if (UNIVERSAL::isa($copy, __PACKAGE__)) {
1931 0 0       0 if (my $table = $self->primary_table) {
1932            
1933 0         0 my @primary_cols = $table->alias_column($table->primary_cols);
1934            
1935 0         0 foreach my $p (@primary_cols) {
1936 0         0 $copy->$p(undef);
1937             };
1938             }
1939            
1940 0         0 $copy->loaded(0);
1941 0         0 $copy->committed(0);
1942 0         0 $copy->in_db(0);
1943 0         0 $copy->deleted(0);
1944             }
1945            
1946 0         0 return $copy;
1947            
1948             }
1949              
1950             =pod
1951              
1952             =begin btest(copy)
1953              
1954             =end btest(copy)
1955              
1956             =cut
1957              
1958             =pod
1959              
1960             =item commit
1961              
1962             There is a lot of internal magic here which I'll decline to get into at the moment. Suffice to say, that ->commit()
1963             will store your object in the database, and that all of the Right Things will happen during the commit.
1964              
1965             $object->commit();
1966             if ($object->committed){
1967             print "Success!\n";
1968             } else {
1969             print "Failure : " . $object->errstring . "\n";
1970             };
1971              
1972             =cut
1973              
1974             =pod
1975              
1976             =begin btest(commit)
1977              
1978             =end btest(commit)
1979              
1980             =cut
1981              
1982             sub commit {
1983              
1984 0     0 1 0 my $self = shift;
1985              
1986 0 0       0 if ($self->should_be_deleted()) {
1987 0         0 $self->should_be_deleted(1);
1988 0         0 return $self->delete(@_);
1989             }
1990              
1991 0 0 0     0 if ($self->tied_to_parent && ! $self->should_be_committed) {
1992 0         0 return $self;
1993             }
1994              
1995 0 0       0 if ($self->deleted) {
1996 0         0 $self->notify('warnings', "attempted to commit deleted object : $self");
1997 0         0 return $self;
1998             };
1999              
2000 0         0 $self->committed(0);
2001 0         0 $self->committing(1);
2002              
2003 0 0       0 $self->begin() or return $self->fatalerror($self->errvals);
2004              
2005 0 0       0 $self->cleanup() or return $self->fatalerror($self->errvals);
2006              
2007             #we need to commit our singletons first, since their ids are stored in our table.
2008 0 0       0 $self->commit_relationships('singletons') or return $self->fatalerror($self->errvals);
2009              
2010 0 0       0 my @tables = @{$self->tables} or return $self->fatalerror("Cannot commit with no table", "BOP-01");
  0         0  
2011              
2012 0         0 foreach my $table (@tables) {
2013              
2014             #we're updating, if this object has previously been loaded or committed and if we don't force inserts
2015 0 0 0     0 if (! $self->force_insert && $self->in_db) {
2016              
2017 0 0       0 my $update_query = $table->update_query or return $self->fatalerror($table->errvals);
2018              
2019 0         0 my $query = $table->attach_to_query(
2020             $update_query,
2021             {
2022 0 0       0 'where' => join(' and ', map {"$_ = ?"} $table->primary_cols)
2023             }
2024             ) or return $self->fatalerror($table->errvals);
2025              
2026 0 0       0 my @values = map {$self->$_()} $table->alias_column($table->update_bindables) or return $self->fatalerror($self->errvals);
  0         0  
2027              
2028 0 0       0 $self->arbitrary_sql(
2029             'query' => $query,
2030             'vars' => \@values,
2031             'table' => $table,
2032             'cols' => [$table->update_bindables]
2033             ) or return $self->fatalerror($self->errvals);
2034              
2035             }
2036             #or we're inserting
2037             else {
2038              
2039 0 0       0 my $insert_query = $table->insert_query or return $self->fatalerror($table->errvals);
2040              
2041 0 0       0 my @values = map {$self->$_()} $table->alias_column($table->insert_bindables) or return $self->fatalerror($self->errvals);
  0         0  
2042              
2043 0 0       0 $self->arbitrary_sql(
2044             'query' => $insert_query,
2045             'vars' => \@values,
2046             'table' => $table,
2047             'cols' => [$table->insert_bindables]
2048             ) or return $self->fatalerror($self->errvals);
2049              
2050 0 0       0 if ($table->autogenerated){
2051              
2052 0 0       0 my $driver = $self->driver or return $self->fatalerror($self->errvals);
2053              
2054 0 0       0 my $id_stmt = $driver->prepare_cached($table->last_insert_query())
2055             or return $self->fatalerror($driver->errstr, "BOP-05");
2056              
2057 0 0       0 $id_stmt->execute()
2058             or return $self->fatalerror($id_stmt->errstr, "BOP-04");
2059              
2060 0         0 my ($id) = $id_stmt->fetchrow_array;
2061              
2062 0 0       0 $id_stmt->finish()
2063             or return $self->fatalerror($id_stmt->errstr, "BOP-10");
2064              
2065 0         0 my $primary = $table->alias_column($table->primary_column);
2066 0         0 $self->$primary($id);
2067             };
2068              
2069             };
2070             }
2071              
2072             #commit our nonsingleton tied relationships
2073 0 0       0 $self->commit_relationships('nonsingletons') or return $self->fatalerror($self->errvals);
2074              
2075             #we have committed this object
2076 0         0 $self->committed(1);
2077             #and it's in the database
2078 0         0 $self->in_db(1);
2079              
2080 0         0 my $primary_identifier = $self->primary_identifier('string');
2081 0         0 my $load_cache = $self->central_load_cache;
2082 0 0       0 unless (defined $load_cache->{$primary_identifier}) {
2083 0         0 $load_cache->{$primary_identifier} = $self;
2084 0         0 weaken($load_cache->{$primary_identifier});
2085             }
2086              
2087 0 0       0 $self->end() or return $self->fatalerror($self->errvals);
2088              
2089 0         0 $self->committing(0);
2090              
2091 0         0 return $self;
2092              
2093             }
2094              
2095             =pod
2096              
2097             =item writable_method
2098              
2099             Given a method name, returns true if the value of this method will be written out to disk on the
2100             next commit, and false if it will not be written out.
2101              
2102             my $output = $object->writable_method('id');
2103             if ($output) {
2104             print "object will store id\n";
2105             } else {
2106             print "object will not store id\n";
2107             }
2108              
2109             =cut
2110              
2111             =pod
2112              
2113             =begin btest(writable_method)
2114              
2115             $test->is(scalar(__PACKAGE__->writable_method), undef, "Cannot determine if writable on a class");
2116             $test->is(__PACKAGE__->errcode, "BOP-62", "proper error code");
2117              
2118             my $subclass = "Basset::Test::Testing::__PACKAGE__::writable_method::Subclass1";
2119              
2120             package Basset::Test::Testing::__PACKAGE__::writable_method::Subclass1;
2121             our @ISA = qw(__PACKAGE__);
2122              
2123             $subclass->add_attr('one');
2124             $subclass->add_attr('two');
2125             $subclass->add_attr('three');
2126              
2127             package __PACKAGE__;
2128              
2129             my $o = $subclass->new();
2130             $test->ok($o, "Got object");
2131              
2132             $test->is(scalar($o->writable_method), undef, "Cannot determine if writable w/o method");
2133             $test->is($o->errcode, "BOP-63", "proper error code");
2134              
2135             $test->is(scalar($o->writable_method('one')), undef, "Cannot determine if writable w/o primary table");
2136             $test->is($o->errcode, 'BOP-64', "proper error code");
2137              
2138             $subclass->add_primarytable(
2139             'name' => 'test_table',
2140             'definition' => {
2141             'one' => 'SQL_INTEGER',
2142             'two' => 'SQL_INTEGER',
2143             'three' => 'SQL_INTEGER',
2144             },
2145             #'insert_columns' => ['two'],
2146             #'update_columns' => ['three'],
2147             );
2148              
2149             $test->is($o->writable_method('one'), 1, "method is writable w/o insert or update columns on insert");
2150             $test->is($o->loaded(1), 1, 'loaded is one');
2151             $test->is($o->writable_method('one'), 1, "method is writable w/o insert or update columns on update, loaded");
2152             $test->is($o->loaded(0), 0, 'loaded is zero');
2153             $test->is($o->committed(1), 1, 'committed is 1');
2154             $test->is($o->writable_method('one'), 1, "method is writable w/o insert or update columns on update, committed");
2155             $test->is($o->loaded(1), 1, 'loaded is 1');
2156             $test->is($o->committed(1), 1, 'committed is 1');
2157             $test->is($o->writable_method('one'), 1, "method is writable w/o insert or update columns on insert, force_insert");
2158             $test->is($o->loaded(0), 0, 'loaded is 0');
2159             $test->is($o->committed(0), 0, 'committed is 0');
2160             $test->is($o->writable_method('one'), 1, "method is writable w/o insert or update columns on insert, force_insert");
2161              
2162             $subclass->add_primarytable(
2163             'name' => 'test_table',
2164             'definition' => {
2165             'one' => 'SQL_INTEGER',
2166             'two' => 'SQL_INTEGER',
2167             'three' => 'SQL_INTEGER',
2168             },
2169             'insert_columns' => ['two'],
2170             'update_columns' => ['three'],
2171             );
2172              
2173             $test->is($o->writable_method('one'), 0, "method one is not writable w/ insert and update columns on insert");
2174             $test->is($o->loaded(1), 1, 'loaded is 1');
2175             $test->is($o->writable_method('one'), 0, "method one is not writable w/ insert and update columns on update, loaded");
2176             $test->is($o->loaded(0), 0, 'loaded is zero');
2177             $test->is($o->committed(1), 1, 'committed is 1');
2178             $test->is($o->writable_method('one'), 0, "method one is not writable w/ insert and update columns on update, committed");
2179             $test->is($o->loaded(1), 1, 'loaded is 1');
2180             $test->is($o->committed(1), 1, 'committed is 1');
2181             $test->is($o->force_insert(1), 1, 'force_insert is 1');
2182             $test->is($o->writable_method('one'), 0, "method one is not writable w/ insert and update columns on insert, force_insert");
2183             $test->is($o->loaded(0), 0, 'loaded is 0');
2184             $test->is($o->committed(0), 0, 'committed is 0');
2185             $test->is($o->writable_method('one'), 0, "method one is not writable w/ insert and update columns on insert, force_insert");
2186             $test->is($o->force_insert(0), 0, 'force_insert is 0');
2187              
2188             $test->is($o->writable_method('two'), 1, "method two is writable w/ insert and update columns on insert");
2189             $test->is($o->loaded(1), 1, 'loaded is 1');
2190             $test->is($o->writable_method('two'), 0, "method two is not writable w/ insert and update columns on update, loaded");
2191             $test->is($o->loaded(0), 0, 'loaded is zero');
2192             $test->is($o->committed(1), 1, 'committed is 1');
2193             $test->is($o->writable_method('two'), 0, "method two is not writable w/ insert and update columns on update, committed");
2194             $test->is($o->loaded(1), 1, 'loaded is 1');
2195             $test->is($o->committed(1), 1, 'committed is 1');
2196             $test->is($o->force_insert(1), 1, 'force_insert is 1');
2197             $test->is($o->writable_method('two'), 1, "method two is writable w/ insert and update columns on insert, force_insert");
2198             $test->is($o->loaded(0), 0, 'loaded is 0');
2199             $test->is($o->committed(0), 0, 'committed is 0');
2200             $test->is($o->writable_method('two'), 1, "method two is writable w/ insert and update columns on insert, force_insert");
2201             $test->is($o->force_insert(0), 0, 'force_insert is 0');
2202              
2203             $test->is($o->writable_method('three'), 0, "method three is not writable w/ insert and update columns on insert");
2204             $test->is($o->loaded(1), 1, 'loaded is 1');
2205             $test->is($o->writable_method('three'), 1, "method three is writable w/ insert and update columns on update, loaded");
2206             $test->is($o->loaded(0), 0, 'loaded is zero');
2207             $test->is($o->committed(1), 1, 'committed is 1');
2208             $test->is($o->writable_method('three'), 1, "method three is writable w/ insert and update columns on update, committed");
2209             $test->is($o->loaded(1), 1, 'loaded is 1');
2210             $test->is($o->committed(1), 1, 'committed is 1');
2211             $test->is($o->force_insert(1), 1, 'force_insert is 1');
2212             $test->is($o->writable_method('three'), 0, "method three is not writable w/ insert and update columns on insert, force_insert");
2213             $test->is($o->loaded(0), 0, 'loaded is 0');
2214             $test->is($o->committed(0), 0, 'committed is 0');
2215             $test->is($o->writable_method('three'), 0, "method three is not writable w/ insert and update columns on insert, force_insert");
2216             $test->is($o->force_insert(0), 0, 'force_insert is 0');
2217              
2218             $subclass->add_primarytable(
2219             'name' => 'test_table',
2220             'definition' => {
2221             'alpha' => 'SQL_INTEGER',
2222             'beta' => 'SQL_INTEGER',
2223             'gamma' => 'SQL_INTEGER',
2224             },
2225             'insert_columns' => ['beta'],
2226             'update_columns' => ['gamma'],
2227             'column_aliases' => {
2228             'alpha' => 'one',
2229             'beta' => 'two',
2230             'gamma' => 'three',
2231             },
2232             );
2233              
2234             $test->is($o->writable_method('one'), 0, "method one (from alpha) is not writable w/ insert and update columns on insert");
2235             $test->is($o->loaded(1), 1, 'loaded is 1');
2236             $test->is($o->writable_method('one'), 0, "method one (from alpha) is not writable w/ insert and update columns on update, loaded");
2237             $test->is($o->loaded(0), 0, 'loaded is zero');
2238             $test->is($o->committed(1), 1, 'committed is 1');
2239             $test->is($o->writable_method('one'), 0, "method one (from alpha) is not writable w/ insert and update columns on update, committed");
2240             $test->is($o->loaded(1), 1, 'loaded is 1');
2241             $test->is($o->committed(1), 1, 'committed is 1');
2242             $test->is($o->force_insert(1), 1, 'force_insert is 1');
2243             $test->is($o->writable_method('one'), 0, "method one (from alpha) is not writable w/ insert and update columns on insert, force_insert");
2244             $test->is($o->loaded(0), 0, 'loaded is 0');
2245             $test->is($o->committed(0), 0, 'committed is 0');
2246             $test->is($o->writable_method('one'), 0, "method one (from alpha) is not writable w/ insert and update columns on insert, force_insert");
2247             $test->is($o->force_insert(0), 0, 'force_insert is 0');
2248              
2249             $test->is($o->writable_method('two'), 1, "method two (from beta) is writable w/ insert and update columns on insert");
2250             $test->is($o->loaded(1), 1, 'loaded is 1');
2251             $test->is($o->writable_method('two'), 0, "method two (from beta) is not writable w/ insert and update columns on update, loaded");
2252             $test->is($o->loaded(0), 0, 'loaded is zero');
2253             $test->is($o->committed(1), 1, 'committed is 1');
2254             $test->is($o->writable_method('two'), 0, "method two (from beta) is not writable w/ insert and update columns on update, committed");
2255             $test->is($o->loaded(1), 1, 'loaded is 1');
2256             $test->is($o->committed(1), 1, 'committed is 1');
2257             $test->is($o->force_insert(1), 1, 'force_insert is 1');
2258             $test->is($o->writable_method('two'), 1, "method two (from beta) is writable w/ insert and update columns on insert, force_insert");
2259             $test->is($o->loaded(0), 0, 'loaded is 0');
2260             $test->is($o->committed(0), 0, 'committed is 0');
2261             $test->is($o->writable_method('two'), 1, "method two (from beta) is writable w/ insert and update columns on insert, force_insert");
2262             $test->is($o->force_insert(0), 0, 'force_insert is 0');
2263              
2264             $test->is($o->writable_method('three'), 0, "method three (from gamma) is not writable w/ insert and update columns on insert");
2265             $test->is($o->loaded(1), 1, 'loaded is 1');
2266             $test->is($o->writable_method('three'), 1, "method three (from gamma) is writable w/ insert and update columns on update, loaded");
2267             $test->is($o->loaded(0), 0, 'loaded is zero');
2268             $test->is($o->committed(1), 1, 'committed is 1');
2269             $test->is($o->writable_method('three'), 1, "method three (from gamma) is writable w/ insert and update columns on update, committed");
2270             $test->is($o->loaded(1), 1, 'loaded is 1');
2271             $test->is($o->committed(1), 1, 'committed is 1');
2272             $test->is($o->force_insert(1), 1, 'force_insert is 1');
2273             $test->is($o->writable_method('three'), 0, "method three (from gamma) is not writable w/ insert and update columns on insert, force_insert");
2274             $test->is($o->loaded(0), 0, 'loaded is 0');
2275             $test->is($o->committed(0), 0, 'committed is 0');
2276             $test->is($o->writable_method('three'), 0, "method three (from gamma) is not writable w/ insert and update columns on insert, force_insert");
2277             $test->is($o->force_insert(0), 0, 'force_insert is 0');
2278              
2279             =end btest(writable_method)
2280              
2281             =cut
2282              
2283             sub writable_method {
2284 38     38 1 1238 my $self = shift;
2285              
2286 38 100       117 return $self->error("Cannot determine if writable on a class", "BOP-62") unless ref $self;
2287              
2288 37 100       101 my $method = shift or return $self->error("Cannot determine if writable w/o method", "BOP-63");
2289              
2290 36 100       109 my $table = $self->primary_table or return $self->error("Cannot determine if writable with no table", "BOP-64");
2291              
2292 35         50 my @bindables;
2293              
2294             #we're updating, if this object has previously been loaded or committed and if we don't force inserts
2295 35 100 100     102 if (! $self->force_insert && ($self->loaded || $self->committed)){
      66        
2296 15         54 @bindables = $table->update_columns;
2297             } else {
2298 20         78 @bindables = $table->insert_columns;
2299             }
2300              
2301 35         68 @bindables = map {$table->alias_column($_)} @bindables;
  45         403  
2302              
2303 35         76 foreach my $bindable (@bindables) {
2304 35 100       162 return 1 if $bindable eq $method
2305             }
2306              
2307 20         102 return 0;
2308              
2309             }
2310              
2311             =pod
2312              
2313             =item load
2314              
2315             the load method loads an object from the database. The arguments passed must be the
2316             primary_column specified in your primary table, in that order.
2317              
2318             __PACKAGE__->add_primarytable(
2319             .
2320             .
2321             .
2322             'primary_column' => 'id'
2323             );
2324              
2325             my $obj = Some::Package->load($id);
2326              
2327             __PACKAGE__->add_primarytable(
2328             .
2329             .
2330             .
2331             'primary_column' => [qw(foo bar baz)]
2332             );
2333              
2334             my $obj = Some::Package->load($foo, $bar, $baz);
2335              
2336             The arguments passed must be in the same order they were defined.
2337              
2338             Returns an error if no object found that matches
2339              
2340             =cut
2341              
2342             =pod
2343              
2344             =begin btest(load)
2345              
2346             =end btest(load)
2347              
2348             =cut
2349              
2350             sub load {
2351 0     0 1 0 my $class = shift;
2352              
2353 0 0       0 return $class->error("Cannot load with no ID!", "BOP-09") unless @_;
2354              
2355 0 0       0 my $table = $class->primary_table or return $class->error("Cannot load with no table", "BOP-01");
2356              
2357 0         0 my %input = ();
2358 0         0 @input{$table->primary_cols} = @_;
2359              
2360 0         0 return $class->load_where([%input], {'singleton' => 1});
2361              
2362             }
2363              
2364             =pod
2365              
2366             =item load_or_new
2367              
2368             Does what it sounds like, it tries to load an object, and if it fails, it creates a new B
2369             object instead. Basically, this allows some lazy object creation for things like stateless
2370             applications (such as cgis) that don't know in advance what they're operating on, and don't really
2371             care. So you can try to load an object if values were passed back to you, and if they weren't
2372             then you create an automatically create a new one for yourself.
2373              
2374             =cut
2375              
2376             =pod
2377              
2378             =begin btest(load_or_new)
2379              
2380             =end btest(load_or_new)
2381              
2382             =cut
2383              
2384             sub load_or_new {
2385 0     0 1 0 my $class = shift;
2386              
2387 0   0     0 return $class->load(@_) || $class->new();
2388             };
2389              
2390             =pod
2391              
2392             =item load_many
2393              
2394             Convenience method. If you have a class that only uses one primary column (a unique ID, for instance) and
2395             you want to load certain objects with given IDs, you can use load_many.
2396              
2397             my $objects = $self->load_many(1,2,3,4,5);
2398              
2399             =cut
2400              
2401             =pod
2402              
2403             =begin btest(load_many)
2404              
2405             =end btest(load_many)
2406              
2407             =cut
2408              
2409             sub load_many {
2410              
2411 0     0 1 0 my $class = shift;
2412 0         0 my @ids = @_;
2413              
2414 0 0       0 return $class->error("Cannot load many w/o ids", "BOP-67") unless @ids;
2415              
2416 0 0       0 my $table = $class->primary_table() or return $class->error("Cannot load many w/o primary table", "BOP-65");
2417              
2418 0         0 my @cols = $table->primary_cols();
2419 0 0       0 if (@cols > 1) {
2420 0         0 return $class->error("Cannot load many w/multiple primary columns", "BOP-66");
2421             };
2422              
2423 0         0 return $class->load_where($cols[0] => \@ids);
2424              
2425             }
2426              
2427             =pod
2428              
2429             =item load_next
2430              
2431             =cut
2432              
2433             sub load_next {
2434 0     0 1 0 my $class = shift;
2435 0   0     0 my $clauses = shift || {};
2436              
2437 0 0       0 my $iterator = $class->iterator or return $class->error("Cannot load next w/o iterator", "BOP-83");
2438              
2439 0         0 return $class->load_all(
2440             {
2441             'iterator' => 1,
2442             '_loading_next' => 1,
2443             %$clauses,
2444             },
2445             @_
2446             );
2447             };
2448              
2449             =pod
2450              
2451             =begin btest(load_next)
2452              
2453             =end btest(load_next)
2454              
2455             =cut
2456              
2457              
2458             =pod
2459              
2460             =item create
2461              
2462             Convenience method. Instantiates a brand new object and then immediately commits it to the
2463             database.
2464              
2465             =cut
2466              
2467             =pod
2468              
2469             =begin btest(create)
2470              
2471             =end btest(create)
2472              
2473             =cut
2474              
2475             sub create {
2476 0     0 1 0 my $class = shift;
2477              
2478 0 0       0 my $self = $class->new(@_) or return;
2479              
2480 0 0       0 $self->commit or return $class->error($self->errvals);
2481              
2482 0         0 return $self;
2483             }
2484              
2485             =pod
2486              
2487             =item load_all
2488              
2489             load_all loads all objects of a given package and returns them in an arrayref.
2490              
2491             my $objects = Some::Package->load_all();
2492              
2493             load_all optionally takes an arbitrary number of arguments, where the first is a hashref that defines a set of constraints
2494             and the rest are column values to bind to those constraints.
2495              
2496             my $objects = Some::Package->load_all(
2497             {
2498             'where' => 'name = ? and company = ?',
2499             'order by' => 'id'
2500             },
2501             'Jim', 'FooFram'
2502             );
2503              
2504             Will return an arrayref containing all objects with a name of "Jim" and a company of "FooFram"
2505              
2506             A list of all valid constraints is provided in the Basset::DB::Table object.
2507              
2508             Note that load_all is faster than loading objects individually, since it combines its SQL to minimize the number of queries.
2509             However, all queries dones internally to auto-instantiated relationships will still be performed one at a time, and not in aggregate.
2510              
2511             B - with load_all, you are B to pass in actually column names, not aliases attribute names. You would pass in
2512             aliased attribute names to load_where.
2513              
2514             Returns an empty arrayref if no objects found.
2515              
2516             The loader can also accept various 'flag' attributes passed in the constraints hash. The flags will not be passed onto the SQL generator.
2517              
2518             =over 8
2519              
2520             =item iterator
2521              
2522             The iterator flag allows you to load up objects in sequence using load_next.
2523              
2524             my $objs = Some::Class->load_all();
2525             foreach my $o (@$objs) {
2526             $o->do_something;
2527             };
2528              
2529             is equivalent to:
2530              
2531             Some::Class->load_all({'iterator' => 1});
2532             while (my $o = Some::Class->load_nex) {
2533             $o->do_something;
2534             };
2535              
2536             The advantage is that you won't have all of the objects in memory at one time. Note that if you
2537             subsequently call a load* method in the same class that you will wipe out the current iterator.
2538              
2539             =item constructor
2540              
2541             A hashref of constructor args. As data is loaded from the database, objects will be created and initialized with the data loaded. But
2542             sometimes you need to load objects and populate in new values or override existing values with new ones. That's where the constructor
2543             comes in. It will override the values of those attributes in the database with new ones.
2544              
2545             my $objs = Some::Class->load_all(
2546             {
2547             'constructor' => {
2548             'foo' => 'bar'
2549             }
2550             }
2551             );
2552              
2553             Now all objects in $objs will have their foo attribute set to 'bar'
2554              
2555             =item singleton
2556              
2557             Sometimes, you build up a complicated query but know that you'll only get back one object. If you pass in the 'singleton' flag, then you'll
2558             only get back a single object instead of an arrayref containing a single object.
2559              
2560             =item transform
2561              
2562             Will transform the loaded object into one of its related objects declared via a has_a or has_many
2563             relationship.
2564              
2565             Some::User->has_a('pelican' => 'Some::Pelican');
2566              
2567             my $pelican = Some::User->load_all({'where' => 'user_id = ?', 'transform' => 'pelican'});
2568              
2569             Directly using this as a loader flag is dubious at best, it is most useful with relationships.
2570              
2571             =item force_arrayref
2572              
2573             There are several flags that will return the resutls of load_all in a different format (key or singleton, for example),
2574             but this makes subclassing difficult. You can't easily override the load_all method, since you don't know what SUPER's implementation will return
2575             to you. So you can pass the force_arrayref flag. That will return a list with the actual original arrayref first, and the value to return to the user second.
2576             Along these lines:
2577              
2578             package Some::Subclass;
2579            
2580             sub load_all {
2581             #not quite right...this wipes out the existing clauses hashref.
2582             my ($values, $return) = shift->SUPER::load_all({'force_arrayref' => 1}, @_);
2583            
2584             foreach my $value (@$values) {
2585             #do interesting thing;
2586             }
2587            
2588             return $return;
2589             }
2590              
2591             =back
2592              
2593             =cut
2594              
2595             =pod
2596              
2597             =begin btest(load_all)
2598              
2599             =end btest(load_all)
2600              
2601             =cut
2602              
2603             sub load_all {
2604 0     0 1 0 my $class = shift;
2605              
2606 0         0 my $clauses = {};
2607 0         0 my @args = ();
2608              
2609 0 0       0 if (@_){
2610 0         0 $clauses = shift;
2611 0         0 @args = @_;
2612             };
2613              
2614 0         0 my $tables = $class->tables;
2615              
2616 0         0 my $omit_tables = undef;
2617              
2618 0 0       0 if ($clauses->{'tables'}) {
2619 0         0 $tables = [@{$class->tables}, @{$clauses->{'tables'}}];
  0         0  
  0         0  
2620 0         0 $omit_tables = $clauses->{'tables'};
2621 0         0 delete $clauses->{'tables'};
2622             }
2623              
2624 0 0       0 return $class->error("Cannot load with no table", "BOP-01") unless @$tables;
2625              
2626 0   0     0 my $iterated = $clauses->{'iterator'} || 0;
2627 0         0 delete $clauses->{'iterator'};
2628              
2629 0 0       0 my $tableClass = $class->pkg_for_type('table') or return;
2630              
2631 0 0       0 my $multiselect_query = $tableClass->multiselect_query(
2632             'tables' => $tables,
2633             'omit_columns_from_tables' => $omit_tables,
2634             'use_aliases' => 1,
2635             ) or return $class->error($tableClass->errvals);
2636              
2637 0 0       0 my $query = $tableClass->attach_to_query(
2638             $multiselect_query,
2639             $clauses
2640             ) or return $class->error($tableClass->errvals);
2641              
2642 0 0       0 $class->iterator(undef) unless $clauses->{'_loading_next'};
2643              
2644 0 0 0     0 my $stmt = $class->iterator || $class->arbitrary_sql(
2645             'query' => $query,
2646             'vars' => \@args,
2647             'iterator' => 1,
2648             ) or return;
2649              
2650 0         0 my @objs = ();
2651              
2652 0 0 0     0 if ($iterated && ! $class->iterator) {
2653 0         0 $class->iterator($stmt);
2654 0         0 return $stmt;
2655             }
2656              
2657 0         0 my $load_cache = $class->central_load_cache;
2658              
2659 0         0 while (my $stuff = $stmt->fetchrow_hashref('NAME_lc')){
2660              
2661 0 0       0 my $obj = $class->new('loading' => 1, 'in_db' => 1, %$stuff, %{$clauses->{'constructor'}}, 'loaded' => 1)
  0         0  
2662             or return $class->error("Cannot create object : " . $class->error, "BOP-06");
2663 0         0 $obj->loading(0);
2664              
2665 0         0 my $primary_identifier = $obj->primary_identifier('string');
2666            
2667 0 0       0 if (defined $load_cache->{$primary_identifier}) {
2668 0         0 $obj = $load_cache->{$primary_identifier};
2669             }
2670             else {
2671              
2672 0         0 $load_cache->{$primary_identifier} = $obj;
2673 0         0 weaken($load_cache->{$primary_identifier});
2674              
2675 0 0 0     0 $obj->setup() or return $class->error("Setup failed in object : " . $obj->error, $obj->errcode || "BOP-47");
2676             }
2677              
2678             #no matter what, we nuke our instantiated relationships, they can no longer be trusted.
2679 0         0 $obj->instantiated_relationships({});
2680              
2681 0 0       0 if (my $transform = $clauses->{'transform'}) {
2682 0         0 my $transformed = $obj->$transform();
2683 0 0 0     0 return $class->error("Cannot transform object into non-object", "BOP-91")
2684             unless $obj->is_relationship($transform) && ref $transformed;
2685 0         0 $obj = $transformed;
2686             };
2687              
2688 0         0 push @objs, $obj;
2689              
2690 0 0       0 if ($iterated) {
2691 0         0 return $obj;
2692             };
2693             };
2694              
2695 0 0       0 $stmt->finish()
2696             or return $class->error($stmt->errstr, "BOP-10");
2697              
2698 0 0 0     0 if ($iterated && ! @objs) {
2699 0         0 $class->iterator(undef);
2700 0         0 return;
2701             };
2702              
2703 0 0       0 if ($clauses->{'singleton'}) {
2704 0 0       0 my $return = $objs[0] or return $class->error("Cannot load single object - no objects returned", "BOP-84");
2705 0 0       0 return $clauses->{'force_arrayref'} ? (\@objs, $return) : $return;
2706             }
2707             else {
2708 0         0 my $return;
2709 0 0       0 if (my $key = $clauses->{'key'}) {
2710 0         0 my %objs = map {$_->$key(), $_} @objs;
  0         0  
2711 0         0 $return = \%objs;
2712             } else {
2713 0         0 $return = \@objs;
2714             }
2715 0 0       0 return $clauses->{'force_arrayref'} ? (\@objs, $return) : $return;
2716             }
2717              
2718             };
2719              
2720             =pod
2721              
2722             =item exists
2723              
2724             Query to quickly determine if a given object (or set of objects) exists in the database. The objects will not be loaded.
2725             Returns a count of the number of objects that exist.
2726              
2727             my $itsthere = Basset::User->exists(1); #user id 1 exists in the database
2728              
2729             =cut
2730              
2731             =pod
2732              
2733             =begin btest(exists)
2734              
2735             =end btest(exists)
2736              
2737             =cut
2738              
2739             sub exists {
2740 0     0 1 0 my $class = shift;
2741              
2742 0 0       0 my $table = $class->primary_table or return $class->error("Cannot load with no table", "BOP-01");
2743              
2744             #our default where clause - built on the primary keys
2745 0         0 my $where = join(' and ', map {"$_ = ?"} $table->primary_cols);
  0         0  
2746              
2747 0         0 my @args = @_;
2748             #if we have arguments, our clause should be on the primary key. No clause otherwise.
2749 0 0       0 my $clauses = @args ? {'where' => $where} : {};
2750              
2751             #override the clause with any passed clause
2752 0 0       0 if (ref $args[0] eq 'HASH'){
2753 0         0 $clauses = shift @args;
2754             };
2755              
2756 0 0       0 my $query = $table->attach_to_query(
2757             $table->count_query,
2758             $clauses
2759             ) or return $class->error($table->errvals);
2760              
2761 0 0       0 my $data = $class->arbitrary_sql(
2762             'query' => $query,
2763             'vars' => [@args],
2764             'into' => 'hash',
2765             ) or return;
2766              
2767 0   0     0 return $data->[0]->{'count'} || 0;
2768              
2769             };
2770              
2771             =pod
2772              
2773             =item delete
2774              
2775             This will delete an object from the database
2776              
2777             $object->delete();
2778              
2779             The object itself will not be affected, except for the fact that its deleted flag will be set.
2780              
2781             =cut
2782              
2783             =pod
2784              
2785             =begin btest(delete)
2786              
2787             =end btest(delete)
2788              
2789             =cut
2790              
2791             sub delete {
2792              
2793 0     0 1 0 my $self = shift;
2794              
2795             #if we haven't loaded the object, we have nothing to delete, so we just pretend
2796 0 0       0 unless ($self->in_db) {
2797 0         0 $self->deleted(1);
2798 0         0 return $self;
2799             };
2800              
2801 0 0 0     0 if ($self->tied_to_parent && ! $self->should_be_deleted) {
2802 0         0 return $self;
2803             };
2804              
2805 0         0 $self->deleting(1);
2806              
2807 0 0       0 my $table = $self->primary_table or return $self->error("Cannot delete with no table", "BOP-01");
2808              
2809 0 0       0 $self->begin() or return;
2810              
2811 0         0 my $query = $table->attach_to_query(
2812             $table->delete_query(),
2813             {
2814 0 0       0 'where' => join(' and ', map {"$_ = ?"} $table->primary_cols)
2815             }
2816             ) or return $self->error($table->errvals);
2817              
2818 0 0       0 my @values = map {$self->$_()} $table->alias_column($table->delete_bindables) or return;
  0         0  
2819              
2820 0 0       0 $self->arbitrary_sql(
2821             'query' => $query,
2822             'vars' => \@values,
2823             'table' => $table,
2824             'cols' => [$table->delete_bindables]
2825             ) or return;
2826              
2827 0 0       0 $self->delete_relationships or return;
2828              
2829 0 0       0 $self->end or return;
2830              
2831 0         0 $self->deleting(0);
2832              
2833 0         0 $self->deleted(1);
2834              
2835 0         0 return $self;
2836              
2837             };
2838              
2839              
2840             =pod
2841              
2842             =item load_where
2843              
2844             Simple wrapper around load_all. Takes key/value pairs.
2845              
2846             my $users = Some::Class->load_where(
2847             'user' => 3,
2848             'location' => 'mountains',
2849             'weather' => 'sunny'
2850             );
2851              
2852             This is exactly equivalent to:
2853              
2854             my $users = Some::Class->load_all(
2855             {
2856             'where' => 'user = ? and location = ? and weather = ?'
2857             },
2858             3, 'mountains', 'sunny'
2859             );
2860              
2861             It just looks prettier and hides more of the SQL.
2862              
2863             Even better, you can also stick in an array for multiple value loads.
2864              
2865             my $users = Some::Class->load_where(
2866             'state' => 'PA',
2867             'last_name' => [qw(Smith Jones Johnson)]
2868             );
2869              
2870             Is exactly the same as:
2871              
2872             my $users = Some::Class->load_all(
2873             {
2874             'where' => 'last_name in (?,?,?) and state = ?'
2875             },
2876             qw(Smith Jones Johnson), 'PA',
2877             );
2878              
2879             There is an alternative syntax, you may pass in one arrayref and one hashref. The arrayref becomes your
2880             where clause, the second contains additional loader args (such as 'order by', 'limit', etc.)
2881              
2882             my $users = Some::Class->load_where(
2883             #where array
2884             [
2885             'state' => 'PA',
2886             'last_name' => [qw(Smith Jones Johnson)]
2887             ],
2888             #extra loader hash
2889             {
2890             'order by' => 'state desc',
2891             },
2892             );
2893              
2894             Is exactly the same as:
2895              
2896             my $users = Some::Class->load_all(
2897             {
2898             'where' => 'last_name in (?,?,?) and state = ?',
2899             'order by' => state desc',
2900             },
2901             qw(Smith Jones Johnson), 'PA',
2902             );
2903              
2904              
2905             =cut
2906              
2907             sub load_where {
2908 0     0 1 0 my $class = shift;
2909              
2910 0 0       0 my @clauses = @_ or return $class->error("Cannot load_where w/o clauses", "BOP-68");
2911              
2912 0         0 my $additional_clauses = {};
2913              
2914 0 0       0 if (ref $clauses[0] eq 'HASH') {
2915 0         0 $class->notify('warnings', 'load_where with a hashref argument is deprecated. Please load with an array instead.');
2916 0         0 $clauses[0] = [%{$clauses[0]}];
  0         0  
2917             };
2918              
2919 0 0       0 if (ref $clauses[0] eq 'ARRAY') {
2920 0 0       0 $additional_clauses = @clauses == 2 ? pop @clauses : {}; #last one is additional clauses
2921 0         0 @clauses = @{$clauses[0]};
  0         0  
2922             }
2923              
2924 0         0 my ($clause, @values) = $class->primary_table->construct_where_clause(
2925 0 0       0 [@{$class->tables}, $additional_clauses->{'tables'} ? @{$additional_clauses->{'tables'}} : ()],
  0         0  
2926             @clauses
2927             );
2928              
2929 0 0       0 return $class->error($class->primary_table->errvals) unless defined $clause;
2930              
2931 0         0 return $class->load_all({%$additional_clauses, 'where' => $clause}, @values);
2932              
2933             };
2934              
2935             =pod
2936              
2937             =begin btest(load_where)
2938              
2939             =end btest(load_where)
2940              
2941             =cut
2942              
2943             =pod
2944              
2945             =item load_one_where
2946              
2947             convenience method. Simply wrappers a load_where call while passing the singleton parameter
2948              
2949             =cut
2950              
2951             sub load_one_where {
2952 0     0 1 0 my $class = shift;
2953 0 0 0     0 if (ref $_[0] && ref $_[1] eq 'HASH') {
2954 0         0 $_[1]->{'singleton'} = 1;
2955 0         0 return $class->load_where(@_);
2956             }
2957             else {
2958 0         0 return $class->load_where(\@_, {'singleton' => 1});
2959             };
2960             }
2961              
2962             =pod
2963              
2964             =begin btest(load_one_where)
2965              
2966             =end btest(load_one_where)
2967              
2968             =cut
2969              
2970             =pod
2971              
2972             =item arbitrary_sql
2973              
2974             The arbitrary_sql method does what it sounds like, it executes arbitrary sql code. You're expected
2975             to pass at least one parameter:
2976              
2977             query => 'some sql query'; #such as select col1, col2 from table1
2978              
2979             If you want to bind any variables to the query, put them in the vars parameter:
2980              
2981             query => 'select count(*) from table where id = ?',
2982             vars => '7'
2983              
2984             Normally, you'd pass in an arrayref to vars, but if it's just one, you can skip it
2985              
2986             vars => '7'
2987             or
2988             vars => ['7']
2989              
2990             query => 'select count(*) from table where id = ? and type = ?',
2991             vars => ['7', 'animal']
2992              
2993             Binding is done without SQL types, unless you pass in a Basset::DB::Table object and the columns as well, which contains the column types:
2994              
2995             my $t = Basset::DB::Table->new( {table definitions} );
2996             table => $t
2997             cols => ['id', 'type']
2998              
2999             Insertion queries (insert, update, etc.) will return 1 upon success
3000              
3001             If you're running a select, show, set, or desc query, then you end up loading data. It will always be returned in an arrayref containing
3002             the rows. Normally, each row is a hashref, loaded with the ->fetchrow_hashref method from DBI. You can also choose to load
3003             into an array, then pass in into:
3004              
3005             'into' => 'array'
3006              
3007             If you pass anything other than 'into' => 'array', then 'into' => 'hash' is assumed.
3008              
3009             my $data = $class->arbitrary_sql(
3010             'query' => 'select id, name from names where id in (?, ?) and name in (?, ?)',
3011             'vars' => [qw(7 8 Jim Koka)],
3012             );
3013              
3014             foreach my $h (@$data){
3015             print {$_->{id} . " : " . $_->name . "\n"} sort keys %$h;
3016             };
3017              
3018             Alternatively, if you're memory conscious, you can pass in the 'iterator' flag. This will return the actual executed statement handle,
3019             so you can call fetchrow_array, fetchrow_hashref, etc. on it yourself.
3020              
3021             my $sth = $class->arbitrary_sql(
3022             'query' => 'select id, name from names where id in (?, ?) and name in (?, ?)',
3023             'vars' => [qw(7 8 Jim Koka)],
3024             'iterator' => 1,
3025             );
3026              
3027             Another example:
3028              
3029             my $rc = $class->arbitrary_sql(
3030             'query' => 'insert into names (id, name) values (?,?)',
3031             'vars' => ['18', 'Jim 3'],
3032             'table => $names_table,
3033             'cols' => [qw(id name)]
3034             );
3035              
3036             # $rc == 1
3037              
3038             =cut
3039              
3040             =pod
3041              
3042             =begin btest(arbitrary_sql)
3043              
3044             =end btest(arbitrary_sql)
3045              
3046             =cut
3047              
3048             sub arbitrary_sql {
3049              
3050 0     0 1 0 my $self = shift;
3051              
3052 0         0 my %init = @_;
3053              
3054 0 0 0     0 return $self->error("Cannot execute arbitrary SQL w/o SQL", "BOP-38")
3055             unless $init{'query'} || $init{'stmt'};
3056              
3057             # table and cols are used to bind a column to a particular type, so you either need to provide both of them
3058             # or neither
3059 0 0 0     0 return $self->error("Cannot use table w/o cols", "BOP-39")
      0        
      0        
3060             if (($init{'table'} && ! $init{'cols'}) || (! $init{'table'} && $init{'cols'}));
3061              
3062             #assume that we want a hash, if nothing's passed
3063 0   0     0 $init{'into'} ||= 'hash'; #default to a hash
3064              
3065 0 0 0     0 my $driver = $init{'driver'} || $self->driver or return;
3066              
3067             #certain queries return stuff. If so, grab it.
3068 0         0 my $arbitrary_selectables = $self->arbitrary_selectables();
3069              
3070 0         0 my $selecting_query = 0;
3071              
3072 0 0 0     0 if ($init{'selecting_query'} || $init{'query'} =~ /^\s*$arbitrary_selectables/i) {
3073 0         0 $selecting_query = 1;
3074             }
3075              
3076 0 0       0 $self->begin() or return;
3077              
3078 0 0       0 my $errormethod = $selecting_query ? 'error' : 'fatalerror';
3079              
3080 0 0 0     0 my $stmt = $init{'stmt'} || $driver->prepare_cached($init{'query'})
3081             or return $self->$errormethod($driver->errstr(), "BOP-05");
3082              
3083             #if we have vars, then we're binding
3084 0 0       0 if ($init{'vars'}){
3085             #allow the user to pass in a single value as a scalar, not in an arrayref
3086 0 0       0 $init{'vars'} = [$init{'vars'}] unless ref $init{'vars'};
3087              
3088             # my $place = 0;
3089              
3090             #bind our places. If we have table and cols, then we know the type to bind to. Otherwise, use undef.
3091             # foreach my $col (@{$init{'vars'}}) {
3092 0         0 my $max = @{$init{'vars'}};
  0         0  
3093 0 0       0 my $definition = $init{'table'}->definition if $init{'table'};
3094 0 0       0 $self->notify('debug', $init{'query'} . "\nVARS: " . join(', ', map {defined($_) ? $_ : 'NULL'} @{$init{'vars'}}));
  0         0  
  0         0  
3095 0         0 for (my $place = 0; $place < $max; $place++) {
3096             #$self->notify('debug', $init{'vars'}->[$place]);
3097 0 0       0 $stmt->bind_param(
    0          
3098             $place + 1, #place
3099             $init{'vars'}->[$place], #value
3100             $init{'table'} #sql type if we have a table, undef otherwise
3101             ? $driver->sql_type($definition->{$init{'cols'}->[$place]})
3102             : undef
3103             ) or return $self->$errormethod($stmt->errstr, "BOP-03");
3104             # $place++;
3105             };
3106             } else {
3107             #otherwise, just notify with the query
3108 0         0 $self->notify('debug', $init{'query'});
3109             }
3110              
3111 0 0       0 $stmt->execute() or return $self->$errormethod($stmt->errstr, "BOP-04");
3112              
3113 0 0       0 $self->end() or return;
3114              
3115 0 0       0 return $stmt if $init{'iterator'};# && $selecting_query;
3116              
3117 0 0       0 if ($selecting_query){
3118              
3119 0         0 my @data = ();
3120              
3121             #into determines our fetchmethod
3122 0 0       0 my $fetchmethod = $init{'into'} =~ /^array$/i ? 'fetchrow_arrayref' : 'fetchrow_hashref'; #default to hashes
3123              
3124 0         0 while (my $stuff = $stmt->$fetchmethod()){
3125             #push @data, $stuff;
3126 0 0       0 if ($fetchmethod eq 'fetchrow_hashref'){
3127             # $stuff = {map {lc $_, $stuff->{$_}} keys %$stuff};
3128             #push @data, {%$stuff};
3129 0         0 push @data, {map {lc $_, $stuff->{$_}} keys %$stuff};
  0         0  
3130             }
3131             else {
3132 0         0 push @data, [@$stuff];
3133             };
3134             };
3135              
3136 0 0       0 $stmt->finish()
3137             or return $self->error($stmt->errstr, "BOP-10");
3138              
3139 0         0 return \@data;
3140             };
3141              
3142 0 0       0 $stmt->finish()
3143             or return $self->error($stmt->errstr, "BOP-10");
3144              
3145 0         0 return 1;
3146             };
3147              
3148             =pod
3149              
3150             =item driver
3151              
3152             The driver method is just a shortcut wrapper for Basset::DB->new(); Only give it the same arguments in the same
3153             format as you would give to Basset::DB->new() itself. The driver object returned will be cached here for all time,
3154             unless you explicitly wipe it out or set it to something else.
3155              
3156             If the driver hasn't been accessed in the last 5 minutes, then it pings the database handle
3157             before returning the driver to ensure that it's still live. If the ping fails and the driver
3158             has no transaction stack, then you transparently just get back a new driver.
3159              
3160             But if the ping fails AND the driver had an active transaction stack, then you get back an error.
3161             Calling ->driver again will create a new handle, but you would presumably have an error condition
3162             to deal with.
3163              
3164             =cut
3165              
3166             =pod
3167              
3168             =begin btest(driver)
3169              
3170             =end btest(driver)
3171              
3172             =cut
3173              
3174             __PACKAGE__->add_class_attr('_driver');
3175              
3176             sub driver {
3177 0     0 1 0 my $self = shift;
3178              
3179 0 0       0 return $self->local_driver if $self->local_driver;
3180              
3181 0 0       0 if (@_) {
    0          
3182 0         0 return $self->_driver(shift);
3183             } elsif (my $driver = $self->_driver) {
3184             #if ($ENV{'MOD_PERL'} && ! $driver->ping) {
3185 0 0       0 if (! $driver->ping) {
3186 0 0       0 if ($driver->stack) {
3187 0         0 $self->notify("warnings", "Silently disconnecting stale driver with transaction stack");
3188             }
3189 0         0 $driver->recreate_handle;
3190             };
3191 0         0 return $driver;
3192             } else {
3193 0 0       0 my $driver = $self->factory('type' => 'driver') or return;
3194 0         0 return $self->_driver($driver);
3195             }
3196             };
3197              
3198             =pod
3199              
3200             =item local_driver
3201              
3202             Normally, you're always talking to one database with all of your objects in all of your classes. And in a perfect world, that would
3203             always be the case. However, you may need to speak to more than one database at a time, and that's where local_driver comes in. Much like
3204             ->error, this is a method that may be called on either an object or a class to specify a localized driver for that class or object.
3205              
3206             To make all Sub::Class objects talk to a different database:
3207              
3208             Sub::Class->local_driver(
3209             Sub::Class->factory(
3210             'type' => 'driver',
3211             'dsn' => 'dbi:Pg:dbname=otherdatabase'
3212             )
3213             );
3214              
3215             To make just one talk to a different database:
3216              
3217             my $obj = Sub::Class->new(
3218             'local_driver' => Sub::Class->factory(
3219             'type' => 'driver',
3220             'dsn' => 'dbi:Pg:dbname=otherdatabase'
3221             )
3222             );
3223              
3224             B that you are expected to maintain a local driver yourself - it will not be pinged, cleaned up, removed, or anything. You, the
3225             programmer, are inserting in a special case and are expected to pick up after yourself.
3226            
3227             =cut
3228              
3229             __PACKAGE__->add_trickle_class_attr('_pkg_local_driver');
3230             __PACKAGE__->add_attr('_obj_local_driver');
3231              
3232             sub local_driver {
3233 0     0 1 0 my $self = shift;
3234 0 0       0 my $localmethod = ref $self ? "_obj_local_driver" : "_pkg_local_driver";
3235            
3236 0         0 return $self->$localmethod(@_);
3237             }
3238              
3239             =pod
3240              
3241             =item begin
3242              
3243             Database transactions are stack based. ->begin adds onto the stack, ->end removes from the stack.
3244             See Basset::DB for more info.
3245              
3246             You may now begin and end your transaction as normal. Please be aware of the fact that in the current
3247             implementation, beginning a transaction locks the database driver for ALL objects in the system.
3248              
3249             You don't need to begin if you're only committing a single object - individual classes
3250             are expected to do their own locking, stack handling, unlocking, etc. as necessary. You will need to
3251             begin and end if you're doing multiple commits of different objects (or if you're writing your
3252             own module). For example,
3253              
3254             my $user = Basset::User->load(1);
3255             my $user2 = Basset::User->load(2);
3256              
3257             $user->begin(); #start up a transaction stack
3258              
3259             $user->name('Jim'); #set user's name, doesn't need to be in the transaction
3260             $user2->name('Koka'); #set user's name, doesn't need to be in the transaction
3261              
3262             $user->commit(); #doesn't actually commit to the database, it's in a transaction
3263             $user2->commit(); #doesn't actually commit to the database, it's in a transaction
3264              
3265             $user->end(); #closes the transaction stack, now commits
3266              
3267             See Basset::DB for more information about begin, end, fail, etc.
3268              
3269             =cut
3270              
3271             =pod
3272              
3273             =begin btest(begin)
3274              
3275             =end btest(begin)
3276              
3277             =cut
3278              
3279             sub begin {
3280 0     0 1 0 my $self = shift;
3281              
3282 0 0       0 my $driver = $self->driver or return;
3283              
3284 0   0     0 return $driver->begin() || $self->error($driver->errvals);
3285              
3286             }
3287              
3288             =pod
3289              
3290             =item end
3291              
3292             Database transactions are stack based. ->begin adds onto the stack, ->end removes from the stack.
3293             See Basset::DB for more info.
3294              
3295             =cut
3296              
3297             =pod
3298              
3299             =begin btest(end)
3300              
3301             =end btest(end)
3302              
3303             =cut
3304              
3305             sub end {
3306 0     0 1 0 my $self = shift;
3307              
3308 0 0       0 my $driver = $self->driver or return;
3309            
3310 0   0     0 return $driver->end() || $self->error($driver->errvals);
3311             };
3312              
3313             =pod
3314              
3315             =item fail
3316              
3317             Database transactions are stack based. ->fail is a shortcut to shutdown and rollback your
3318             transaction
3319              
3320             =cut
3321              
3322             =pod
3323              
3324             =begin btest(fail)
3325              
3326             =end btest(fail)
3327              
3328             =cut
3329              
3330             sub fail {
3331 0     0 1 0 my $self = shift;
3332              
3333 0 0       0 my $driver = $self->driver or return;
3334              
3335 0   0     0 return $driver->fail || $self->error($driver->errvals);
3336              
3337             };
3338              
3339             =pod
3340              
3341             =item finish
3342              
3343             Database transactions are stack based. ->finish is a shortcut to immediately finish your
3344             transaction
3345              
3346             =cut
3347              
3348             =pod
3349              
3350             =begin btest(finish)
3351              
3352             =end btest(finish)
3353              
3354             =cut
3355              
3356             sub finish {
3357 0     0 1 0 my $self = shift;
3358              
3359 0 0       0 my $driver = $self->driver or return;
3360              
3361 0   0     0 return $driver->finish || $self->error($driver->errvals);
3362             };
3363              
3364             =pod
3365              
3366             =item wipe
3367              
3368             Database transactions are stack based. ->wipe clears out your transaction stack.
3369              
3370             =cut
3371              
3372             =pod
3373              
3374             =begin btest(wipe)
3375              
3376             =end btest(wipe)
3377              
3378             =cut
3379              
3380             sub wipe {
3381 0     0 1 0 my $self = shift;
3382              
3383 0 0       0 my $driver = $self->driver or return;
3384              
3385 0   0     0 return $driver->wipe || $self->error($driver->errvals);
3386              
3387             };
3388              
3389             =pod
3390              
3391             =item fatalerror
3392              
3393             Setting a fatalerror message causes your transaction to fail. Note that you must explicitly pass a
3394             defined value for the transaction stack to be wiped.
3395              
3396             If you need to unfail a failed transaction (say, you know how to recover from the error), then you should call
3397             unfail on the driver and continue.
3398              
3399             $driver->unfail();
3400             # interesting things
3401              
3402             =cut
3403              
3404             =pod
3405              
3406             =begin btest(fatalerror)
3407              
3408             my $o = __PACKAGE__->new();
3409             $test->ok($o, "got object");
3410              
3411             $test->is($o->committing(1), 1, "set committing to 1");
3412             $test->is($o->deleting(1), 1, "set deleting to 1");
3413              
3414             $test->is(scalar($o->fatalerror("fatalerror", "some code")), undef, "set fatalerror");
3415             $test->is($o->errcode, "some code", "proper error code");
3416             $test->is($o->committing, 0, "wiped out committing flag");
3417             $test->is($o->deleting, 0, "wiped out deleting flag");
3418              
3419             $test->is(scalar(__PACKAGE__->fatalerror("pkg error", "pkg error code")), undef, "set pkg error");
3420             $test->is(__PACKAGE__->errcode, "pkg error code", "proper package error code");
3421             $test->is($o->errcode, "some code", "object retains error code");
3422              
3423             =end btest(fatalerror)
3424              
3425             =cut
3426              
3427             sub fatalerror {
3428 2     2 1 5 my $self = shift;
3429              
3430 2         11 my $driver = $self->_driver;
3431              
3432 2 0 33     8 if (defined $driver && @_ && defined $_[0]) {
      33        
3433 0         0 $driver->failed(1);
3434 0         0 $driver->end();
3435             }
3436              
3437 2 100       12 $self->committing(0) if ref $self;
3438 2 100       10 $self->deleting(0) if ref $self;
3439              
3440 2         12 return $self->error(@_);
3441             };
3442              
3443             =pod
3444              
3445             =item setup
3446              
3447             The setup method is called immediately after the object is loaded and initialized in load_all. Basset::Object::Persistent's
3448             method is empty and does nothing. It's designed to be used in subclasses in locations where you need to alter something in
3449             an object after it's loaded from the database and set up properly. Say if you do further initialization
3450             or load in from an object or something.
3451              
3452             =cut
3453              
3454             sub setup {
3455 0     0 1   return shift;
3456             };
3457              
3458             =pod
3459              
3460             =begin btest(setup)
3461              
3462             =end btest(setup)
3463              
3464             =cut
3465              
3466             =pod
3467              
3468             =item cleanup
3469              
3470             The cleanup method is called immediately before the object is committed in commit. Basset::Object::Persistent's
3471             method is empty and does nothing. It's designed to be used in subclasses in locations where you need to alter something in
3472             an object immediately before it's committed to the database.
3473              
3474             =cut
3475              
3476             sub cleanup {
3477 0     0 1   return shift;
3478             };
3479              
3480             =pod
3481              
3482             =begin btest(cleanup)
3483              
3484             =end btest(cleanup)
3485              
3486             =cut
3487              
3488             =pod
3489              
3490             =back
3491              
3492             =cut
3493              
3494             1;