File Coverage

blib/lib/Class/ReluctantORM/Relationship/HasOne.pm
Criterion Covered Total %
statement 43 367 11.7
branch 0 98 0.0
condition 0 29 0.0
subroutine 14 56 25.0
pod 8 8 100.0
total 65 558 11.6


line stmt bran cond sub pod time code
1             package Class::ReluctantORM::Relationship::HasOne;
2              
3             =head1 NAME
4              
5             Class::ReluctantORM::Relationship::HasOne
6              
7             =head1 SYNOPSIS
8              
9             # Add relationships to a ReluctantORM Class
10             Pirate->has_one('Ship');
11              
12             # Now you have:
13             $pirate = Pirate->fetch_with_ship($pirate_id);
14             @bipeds = Pirate->fetch_by_leg_count_with_ship(2);
15              
16             # Get info about the relationship
17             $rel = Pirate->relationships('ship');
18              
19             $str = $rel->type(); # 'has_one';
20             $str = $rel->linked_class(); # 'Ship';
21             $str = $rel->linking_class(); # 'Pirate';
22             @fields = $rel->local_key_fields(); # fields in Pirate that link to Ship
23             @fields = $rel->remote_key_fields(); # array of fields in Ship that link to Pirate
24             $int = $rel->join_depth(); # 1
25              
26             # Class::ReluctantORM::SQL integration
27             @sql_cols = $rel->additional_output_sql_columns();
28             @cols = $rel->local_key_sql_columns();
29             @cols = $rel->remote_key_sql_columns();
30             @empty = $rel->join_local_key_sql_columns(); # always empty for HasOne
31             @empty = $rel->join_remote_key_sql_columns(); # always empty for HasOne
32              
33              
34             =head1 DESCRIPTION
35              
36             =head2 $class->has_one('OtherClass');
37              
38             =head2 $class->has_one(class => 'OtherClass', local_key => [colname,...], remote_key => [colname, ...], => 'key_column', method_name => 'some_name', read_only => 1);
39              
40             Describes a (possibly optional) relationship between two classes/tables.
41              
42             The local table should have a column (or columns) that act as foreign keys
43             into the remote table. An accessor/mutator wil be created that provides
44             access to the related object.
45              
46             Additionally, a new constructor is created, named $class->fetch_with_METHOD.
47             This constructor has the special feature that it performs an outer join and
48             pre-fetches the named object. Finally, additional constructors named
49             $class->fetch_by_ATTRIBUTE_with_METHOD will also be available via AUTOLOAD.
50              
51             In the first form, OtherClass is taken to be the 'class' argument, and all
52             other arguments are determined from that.
53              
54             Arguments:
55              
56             =over
57              
58             =item class (string classname, required)
59              
60             The name of the remote ReluctantORM class.
61              
62             =item local_key (string or arrayref)
63              
64             The name of the foreign key column (or columns) in the local table. Optional
65             - default is OtherClass->primary_key_columns().
66              
67             =item remote_key (string or arrayref)
68              
69             The name of the foreign key column (or columns) in the remote table. Optional
70             - default is OtherClass->primary_key_columns().
71              
72             =item method_name (string)
73              
74             The name of the accessor/mutator method to be created. Optional - default is
75             the lowercased and underscore-spaced version of the class name of OtherClass.
76              
77             =item foreign_key (string, deprecated)
78              
79             Deprecated synonym for local_key.
80              
81             =back
82              
83              
84             The mutator will set the corresponding local key column.
85              
86             The accessor will display some behavior intended to help with scalability.
87             If the value has already been fetched, it will be returned normally. If a
88             trip to the database would be required, the method dies with an
89             Class::ReluctantORM::Exception::Data::FetchRequired. You can then actually
90             fetch the value using $instance->fetch_METHOD .
91              
92             =cut
93              
94 1     1   5 use strict;
  1         1  
  1         36  
95 1     1   5 use warnings;
  1         2  
  1         23  
96              
97 1     1   4 use Data::Dumper;
  1         2  
  1         43  
