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