File Coverage

blib/lib/Class/ReluctantORM/Relationship/HasMany.pm
Criterion Covered Total %
statement 49 380 12.8
branch 0 84 0.0
condition 0 32 0.0
subroutine 16 64 25.0
pod 7 7 100.0
total 72 567 12.7


line stmt bran cond sub pod time code
1             package Class::ReluctantORM::Relationship::HasMany;
2 1     1   5 use strict;
  1         2  
  1         27  
3 1     1   5 use warnings;
  1         1  
  1         42  
4              
5              
6             =head1 NAME
7              
8             Class::ReluctantORM::Relationship::HasMany
9              
10             =head1 SYNOPSIS
11              
12             # Add relationships to a ReluctantORM Class
13             Ship->has_many('Pirate');
14             Ship->has_many(
15             class => 'Pirate'
16             local_key => 'ship_id', # New in 0.4: multi-column keys allowed via
17             remote_key => 'ship_id', # arrayrefs here!
18             method_name => 'pirates',
19             );
20              
21             # Now you have:
22             $pirates_collection = $ship->pirates();
23              
24             # New in 0.4: in array context, implicitly do $pirates_collection->all_items
25             @mateys = $ship->pirates();
26              
27             # Fetchers defined automatically
28             $ship = Ship->fetch_with_pirates($ship_id);
29             @unarmed = Ship->fetch_by_gun_count_with_pirates(0);
30              
31             # Get info about the relationship
32             $rel = Ship->relationships('pirates');
33              
34             $str = $rel->type(); # 'has_many';
35             $str = $rel->linked_class(); # 'Pirate';
36             $str = $rel->linking_class(); # 'Ship';
37             @fields = $rel->local_key_fields(); # fields in Ship that link to Pirate
38             @fields = $rel->remote_key_fields(); # fields in Pirate that link to Ship
39             $int = $rel->join_depth(); # 1
40              
41             # Class::ReluctantORM::SQL integration
42             @sql_cols = $rel->additional_output_sql_columns();
43             @cols = $rel->local_key_sql_columns();
44             @cols = $rel->remote_key_sql_columns();
45             @empty = $rel->join_local_key_sql_columns(); # always empty for HasMany
46             @empty = $rel->join_remote_key_sql_columns(); # always empty for HasMany
47              
48              
49             =head1 DESCRIPTION
50              
51             =head1 CREATING A RELATIONSHIP
52              
53             =head2 $tb_class->has_many('OtherClass');
54              
55             =head2 $tb_class->has_many(class => 'OtherClass', local_key => 'key_column', remote_key => 'key_column', method_name => 'other_class');
56              
57             =head2 $tb_class->has_many(... join_table => 'table_name' ...);
58              
59             join_table => 'table_name', join_table_schema => 'schema_name',
60              
61             Initiates a one-to-many relationship between two classes/tables.
62             Results are handled with assistance of a simple container class,
63             Class::ReluctantORM::Collection.
64              
65             An accessor will be created named other_classes (or method_name). Note that this
66             should be plural for readability. The accessor will return a Collection object.
67              
68             Additionally, a new constructor is created, named $class->fetch_with_METHOD.
69             This constructor has the special feature that it performs an outer join and
70             prepopulates the Collection. Thus, Ship->fetch_with_pirates(23) is only
71             one DB query.
72              
73             Finally, additional constructors named $class->fetch_by_ATTRIBUTE_with_METHOD
74             will also be available via AUTOLOAD.
75              
76             Obtaining the Collection object does NOT result in trips to the database. Operations
77             on the Collection object DO require trips to the database.
78              
79             Note that a one-to-many relationship does not imply a reciprocal has_one relationship going the other way.
80             It's OK to set that up manually, though.
81              
82             In the first form, a relationship is setup to OtherClass using defaults, described below.
83              
84             In the second form, options are made explicit:
85              
86             =over
87              
88             =item class (required)
89              
90             The linked class. This is the class on the remote end of the one-to-many.
91             That means it will have foreign key(s) to the local (linking) class.
92              
93             =item method_name
94              
95             The name of the method that will be used to access the relationship. This is also the name for the relationship, which you can pass to $tb_class->relationships. Default is the lower-cased and pluralized OtherClass. For example, if you say Ship->has_many('Pirate'), you'll get $ship->pirates(). Pluralization is performed using Lingua.
96              
97             =item local_key (optional string or arrayref)
98              
99             Name or names of columns on the local table acting as keys in the relationship.
100             Defaults to $tb_class->primary_key_columns().
101              
102             =item remote_key (optional string or arrayref)
103              
104             Name or names of columns on the remote table acting as keys in the relationship.
105             Defaults to looking for columns in OtherClass with the names $tb_class->primary_key_columns().
106              
107             =item foreign_key
108              
109             Deprecated synonym for remote_key.
110              
111             =back
112              
113             In the third form, all arguments will be passed to Class::ReluctantORM::Relationshipo::HasManyMany. This form is somewhat discouraged, but remains because some find it more readable.
114              
115             =cut
116              
117              
118 1     1   5 use Data::Dumper;
  1         1  
  1         42  