98 1     1   5 use Scalar::Util qw(blessed);
  1         1  
  1         42  
99 1     1   5 use Class::ReluctantORM::Utilities qw(install_method conditional_load array_shallow_eq);
  1         2  
  1         64  
100 1     1   5 use Class::ReluctantORM::Exception;
  1         2  
  1         38  
101             our $DEBUG ||= 0;
102              
103 1     1   10 use base 'Class::ReluctantORM::Relationship';
  1         1  
  1         2620  
104              
105             sub _initialize {
106 1     1   2 my $class = shift;
107 1     0   4 install_method('Class::ReluctantORM::Relationship', 'is_has_one', sub { return 0; });
  0     0   0  
108 1         5 install_method('Class::ReluctantORM', 'has_one', \&__setup_has_one);
109 1         3 install_method('Class::ReluctantORM', 'is_field_has_one', \&is_field_has_one);
110             }
111              
112             =head2 $str = $rel->type();
113              
114             Returns 'has_one'.
115              
116             =cut
117              
118 0     0 1   sub type { return 'has_one'; }
119              
120             =head2 $bool = $rel->is_has_one();
121              
122             Returns true.
123              
124             =cut
125              
126 0     0 1   sub is_has_one { return 1; }
127              
128             =head2 $int = $rel->join_depth();
129              
130             Returns 1.
131              
132             =cut
133              
134 0     0 1   sub join_depth { return 1; }
135              
136             =head2 $str = $rel->join_type();
137              
138             Returns 'LEFT OUTER'
139              
140             =cut
141              
142 0     0 1   sub join_type { return 'LEFT OUTER'; }
143              
144             =head2 $int = $rel->lower_multiplicity()
145              
146             Returns 0.
147              
148             =cut
149              
150 0     0 1   sub lower_multiplicity { return 0; }
151              
152             =head2 $int = $rel->upper_multiplicity()
153              
154             Returns 1.
155              
156             =cut
157              
158 0     0 1   sub upper_multiplicity { return 1; }
159              
160              
161             =begin devdocs
162              
163             Not sure this is public.... or if that calling pattern is right.
164              
165             =head2 $bool = $cro_obj->is_field_has_one('field');
166              
167             Returns true if the given field is a HasOne field.
168              
169             =cut
170              
171             sub is_field_has_one {
172 0     0     my $inv = shift;
173 0           my $field = shift;
174 0 0         my $tb_class = ref($inv) ? ref($inv) : $inv; # wtf
175 0           my $rel = $tb_class->relationships($field);
176 0 0         return $rel ? $rel->is_has_one() : undef;
177             }
178              
179             =head2 @cols = $h1->additional_output_sql_columns();
180              
181             Returns the essential columns of the linked table.
182              
183             =cut
184              
185             sub additional_output_sql_columns {
186 0     0 1   my $rel = shift;
187 0           return $rel->linked_class->essential_sql_columns();
188             }
189              
190             =begin devnotes
191              
192             In order to use a Collection, while appearing not to,
193             we will actually use a secondary field to store the
194             collection.
195              
196              
197             =cut
198              
199             sub __setup_has_one {
200 0     0     my $cro_base_class = shift;
201 0           my $has_one_class = __PACKAGE__;
202 0           my %raw_args = ();
203              
204 0 0         if (@_ == 1) {
205 0           %raw_args = (class => shift());
206             } else {
207 0 0         if (@_ % 2) { Class::ReluctantORM::Exception::Param::ExpectedHash->croak(); }
  0            
208 0           %raw_args = @_;
209             }
210              
211             # Validate Args
212 0           my %args;
213              
214 0 0         unless ($raw_args{class}) { Class::ReluctantORM::Exception::Param::Missing->croak(param => 'class'); }
  0            
215 0           $args{class} = $raw_args{class};
216 0           delete $raw_args{class};
217              
218 0           $args{method_name} = $raw_args{method_name};
219 0           delete $raw_args{method_name};
220 0   0       $args{method_name} ||= Class::ReluctantORM::Utilities::camel_case_to_underscore_case((split('::', $args{class}))[-1]);
221              
222              
223 0           $args{local_key} = $raw_args{local_key};
224 0           delete $raw_args{local_key};
225 0   0       $args{local_key} ||= $args{class}->primary_key_columns();
226 0 0         $args{local_key} = ref($args{local_key}) eq 'ARRAY' ? $args{local_key} : [ $args{local_key} ];
227 0           foreach my $key (@{$args{local_key}}) {
  0            
228 0 0         unless ($cro_base_class->field_name($key)) {
229 0           Class::ReluctantORM::Exception::Param::BadValue->croak
230             (
231             param => 'local_key',
232             value => $key,
233             error => "Local key '$key' does not appear to be a column on " . $cro_base_class->table_name,
234             );
235             }
236             }
237              
238 0           $args{remote_key} = $raw_args{remote_key};
239 0           delete $raw_args{remote_key};
240 0   0       $args{remote_key} ||= $args{class}->primary_key_columns();
241 0 0         $args{remote_key} = ref($args{remote_key}) eq 'ARRAY' ? $args{remote_key} : [ $args{remote_key} ];
242 0           foreach my $key (@{$args{remote_key}}) {
  0            
243 0 0         unless ($args{class}->field_name($key)) {
244 0           Class::ReluctantORM::Exception::Param::BadValue->croak
245             (
246             param => 'remote_key',
247             value => $key,
248             error => "Remote key '$key' does not appear to be a column on " . $args{class}->table_name,
249             );
250             }
251             }
252              
253             # Should have no more args at this point
254 0 0         if (keys %raw_args) {
255 0           Class::ReluctantORM::Exception::Param::Spurious->croak
256             (
257             param => join(',', keys %raw_args),
258             error => "Extra args to 'has_one'",
259             );
260             }
261              
262             # Load class
263 0           conditional_load($args{class});
264              
265 0           $has_one_class->delay_until_class_is_available
266             ($args{class}, $has_one_class->__relationship_installer(%args, cro_base_class => $cro_base_class));
267 0           $has_one_class->delay_until_class_is_available
268             ($args{class}, $has_one_class->__inverse_relationship_finder(%args, cro_base_class => $cro_base_class));
269             }
270              
271             sub __relationship_installer {
272 0     0     my $has_one_class = shift;
273 0           my %args = @_;
274             return sub {
275 0 0   0     if ($DEBUG > 1) {
276 0           print STDERR __PACKAGE__ . ':' . __LINE__ . " - in HasOne setup callback\n";
277             }
278 0           my $rel = Class::ReluctantORM::Relationship::HasOne->new();
279 0           $rel->method_name($args{method_name});
280 0           $rel->linked_class($args{class});
281 0           $rel->linking_class($args{cro_base_class});
282 0           $rel->local_key_fields($args{cro_base_class}->field_name(@{$args{local_key}}));
  0            
283 0           $rel->remote_key_fields($args{class}->field_name(@{$args{remote_key}}));
  0            
284              
285 0           install_method($args{cro_base_class}, $rel->method_name, $rel->__make_has_one_accessor());
286 0           install_method($args{cro_base_class}, 'fetch_' . $rel->method_name, $rel->__make_has_one_fetch_accessor());
287              
288 0           $rel->_install_search_by_with_methods();
289              
290 0           my @args_copy = map { ($_, $args{$_} ) } grep { $_ ne 'cro_base_class' } keys %args;
  0            
  0            
291 0           $rel->_original_args_arrayref(\@args_copy);
292              
293 0           $args{cro_base_class}->register_relationship($rel);
294 0           };
295             }
296              
297             sub __inverse_relationship_finder {
298 0     0     my $has_one_class = shift;
299 0           my %args = @_;
300             return sub {
301 0     0     my $cro_local_class = $args{cro_base_class};
302 0           my $cro_remote_class = $args{class};
303 0           my $local_relname = $args{method_name};
304 0           my $local_rel = $cro_local_class->relationships($local_relname);
305 0 0 0       unless ($local_rel && $local_rel->is_has_one) { return; }
  0            
306 0 0         if ($local_rel->inverse_relationship()) {
307             # Assume we already found it
308 0           return;
309             }
310              
311             # List the has_many relationships on the linked class
312             # that point to this class
313 0           my @remote_has_many_rels =
314 0           grep { $_->linked_class eq $cro_local_class }
315 0           grep { $_->is_has_many } $cro_remote_class->relationships();
316 0 0         unless (@remote_has_many_rels) { return; }
  0            
317              
318 0           my @matches = ();
319 0           foreach my $remote_rel (@remote_has_many_rels) {
320              
321             # These are lists of keys that should be on the local table,
322             # and should be identical
323 0           my @remote_keys1 = $remote_rel->remote_key_fields();
324 0           my @local_keys1 = $local_rel->local_key_fields();
325 0 0         next unless (array_shallow_eq(\@remote_keys1, \@local_keys1));
326              
327             # These are lists of keys that should be on the remote table,
328             # and should be identical
329 0           my @remote_keys2 = $remote_rel->local_key_fields();
330 0           my @local_keys2 = $local_rel->remote_key_fields();
331 0 0         next unless (array_shallow_eq(\@remote_keys2, \@local_keys2));
332              
333 0           push @matches, $remote_rel;
334              
335             }
336              
337 0 0         if (@matches == 1) {
338 0           $local_rel->inverse_relationship($matches[0]);
339 0           $matches[0]->inverse_relationship($local_rel);
340             } else {
341             # Not touching that with a 10-foot pole
342             }
343              
344 0           };
345             }
346              
347             =head2 $bool = $rel->is_populated_in_object($cro_obj);
348              
349             Returns true if the CRO object has had this relationship fetched.
350              
351             =cut
352              
353             sub is_populated_in_object {
354 0     0 1   my $rel = shift;
355 0           my $cro_obj = shift;
356              
357             # Obtain the underlying collection
358 0           my $collection_slot = '_' . $rel->method_name . '_coll';
359 0           my $collection = $cro_obj->get($collection_slot);
360 0 0         unless ($collection) {
361 0           return 0;
362             }
363              
364 0           return $collection->is_populated();
365             }
366              
367             sub __make_has_one_accessor {
368 0     0     my $rel = shift;
369              
370             # Setup accessor
371             my $code = sub {
372 0     0     my $cro_obj = shift;
373 0           my $obj_field = $rel->method_name();
374              
375             # Fetch the underlying collection
376 0           my $collection_slot = '_' . $rel->method_name . '_coll';
377 0           my $collection = $cro_obj->get($collection_slot);
378 0 0         unless (defined $collection) {
379 0           $collection = Class::ReluctantORM::Collection::One->_new(
380             relationship => $rel,
381             linking_object => $cro_obj
382             );
383 0           $cro_obj->set($collection_slot, $collection);
384             }
385              
386 0 0         if (@_) {
387              
388             # Acting as mutator
389 0           my $raw_linked_object = shift;
390 0           my @local_keys = $rel->local_key_fields;
391              
392 0 0         if (defined $raw_linked_object) {
393              
394 0 0 0       unless (blessed($raw_linked_object) && $raw_linked_object->isa($rel->linked_class)) {
395 0           Class::ReluctantORM::Exception::Param::WrongType->croak(
396             param => 'value',
397             expected => $rel->linked_class,
398             value => $raw_linked_object
399             );
400             }
401              
402             # Run write filters
403 0           my $cooked_linked_obj = $cro_obj->__apply_field_write_filters($obj_field, $raw_linked_object);
404              
405             # Set the keys
406 0           my @remote_keys = $rel->remote_key_fields;
407 0           for my $key_num (0..(@remote_keys -1)) {
408 0           my $remote_key = $remote_keys[$key_num];
409 0           my $local_key = $local_keys[$key_num];
410 0           $cro_obj->$local_key($cooked_linked_obj->$remote_key());
411             }
412              
413             # Set the collection contents
414 0           $collection->_set_single_value($cooked_linked_obj);
415              
416             } else {
417             # Clear the keys
418 0           foreach my $key (@local_keys) {
419 0           $cro_obj->$key(undef);
420             }
421              
422             # Set the collection to be fetched but empty
423 0           $collection->_set_empty_but_populated();
424             }
425             }
426              
427 0 0         if ($collection->is_populated) {
428 0           my $raw_value = $collection->first();
429 0           my $cooked_value = $cro_obj->__apply_field_read_filters($obj_field, $raw_value);
430 0           return $cooked_value;
431             } else {
432              
433 0 0         if ($rel->linked_class->is_static) {
434             # Go ahead and fetch
435 0           my @linking_keys = map { $cro_obj->$_() } $rel->local_key_fields;
  0            
436 0           my $raw_value = $rel->linked_class->fetch(@linking_keys);
437 0           my $cooked_value = $cro_obj->__apply_field_read_filters($obj_field, $raw_value);
438 0           $collection->_set_single_value($cooked_value);
439 0           return $cooked_value;
440             } else {
441 0           Class::ReluctantORM::Exception::Data::FetchRequired->croak(called => $rel->method_name, call_instead => 'fetch_' . $rel->method_name, fetch_locations => [ $cro_obj->all_origin_traces ]);
442             }
443             }
444 0           };
445 0           return $code;
446             }
447              
448              
449             sub _raw_mutator {
450 0     0     my $rel = shift;
451 0           my $cro_obj = shift;
452 0           my @newval = @_;
453              
454 0           my $has_one_field = $rel->method_name();
455              
456             # Fetch the underlying collection
457 0           my $collection_slot = '_' . $rel->method_name . '_coll';
458 0           my $collection = $cro_obj->get($collection_slot);
459 0 0         unless (defined $collection) {
460 0           $collection = Class::ReluctantORM::Collection::One->_new(
461             relationship => $rel,
462             linking_object => $cro_obj
463             );
464 0           $cro_obj->set($collection_slot, $collection);
465             }
466              
467 0 0         if (@newval) {
468             # Set the collection contents
469 0           my $newval = $newval[0]; # Only allows one
470              
471 0 0         if (defined ($newval)) {
472 0           $collection->_set_single_value($newval);
473             } else {
474             # Set the collection to be fetched but empty
475 0           $collection->_set_empty_but_populated();
476             }
477              
478 0           $cro_obj->_mark_field_dirty($has_one_field);
479             }
480              
481 0 0         if ($collection->is_populated) {
482 0           my $raw_value = $collection->first();
483 0           return $raw_value;
484             } else {
485 0           Class::ReluctantORM::Exception::Data::FetchRequired->croak
486             (
487             called => $rel->method_name,
488             call_instead => 'fetch_' . $rel->method_name,
489             fetch_locations => [ $cro_obj->all_origin_traces ],
490             );
491             }
492             }
493              
494             sub __make_has_one_fetch_accessor {
495 0     0     my $rel = shift;
496             # Setup accessor
497             my $code = sub {
498 0     0     my $cro_obj = shift;
499              
500             # Fetch the underlying collection
501 0           my $collection_slot = '_' . $rel->method_name . '_coll';
502 0           my $collection = $cro_obj->get($collection_slot);
503 0 0         unless (defined $collection) {
504 0           $collection = Class::ReluctantORM::Collection::One->_new(
505             relationship => $rel,
506             linking_object => $cro_obj
507             );
508 0           $cro_obj->set($collection_slot, $collection);
509             }
510              
511 0           $collection->depopulate;
512 0           $collection->fetch_all();
513 0           return $collection->first;
514 0           };
515 0           return $code;
516             }
517              
518              
519             # Do nothing
520 0     0     sub _handle_implicit_create { }
521              
522             # Called from ReluctantORM::new()
523             sub _handle_implicit_new {
524 0     0     my $rel = shift;
525 0           my $linking_object = shift;
526 0           my $new_args = shift;
527              
528 0           my @key_fields = $rel->local_key_fields();
529 0           my $rel_field = $rel->method_name();
530              
531 0           my $any_key_present = 0;
532 0           my $all_keys_present = 1;
533 0           for my $key (@key_fields) {
534 0   0       $any_key_present ||= exists $new_args->{$key};
535 0   0       $all_keys_present &&= exists $new_args->{$key};
536             }
537              
538 0           my $rel_field_present = exists $new_args->{$rel_field};
539 0           my $child_obj;
540 0 0         if ($rel_field_present) {
541 0           $child_obj = $new_args->{$rel_field};
542 0 0         if (ref($child_obj) eq 'ARRAY') {
543             # Fetch_deep will build things passing children in array refs - unpack it
544 0           $child_obj = $child_obj->[0];
545             }
546             }
547              
548              
549             # This stanza causes a bunch of tests in 11-has_one to fail
550             # if ($rel_field_present && $any_key_present) {
551             # Class::ReluctantORM::Exception::Param::Duplicate->croak
552             # (
553             # error => "You specified both the related field and one or more local keys for a Has-One relationship. Please specify one or the other.",
554             # param => join ',', ($rel_field, @key_fields),
555             # );
556             # }
557              
558              
559              
560              
561 0 0         if ($rel_field_present) {
    0          
562             # The linked object was provided. Set it.
563             # (the keys will be set by the mutator call)
564 0           $linking_object->$rel_field($child_obj);
565 0           my $inverse_rel = $rel->inverse_relationship();
566 0 0 0       if ($inverse_rel && $child_obj) {
567 0           my $method = $inverse_rel->method_name();
568 0           $child_obj->$method->attach($linking_object);
569             }
570              
571             } elsif ($all_keys_present) {
572             # They've all already been set by new(), since all the local keys are actual fields.
573             # So we have an unfetched relation, which is handled by the fetching accessor.
574             } else {
575             # Neither object nor keys. Set it up as a fetched, empty collection.
576 0           my $collection_slot = '_' . $rel->method_name . '_coll';
577 0           my $collection = Class::ReluctantORM::Collection::One->_new(
578             relationship => $rel,
579             linking_object => $linking_object,
580             children => [],
581             );
582 0           $linking_object->set($collection_slot, $collection);
583             }
584              
585             }
586              
587             sub _mark_unpopulated_in_object {
588 0     0     my $rel = shift;
589 0           my $cro_obj = shift;
590              
591             # Obtain the underlying collection
592 0           my $collection_slot = '_' . $rel->method_name . '_coll';
593 0           my $collection = $cro_obj->get($collection_slot);
594 0 0         unless ($collection) { return; }
  0            
595              
596 0           $collection->depopulate();
597              
598              
599             }
600              
601              
602             sub _notify_key_change_on_linking_object {
603 0     0     my $rel = shift;
604 0           my $changed_linking_object = shift;
605 0 0         if ($Class::ReluctantORM::SOFT_TODO_MESSAGES) {
606 0           print STDERR __PACKAGE__ . ':' . __LINE__ . " - soft TODO - HasOne::_notify_key_change_on_linking_object()\n";
607             }
608             }
609              
610             sub _merge_children {
611 0     0     my $rel = shift;
612 0           my $cro_obj = shift;
613 0           my $children_ref = shift;
614              
615             # Nothing to do if children is undef
616 0 0         return unless (defined $children_ref);
617              
618             # Has one should only ever get one child, derp
619 0           my $new_child = $children_ref->[0];
620              
621 0           my $relname = $rel->name();
622 0           my $existing_child = $cro_obj->$relname; # We know this is populated
623              
624 0 0         if ($new_child->id eq $existing_child->id()) {
625             # Recurse into fetched relations and merge?
626 0           foreach my $child_rel ($existing_child->relationships) {
627 0           my $child_rel_name = $child_rel->name();
628 0 0         if ($existing_child->is_fetched($child_rel_name)) {
    0          
629 0 0         if ($new_child->is_fetched($child_rel_name)) {
630 0           $child_rel->merge_children($existing_child, [ $new_child->$child_rel_name ]);
631             }
632             } elsif ($new_child->is_fetched($child_rel_name)) {
633 0           $child_rel->handle_implicit_new($existing_child, [ $new_child->$child_rel_name ]);
634             }
635             }
636             } else {
637             # new_child is fresh from the DB, while existing_child is in ram
638             # Which is more correct to keep?
639             # I'd say keep the existing one, since it may have been messed with
640             # So, nothing to do? But what if the fetch deep maps were different?
641 0           Class::ReluctantORM::Exception::NotImplemented->croak("Cannot merge kids, ids don't match");
642             }
643             }
644              
645             1;
646              
647              
648              
649             #=============================================================================#
650             #=============================================================================#
651             # Collection Subclass
652             #=============================================================================#
653             #=============================================================================#
654              
655             package Class::ReluctantORM::Collection::One;
656 1     1   6 use strict;
  1         1  
  1         23  
