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
|
|
|
|
|
|
|
|