119 1     1   5 use Scalar::Util qw(blessed);
  1         2  
  1         36  
120 1     1   5 use Class::ReluctantORM::Utilities qw(install_method conditional_load pluralize array_shallow_eq check_args);
  1         2  
  1         56  
121 1     1   5 use Class::ReluctantORM::Collection;
  1         1  
  1         41  
122              
123             our $DEBUG = 0;
124              
125 1     1   6 use base 'Class::ReluctantORM::Relationship';
  1         7  
  1         1840  
126              
127             sub _initialize {
128 1     1   2 my $class = shift;
129 1     0   6 install_method('Class::ReluctantORM::Relationship', 'is_has_many', sub { return 0; });
  0     0   0  
130 1         5 install_method('Class::ReluctantORM', 'has_many', \&__setup_has_many);
131 1         4 install_method('Class::ReluctantORM', 'is_field_has_many', \&is_field_has_many);
132             }
133              
134             =head2 $str = $rel->type();
135              
136             Returns 'has_many'.
137              
138             =cut
139              
140 0     0 1   sub type { return 'has_many'; }
141              
142             =head2 $int = $rel->join_depth();
143              
144             Returns 1.
145              
146             =cut
147              
148 0     0 1   sub join_depth { return 1; }
149              
150             =head2 $str = $rel->join_type();
151              
152             Returns 'LEFT OUTER'
153              
154             =cut
155              
156 0     0 1   sub join_type { return 'LEFT OUTER'; }
157              
158             =head2 $int = $rel->lower_multiplicity()
159              
160             Returns 0.
161              
162             =cut
163              
164 0     0 1   sub lower_multiplicity { return 0; }
165              
166             =head2 $int = $rel->upper_multiplicity()
167              
168             Returns undef.
169              
170             =cut
171              
172 0     0 1   sub upper_multiplicity { return undef; }
173              
174             =head2 $bool = $rel->is_has_many();
175              
176             Returns true.
177              
178             =cut
179              
180 0     0 1   sub is_has_many { return 1; }
181              
182             =head2 $bool = $rel->is_populated_in_object($cro_obj);
183              
184             Returns true if the CRO object has had this relationship fetched.
185              
186             =cut
187              
188             sub is_populated_in_object {
189 0     0 1   my $rel = shift;
190 0           my $cro_obj = shift;
191              
192             # Obtain the underlying collection
193 0           my $collection = $cro_obj->get($rel->method_name());
194 0 0         unless ($collection) {
195 0           return 0;
196             }
197              
198 0           return $collection->is_populated();
199             }
200              
201             sub _mark_unpopulated_in_object {
202 0     0     my $rel = shift;
203 0           my $cro_obj = shift;
204              
205             # Obtain the underlying collection
206 0           my $collection = $cro_obj->get($rel->method_name());
207 0 0         unless ($collection) { return; }
  0            
208 0           $collection->depopulate();
209              
210             }
211              
212              
213             =begin devdocs
214              
215             Not sure this is public.... or if that calling pattern is right.
216              
217             =head2 $bool = $cro_obj->is_field_has_many('field');
218              
219             Returns true if the given field is a HasOne field.
220              
221             =cut
222              
223             sub is_field_has_many {
224 0     0     my $inv = shift;
225 0           my $field = shift;
226 0 0         my $tb_class = ref($inv) ? ref($inv) : $inv;
227 0           my $rel = $tb_class->relationships($field);
228 0 0         return $rel ? $rel->is_has_many() : undef;
229             }
230              
231             sub _notify_key_change_on_linking_object {
232 0     0     my $rel = shift;
233 0           my $parent = shift;
234 0           my $method = $rel->method_name();
235 0           my $collection = $parent->$method();
236 0 0         if ($collection->is_populated) {
237             # Note that $collection already knows $parent via linking_object();
238 0           $collection->__hm_set_keys_on_children_from_parent();
239             }
240             }
241              
242             # Do nothing
243 0     0     sub _handle_implicit_create { }
244              
245             # Called from ReluctantORM::new()
246             sub _handle_implicit_new {
247 0     0     my $rel = shift;
248 0           my $linking_object = shift;
249 0           my $new_args = shift;
250              
251 0   0       my $children = $new_args->{$rel->method_name} || undef; # Default to unpopulated
252              
253 0           my $all_exist = 1;
254 0 0 0       for my $c (@{$children || []}) { $all_exist &&= $c->is_inserted; }
  0            
  0            
255              
256 0 0         unless ($all_exist) {
257 0           Class::ReluctantORM::Exception::Data::UnsupportedCascade->croak('Cascading inserts not supported');
258             }
259              
260 0           my $inverse_rel = $rel->inverse_relationship();
261 0 0         if ($inverse_rel) {
262 0           my $method = $inverse_rel->method_name();
263 0 0         for my $c (@{$children || []}) {
  0            
264             # Set parent reference in each child, if backreferences are requested
265 0 0         if (Class::ReluctantORM->get_global_option('populate_inverse_relationships')) {
266 0           $c->$method($linking_object);
267             }
268             }
269             }
270              
271 0           my $collection = Class::ReluctantORM::Collection::OneToMany->_new(
272             relationship => $rel,
273             linking_object => $linking_object,
274             children => $children,
275             );
276 0           $linking_object->set($rel->method_name, $collection);
277 0           delete $new_args->{$rel->method_name};
278              
279 0           return;
280             }
281              
282             sub __setup_has_many {
283 0     0     my $cro_base_class = shift;
284 0           my $has_many_class = __PACKAGE__;
285 0           my %args = ();
286              
287 0 0         if (@_ == 1) {
288 0           %args = (class => shift());
289             } else {
290 0 0         if (@_ % 2) { Class::ReluctantORM::Exception::Param::ExpectedHash->croak(); }
  0            
291 0           %args = @_;
292             }
293              
294 0           %args = check_args(
295             args => \%args,
296             optional => [qw(remote_key local_key method_name)],
297             required => [qw(class)],
298             );
299              
300              
301             # Determine method name
302 0   0       $args{method_name} ||= pluralize(Class::ReluctantORM::Utilities::camel_case_to_underscore_case((split('::', $args{class}))[-1]));
303              
304             # Coerce local and foreign keys to be arrayrefs
305 0   0       $args{remote_key} ||= $cro_base_class->primary_key_columns();
306 0 0         $args{remote_key} = ref($args{remote_key}) eq 'ARRAY' ? $args{remote_key} : [ $args{remote_key} ];
307 0   0       $args{local_key} ||= $cro_base_class->primary_key_columns();
308 0 0         $args{local_key} = ref($args{local_key}) eq 'ARRAY' ? $args{local_key} : [ $args{local_key} ];
309              
310 0           conditional_load($args{class});
311              
312 0           $has_many_class->delay_until_class_is_available
313             ($args{class}, $has_many_class->__relationship_installer(%args, cro_base_class => $cro_base_class));
314 0           $has_many_class->delay_until_class_is_available
315             ($args{class}, $has_many_class->__inverse_relationship_finder(%args, cro_base_class => $cro_base_class));
316              
317             }
318              
319             sub __relationship_installer {
320 0     0     my $has_many_class = shift;
321 0           my %args = @_;
322             return sub {
323 0 0   0     if ($DEBUG > 1) {
324 0           print STDERR __PACKAGE__ . ':' . __LINE__ . " - in HasMany setup callback\n";
325             }
326 0           my $rel = Class::ReluctantORM::Relationship::HasMany->new();
327 0           $rel->method_name($args{method_name});
328 0           $rel->linked_class($args{class});
329 0           $rel->linking_class($args{cro_base_class});
330 0           $rel->local_key_fields($args{cro_base_class}->field_name(@{$args{local_key}}));
  0            
331 0           $rel->remote_key_fields($args{class}->field_name(@{$args{remote_key}}));
  0            
332              
333 0           install_method($args{cro_base_class}, $rel->method_name, $rel->__make_has_many_accessor());
334 0           install_method($args{cro_base_class}, 'fetch_' . $rel->method_name, $rel->__make_has_many_fetch_accessor());
335 0           $rel->_install_search_by_with_methods();
336              
337 0           my @args_copy = map { ($_, $args{$_} ) } grep { $_ ne 'cro_base_class' } keys %args;
  0            
  0            
338 0           $rel->_original_args_arrayref(\@args_copy);
339              
340 0           $args{cro_base_class}->register_relationship($rel);
341 0           };
342             }
343              
344             sub __inverse_relationship_finder {
345 0     0     my $has_many_class = shift;
346 0           my %args = @_;
347             return sub {
348 0     0     my $cro_local_class = $args{cro_base_class};
349 0           my $cro_remote_class = $args{class};
350 0           my $local_relname = $args{method_name};
351 0           my $local_rel = $cro_local_class->relationships($local_relname);
352 0 0 0       unless ($local_rel && $local_rel->is_has_many) { return; }
  0            
353 0 0         if ($local_rel->inverse_relationship()) {
354             # Assume we already found it
355 0           return;
356             }
357              
358             # List the has_one relationships on the linked class
359             # that point to this class
360 0           my @remote_has_one_rels =
361 0           grep { $_->linked_class eq $cro_local_class }
362 0           grep { $_->is_has_one } $cro_remote_class->relationships();
363 0 0         unless (@remote_has_one_rels) { return; }
  0            
364              
365 0           my @matches = ();
366 0           foreach my $remote_rel (@remote_has_one_rels) {
367              
368             # These are lists of keys that should be on the local table,
369             # and should be identical
370 0           my @remote_keys1 = $remote_rel->remote_key_fields();
371 0           my @local_keys1 = $local_rel->local_key_fields();
372 0 0         next unless (array_shallow_eq(\@remote_keys1, \@local_keys1));
373              
374             # These are lists of keys that should be on the remote table,
375             # and should be identical
376 0           my @remote_keys2 = $remote_rel->local_key_fields();
377 0           my @local_keys2 = $local_rel->remote_key_fields();
378 0 0         next unless (array_shallow_eq(\@remote_keys2, \@local_keys2));
379              
380 0           push @matches, $remote_rel;
381              
382             }
383              
384 0 0         if (@matches == 1) {
385 0           $local_rel->inverse_relationship($matches[0]);
386 0           $matches[0]->inverse_relationship($local_rel);
387             } else {
388             # Not touching that with a 10-foot pole
389             }
390              
391 0           };
392             }
393              
394              
395              
396             sub __make_has_many_accessor {
397 0     0     my $rel = shift;
398              
399             # Setup accessor
400             my $code = sub {
401 0     0     my $tb_obj = shift;
402 0           my $collection = $tb_obj->get($rel->method_name);
403 0 0         unless (defined $collection) {
404 0           $collection = Class::ReluctantORM::Collection::OneToMany->_new(
405             relationship => $rel,
406             linking_object => $tb_obj
407             );
408 0           $tb_obj->set($rel->method_name, $collection);
409             }
410             # New feature
411 0 0         return wantarray ? $collection->all() : $collection;
412 0           };
413 0           return $code;
414             }
415              
416             sub __make_has_many_fetch_accessor {
417 0     0     my $rel = shift;
418              
419             # Setup accessor
420             my $code = sub {
421 0     0     my $tb_obj = shift;
422 0           my $collection = $tb_obj->get($rel->method_name);
423 0 0         unless (defined $collection) {
424 0           $collection = Class::ReluctantORM::Collection::OneToMany->_new(
425             relationship => $rel,
426             linking_object => $tb_obj
427             );
428 0           $tb_obj->set($rel->method_name, $collection);
429             }
430 0           $collection->depopulate();
431 0           $collection->fetch_all();
432              
433             # New feature
434 0 0         return wantarray ? $collection->all() : $collection;
435 0           };
436 0           return $code;
437             }
438              
439              
440             #=============================================================================#
441             #=============================================================================#
442             # Collection Subclass
443             #=============================================================================#
444             #=============================================================================#
445              
446             package Class::ReluctantORM::Collection::OneToMany;
447 1     1   10 use strict;
  1         2  
  1         23  