657 1     1   4 use warnings;
  1         2  
  1         25  
658              
659 1     1   4 use Data::Dumper;
  1         2  
  1         40  
660 1     1   4 use base 'Class::ReluctantORM::Collection';
  1         2  
  1         61  
661 1     1   5 use Class::ReluctantORM::SQL::Aliases;
  1         2  
  1         120  
662 1     1   4 use Scalar::Util qw(weaken);
  1         2  
  1         1030  
663              
664             our $DEBUG = 0;
665              
666 0     0     sub rel { return shift->{relationship}; }
667              
668             sub _new {
669 0     0     my ($class, %args) = @_;
670 0           foreach my $f (qw(master_class master_key_name master_key_value child_key_name child_class) ) {
671 0 0         if (exists $args{$f}) { Class::ReluctantORM::Exception::Call::Deprecated->croak("May not use param $f for Colelction::OneToMany::_new in 0.4 code"); }
  0            
672             }
673 0           foreach my $f (qw(relationship linking_object)) {
674 0 0         unless (exists $args{$f}) { Class::ReluctantORM::Exception::Param::Missing->croak(param => $f); }
  0            
675             }
676              
677 0           my $self = bless \%args, $class;
678 0           weaken($self->{linking_object});
679              
680 0 0         if ($args{children}) {
681 0           $self->{_children} = $args{children};
682 0           $self->{_populated} = 1;
683 0           $self->{_count} = scalar @{$args{children}};
  0            
684             } else {
685 0           $self->{_populated} = 0;
686 0           $self->{_count} = undef;
687 0           $self->{_children} = [];
688             }
689              
690 0           return $self;
691             }
692              
693             sub all_items {
694 0     0     my $self = shift;
695 0 0         if ($self->is_populated) {
696 0           return @{$self->{_children}};
  0            
697             } else {
698 0           Class::ReluctantORM::Exception::Data::FetchRequired->croak(called => 'all_items', call_instead => 'fetch_all', fetch_locations => [ $self->linking_object->all_origin_traces ]);
699             }
700             }
701              
702 0     0     sub all { goto &all_items; }
703              
704             sub _check_correct_child_class {
705 0     0     my ($self, $object) = @_;
706 0 0         unless ($object->isa($self->rel->linked_class)) {
707 0           Class::ReluctantORM::Exception::Data::WrongType->croak(param => 'object', expected => $self->rel->linked_class, frames => 2);
708             }
709             }
710              
711 0     0     sub is_populated { return shift->{_populated}; }
712             sub depopulate {
713 0     0     my $self = shift;
714 0           $self->{_populated} = 0;
715 0           $self->{_count} = undef;
716 0           $self->{_children} = [];
717             }
718              
719             sub count {
720 0     0     my $self = shift;
721 0 0 0       if ($self->is_populated || defined($self->{_count})) {
722 0           return $self->{_count};
723             } else {
724 0           Class::ReluctantORM::Exception::Data::FetchRequired->croak(called => 'count', call_instead => 'fetch_count', fetch_locations => [ $self->linking_object->all_origin_traces ]);
725             }
726             }
727              
728              
729             sub fetch_count {
730 0     0     my $self = shift;
731              
732 0           my $field = $self->rel->linked_class->first_primary_key_field();
733 0           my $method = 'count_of_' . $field;
734              
735             # Rely on aggregate mechanism
736 0           $self->{_count} = $self->$method;
737 0           return $self->{_count};
738             }
739              
740             sub fetch_all {
741 0     0     my $self = shift;
742              
743             # Determine FK values in the parent
744 0           my $parent = $self->linking_object();
745 0           my @fk_values = map { $parent->$_() } $self->rel->local_key_fields();
  0            
746              
747 0           my $child = $self->rel->linked_class->fetch(@fk_values);
748              
749             # This counts as an origin on the parent
750 0           $parent->capture_origin();
751              
752 0           $self->{_children} = [ $child ];
753 0           $self->{_populated} = 1;
754 0           $self->{_count} = 1;
755 0           my @results = @{$self->{_children}};
  0            
756 0           return @results;
757             }
758              
759             sub __make_link_where {
760 0     0     my $self = shift;
761 0           my $linking_class = $self->rel->linking_class();
762 0           my @where;
763             my @execargs;
764              
765 0           foreach my $colname ($self->rel->local_key_columns) {
766 0           push @where, 'MACRO__parent__' . $self->rel->method_name() . '__.' . $colname . ' = ?';
767              
768 0           my $f = $linking_class->field_name($colname);
769 0           my $value = $self->linking_object->raw_field_value($f);
770 0           push @execargs, $value;
771              
772             }
773              
774 0           return (where => join(' AND ', @where), execargs => \@execargs);
775             }
776              
777             sub __make_link_where_sql {
778 0     0     my $self = shift;
779 0           my $linking_class = $self->rel->linking_class();
780 0           my $crit;
781              
782 0           foreach my $col ($self->rel->local_key_sql_columns) {
783 0           my $f = $linking_class->field_name($col->column);
784 0           my $param = Param->new();
785 0           $param->bind_value($self->linking_object->raw_field_value($f));
786              
787 0           my $this_crit = Criterion->new('=', $col, $param);
788 0 0         $crit = $crit ? Criterion->new('AND', $crit, $this_crit) : $this_crit;
789             }
790              
791 0           return (where => Where->new($crit));
792             }
793              
794              
795             # Note: AUTOLOAD defined in Collection base class
796             sub __setup_aggregate_autoload {
797 0     0     my ($self1, $AUTOLOAD, $method, $args, $agg_type, $agg_field) = @_;
798              
799 0           my $linked_class = $self1->rel->linked_class;
800              
801             # Generate a coderef
802             my $code = sub {
803 0     0     my $self = shift;
804 0           my %args = @_;
805 0           my %where_args = $self->__make_link_where();
806              
807             # Append args
808 0   0       $where_args{where} .= $args{where} || '1=1';
809 0 0         push @{$where_args{execargs}}, @{$args{execargs} || []};
  0            
  0            
810              
811             # Use aggregate method defined by child class
812 0           return $linked_class->$method(%where_args);
813 0           };
814              
815             # Don't install coderef in symbol table
816             # The name of this will vary based on the classes linked
817 0           $code->($self1, @$args);
818             }
819              
820             sub _set_single_value {
821 0     0     my $self = shift;
822 0           my $val = shift;
823 0           $self->{_children} = [ $val ];
824 0           $self->{_populated} = 1;
825 0           $self->{_count} = 1;
826 0           return;
827             }
828              
829             sub _set_empty_but_populated {
830 0     0     my $self = shift;
831 0           $self->{_children} = [ ];
832 0           $self->{_populated} = 1;
833 0           $self->{_count} = 0;
834 0           return;
835              
836             }
837              
838              
839             1;
840              
841              
842