448 1     1   3 use warnings;
  1         3  
  1         37  
449              
450 1     1   5 use Scalar::Util qw(blessed);
  1         2  
  1         38  
451 1     1   4 use Data::Dumper;
  1         4  
  1         40  
452 1     1   5 use Class::ReluctantORM::SQL::Aliases;
  1         1  
  1         102  
453 1     1   5 use Class::ReluctantORM::Utilities qw(nz check_args);
  1         2  
  1         37  
454 1     1   5 use base 'Class::ReluctantORM::Collection';
  1         2  
  1         96  
455 1     1   4 use Scalar::Util qw(weaken);
  1         1  
  1         2009  
456             our $DEBUG = 0;
457              
458             my %COLLECTION_REGISTRY_BY_RELATION;
459              
460 0     0     sub rel { return shift->{relationship}; }
461              
462             sub _new {
463 0     0     my ($class, %args) = @_;
464 0           foreach my $f (qw(master_class master_key_name master_key_value child_key_name child_class) ) {
465 0 0         if (exists $args{$f}) { Class::ReluctantORM::Exception::Call::Deprecated->croak("May not use param $f for Collection::OneToMany::_new in 0.4 code"); }
  0            
466             }
467 0           foreach my $f (qw(relationship linking_object)) {
468 0 0         unless (exists $args{$f}) { Class::ReluctantORM::Exception::Param::Missing->croak(param => $f); }
  0            
469             }
470              
471 0           my $self = bless \%args, $class;
472 0           weaken($self->{linking_object});
473              
474 0 0         if ($args{children}) {
475 0           $self->{_children} = $args{children};
476 0           $self->{_populated} = 1;
477 0           $self->{_count} = scalar @{$args{children}};
  0            
478             } else {
479 0           $self->{_populated} = 0;
480 0           $self->{_count} = undef;
481 0           $self->{_children} = [];
482             }
483              
484             # Add to collection registry so that we can find other collections
485             # when we need to do a global remove
486 0   0       $COLLECTION_REGISTRY_BY_RELATION{$args{relationship}} ||= [];
487 0           push @{$COLLECTION_REGISTRY_BY_RELATION{$args{relationship}}}, $self;
  0            
488 0           weaken($COLLECTION_REGISTRY_BY_RELATION{$args{relationship}}->[-1]);
489              
490 0           return $self;
491             }
492              
493             sub __list_collections_on_relation {
494 0     0     my $collection = shift;
495 0           my $rel = $collection->rel();
496 0           my @colls = @{$COLLECTION_REGISTRY_BY_RELATION{$rel}}; # Hash lookup by memory address
  0            
497 0           return grep { defined($_) } @colls; # may not be defined because it was weakened
  0            
498             }
499              
500             sub all_items {
501 0     0     my $self = shift;
502 0 0         if ($self->is_populated) {
503 0           return @{$self->{_children}};
  0            
504             } else {
505 0           Class::ReluctantORM::Exception::Data::FetchRequired->croak(called => 'all_items', call_instead => 'fetch_all', fetch_locations => [ $self->linking_object->all_origin_traces ]);
506             }
507             }
508              
509 0     0     sub all { goto &all_items; }
510              
511             sub _check_correct_child_class {
512 0     0     my ($self, $object) = @_;
513 0 0 0       unless (blessed($object) && $object->isa($self->rel->linked_class)) {
514 0           Class::ReluctantORM::Exception::Data::WrongType->croak(param => 'object', expected => $self->rel->linked_class, frames => 2);
515             }
516             }
517              
518 0     0     sub is_populated { return shift->{_populated}; }
519             sub depopulate {
520 0     0     my $self = shift;
521 0           $self->{_populated} = 0;
522 0           $self->{_count} = undef;
523 0           $self->{_children} = [];
524             }
525              
526             sub count {
527 0     0     my $self = shift;
528 0 0 0       if ($self->is_populated || defined($self->{_count})) {
529 0           return $self->{_count};
530             } else {
531 0           Class::ReluctantORM::Exception::Data::FetchRequired->croak(called => 'count', call_instead => 'fetch_count', fetch_locations => [ $self->linking_object->all_origin_traces ]);
532             }
533             }
534              
535             sub fetch_count {
536 0     0     my $self = shift;
537              
538 0           my $field = $self->rel->linked_class->first_primary_key_field();
539 0           my $method = 'count_of_' . $field;
540              
541             # Rely on aggregate mechanism
542 0           $self->{_count} = $self->$method;
543 0           return $self->{_count};
544             }
545              
546             # Provides where and execargs options for a fetchdeep on the linked class
547             #
548             sub __make_link_where {
549 0     0     my $collection = shift;
550 0   0       my $configure_for_join = shift || 0;
551 0           my $rel = $collection->rel;
552              
553 0           my @where = ();
554 0           my @execargs = ();
555              
556 0           my @remote_key_cols = $rel->remote_key_columns();
557 0           my @local_key_cols = $rel->local_key_columns();
558              
559 0           foreach my $index (0..$#local_key_cols) {
560 0           my $remote_column_name = $remote_key_cols[$index];
561 0           my $local_field = $rel->linked_class->field_name($local_key_cols[$index]);
562              
563 0           my $crit;
564 0 0         if ($configure_for_join) {
565 0           $crit = 'MACRO__child__' . $rel->method_name() . '__.' . $remote_column_name . ' = ?';
566             } else {
567 0           $crit = $remote_column_name . ' = ?';
568             }
569              
570 0           push @where, $crit;
571 0           push @execargs, $collection->linking_object->raw_field_value($local_field);
572             }
573 0           return (where => (join ' AND ', @where), execargs => \@execargs);
574             }
575              
576             sub __make_link_crit {
577 0     0     my $collection = shift;
578 0           my $configure_for_join = shift;
579 0           my %where_args = $collection->__make_link_where($configure_for_join);
580 0           my $driver = $collection->rel->linked_class->driver();
581 0           my $where = $driver->parse_where($where_args{where});
582 0           $where->bind_params(@{$where_args{execargs}});
  0            
583 0           return $where->root_criterion();
584             }
585              
586             sub __hm_set_keys_on_children_from_parent {
587 0     0     my $collection = shift;
588 0           my $child_ref = shift; # May pass arrayref here to only work on a few childrens
589 0 0         my @children = $child_ref ? @$child_ref : $collection->all();
590              
591 0           my $parent = $collection->linking_object();
592 0           my $rel = $collection->rel();
593 0           my %parent_key2child_key;
594 0           my @parent_keys = $rel->local_key_fields();
595 0           my @child_keys = $rel->remote_key_fields();
596 0           @parent_key2child_key{@parent_keys} = @child_keys;
597              
598 0           foreach my $child (@children) {
599 0           foreach my $parent_key_field (@parent_keys) {
600 0           my $child_key_field = $parent_key2child_key{$parent_key_field};
601 0           $child->raw_field_value($child_key_field, $parent->raw_field_value($parent_key_field));
602             }
603             }
604             }
605              
606             sub __hm_clear_keys_on_child {
607 0     0     my $collection = shift;
608 0           my $child = shift;
609              
610 0           my $rel = $collection->rel();
611 0           my %parent_key2child_key;
612 0           my @parent_keys = $rel->local_key_fields();
613 0           my @child_keys = $rel->remote_key_fields();
614 0           @parent_key2child_key{@parent_keys} = @child_keys;
615              
616 0           foreach my $parent_key_field (@parent_keys) {
617 0           my $child_key_field = $parent_key2child_key{$parent_key_field};
618 0           $child->raw_field_value($child_key_field, undef);
619             }
620             }
621              
622              
623             sub fetch_all {
624 0     0     my $self = shift;
625              
626 0           my %where_args = $self->__make_link_where(0);
627 0           my $child_class = $self->rel->linked_class();
628 0           my @children = $child_class->search(%where_args);
629              
630 0           $self->linking_object->capture_origin();
631              
632 0           $self->{_children} = \@children;
633 0           $self->{_populated} = 1;
634 0           $self->{_count} = scalar @children;
635 0           return @children;
636             }
637              
638             sub fetch_deep {
639 0     0     my $self = shift;
640 0           my %args = check_args
641             (
642             args => \@_,
643             required => [ qw(with) ], # As of CRO 0.5, no where, limit, or ordering permitted
644             );
645              
646 0           my %where_args = $self->__make_link_where(0);
647 0           my $child_class = $self->rel->linked_class();
648 0           my @children = $child_class->search_deep(%where_args, with => $args{with});
649              
650 0           $self->linking_object->capture_origin();
651              
652 0           $self->{_children} = \@children;
653 0           $self->{_populated} = 1;
654 0           $self->{_count} = scalar @children;
655 0           return @children;
656              
657             }
658              
659             # Note: AUTOLOAD defined in Collection base class
660             sub __setup_aggregate_autoload {
661 0     0     my ($self1, $AUTOLOAD, $method, $args, $agg_type, $agg_field) = @_;
662              
663 0           my $linked_class = $self1->rel->linked_class;
664              
665             # Generate a coderef
666             my $code = sub {
667 0     0     my $self = shift;
668 0           my %args = @_;
669 0           my %where_args = $self->__make_link_where(0);
670              
671             # Append args
672 0   0       $where_args{where} .= ' AND ' . ($args{where} || '1=1');
673 0 0         push @{$where_args{execargs}}, @{$args{execargs} || []};
  0            
  0            
674              
675             # Use aggregate method defined by child class
676 0           return $linked_class->$method(%where_args);
677 0           };
678              
679             # Don't install coderef in symbol table
680             # The name of this will vary based on the classes linked
681 0           $code->($self1, @$args);
682             }
683              
684             =for devnotes
685              
686             =head2 $collection->_set_contents(@children);
687              
688             Assuming you know what you are doing, this method replaces the in-memory guts of the collection. The populated flag is set to true, and the count is set to the new count, but keys are not updated, dirtiness is not changed, and no db activity occurs.
689              
690             =cut
691              
692             sub _set_contents {
693 0     0     my $self = shift;
694 0           my @children = @_;
695              
696 0           $self->{_children} = \@children;
697 0           $self->{_populated} = 1;
698 0           $self->{_count} = scalar @children;
699              
700             }
701              
702              
703             =head2 $collection->attach($child);
704              
705             Attach the child object to the parent in memory, and remove it from
706             any other collections on the same relationship.
707              
708             For HasMany collections, this sets the keys in the child. If the
709             collection is populated, adds the child to the in-memory collection
710             and increments the count.
711              
712             The child is now dirty. No database activity occurs. To attach
713             and immediately commit the change, use $collection->add().
714              
715             =cut
716              
717             sub attach {
718 0     0     my ($collection, $child) = @_;
719 0           $collection->_check_correct_child_class($child);
720              
721 0           $collection->__remove_from_from_all_related_collections($child);
722              
723             # Set keys in child object
724 0           $collection->__hm_set_keys_on_children_from_parent([$child]);
725              
726             # If populated, adjust the collection
727 0 0         if ($collection->is_populated()) {
728 0           push @{$collection->{_children}}, $child;
  0            
729 0           $collection->{_count}++;
730             }
731             }
732              
733             =head2 $collection->add($child);
734              
735             Removes the child object from all other collections based on this relationship, then
736             attaches the child object to the collection in memory, and finally saves the child
737             object to the database with its new keys.
738              
739             The child is briefly dirty during this operation, but ends up non-dirty.
740              
741             =cut
742              
743             sub add {
744 0     0     my ($collection, $child) = @_;
745 0           $collection->attach( $child );
746 0           $child->save();
747             }
748              
749              
750             =head2 $collection->remove($child);
751              
752             If the collection is populated, remove the child from the in-memory collection.
753              
754             Regardless of whether the collection is populated, clear the foreign keys on the child.
755              
756             The child is marked dirty. No database activity occurs.
757              
758             =cut
759              
760             sub remove {
761 0     0     my ($collection, $child) = @_;
762              
763 0 0         if ($collection->is_populated()) {
764 0           $collection->{_children} =
765 0           [ grep { nz($_->id,0) ne nz($child->id,0) } @{$collection->{_children}} ];
  0            
766 0           $collection->{_count} = @{$collection->{_children}};
  0            
767             }
768              
769 0           $collection->__hm_clear_keys_on_child($child);
770              
771 0           return $child;
772             }
773              
774             sub __remove_from_from_all_related_collections {
775 0     0     my $collection = shift;
776 0           my $child = shift;
777 0           my @sisters = $collection->__list_collections_on_relation();
778 0           foreach my $coll (@sisters) {
779 0           $coll->remove($child);
780             }
781             }
782              
783             =head2 $collection->delete($child);
784              
785             Removes the child object from the collection in memory, and deletes
786             the child object from the database.
787              
788             =cut
789              
790             sub delete {
791 0     0     my ($collection, $child) = @_;
792              
793             # Not sure this is needed....
794 0 0         unless ($collection->is_populated) {
795 0           Class::ReluctantORM::Exception::Data::FetchRequired->croak(called => 'delete', call_instead => 'fetch_all or delete_where', fetch_locations => [ $collection->linking_object->all_origin_traces ]);
796             }
797              
798 0           $collection->_check_correct_child_class($child);
799              
800 0 0         unless ($collection->is_present($child)) { return; }
  0            
801              
802             # Remove collection - should this remove from all collections?
803 0           $collection->remove($child);
804              
805             # Delete the child
806 0           $child->delete();
807              
808 0           return;
809             }
810              
811             =head2 $collection->delete_where(where => $str, execargs => \@args);
812              
813             =head2 $collection->delete_where(where => $where_obj);
814              
815             Executes a DELETE against the child table using the provided WHERE clause. A set of criteria is added to ensure that only records associated with the parent record.
816              
817             The where argusment may be either a SQL string or a SQL::Where object.
818              
819             =cut
820              
821             sub delete_where {
822 0     0     my $collection = shift;
823 0 0         if (@_ == 1) { @_ = (where => $_[0]); }
  0            
824 0 0         if (@_ % 2) { Class::ReluctantORM::Exception::Param::ExpectedHash->croak(); }
  0            
825 0           my %args = @_;
826 0 0         unless (defined $args{where}) { Class::ReluctantORM::Exception::Param::Missing->croak(param => 'where'); }
  0            
827              
828 0           my $where;
829 0 0 0       if (blessed($args{where}) && $args{where}->isa(Where())) {
830 0           $where = $args{where};
831             } else {
832 0           my $driver = $collection->rel->linked_class->driver();
833 0           $where = $driver->parse_where($args{where});
834 0           $where->bind_params(@{$args{execargs}});
  0            
835             }
836              
837 0           my $link_crit = $collection->__make_link_crit(0);
838 0           $where = Where->new(
839             Criterion->new('AND',
840             $where->root_criterion(),
841             $link_crit),
842             );
843              
844 0           my $sql = SQL->new('DELETE');
845 0           $sql->table($collection->rel->remote_sql_table());
846 0           $sql->where($where);
847              
848 0           $collection->linking_object->driver->run_sql($sql);
849 0           $collection->depopulate();
850              
851 0           return;
852             }
853              
854              
855             1;
856              
857              
858              
859              
860