File Coverage

lib/UR/Object/Type/AccessorWriter.pm
Criterion Covered Total %
statement 756 1077 70.1
branch 322 530 60.7
condition 92 191 48.1
subroutine 429 441 97.2
pod 0 13 0.0
total 1599 2252 71.0


line stmt bran cond sub pod time code
1              
2             package UR::Object::Type::AccessorWriter;
3              
4             package UR::Object::Type;
5              
6 266     4388670   1079 use strict;
  266         353  
  266         6868  
7 266     1248061   935 use warnings;
  266         348  
  266         9763  
8             require UR;
9             our $VERSION = "0.46"; # UR $VERSION;
10             #use warnings FATAL => 'all';
11              
12 266     745668   988 use Carp ();
  266         356  
  266         2900  
13 266     25406   849 use Sub::Name ();
  266         334  
  266         3175  
14 266     40724   868 use Sub::Install ();
  266         315  
  266         4156  
15 266     40724   937 use List::Util;
  266         334  
  266         35995  
16              
17             sub mk_rw_accessor {
18 59960     59960 0 67909 my ($self, $class_name, $accessor_name, $column_name, $property_name, $is_transient) = @_;
19 59960   33     87355 $property_name ||= $accessor_name;
20              
21 59960         92714 my $full_name = join( '::', $class_name, $accessor_name );
22             my $accessor = Sub::Name::subname $full_name => sub {
23 5856393 100   5856393   7622018 if (@_ > 1) {
        5893275      
        5856393      
        5856393      
        5856393      
        5856393      
        5856393      
        5910470      
        165489      
        5856393      
        10061374      
        14226027      
        14772551      
        10021046      
        8494795      
        14185699      
        5947957      
        5856393      
        5856393      
        5856393      
        5856393      
        6091238      
        6095564      
        5908596      
        5856393      
        5856393      
        5856393      
        5856393      
        6079359      
        5896721      
        5856393      
        10659142      
        10862042      
        6357972      
        5896721      
        11267824      
        11081715      
        6368675      
        5937049      
        11348480      
        1571344      
        5987817      
        5896721      
        926480      
        261430      
        5917080      
        5917080      
        5876752      
        11288183      
        5935394      
        6029844      
        11288183      
        7542986      
        10289745      
        663562      
        6116198      
        6015123      
        5948935      
        15542550      
        11100119      
        6358337      
        5911978      
        11031459      
        9182768      
        5856393      
        5856393      
        5856393      
        11267824      
        5868267      
        6279018      
        5914959      
        5896721      
        11308152      
        6123103      
        5856393      
        5856393      
        11198943      
        6174697      
        5856393      
        5856393      
        348269      
        5947489      
        5856393      
        5856393      
        11267824      
        5856393      
        5867439      
        5856393      
        5856393      
        5856393      
        5856393      
        5856393      
        5856393      
        5856393      
        6120127      
        5856393      
        11267824      
        5934119      
        5856393      
        11267824      
        5856393      
        5856393      
        5907243      
        5856393      
        11267824      
        12407087      
        16151904      
        11295608      
        16615224      
        4157166      
        6302779      
        11534534      
        10061374      
        5906943      
        6050749      
        7198374      
        6336188      
        6192944      
        5935910      
        5917672      
        4653639      
        10160943      
        8285359      
        7624364      
        7493449      
        7156277      
        7217981      
        10357070      
        6294737      
        10419637      
        10684993      
        16223375      
        7791629      
        11560621      
        6117025      
        6052739      
        10207608      
        10140023      
        15497076      
        14456373      
        15432477      
        15319824      
        11515023      
        6287594      
        6527077      
        6049751      
        10061374      
        6004832      
        5938644      
        11308152      
        10021046      
        11267824      
        10038690      
        5874037      
        11267824      
        11267824      
        5856393      
        5946692      
        5896721      
        6063754      
        5896721      
        5655658      
        7926063      
        6175959      
        6133850      
        11467455      
        5954132      
        11323409      
        5872346      
        11267824      
        7230330      
        6266633      
        5856393      
        19130302      
        5799211      
        570449      
        144948      
        44871      
        10450      
        10450      
        10450      
        10450      
        10450      
        10450      
        10450      
        10450      
24 11059         14250 my $old = $_[0]->{ $property_name };
25 11059         10302 my $new = $_[1];
26              
27             # The accessors may compare undef and an empty
28             # string. For speed, we turn warnings off rather
29             # than add extra code to make the warning disappear.
30 11059         8980 local $@;
31 266     25406   1114 my $different = eval { no warnings; $old ne $new };
  266         359  
  266         64158  
  11059         11584  
  11059         19666  
32 11059 100 66     26653 if ($different or $@ =~ m/has no overloaded magic/)
33             {
34 9616         12557 $_[0]->{ $property_name } = $new;
35 9616 100       19391 $_[0]->__signal_change__( $property_name, $old, $new ) unless $is_transient; # FIXME is $is_transient right here? Maybe is_volatile instead (if at all)?
36             }
37 11059         18498 return $new;
38             }
39 5845334         11041980 $_[0]->{ $property_name }
40 59960         338380 };
41              
42 59960 100       95224 if (_class_is_singleton($class_name)) {
43 2348         2340 my $basic_accessor = $accessor;
44             $accessor = sub {
45 19057     4194245   319477 shift->_singleton_object->$basic_accessor(@_);
46 2348         6561 };
47             }
48              
49             Sub::Install::reinstall_sub({
50 59960         178825 into => $class_name,
51             as => $accessor_name,
52             code => $accessor,
53             });
54              
55             }
56              
57             sub mk_ro_accessor {
58 7906     7906 0 12325 my ($self, $class_name, $accessor_name, $column_name, $property_name) = @_;
59 7906   66     14411 $property_name ||= $accessor_name;
60              
61 7906         15799 my $full_name = join( '::', $class_name, $accessor_name );
62             my $accessor = Sub::Name::subname $full_name => sub {
63 4448369 100   4463042   5889855 if (@_ > 1) {
        4463042      
        5662324      
        5249034      
        4543647      
        4448369      
        4448369      
        8579932      
        9132651      
        10640180      
        7597649      
        5852770      
        9605729      
        8846873      
        10331585      
        7163769      
        6147977      
        4950946      
        8756277      
        5240257      
        7762353      
        7187811      
64 10         18 my $old = $_[0]->{ $property_name};
65 10         14 my $new = $_[1];
66              
67 10         11 my $different;
68 10         12 my $exception = do {
69 10         11 local $@;
70 266     266   1229 $different = eval { no warnings; $old ne $new };
  266         375  
  266         1189078  
  10         13  
  10         20  
71 10         16 $@;
72             };
73 10 100 66     55 if ($different or $exception =~ m/has no overloaded magic/)
74             {
75 1         12 Carp::croak("Cannot change read-only property $accessor_name for class $class_name!"
76             . " Failed to update " . $_[0]->__display_name__ . " property: $property_name from $old to $new");
77             }
78 9         19 return $new;
79             }
80 4448359         7560695 return $_[0]->{ $property_name };
81 7906         51783 };
82              
83 7906 100       14861 if (_class_is_singleton($class_name)) {
84 1         2 my $basic_accessor = $accessor;
85             $accessor = sub {
86 3     1752301   279 shift->_singleton_object->$basic_accessor(@_);
87 1         7 };
88             }
89              
90             Sub::Install::reinstall_sub({
91 7906         29000 into => $class_name,
92             as => $accessor_name,
93             code => $accessor,
94             });
95              
96             }
97              
98             sub _class_is_singleton {
99 67866     910819   61226 my $class_name = shift;
100 67866         51110 return grep { $_->isa('UR::Singleton') } @{ $class_name->__meta__->{is} };
  69030         333818  
  67866         146700  
101             }
102              
103             sub mk_id_based_flex_accessor {
104 0     831610 0 0 my ($self, $class_name, $accessor_name, $id_by, $r_class_name, $where, $id_class_by) = @_;
105              
106 0 0       0 unless (ref($id_by)) {
107 0         0 $id_by = [ $id_by ];
108             }
109              
110 0         0 my $id_resolver;
111             my $id_decomposer;
112 0         0 my @id;
113 0         0 my $id;
114 0         0 my $full_name = join( '::', $class_name, $accessor_name );
115 0         0 my $concrete_r_class_name = $r_class_name;
116             my $accessor = Sub::Name::subname $full_name => sub {
117 0     764673   0 my $self = shift;
118 0 0       0 if (@_ == 1) {
119             # This one is to support syntax like this
120             # $cd->artist($different_artist);
121             # to switch which artist object this cd points to
122 0         0 my $object_value = shift;
123 0 0 0     0 if ($id_class_by and not ref $object_value) {
124             # when we have an id-class-by accessor and get a primitive, store it as a UR::Value
125 0         0 $object_value = UR::Value->get($object_value);
126             }
127 0 0       0 if (defined $object_value) {
128 0 0 0     0 if ($id_class_by) {
    0          
129 0 0       0 $concrete_r_class_name = ($object_value->can('class') ? $object_value->class : ref($object_value));
130 0         0 $id_decomposer = undef;
131 0         0 $id_resolver = undef;
132 0         0 $self->$id_class_by($concrete_r_class_name);
133             } elsif (! Scalar::Util::blessed($object_value) and ! $object_value->can('id')) {
134 0         0 Carp::croak("Can't call method \"id\" without a package or object reference. Expected an object as parameter to '$accessor_name', not the value '$object_value'");
135             }
136              
137 0         0 my $r_class_meta = do {
138 0         0 local $@;
139 0         0 eval { $concrete_r_class_name->__meta__ };
  0         0  
140             };
141 0 0       0 unless ($r_class_meta) {
142 0         0 Carp::croak("Can't get metadata for class $concrete_r_class_name. Is it a UR class?");
143             }
144              
145 0   0     0 $id_decomposer ||= $r_class_meta->get_composite_id_decomposer;
146 0         0 @id = $id_decomposer->($object_value->id);
147 0 0       0 if (@$id_by == 1) {
148 0         0 my $id_property_name = $id_by->[0];
149 0         0 $self->$id_property_name($object_value->id);
150             } else {
151 0         0 @id = $id_decomposer->($object_value->id);
152 0         0 Carp::croak("Cannot alter value for '$accessor_name' on $class_name: The passed-in object of type "
153             . $object_value->class . " has " . scalar(@id) . " id properties, but the accessor '$accessor_name' has "
154             . scalar(@$id_by) . " id_by properties");
155 0         0 for my $id_property_name (@$id_by) {
156 0         0 $self->$id_property_name(shift @id);
157             }
158             }
159             }
160             else {
161 0 0       0 if ($id_class_by) {
162 0         0 $self->$id_class_by(undef);
163             }
164 0         0 for my $id_property_name (@$id_by) {
165 0         0 $self->$id_property_name(undef);
166             }
167             }
168 0         0 return $object_value;
169             }
170             else {
171 0 0       0 if ($id_class_by) {
172 0         0 $concrete_r_class_name = $self->$id_class_by;
173 0         0 $id_decomposer = undef;
174 0         0 $id_resolver = undef;
175 0 0       0 return unless $concrete_r_class_name;
176             }
177 0 0       0 unless ($id_resolver) {
178 0         0 my $concrete_r_class_meta = UR::Object::Type->get($concrete_r_class_name);
179 0 0       0 unless ($concrete_r_class_meta) {
180 0         0 Carp::croak("Can't resolve value for '$accessor_name' on class $class_name id '".$self->id
181             . "': No class metadata for value '$concrete_r_class_name' referenced as property '$id_class_by'");
182             }
183 0         0 $id_resolver = $concrete_r_class_meta->get_composite_id_resolver;
184             }
185              
186             # eliminate the old map{} because of side effects with $_
187             # when the id_by property happens to be calculated
188             #@id = map { $self->$_ } @$id_by;
189 0         0 @id=();
190 0         0 for my $property_name (@$id_by) { # no implicit topic
191 0         0 my $value = $self->$property_name; # scalar context
192 0         0 push @id, $value;
193             }
194              
195 0         0 $id = $id_resolver->(@id);
196 0 0       0 return if not defined $id;
197 0 0       0 if ($concrete_r_class_name eq 'UR::Object') {
198 0         0 Carp::carp("Querying by using UR::Object class is deprecated.");
199             }
200              
201 0 0       0 if ($concrete_r_class_name->isa("UR::Value")) {
202 0         0 return $id;
203             }
204             else {
205 0 0 0     0 if (@_ || $where) {
206             # There were additional params passed in
207 0         0 return $concrete_r_class_name->get(id => $id, @_, @$where);
208             } else {
209 0         0 return $concrete_r_class_name->get($id);
210             }
211             }
212             }
213 0         0 };
214              
215 0         0 Sub::Install::reinstall_sub({
216             into => $class_name,
217             as => $accessor_name,
218             code => $accessor,
219             });
220             }
221              
222             sub mk_id_based_object_accessor {
223 4684     434758 0 12824 my ($self, $class_name, $accessor_name, $id_by, $r_class_name, $where, $id_class_by) = @_;
224              
225 4684 50       11092 unless (ref($id_by)) {
226 0         0 $id_by = [ $id_by ];
227             }
228              
229 4684         5158 my $id_resolver;
230             my $id_decomposer;
231 0         0 my @id;
232 0         0 my $id;
233 4684         10304 my $full_name = join( '::', $class_name, $accessor_name );
234 4684         5547 my $concrete_r_class_name = $r_class_name;
235             my $accessor = Sub::Name::subname $full_name => sub {
236 516548     516548   432346 my $self = shift;
        516548      
        516548      
        516548      
        516548      
        999695      
        1034971      
        1225331      
        777430      
        1188585      
        761757      
        1324494      
        619471      
        128256      
237 516548 100       720376 if (@_ == 1) {
238             # This one is to support syntax like this
239             # $cd->artist($different_artist);
240             # to switch which artist object this cd points to
241 87         111 my $object_value = shift;
242 87 100       190 if (defined $object_value) {
243 86 100 66     448 if ($id_class_by) {
    100          
244 36 50       127 $concrete_r_class_name = ($object_value->can('class') ? $object_value->class : ref($object_value));
245 36         56 $id_decomposer = undef;
246 36         38 $id_resolver = undef;
247 36         92 $self->$id_class_by($concrete_r_class_name);
248             } elsif (! Scalar::Util::blessed($object_value) and ! $object_value->can('id')) {
249 1         274 Carp::croak("Can't call method \"id\" without a package or object reference. Expected an object as parameter to '$accessor_name', not the value '$object_value'");
250             }
251              
252 85         109 my $r_class_meta = do {
253 85         109 local $@;
254 85         125 eval { $concrete_r_class_name->__meta__ };
  85         316  
255             };
256 85 50       211 unless ($r_class_meta) {
257 0         0 Carp::croak("Can't get metadata for class $concrete_r_class_name. Is it a UR class?");
258             }
259              
260 85   66     507 $id_decomposer ||= $r_class_meta->get_composite_id_decomposer;
261 85         231 @id = $id_decomposer->($object_value->id);
262 85 50       242 if (@$id_by == 1) {
263 85         136 my $id_property_name = $id_by->[0];
264 85         202 $self->$id_property_name($object_value->id);
265             } else {
266 0         0 @id = $id_decomposer->($object_value->id);
267 0         0 Carp::croak("Cannot alter value for '$accessor_name' on $class_name: The passed-in object of type "
268             . $object_value->class . " has " . scalar(@id) . " id properties, but the accessor '$accessor_name' has "
269             . scalar(@$id_by) . " id_by properties");
270 0         0 for my $id_property_name (@$id_by) {
271 0         0 $self->$id_property_name(shift @id);
272             }
273             }
274             }
275             else {
276 1 50       4 if ($id_class_by) {
277 0         0 $self->$id_class_by(undef);
278             }
279 1         2 for my $id_property_name (@$id_by) {
280 1         4 $self->$id_property_name(undef);
281             }
282             }
283 86         195 return $object_value;
284             }
285             else {
286 516461 100       726614 if ($id_class_by) {
287 363         760 $concrete_r_class_name = $self->$id_class_by;
288 363         394 $id_decomposer = undef;
289 363         330 $id_resolver = undef;
290 363 50       543 return unless $concrete_r_class_name;
291             }
292 516461 100       719569 unless ($id_resolver) {
293 1147         3848 my $concrete_r_class_meta = UR::Object::Type->get($concrete_r_class_name);
294 1147 100       2391 unless ($concrete_r_class_meta) {
295 1         8 Carp::croak("Can't resolve value for '$accessor_name' on class $class_name id '".$self->id
296             . "': No class metadata for value '$concrete_r_class_name' referenced as property '$id_class_by'");
297             }
298 1146         15370 $id_resolver = $concrete_r_class_meta->get_composite_id_resolver;
299             }
300              
301 516460         626276 @id=();
302 516460         576499 for my $property_name (@$id_by) { # no implicit topic
303 534360         901104 my $value = $self->$property_name; # scalar context
304 534360         710420 push @id, $value;
305             }
306              
307 516460 100       804579 $id = @id > 1
308             ? $id_resolver->(@id)
309             : $id[0];
310              
311 516460 100       755219 return if not defined $id;
312 516409 50       752975 if ($concrete_r_class_name eq 'UR::Object') {
313 0         0 Carp::carp("Querying by using UR::Object class is deprecated.");
314             }
315 516409 50 33     1464172 if (@_ || $where) {
316             # There were additional params passed in
317 0         0 return $concrete_r_class_name->get(id => $id, @_, @$where);
318             } else {
319 516409         1114824 return $concrete_r_class_name->get($id);
320             }
321             }
322 4684         50544 };
323              
324 4684         21069 Sub::Install::reinstall_sub({
325             into => $class_name,
326             as => $accessor_name,
327             code => $accessor,
328             });
329             }
330              
331              
332             sub _resolve_bridge_logic_for_indirect_property {
333 1800     19602   3935 my ($ur_object_type, $class_name, $accessor_name, $via, $to, $where) = @_;
334              
335             my $bridge_collector = sub {
336 863501     873939   604972 my $self = shift;
337 863501         1573390 my @results = $self->$via(@$where);
338             # Indirect has one properties must return a single undef value for an empty result, even in list context.
339 863501 50 66     2745896 return if @results == 1 and not defined $results[0];
340 863501         1029414 return @results;
341 1800         7783 };
342             my $bridge_crosser = sub {
343 863565     867392   631064 my $bridges = shift;
344 863565         813495 return map { $_->$to(@_) } @$bridges;
  866274         1377891  
345 1800         5183 };
346              
347 1800 100       5161 return($bridge_collector, $bridge_crosser) if ($UR::Object::Type::bootstrapping);
348              
349             # bail out and use the default subs if any of these fail
350 736         1081 my ($my_class_meta, $my_property_meta, $via_property_meta, $to_property_meta);
351              
352 736         2878 $my_class_meta = $class_name->__meta__;
353 736 50       3634 $my_property_meta = $my_class_meta->property_meta_for_name($accessor_name) if ($my_class_meta);
354 736 50       2829 $via_property_meta = $my_class_meta->property_meta_for_name($via) if ($my_class_meta);
355 736 50       4147 $to_property_meta = $my_property_meta->to_property_meta() if ($my_property_meta);
356              
357 736 100 33     6000 if (! $my_class_meta || ! $my_property_meta || ! $via_property_meta || ! $to_property_meta) {
      33        
      66        
358             # Something didn't link right, use the default methods
359 25         77 return ($bridge_collector, $bridge_crosser);
360             }
361              
362 711 50 66     1586 if ($my_property_meta->is_delegated and $my_property_meta->is_many
      100        
      100        
      66        
      66        
363             and $via_property_meta->is_many and $via_property_meta->reverse_as
364             and $via_property_meta->data_type and $via_property_meta->data_type->isa('UR::Object')
365             ) {
366 40         106 my $bridge_class = $via_property_meta->data_type;
367              
368 40         81 my @via_join_properties = do {
369 40         60 local $@;
370 40         95 eval { $via_property_meta->get_property_name_pairs_for_join };
  40         159  
371             };
372 40 100       124 if (! @via_join_properties) {
373             # this can happen if the properties aren't linked together as expected.
374             # For example, a property involved in a many-to-many relationship, but is
375             # defined as a one-to-many with reverse_as.
376 1         4 return ($bridge_collector, $bridge_crosser);
377             }
378              
379 39         58 my (@my_join_properties,@their_join_properties);
380 39         141 for (my $i = 0; $i < @via_join_properties; $i++) {
381 40         57 ($my_join_properties[$i], $their_join_properties[$i]) = @{ $via_join_properties[$i] };
  40         177  
382             }
383              
384 39         70 my(@where_properties, @where_values, %bridge_meta_params);
385 39 50 33     178 if ($where or $via_property_meta->where) {
386 39         55 my @collected_where;
387 39 50       149 @collected_where = @$where if ($where);
388 39 100       123 push @collected_where, @{ $via_property_meta->where } if ($via_property_meta->where);
  5         13  
389 39         229 while (@collected_where) {
390 27         42 my $where_property = shift @collected_where;
391 27         38 my $where_value = shift @collected_where;
392              
393 27 100       80 if (UR::BoolExpr::Util::is_meta_param($where_property)) {
394 9         39 $bridge_meta_params{$where_property} = $where_value;
395              
396             } else {
397 18 0 33     63 if (ref($where_value) eq 'HASH' and $where_value->{'operator'}) {
398 0         0 $where_property .= ' ' .$where_value->{'operator'};
399 0         0 $where_value = $where_value->{'value'};
400             }
401 18         34 push @where_properties, $where_property;
402 18         49 push @where_values, $where_value;
403             }
404             }
405             }
406              
407 39         240 my $bridge_template = UR::BoolExpr::Template->resolve($bridge_class, @their_join_properties, @where_properties, %bridge_meta_params);
408              
409             $bridge_collector = sub {
410 238     5584   285 my $self = shift;
411 238         360 my @my_values = map { $self->$_} @my_join_properties;
  240         852  
412 238         719 my $bx = $bridge_template->get_rule_for_values(@my_values, @where_values);
413 238         777 return $bridge_class->get($bx);
414 39         263 };
415              
416 39 100 100     236 if ($to_property_meta->is_delegated
417             and
418             my $doubly_deledated_bridge_crosser = _resolve_bridge_crosser_for_doubly_delegated_property($to_property_meta, \%bridge_meta_params)
419             ) {
420 16         45 $bridge_crosser = $doubly_deledated_bridge_crosser;
421             }
422             }
423 710         1971 return ($bridge_collector, $bridge_crosser);
424             }
425              
426             sub _make_results_sorter_for_doubly_delegated_bridge_crosser {
427 3     2133   5 my($bridges, $bridge_linker, $results_linker) = @_;
428              
429 3         3 my $rank = 0;
430 3         6 my %bridge_rankings = map { $bridge_linker->() => $rank++ } @$bridges;
  15         17  
431             return sub {
432 3     2133   5 my $results = shift;
433              
434 15         26 return map { $_->[1] }
435 18         24 sort { $bridge_rankings{ $a->[0] } <=> $bridge_rankings{ $b->[0] } }
436 3         6 map { [ $results_linker->(), $_ ] }
  15         16  
437             @$results;
438 3         27 };
439             }
440              
441             sub _resolve_bridge_crosser_for_doubly_delegated_property {
442 19     19   35 my($to_property_meta, $bridge_meta_params) = @_;
443             # This property's value is doubly delegated. The simple thing to
444             # do is to collect the bridge objects, then call the second
445             # delegation method on each bridge in a loop to collect the final
446             # results, which may trigger one query per result. Depending on
447             # the type of delegation, the final results can be collected with
448             # one query
449              
450 19         35 my($result_class_resolver, $bridge_linking_properties, $final_result_property_name, $result_filtering_property);
451 19 100       51 if ($to_property_meta->via) {
    100          
    50          
452             # bridges through another via-to property
453 4         12 my $second_via_property_meta = $to_property_meta->via_property_meta;
454 4         13 my $final_class_name = $second_via_property_meta->data_type;
455 4 50 33     55 if ($final_class_name and $final_class_name ne 'UR::Value' and $final_class_name->isa('UR::Object')) {
      33        
456 4 100       15 if ( 1 == (my @via2_join_properties = $second_via_property_meta->get_property_name_pairs_for_join)) {
457 2         5 $bridge_linking_properties = [ $via2_join_properties[0]->[0] ];
458 2         4 $result_filtering_property = $via2_join_properties[0]->[1];
459 2     19   16 $result_class_resolver = sub { $final_class_name };
  19         16  
460              
461 2         9 $final_result_property_name = $to_property_meta->to;
462             }
463             }
464              
465             } elsif ($to_property_meta->id_by) {
466 13         31 $bridge_linking_properties = $to_property_meta->id_by;
467 13         30 $result_filtering_property = 'id';
468 13 100       217 if ($to_property_meta->id_class_by) {
469             # Bridging through an 'id_class_by' property
470             # bucket the bridge items by the result class and do a get for
471             # each of those classes with a listref of IDs
472 5         12 my $result_class_resolving_property = $to_property_meta->id_class_by;
473 5     71   21 $result_class_resolver = sub { shift->$result_class_resolving_property };
  71         108  
474              
475             } else {
476             # Bridging through a regular id-by property
477 8         27 my $result_class = $to_property_meta->data_type;
478 8     71   47 $result_class_resolver = sub { $result_class };
  71         89  
479             }
480              
481             } elsif ($to_property_meta->reverse_as) {
482 2 100       8 if (1 == (my @reverse_as_join_properties = $to_property_meta->get_property_name_pairs_for_join)) {
483 1         2 $bridge_linking_properties = [ map { $_->[0] } @reverse_as_join_properties ];
  1         3  
484 1         3 $result_filtering_property = $reverse_as_join_properties[0]->[1];
485              
486 1         2 my $result_class = $to_property_meta->data_type;
487 1     4   4 $result_class_resolver = sub { $result_class };
  4         5  
488             }
489             }
490              
491 19 100       74 if ($result_class_resolver) {
492 16         21 my $linking_id_value_for_bridge = do {
493 16         23 my %composite_id_resolver_for_class;
494              
495             sub {
496 90     90   83 my $bridge = shift;
497 90         110 my @id = map { $bridge->$_ } @$bridge_linking_properties;
  90         163  
498              
499 90         126 my $result_class = $result_class_resolver->($bridge);
500 90   66     293 my $id_resolver = $composite_id_resolver_for_class{ $result_class }
501             ||= $result_class->__meta__->get_composite_id_resolver;
502              
503 90         169 return $id_resolver->(@id);
504 16         96 };
505             };
506              
507             return sub {
508 33     33   48 my $bridges = shift;
509 33         48 my %result_class_names_and_ids;
510              
511 33         66 foreach my $bridge ( @$bridges ) {
512 75         117 my $result_class = $result_class_resolver->($bridge);
513 75   100     277 $result_class_names_and_ids{$result_class} ||= [];
514              
515 75         130 my $id = $linking_id_value_for_bridge->($bridge);
516 75         70 push @{ $result_class_names_and_ids{ $result_class } }, $id;
  75         143  
517             }
518              
519 33         39 my @results;
520 33         85 foreach my $result_class ( keys %result_class_names_and_ids ) {
521 44 50       231 if($result_class->isa('UR::Value')) { #can't group queries together for UR::Values
522 0         0 push @results, map { $result_class->get($result_filtering_property => $_, @_) } @{$result_class_names_and_ids{$result_class}};
  0         0  
  0         0  
523             } else {
524 44         201 push @results, $result_class->get($result_filtering_property => $result_class_names_and_ids{$result_class}, @_);
525             }
526             }
527              
528 33 100 33     188 if ($bridge_meta_params->{'-order'} || $bridge_meta_params->{'-order_by'}) {
529             my $results_sorter = _make_results_sorter_for_doubly_delegated_bridge_crosser(
530             $bridges,
531 15         15 sub { return $linking_id_value_for_bridge->($_) },
532 3         27 sub { $_->id } );
  15         21  
533 3         15 @results = $results_sorter->(\@results);
534             }
535              
536 33 100       107 @results = map { $_->$final_result_property_name } @results if ($to_property_meta->via);
  9         17  
537 33         126 return @results;
538 16         164 };
539             }
540              
541 3         14 return;
542             }
543              
544             sub _is_assignment_value {
545             return (
546 7   33 7   91 @_ == 1
547             and not (ref($_[0]) and Scalar::Util::blessed($_[0]) and $_[0]->isa("UR::BoolExpr"))
548             );
549             }
550              
551             sub mk_indirect_ro_accessor {
552 18005     18005 0 23632 my ($ur_object_type, $class_name, $accessor_name, $via, $to, $where) = @_;
553 18005 100       28800 my @where = ($where ? @$where : ());
554 18005         28419 my $full_name = join( '::', $class_name, $accessor_name );
555 18005         19530 my $filterable_accessor_name = 'get_' . $accessor_name; # FIXME we need a better name for
556 18005         19577 my $filterable_full_name = join( '::', $class_name, $filterable_accessor_name );
557              
558             # This is part of an experimental refactoring of indirect accessors. The goal is to
559             # get rid of all the special cases inside of _resolve_bridge_logic_for_indirect_property()
560             # and do the right thing with the Join data
561 18005         13350 my (@collectors, @crossers);
562             my $accessor2 = Sub::Name::subname $full_name.'_new' => sub {
563 0     0   0 my $self = shift;
564 0 0 0     0 Carp::croak("Assignment value passed to read-only indirect accessor $accessor_name for class $class_name") if @_ and _is_assignment_value(@_);
565              
566 0 0       0 if ($class_name =~ m/^UR::/) {
567             # Some methods will recurse into here if called on a UR::* class (especially
568             # UR::BoolExpr), so do the dumb but safe thing
569             my $bridge_collector = sub {
570 0         0 my $self = shift;
571 0         0 my @results = $self->$via(@$where);
572             # Indirect has one properties must return a single undef value for an empty result, even in list context.
573 0 0 0     0 return if @results == 1 and not defined $results[0];
574 0         0 return @results;
575 0         0 };
576              
577             #TODO: move this crosser closure logic down and get rid of the closure
578 0         0 my @filter = @_;
579 0         0 my $bridge_crosser = sub { return map { $_->$to(@filter) } @_ };
  0         0  
  0         0  
580 0         0 my @bridges = $bridge_collector->($self);
581 0 0       0 return unless @bridges;
582 0 0       0 return $self->context_return(@bridges) if ($to eq '-filter');
583              
584 0         0 my @results = $bridge_crosser->(@bridges);
585 0         0 return $self->context_return(@results);
586             }
587              
588 0 0       0 unless (@collectors) {
589 0         0 require List::MoreUtils;
590              
591 0         0 my $prop_meta = $class_name->__meta__->property_meta_for_name($accessor_name);
592 0         0 my @join_list = $prop_meta->_resolve_join_chain();
593 0         0 foreach my $join ( @join_list ) {
594 0         0 my @source_property_names = @{$join->{source_property_names}};
  0         0  
595             my $collector = sub {
596 0 0       0 my @list = grep { defined && length } map { my $o = $_; map { $o->$_ } @source_property_names} @_;
  0         0  
  0         0  
  0         0  
  0         0  
597 0 0       0 return @list == 1 ? $list[0] : \@list;
598 0         0 };
599 0         0 push @collectors, $collector;
600              
601 0         0 my $foreign_class = $join->{foreign_class};
602 0         0 my $crosser;
603 0 0       0 if (! $foreign_class->isa('UR::Value')) {
604 0         0 my @foreign_property_names = @{$join->{foreign_property_names}};
  0         0  
605              
606             $crosser = sub { my @get_params = List::MoreUtils::pairwise
607 0         0 { $a => $b } @foreign_property_names, @_;
  0         0  
608 0         0 return $foreign_class->get(@get_params); };
  0         0  
609             }
610 0         0 push @crossers, $crosser;
611             }
612             }
613              
614 0         0 my @working = ($self);
615              
616             # This can probably be rewritten with List::Util::reduce
617 0         0 for (my $i = 0; $i < @collectors; $i++) {
618 0 0       0 last unless @working;
619 0         0 my @working = $collectors[$i]->(@working);
620 0 0       0 next unless $crossers[$i];
621 0         0 @working = $crossers[$i]->(@working);
622             }
623 0         0 $self->context_return(@working);
624 18005         147156 };
625             #Sub::Install::reinstall_sub({
626             # into => $class_name,
627             # as => $accessor_name.'_new',
628             # code => $accessor2,
629             #});
630              
631              
632 18005         18031 my($bridge_collector, $bridge_crosser);
633              
634             my $accessor = Sub::Name::subname $full_name => sub {
635 863701     863701   692761 my $self = shift;
        863701      
        863701      
        863701      
        863701      
        863701      
        863701      
        863701      
        882227      
        1666581      
        1663536      
        1663536      
        1659056      
        1659056      
        1659056      
        2454411      
        2454411      
        2454411      
        2454411      
        1659056      
        1659056      
        1659056      
        1659056      
        1659056      
        1659056      
        1659056      
        1791009      
        1755249      
        1711469      
        1709599      
        2493466      
        1659056      
        1659056      
        1374813      
        921252      
        1533173      
        924278      
        1019913      
        2314167      
        1717831      
        256632      
636 863701 50 33     1449962 Carp::croak("Assignment value passed to read-only indirect accessor $accessor_name for class $class_name") if @_ == 1 and _is_assignment_value(@_);
637              
638 863701 100       1117530 unless ($bridge_collector) {
639 1787         8716 ($bridge_collector, $bridge_crosser)
640             = $ur_object_type->_resolve_bridge_logic_for_indirect_property($class_name, $accessor_name, $via, $to, \@where);
641             }
642              
643 863701         997211 my @bridges = $bridge_collector->($self);
644              
645 863701 100       1155119 return unless @bridges;
646 863610 100       1192115 return $self->context_return(@bridges) if ($to eq '-filter');
647              
648 863573         1079927 my @results = $bridge_crosser->(\@bridges, @_);
649 863573         1634860 $self->context_return(@results);
650 18005         85852 };
651              
652 18005 50       30745 unless ($accessor_name) {
653 0         0 Carp::croak("No accessor name specified for read-only indirect accessor $accessor_name for class $class_name");
654             }
655              
656             Sub::Install::reinstall_sub({
657 18005         57837 into => $class_name,
658             as => $accessor_name,
659             code => $accessor,
660             });
661              
662 18005         581968 my $r_class_name;
663             my $r_class_name_resolver = sub {
664 0 0   168082   0 return $r_class_name if $r_class_name;
665              
666 0         0 my $linking_property = UR::Object::Property->get(class_name => $class_name, property_name => $via);
667 0 0       0 unless ($linking_property->data_type) {
668 0         0 Carp::croak "Property ${class_name}::${accessor_name}: via refers to a property with no data_type. Can't process filter";
669             }
670 0         0 my $final_property = UR::Object::Property->get(class_name => $linking_property->data_type,
671             property_name => $to);
672 0 0       0 unless ($final_property->data_type) {
673 0         0 Carp::croak "Property ${class_name}::${accessor_name}: to refers to a property with no data_type. Can't process filter";
674             }
675 0         0 $r_class_name = $final_property->data_type;
676 18005         51993 };
677              
678             my $filterable_accessor = Sub::Name::subname $filterable_full_name => sub {
679 101     56959   124 my $self = shift;
        3175      
        3175      
        3175      
        3175      
        3175      
        3175      
        3175      
        101      
        189      
        189      
        189      
        189      
        189      
        189      
        277      
        277      
        277      
        277      
        189      
        189      
        189      
        189      
        189      
        189      
        189      
        189      
        189      
        189      
        189      
        277      
        189      
        189      
        101      
        101      
        102      
        101      
        101      
        190      
        189      
        0      
680 101         456 my @results = $self->$accessor_name();
681 101 50       290 if (@_) {
682 0         0 my $rule;
683 0 0 0     0 if (@_ == 1 and ref($_[0]) and $_[0]->isa('UR::BoolExpr')) {
      0        
684 0         0 $rule = shift;
685             } else {
686 0   0     0 $r_class_name ||= $r_class_name_resolver->();
687 0         0 $rule = UR::BoolExpr->resolve_normalized($r_class_name, @_);
688             }
689 0         0 @results = grep { $rule->evaluate($_) } @results;
  0         0  
690             }
691 101         245 $self->context_return(@results);
692 18005         99317 };
693 18005         51380 Sub::Install::reinstall_sub({
694             into => $class_name,
695             as => $filterable_accessor_name,
696             code => $filterable_accessor,
697             });
698              
699             }
700              
701              
702             sub mk_indirect_rw_accessor {
703 14     14 0 28 my ($ur_object_type, $class_name, $accessor_name, $via, $to, $where, $singular_name, $property_name) = @_;
704 14   33     36 $property_name ||= $accessor_name;
705 14 100       45 my @where = ($where ? @$where : ());
706 14         34 my $full_name = join( '::', $class_name, $accessor_name );
707              
708 14         18 my $update_strategy; # defined the first time we "set" a value through this
709             my $adder;
710 0         0 my $via_property_meta;
711 0         0 my $r_class_name;
712 0         0 my $is_many;
713              
714             my $resolve_update_strategy = sub {
715 13 50   13   35 unless (defined $update_strategy) {
716             # Resolve the strategy. We need to figure out if $to
717             # refers to an id-property. This is only called once, when the
718             # accessor is first used.
719              
720             # If we reference a remote object, and go to one of its id properties
721             # we must do a delete/create instead of property change. Note that
722             # this is only allowed when the remote object has no direct properties
723             # which are not id properties.
724              
725 13         67 my $my_property_meta = $class_name->__meta__->property_meta_for_name($property_name);
726 13 50       41 unless ($my_property_meta) {
727 0         0 Carp::croak("Failed to find property meta for '$property_name' on class $class_name");
728             }
729 13         34 $is_many = $my_property_meta->is_many;
730              
731 13   33     65 $via_property_meta ||= $class_name->__meta__->property_meta_for_name($via);
732 13 50       32 unless ($via_property_meta) {
733 0         0 Carp::croak("Failed to find property metadata for via property '$via' while resolving property '$property_name' on class $class_name");
734             }
735              
736 13   33     82 $r_class_name ||= $via_property_meta->data_type;
737 13 50       31 unless ($r_class_name) {
738 0         0 Carp::croak("Cannot resolve property '$property_name' on class $class_name: It is via property '$via' which has no data_type");
739             }
740 13         58 my $r_class_meta = $r_class_name->__meta__;
741 13 50       31 unless ($r_class_meta) {
742 0         0 Carp::croak("Cannot resolve property '$property_name' on class $class_name: It is via property '$via' with data_type $r_class_name which is not a valid class name");
743             }
744              
745 13         40 $adder = "add_" . $via_property_meta->singular_name;
746              
747 13 50       62 if ($my_property_meta->_involves_id_property) {
748 13         23 $update_strategy = 'delete-create'
749             }
750             else {
751 0         0 $update_strategy = 'change';
752             }
753             }
754 13         24 return $update_strategy;
755 14         120 };
756              
757 14         17 my ($bridge_collector, $bridge_crosser);
758             my $accessor = Sub::Name::subname $full_name => sub {
759 38     38   13992 my $self = shift;
760              
761 38 100       102 unless ($bridge_collector) {
762 13         121 ($bridge_collector, $bridge_crosser)
763             = $ur_object_type->_resolve_bridge_logic_for_indirect_property($class_name, $accessor_name, $via, $to, \@where);
764             }
765              
766 38         75 my @bridges = $bridge_collector->($self);
767              
768 38 100 66     152 if ( @_ == 1 and _is_assignment_value(@_) ) {
769 7 100       29 $resolve_update_strategy->() unless (defined $update_strategy);
770              
771 7 50       34 if ($update_strategy eq 'change') {
    50          
772 0 0       0 if (@bridges == 0) {
    0          
773             #print "adding via $adder @where :::> $to @_\n";
774 0         0 my $exception = do {
775 0         0 local $@;
776 0         0 @bridges = eval { $self->$adder(@where, $to => $_[0]) };
  0         0  
777 0         0 $@;
778             };
779 0 0       0 if ($exception) {
780 0         0 my $r_class_meta = $r_class_name->__meta__;
781 0         0 my $property_meta = $r_class_meta->property($to);
782 0 0       0 if ($property_meta) {
783             # Re-throw the original exception
784 0         0 die $exception;
785             } else {
786 0         0 Carp::croak("Couldn't create a new object through indirect property "
787             . "'$accessor_name' on $class_name. 'to' is $to which is not a property on $r_class_name.");
788             }
789             }
790             #WAS > Carp::confess("Cannot set $accessor_name on $class_name $self->{id}: property is via $via which is not set!");
791             }
792             elsif (@bridges > 1) {
793 0         0 Carp::croak("Cannot set '$accessor_name' on $class_name id '$self->{id}': multiple instances of '$via' found, via which the property is set");
794             }
795             #print "updating $bridges[0] $to to @_\n";
796 0         0 return $bridges[0]->$to(@_);
797             }
798             elsif ($update_strategy eq 'delete-create') {
799 7 50       18 if (@bridges > 1) {
800 0         0 Carp::croak("Cannot set '$accessor_name' on $class_name $self->{id}: multiple instances of '$via' found, via which the property is set");
801             }
802             else {
803 7 100       24 if (@bridges) {
804             #print "deleting $bridges[0]\n";
805 4         27 $bridges[0]->delete;
806             }
807             #print "adding via $adder @where :::> $to @_\n";
808 7         28 @bridges = $self->$adder(@where, $to => $_[0]);
809 7 50       20 unless (@bridges) {
810 0         0 Carp::croak("Failed to add bridge for '$accessor_name' on $class_name if '$self->{id}': method $adder returned false");
811             }
812             }
813             }
814             }
815 38 100       77 if (not defined $is_many) {
816 5         16 $resolve_update_strategy->();
817             }
818              
819 38 100       82 if ($is_many) {
820 26 100       56 return unless @bridges;
821 25         69 my @results = $bridge_crosser->(\@bridges, @_);
822 25         91 $self->context_return(@results);
823             } else {
824 12 100       23 return undef unless @bridges;
825 11         14 my @results = map { $_->$to(@_) } @bridges;
  11         28  
826 11         47 $self->context_return(@results);
827             }
828 14         149 };
829              
830 14         64 Sub::Install::reinstall_sub({
831             into => $class_name,
832             as => $accessor_name,
833             code => $accessor,
834             });
835              
836 14 100       472 if ($singular_name) { # True if we're defining an is_many indirect property
837             # Add
838 8         9 my $via_adder;
839 8         17 my $adder_method_name = 'add_' . $singular_name;
840 8 50       30 if ($class_name->can($adder_method_name)) {
841 0         0 $adder_method_name = '__' . $adder_method_name;
842             }
843             my $adder_method = Sub::Name::subname $class_name . '::' . $adder_method_name => sub {
844 18     54   3768 my($self) = shift;
845              
846              
847 18 100       52 $resolve_update_strategy->() unless (defined $update_strategy);
848 18 100       44 unless (defined $via_adder) {
849 7         22 $via_adder = "add_" . $via_property_meta->singular_name;
850             }
851              
852             # By default, a single value will come in which is the remote value
853             # we just add the appropriate property name to it. If multiple
854             # values come in we trust the caller to be giving additional params.
855 18 100       47 if (@_ == 1) {
856 16         31 unshift @_, $to;
857             }
858 18         60 $self->$via_adder(@where,@_);
859 8         325 };
860 8         32 Sub::Install::reinstall_sub({
861             into => $class_name,
862             as => $adder_method_name,
863             code => $adder_method,
864             });
865              
866             # Remove
867 8         209 my $via_remover;
868 8         13 my $remover_method_name = 'remove_' . $singular_name;
869 8 50       22 if ($class_name->can($remover_method_name)) {
870 0         0 $remover_method_name = '__' . $remover_method_name;
871             }
872             my $remover_method = Sub::Name::subname $class_name . '::' . $remover_method_name => sub {
873 4     76   3758 my($self) = shift;
874              
875 4 50       14 $resolve_update_strategy->() unless (defined $update_strategy);
876 4 100       12 unless (defined $via_remover) {
877 3         11 $via_remover = "remove_" . $via_property_meta->singular_name;
878             }
879              
880             # By default, a single value will come in which is the remote value
881             # we just remove the appropriate property name to it. If multiple
882             # values come in we trust the caller to be giving removeitional params.
883 4 50       13 if (@_ == 1) {
884 4         9 unshift @_, $to;
885             }
886 4         22 $self->$via_remover(@where,@_);
887 8         289 };
888 8         32 Sub::Install::reinstall_sub({
889             into => $class_name,
890             as => $remover_method_name,
891             code => $remover_method,
892             });
893             }
894              
895             }
896              
897              
898             sub mk_calculation_accessor {
899 1895     1912 0 7299 my ($self, $class_name, $accessor_name, $calculation_src, $calculate_from, $params, $is_cached, $column_name) = @_;
900              
901 1895         2958 my $accessor;
902             my @src;
903              
904 1895 50 33     21072 if (not defined $calculation_src or $calculation_src eq '') {
    100          
    100          
905 0         0 $accessor = \&{ $class_name . '::' . $accessor_name };
  0         0  
906 0 0       0 unless ($accessor) {
907 0         0 Carp::croak "$accessor_name not defined in $class_name! Define it, or specify a calculate => sub{} or calculate => \$perl_src in the class definition.";
908             }
909             }
910             elsif (ref($calculation_src) eq 'CODE') {
911             $accessor = sub {
912 4     8   7 my $self = shift;
913 4 50       13 if (@_) {
914 0         0 Carp::croak("$class_name $accessor_name is a read-only property derived from @$calculate_from");
915             }
916 4         9 return $calculation_src->(map { $self->$_ } @$calculate_from);
  4         25  
917 10         161 };
918             }
919             elsif ($calculation_src =~ /^[^\:\W]+$/) {
920             # built-in formula like 'sum' or 'product'
921 2         7 my $module_name = "UR::Object::Type::AccessorWriter::" . ucfirst(lc($calculation_src));
922 2         2 my $exception = do {
923 2         2 local $@;
924 2         220 eval "use $module_name";
925 2         26 $@;
926             };
927 2 50       5 die $exception if $exception;
928             @src = (
929             "sub ${class_name}::${accessor_name} {",
930             'my $self = $_[0];',
931 2         7 "${module_name}->calculate(\$self, [" . join(",", map { "'$_'" } @$calculate_from) . "], \@_)",
  4         13  
932             '}'
933             );
934             }
935             else {
936             @src = (
937             "sub ${class_name}::${accessor_name} {",
938             ($params ? 'my ($self,%params) = @_;' : 'my $self = $_[0];'),
939 180         1209 (map { "my \$$_ = \$self->$_;" } @$calculate_from),
940 1883 50       12163 ($params ? (map { "my \$$_ = delete \$params{'$_'};" } @$params) : ()),
  0 50       0  
941             $calculation_src,
942             '}'
943             );
944             }
945              
946 1895 100       4426 if (!$accessor) {
947 1885 50       4314 if (@src) {
948 1885         4968 my $src = join("\n",@src);
949             #print ">>$src<<\n";
950 1885         2152 my $exception = do {
951 1885         2340 local $@;
952 1885     10430   168442 eval $src;
  10428     5437   15573  
  10428     191   40882  
  5437         8335  
  5437         21657  
  36         1580  
  191         6127  
  191         1148  
  28         571  
  28         53  
  21         42  
  18         359  
  18         37  
  18         60  
  2         3  
  2         6  
  2         6  
953 1885         5450 $@;
954             };
955 1885 50       4908 if ($exception) {
956 0         0 Carp::croak "ERROR IN CALCULATED PROPERTY SOURCE: $class_name $accessor_name\n$exception\n";
957             }
958 1885         2211 $accessor = \&{ $class_name . '::' . $accessor_name };
  1885         6597  
959 1885 50       5346 unless ($accessor) {
960 0         0 Carp::confess("Failed to generate code body for calculated property ${class_name}::${accessor_name}!");
961             }
962             }
963             else {
964 0         0 Carp::croak "Error implementing calcuation accessor for $class_name $accessor_name!";
965             }
966             }
967              
968 1895 100 66     9668 if ($accessor and $is_cached) {
969             # Wrap the already-compiled accessor in another function to memoize the
970             # result and save the data into the object
971 241         488 my $calculator_sub = $accessor;
972             $accessor = sub {
973 4747 100   4815   23031 if (@_ > 1) {
        4808      
974 1         234 Carp::croak("Cannot change property $accessor_name for class $class_name: cached calculated properties are read-only");
975             }
976 4746 100       11883 unless (exists $_[0]->{$accessor_name}) {
977 577         14791 $_[0]->{$accessor_name} = $calculator_sub->(@_);
978             }
979 4743         9042 return $_[0]->{$accessor_name};
980 241         1712 };
981              
982             # Make a method to clear the cached value and force another calculation
983 241         411 my $invalidator_name;
984 241         678 ($invalidator_name = $accessor_name) =~ s/^_+//;
985 241         703 $invalidator_name = "__invalidate_${invalidator_name}__";
986             Sub::Install::reinstall_sub({
987             into => $class_name,
988             as => $invalidator_name,
989 374     487   11025 code => sub { delete $_[0]->{$accessor_name} },
990 241         1866 });
991             }
992              
993 1895         13424 my $full_name = join( '::', $class_name, $accessor_name );
994 1895         9602 $accessor = Sub::Name::subname $full_name => $accessor;
995 1895         8683 Sub::Install::reinstall_sub({
996             into => $class_name,
997             as => $accessor_name,
998             code => $accessor,
999             });
1000              
1001 1895         72195 return $accessor;
1002             }
1003              
1004             sub mk_dimension_delegate_accessors {
1005 80     33 0 255 my ($self, $accessor_name, $ref_class_name, $non_id_properties, $other_accessor_name, $is_transient) = @_;
1006              
1007             # Like mk_rw_accessor, but knows that this accessor is a foreign
1008             # key to a dimension table, and configures additional accessors.
1009             # Also makes this accessor "smart", to resolve the dimension
1010             # id only when needed.
1011              
1012             # Make EAV-like accessors for all of the remote properties
1013 78         606 my $class_name = $self->class_name;
1014              
1015 45         432 my $full_name = join( '::', $class_name, $other_accessor_name );
1016             my $other_accessor = Sub::Name::subname $full_name => sub {
1017 31     0   734 my $self = shift;
1018 1         5 my $delegate_id = $self->{$accessor_name};
1019 4 0       6 if (defined($delegate_id)) {
1020             # We're currently delegating.
1021 4         10 my $delegate = $ref_class_name->get($delegate_id);
1022 4 0       10 if (not @_) {
1023             # A simple get. Delegate.
1024 4         21 return $delegate->$other_accessor_name(@_);
1025             }
1026             else {
1027             # We're setting a value.
1028             # Switch from delegating to local access.
1029             # We'll switch back next-time the dimension ID
1030             # is actually requested by its accessor
1031             # (farther below).
1032 0         0 my $old = $delegate->$other_accessor_name;
1033 0         0 my $new = shift;
1034 0         0 my $different;
1035 0         0 my $exception = do {
1036 0         0 local $@;
1037 266     266   1746 $different = eval { no warnings; $old ne $new };
  266         470  
  266         35324  
  0         0  
  0         0  
1038 0         0 $@;
1039             };
1040 0 0 0     0 if ($different or $exception =~ m/has no overloaded magic/) {
1041 0         0 $self->{$accessor_name} = undef;
1042 0         0 for my $property (@$non_id_properties) {
1043 0 100       0 if ($property eq $other_accessor_name) {
1044             # set the value locally
1045 0         0 $self->{$property} = $new;
1046             }
1047             else {
1048             # grab the data from the (now previous) delegate
1049 0         0 $self->{$property} = $delegate->$property;
1050             }
1051             }
1052 0 0       0 $self->__signal_change__( $other_accessor_name, $old, $new ) unless $is_transient;
1053 0         0 return $new;
1054             }
1055             }
1056             }
1057             else {
1058             # We are not currently delegating.
1059 0 0       0 if (@_) {
1060             # set
1061 0         0 my $old = $self->{ $other_accessor_name };
1062 0         0 my $new = shift;
1063 0         0 my $different;
1064 0         0 my $exception = do {
1065 0         0 local $@;
1066 266     266   1212 $different = eval { no warnings; $old ne $new };
  266         414  
  266         54971  
  0         0  
  0         0  
1067 0         0 $@;
1068             };
1069 0 0 0     0 if ($different or $exception =~ m/has no overloaded magic/) {
1070 0         0 $self->{ $other_accessor_name } = $new;
1071 0 0       0 $self->__signal_change__( $other_accessor_name, $old, $new ) unless $is_transient;
1072             }
1073 0         0 return $new;
1074             }
1075             else {
1076             # get
1077 0         0 return $self->{ $other_accessor_name };
1078             }
1079             }
1080 45         132 };
1081              
1082 0         0 Sub::Install::reinstall_sub({
1083             into => $class_name,
1084             as => $other_accessor_name,
1085             code => $other_accessor,
1086             });
1087             }
1088              
1089             sub mk_dimension_identifying_accessor {
1090 0     0 0 0 my ($self, $accessor_name, $ref_class_name, $non_id_properties, $is_transient) = @_;
1091              
1092             # Like mk_rw_accessor, but knows that this accessor is a foreign
1093             # key to a dimension table, and configures additional accessors.
1094             # Also makes this accessor "smart", to resolve the dimension
1095             # id only when needed.
1096              
1097             # Make EAV-like accessors for all of the remote properties
1098 0         0 my $class_name = $self->class_name;
1099              
1100             # Make the actual accessor for the id_by property
1101 0         0 my $full_name = join( '::', $class_name, $accessor_name );
1102             my $accessor = Sub::Name::subname $full_name => sub {
1103 0 0   0   0 if (@_ > 1) {
1104 0         0 my $old = $_[0]->{ $accessor_name };
1105 0         0 my $new = $_[1];
1106 0         0 my $different;
1107 0         0 my $exception = do {
1108 0         0 local $@;
1109 266     266   1263 $different = eval { no warnings; $old ne $new };
  266         436  
  266         62829  
  0         0  
  0         0  
1110 0         0 $@;
1111             };
1112 0 0 0     0 if ($different or $exception =~ m/has no overloaded magic/) {
1113 0         0 $_[0]->{ $accessor_name } = $new;
1114 0 0       0 $_[0]->__signal_change__( $accessor_name, $old, $new ) unless $is_transient;
1115             }
1116 0         0 return $new;
1117             }
1118 0 0       0 if (not defined $_[0]->{ $accessor_name }) {
1119             # Resolve an ID for the current set of values
1120             # Switch to delegating to that object.
1121 0         0 my %params;
1122 0         0 my $self = $_[0];
1123 0         0 @params{@$non_id_properties} = delete @$self{@$non_id_properties};
1124 0         0 my $delegate = $ref_class_name->get_or_create(%params);
1125 0 0       0 return undef unless $delegate;
1126 0         0 $_[0]->{ $accessor_name } = $delegate->id;
1127             }
1128 0         0 return $_[0]->{ $accessor_name };
1129 0         0 };
1130              
1131 0         0 Sub::Install::reinstall_sub({
1132             into => $class_name,
1133             as => $accessor_name,
1134             code => $accessor,
1135             });
1136             }
1137              
1138             sub mk_rw_class_accessor
1139             {
1140 3     3 0 6 my ($self, $class_name, $accessor_name, $column_name, $is_transient, $variable_value, $calc_default) = @_;
1141              
1142 3         6 my $full_accessor_name = $class_name . "::" . $accessor_name;
1143             my $accessor = Sub::Name::subname $full_accessor_name => sub {
1144 6 100   6   752 if (@_ > 1) {
    100          
1145 3         5 my $old = $variable_value;
1146 3         5 $variable_value = $_[1];
1147              
1148 3         4 my $different;
1149 3         2 my $exception = do {
1150 3         4 local $@;
1151 266     266   1268 $different = eval { no warnings; $old ne $variable_value };
  266         397  
  266         44829  
  3         5  
  3         6  
1152 3         5 $@;
1153             };
1154 3 50 33     17 if ($different or $exception =~ m/has no overloaded magic/) {
1155 3 50       18 $_[0]->__signal_change__( $accessor_name, $old, $variable_value ) unless $is_transient;
1156             }
1157             } elsif (defined $calc_default) {
1158 1         5 $variable_value = $calc_default->();
1159             }
1160 6         13 undef $calc_default;
1161 6         21 return $variable_value;
1162 3         31 };
1163 3         12 Sub::Install::reinstall_sub({
1164             into => $class_name,
1165             as => $accessor_name,
1166             code => $accessor,
1167             });
1168              
1169             }
1170              
1171             sub mk_ro_class_accessor {
1172 180     181 0 419 my($self, $class_name, $accessor_name, $column_name, $variable_value, $calc_default) = @_;
1173              
1174 180         537 my $full_accessor_name = $class_name . "::" . $accessor_name;
1175             my $accessor = Sub::Name::subname $full_accessor_name => sub {
1176 496 100   496   9761 if (@_ > 1) {
    100          
1177 1         2 my $new = $_[1];
1178              
1179 1         2 my $different;
1180 1         2 my $exception = do{
1181 1         1 local $@;
1182 266     266   1193 $different = eval { no warnings; $variable_value ne $new };
  266         400  
  266         261038  
  1         2  
  1         3  
1183 1         2 $@;
1184             };
1185 1 50 33     4 if ($different or $exception =~ m/has no overloaded magic/) {
1186 1 50       3 $new = defined($new) ? $new : '(undef)';
1187 1 50       3 my $report_variable_value = defined($variable_value) ? $variable_value : '(undef)';
1188 1         272 Carp::croak("Cannot change read-only class-wide property $accessor_name for class $class_name from $report_variable_value to $new!");
1189             }
1190             } elsif (defined $calc_default) {
1191 1         3 $variable_value = $calc_default->();
1192             }
1193 495         493 undef $calc_default;
1194 495         1013 return $variable_value;
1195 180         2046 };
1196 180         946 Sub::Install::reinstall_sub({
1197             into => $class_name,
1198             as => $accessor_name,
1199             code => $accessor,
1200             });
1201             }
1202              
1203              
1204              
1205              
1206             sub mk_object_set_accessors {
1207 2736     3236 0 5118 my ($self, $class_name, $singular_name, $plural_name, $reverse_as, $r_class_name, $where) = @_;
1208              
1209             # These are set by the resolver closure below, and kept in scope by the other closures
1210 2736         3050 my $rule_template;
1211             my $r_class_meta;
1212 0         0 my @property_names;
1213 2736 100       9293 my @where = ($where ? @$where : ());
1214              
1215             my $rule_resolver = sub {
1216 685     1179   1055 my ($obj) = @_;
1217 685         1444 my $loading_r_class_error = '';
1218 685 100       1762 if (defined $r_class_name) {
1219 671         821 my $exception = do {
1220 671         854 local $@;
1221 671         1031 eval {
1222 671         2822 $r_class_meta = UR::Object::Type->is_loaded($r_class_name);
1223 671 100 100     3097 unless ($r_class_meta or __PACKAGE__->use_module_with_namespace_constraints($r_class_name)) {
1224             # Don't die yet. The named class may not have a file associated with it
1225 310         640 $loading_r_class_error = "Couldn't load class $r_class_name: $@";
1226 310         370 $@ = '';
1227             }
1228              
1229 671 100       1646 unless ($r_class_meta) {
1230 326         2259 $r_class_name->class;
1231 16         57 $r_class_meta = UR::Object::Type->get(class_name => $r_class_name);
1232             }
1233             };
1234 671         1545 $@;
1235             };
1236 671 100       1660 if ($exception) {
1237 310         951 $loading_r_class_error .= "Couldn't get class object for $r_class_name: $exception";
1238             }
1239             }
1240 685 100 100     3301 if ($r_class_meta and not $reverse_as) {
1241             # We have a real class on the other end, and it did not specify know to link back to us.
1242             # Try to infer how, otherwise fall back to the same logic we use with "primitives".
1243 25         38 my @possible_relationships = grep { $_->data_type eq $class_name }
1244 7         46 grep { defined $_->data_type }
  25         39  
1245             $r_class_meta->all_property_metas();
1246              
1247 7 50       45 if (@possible_relationships > 1) {
    100          
    50          
1248             Carp::croak "$class_name has an ambiguous definition for property \"$singular_name\"."
1249             . " The target class $r_class_name has " . scalar(@possible_relationships)
1250             . " relationships which reference back to $class_name."
1251             . " Correct by adding \"reverse_as => X\" to ${class_name}'s \"$singular_name\" definition one of the following values: "
1252 0         0 . join(",",map { '"' . $_->delegation_name . '"' } @possible_relationships) . ".\n";
  0         0  
1253             }
1254             elsif (@possible_relationships == 1) {
1255 3         10 $reverse_as = $possible_relationships[0]->property_name;
1256             }
1257             elsif (@possible_relationships == 0) {
1258             # we now fall through to the logic below and try direct arrayref storage
1259             #die "No relationships found between $r_class_name and $class_name. Error in definition for $class_name $singular_name!"
1260             }
1261             }
1262 685 50 66     3091 if ($reverse_as and ! $r_class_meta) {
1263             # we've resolved reverse_as, but there's not r_class_meta?!
1264 0         0 $self->error_message("Can't resolve reverse relationship $class_name -> $plural_name. No class metadata for $r_class_name");
1265 0 0       0 if ($loading_r_class_error) {
1266 0         0 Carp::croak "While loading $r_class_name: $loading_r_class_error";
1267             } else {
1268 0         0 Carp::croak "Is class $r_class_name defined anywhere?";
1269             }
1270             }
1271              
1272 685 100       1878 if ($reverse_as) {
1273             # join to get the data...
1274 357 50       1083 unless ($r_class_meta) {
1275 0         0 Carp::croak("No remote class metadata found for class $r_class_name while resolving property '$singular_name' of class $class_name");
1276             }
1277 357         1557 my $property_meta = $r_class_meta->property_meta_for_name($reverse_as);
1278 357 50       1208 unless ($property_meta) {
1279 0         0 Carp::croak "Can't resolve reverse relationship $class_name -> $plural_name. Remote class $r_class_name has no property $reverse_as";
1280             }
1281 357         617 my @get_params;
1282 357 100       1411 if ($property_meta->via) {
1283             # get_property_name_pairs_for_join() only works for properties connected directly.
1284             # we still need to use it during initialization, but for more complicated relationships
1285             # this should do the right thing
1286 1         4 push @get_params, $property_meta->property_name . '.id' => $obj->id;
1287 1         3 push @property_names, 'id';
1288             } else {
1289 356         1897 my @property_links = $property_meta->get_property_name_pairs_for_join;
1290 356         801 for my $link (@property_links) {
1291 357         661 my $my_property_name = $link->[1];
1292 357         752 push @property_names, $my_property_name;
1293 357 50       1628 unless ($obj->can($my_property_name)) {
1294 0         0 Carp::croak "Cannot handle indirect relationship $r_class_name -> $reverse_as. Class $class_name has no property named $my_property_name";
1295             }
1296 357   50     3370 push @get_params, $link->[0], ($obj->$my_property_name || undef);
1297             }
1298             }
1299              
1300 357 100       1302 if (my $id_class_by = $property_meta->id_class_by) {
1301 3         18 push @get_params, $id_class_by, $obj->class;
1302 3         5 push @property_names, 'class';
1303             }
1304 357         3102 my $tmp_rule = $r_class_name->define_boolexpr(@get_params,@where);
1305 357 50       1508 if (my $order_by = $property_meta->order_by) {
1306 0         0 push @get_params, $order_by;
1307             }
1308 357         1204 $rule_template = $tmp_rule->template;
1309 357 50       768 unless ($rule_template) {
1310 0         0 Carp::croak "Error generating rule template to handle indirect relationship $class_name $singular_name referencing $r_class_name!";
1311             }
1312 357         1036 return $tmp_rule;
1313             }
1314             else {
1315             # data is stored locally on the hashref
1316             #die "No relationships found between $r_class_name and $class_name. Error in definition for $class_name $singular_name!"
1317             }
1318 2736         26220 };
1319              
1320 2736         3231 my @where_values;
1321 2736         8152 for (my $i = 1; $i < @where; $i+=2) {
1322 1475 100 66     4327 if (ref($where[$i]) eq 'HASH' and exists($where[$i]->{'operator'})) {
1323 2         9 push @where_values, $where[$i]->{'value'}; # the operator is already stored in the template
1324             } else {
1325 1473         3807 push @where_values, $where[$i];
1326             }
1327             }
1328              
1329             # These will behave specially if the rule does not specify the ID, or all of the ID.
1330 2736         2823 my @params_prefix;
1331 2736         3003 my $params_prefix_resolved = 0;
1332             my $params_prefix_resolver = sub {
1333             # handle the case of has-many primitives
1334 31 50   504   96 return unless $r_class_meta;
1335              
1336 31         108 my $r_ids = $r_class_meta->property_meta_for_name($reverse_as)->{id_by};
1337              
1338 31         131 my $cmeta = UR::Object::Type->get($class_name);
1339 31 100       178 my $pmeta = $plural_name ? $cmeta->{has}{$plural_name} : $cmeta->{has}{$singular_name};
1340 31 100       140 if (my $specify_by = $pmeta->{specify_by}) {
1341 3         9 @params_prefix = ($specify_by);
1342             }
1343             else {
1344             # TODO: should this really be an auto-setting of the specify_by meta property?
1345 28         109 my @id_property_names = $r_class_name->__meta__->id_property_names;
1346             @params_prefix =
1347             grep {
1348 28         63 my $id_property_name = $_;
  47         64  
1349 47 100       63 ( (grep { $id_property_name eq $_ } @$r_ids) ? 0 : 1)
  47         175  
1350             }
1351             @id_property_names;
1352              
1353             # We only do the special single-value spec when there is one property not specified by the rule.
1354             # This is common for a multi-column primary key where all columns reference a parent object, except an index value, etc.
1355 28 100       100 @params_prefix = () unless scalar(@params_prefix) == 1;
1356             }
1357 31         63 $params_prefix_resolved = 1;
1358 2736         11409 };
1359              
1360 2736 100 100     13129 if (!$plural_name || $singular_name ne $plural_name) {
1361             my $single_accessor = Sub::Name::subname $class_name ."::$singular_name" => sub {
1362 17     480   4007 my $self = shift;
        458      
        467      
        464      
        57      
        57      
        74      
        51      
1363 17         23 my $rule;
1364 17 100       67 $rule = $rule_resolver->($self) unless (defined $rule_template);
1365 17 100       63 if ($rule_template) {
1366 13 100       59 $rule = $rule_template->get_rule_for_values((map { $self->$_ } @property_names), @where_values) unless (defined $rule);
  10         36  
1367 13 100       45 $params_prefix_resolver->() unless $params_prefix_resolved;
1368 13 100       38 unshift @_, @params_prefix if @_ == 1;
1369 13 100       32 if (@_) {
1370 7         21 return my $obj = $r_class_name->get($rule->params_list,@_);
1371             }
1372             else {
1373 6         30 return my $obj = $r_class_name->get($rule);
1374             }
1375             }
1376             else {
1377 4 100       15 return unless $self->{$plural_name};
1378 2 50       90 return unless @_; # Can't compare our list to nothing...
1379 2 50       5 if (@_ > 1) {
1380 0         0 Carp::croak "rule-based selection of single-item accessor not supported. Instead of single value, got @_";
1381             }
1382 2 50       7 unless (ref($self->{$plural_name}) eq 'ARRAY') {
1383 0         0 Carp::croak("${class_name}::$singular_name($_[0]): $plural_name does not contain an arrayref");
1384             }
1385 266     266   1359 no warnings 'uninitialized';
  266         420  
  266         392812  
1386 2         3 my @matches = grep { $_ eq $_[0] } @{ $self->{$plural_name} };
  3         8  
  2         3  
1387 2 50       8 return $matches[0] if @matches < 2;
1388 0         0 return $self->context_return(@matches);
1389             }
1390 2716         27674 };
1391 2716         11574 Sub::Install::reinstall_sub({
1392             into => $class_name,
1393             as => $singular_name,
1394             code => $single_accessor,
1395             });
1396              
1397             # return now for reverse_as but not is_many
1398 2716 100       99782 unless ($plural_name) {
1399 2         7 return;
1400             }
1401             }
1402              
1403 2734         10522 my $rule_name = $self->rule_accessor_name_for_is_many_accessor($plural_name);
1404             my $rule_accessor = Sub::Name::subname $class_name ."::$rule_name" => sub {
1405 0     15   0 my $self = shift;
        4      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
1406 0 0       0 $rule_resolver->($self) unless ($rule_template);
1407 0 0       0 unless ($rule_template) {
1408 0         0 Carp::croak "No indirect rule available for locally-stored 'has-many' relationship";
1409             }
1410 0 0       0 if (@_) {
1411 0         0 my $tmp_rule = $rule_template->get_rule_for_values((map { $self->$_ } @property_names), @where_values);
  0         0  
1412 0         0 return $r_class_name->define_boolexpr($tmp_rule->params_list, @_);
1413             }
1414             else {
1415 0         0 return $rule_template->get_rule_for_values((map { $self->$_ } @property_names),@where_values);
  0         0  
1416             }
1417 2734         22554 };
1418              
1419 2734         9403 Sub::Install::reinstall_sub({
1420             into => $class_name,
1421             as => $rule_name,
1422             code => $rule_accessor,
1423             });
1424              
1425             my $list_accessor = Sub::Name::subname $class_name ."::$plural_name" => sub {
1426 7089     7089   35048 my $self = shift;
        7089      
        741      
        7089      
        8793      
        8117      
        8201      
        14475      
        20467      
1427 7089         6078 my $rule;
1428 7089 100       13901 $rule = $rule_resolver->($self) unless (defined $rule_template);
1429 7089 100       20999 if ($rule_template) {
1430 6945 100       15559 $rule = $rule_template->get_rule_for_values((map { $self->$_ } @property_names), @where_values) unless (defined $rule);
  6615         15278  
1431 6945 100       12364 if (@_) {
1432 2682         6390 return $UR::Context::current->query($r_class_name, $rule->params_list,@_);
1433             }
1434             else {
1435 4263         11458 return $UR::Context::current->query($r_class_name, $rule);
1436             }
1437             }
1438             else {
1439 144 100       373 if (@_) {
1440 4 50       34 if (@_ != 1) {
    100          
1441 0         0 Carp::croak "expected a single arrayref when setting a multi-value $class_name $plural_name! Got " . scalar(@_) . " args";
1442             } elsif ( ref($_[0]) ne 'ARRAY' ) {
1443 1         2 $self->{$plural_name} = [ $_[0] ];
1444             } else {
1445 3         5 $self->{$plural_name} = [ @{$_[0]} ];
  3         12  
1446             }
1447 4         9 return @{ $self->{$plural_name} };
  4         10  
1448             }
1449             else {
1450 140 100       565 return unless $self->{$plural_name};
1451 38 50       119 if (ref($self->{$plural_name}) ne 'ARRAY') {
1452 0         0 Carp::carp("$class_name with id ".$self->id." does not hold an arrayref in its $plural_name property");
1453 0         0 $self->{$plural_name} = [ $self->{$plural_name} ];
1454             }
1455 38         45 return @{ $self->{$plural_name} };
  38         212  
1456             }
1457             }
1458 2734         94354 };
1459              
1460 2734         9620 Sub::Install::reinstall_sub({
1461             into => $class_name,
1462             as => $plural_name,
1463             code => $list_accessor,
1464             });
1465              
1466 2734         74417 Sub::Install::reinstall_sub({
1467             into => $class_name,
1468             as => $singular_name . '_list',
1469             code => $list_accessor,
1470             });
1471              
1472 2734         72695 my $arrayref_name = $self->arrayref_accessor_name_for_is_many_accessor($plural_name);
1473             my $arrayref_accessor = Sub::Name::subname $class_name ."::$arrayref_name" => sub {
1474 3     3008   1198 return [ $list_accessor->(@_) ];
        1454      
        1029      
        950      
        654      
        3      
        3      
        6      
        9      
1475 2734         17012 };
1476              
1477 2734         8827 Sub::Install::reinstall_sub({
1478             into => $class_name,
1479             as => $arrayref_name,
1480             code => $arrayref_accessor,
1481             });
1482              
1483 2734         77853 my $iterator_name = $self->iterator_accessor_name_for_is_many_accessor($plural_name);
1484             my $iterator_accessor = Sub::Name::subname $class_name ."::$iterator_name" => sub {
1485 21     23   151 my $self = shift;
        22      
        1      
        21      
        21      
        41      
        21      
        42      
        63      
1486 21         31 my $rule;
1487 21 100       66 $rule = $rule_resolver->($self) unless (defined $rule_template);
1488 21 100       72 if ($rule_template) {
1489 20 50       75 $rule = $rule_template->get_rule_for_values((map { $self->$_ } @property_names), @where_values) unless (defined $rule);
  20         86  
1490 20 100       62 if (@_) {
1491 15         60 return $r_class_name->create_iterator($rule->params_list,@_);
1492             } else {
1493 5         47 return UR::Object::Iterator->create_for_filter_rule($rule);
1494             }
1495             }
1496             else {
1497 1   50     12 return UR::Value::Iterator->create_for_value_arrayref($self->{$plural_name} || []);
1498             }
1499 2734         18260 };
1500 2734         8647 Sub::Install::reinstall_sub({
1501             into => $class_name,
1502             as => $iterator_name,
1503             code => $iterator_accessor,
1504             });
1505              
1506 2734         77825 my $set_name = $self->set_accessor_name_for_is_many_accessor($plural_name);
1507             my $set_accessor = Sub::Name::subname $class_name ."::$set_name" => sub {
1508 4     44   711 my $self = shift;
        24      
        25      
        24      
        28      
        4      
        4      
        8      
        12      
1509 4         7 my $rule;
1510 4 50       15 $rule = $rule_resolver->($self) unless (defined $rule_template);
1511 4 50       13 if ($rule_template) {
1512 4 50       18 $rule = $rule_template->get_rule_for_values((map { $self->$_ } @property_names),@where_values) unless (defined $rule);
  4         15  
1513 4         16 return $r_class_name->define_set($rule->params_list,@_);
1514             }
1515             else {
1516             # this is a bit inside-out, but works for primitives
1517 0         0 my @members = $self->$plural_name;
1518 0         0 return UR::Value->define_set(id => \@members);
1519             }
1520 2734         19380 };
1521 2734         8975 Sub::Install::reinstall_sub({
1522             into => $class_name,
1523             as => $set_name,
1524             code => $set_accessor,
1525             });
1526              
1527 2734         84223 my $adder_method_name = $self->adder_name_for_is_many_accessor($plural_name);
1528 2734 100       10441 if ($class_name->can($adder_method_name)) {
1529 1         14 $adder_method_name = '__' . $adder_method_name;
1530             }
1531             my $adder_method = Sub::Name::subname $class_name . '::' . $adder_method_name => sub {
1532             # TODO: this handles only a single item when making objects: support a list of hashrefs
1533 353     353   9002 my $self = shift;
        354      
        53      
        353      
        427      
        384      
        382      
        734      
        1057      
1534 353         371 my $rule;
1535 353 100       953 $rule = $rule_resolver->($self) unless (defined $rule_template);
1536 353 100       825 if ($rule_template) {
1537 176 100       435 $params_prefix_resolver->() unless $params_prefix_resolved;
1538 176 100       545 unshift @_, @params_prefix if @_ == 1;
1539 176 100       567 $rule = $rule_template->get_rule_for_values((map { $self->$_ } @property_names), @where_values) unless (defined $rule);
  160         452  
1540 176         470 $r_class_name->create($rule->params_list,@_);
1541             }
1542             else {
1543 177 100       301 if ($r_class_meta) {
1544 1         2 my $obj;
1545 1 50 33     15 if (@_ == 1 and $_[0]->isa($r_class_name)) {
1546 1         3 $obj = $_[0];
1547             }
1548             else {
1549 0         0 $obj = $r_class_name->create(@where,@_);
1550 0 0       0 unless ($obj) {
1551 0         0 $self->error_message("Failed to add $singular_name:" . $r_class_name->error_message);
1552 0         0 return;
1553             }
1554             }
1555 1   50     1 push @{ $self->{$plural_name} ||= [] }, $obj;
  1         15  
1556             }
1557             else {
1558 176 50       380 if (@_ != 1) {
1559 0         0 Carp::croak "$class_name $adder_method_name expects a single value to add. Got " . scalar(@_) . " args";
1560             }
1561 176   100     181 push @{ $self->{$plural_name} ||= [] }, $_[0];
  176         786  
1562 176         519 return $_[0];
1563             }
1564             }
1565 2734         122109 };
1566 2734         10016 Sub::Install::reinstall_sub({
1567             into => $class_name,
1568             as => $adder_method_name,
1569             code => $adder_method,
1570             });
1571              
1572 2734         84137 my $remover_method_name = $self->remover_name_for_is_many_accessor($plural_name);
1573 2734 100       6691 if ($class_name->can($remover_method_name)) {
1574 1         12 $remover_method_name = '__' . $remover_method_name;
1575             }
1576             my $remover_method = Sub::Name::subname $class_name . '::' . $remover_method_name => sub {
1577 9     296   2104 my $self = shift;
        161      
        41      
        40      
        40      
        9      
        9      
        17      
        25      
1578 9         15 my $rule;
1579 9 100       35 $rule = $rule_resolver->($self) unless (defined $rule_template);
1580 9 100       32 if ($rule_template) {
1581             # an id-linked "has-many"
1582 7 50       23 $rule = $rule_template->get_rule_for_values((map { $self->$_ } @property_names), @where_values) unless (defined $rule);
  7         21  
1583 7 50       21 $params_prefix_resolver->() unless $params_prefix_resolved;
1584 7         14 my @matches;
1585 7 100 100     41 if (@_ == 1 and ref($_[0])) {
1586             # the object to remove was passed-in
1587 1 50       4 unless ($rule->evaluate($_[0])) {
1588 0         0 Carp::croak "Object " . $_[0]->__display_name__ . " is not a member of the $singular_name set!";
1589             }
1590 1         3 @matches = ($_[0]);
1591             }
1592             else {
1593             # the parameters to find objects to remove were passed-in
1594 6 100       20 unshift @_, @params_prefix if @_ == 1; # a single "id" is the remainder of the id of the object
1595 6         21 @matches = $r_class_name->get($rule->params_list,@_);
1596             }
1597 7         52 my $trans = UR::Context::Transaction->begin;
1598             @matches = map {
1599 7 50       16 $_->delete or Carp::croak "Error deleting $r_class_name " . $_->id . " for $remover_method_name!: " . $_->error_message;
  7         27  
1600             } @matches;
1601 7         31 $trans->commit;
1602 7         23 return @matches;
1603             }
1604             else {
1605             # direct storage in an arrayref
1606 2   50     8 $self->{$plural_name} ||= [];
1607 2 50       6 if ($r_class_meta) {
1608             # object
1609 0         0 my @remove;
1610             my @keep;
1611 0         0 my $rule = $r_class_name->define_boolexpr(@_);
1612 0         0 for my $value (@{ $self->{$plural_name} }) {
  0         0  
1613 0 0       0 if ($rule->evaluate($value)) {
1614 0         0 push @keep, $value;
1615             }
1616             else {
1617 0         0 push @remove, $value;
1618             }
1619             }
1620 0 0       0 if (@remove) {
1621 0         0 @{ $self->{$plural_name} } = @keep;
  0         0  
1622             }
1623 0         0 return @remove;
1624             }
1625             else {
1626             # value (or non-ur object)
1627 2 50       6 if (@_ == 1) {
    0          
1628             # remove specific value
1629 2         3 my $removed;
1630 2         3 my $n = 0;
1631 2         3 for my $value (@{ $self->{$plural_name} }) {
  2         6  
1632 5 100       9 if ($value eq $_[0]) {
1633 2         3 $removed = splice(@{ $self->{$plural_name} }, $n, 1);
  2         6  
1634 2 50       5 Carp::croak("Internal object inconsistency removing value '$value'. Value '$removed' was removed instead!?") unless $removed eq $value;
1635 2         5 return $removed;
1636             }
1637 3         3 $n++;
1638             }
1639 0         0 Carp::croak("Failed to find item $_[0] in $class_name $plural_name. Object has " . scalar(@{$self->{$plural_name}}) . " values: ".join(', ', @{$self->{$plural_name}}));
  0         0  
  0         0  
1640             }
1641             elsif (@_ == 0) {
1642             # remove all if no params are specified
1643 0   0     0 @{ $self->{$plural_name} ||= [] } = ();
  0         0  
1644             }
1645             else {
1646 0         0 Carp::croak("$class_name $remover_method_name should be called with zero or one arg, got ".scalar(@_));
1647             }
1648             }
1649             }
1650 2734         119532 };
1651              
1652             # check here
1653 2734         10418 Sub::Install::reinstall_sub({
1654             into => $class_name,
1655             as => $remover_method_name,
1656             code => $remover_method,
1657             });
1658              
1659             }
1660              
1661 266     266   1608 use Data::Dumper;
  266         423  
  266         85327  
1662              
1663             sub initialize_direct_accessors {
1664 24690     24699 0 25793 my $self = shift;
1665 24690         32940 my $class_name = $self->{class_name};
1666              
1667 24690         21553 my %id_property_names;
1668 24690         22872 for my $property_name (@{ $self->{id_by} }) {
  24690         49982  
1669 6895         10680 $id_property_names{$property_name} = 1;
1670 6895 100       15205 next if $property_name eq "id";
1671             }
1672              
1673 24690         24282 my %dimensions_by_fk;
1674 24690         25038 for my $property_name (sort keys %{ $self->{has} }) {
  24690         97570  
1675 95383         73374 my $property_data = $self->{has}{$property_name};
1676 95383 50       140299 if ($property_data->{is_dimension}) {
1677 0         0 my $id_by = $property_data->{id_by};
1678 0 0       0 unless ($id_by) {
1679 0         0 Carp::croak "No id_by specified for dimension $property_name?";
1680             }
1681 0 0       0 if (@$id_by != 1) {
1682 0         0 Carp::croak "The id_by specified for dimension $property_name must list a single property name!";
1683             }
1684              
1685 0         0 my $dimension_class_name = $property_data->{data_type};
1686 0         0 $dimensions_by_fk{$id_by->[0]} = $dimension_class_name;
1687              
1688 0         0 my $ref_class_meta = $dimension_class_name->__meta__;
1689 0         0 my %remote_id_properties = map { $_ => 1 } $ref_class_meta->id_property_names;
  0         0  
1690 0         0 my @non_id_properties = grep { not $remote_id_properties{$_} } $ref_class_meta->all_property_names;
  0         0  
1691 0         0 for my $expected_delegate_property_name (@non_id_properties) {
1692 0 0       0 unless ($self->{has}{$expected_delegate_property_name}) {
1693 0         0 $self->{has}{$expected_delegate_property_name} = {
1694             $self->_normalize_property_description(
1695             $expected_delegate_property_name,
1696             { via => $property_name, to => $expected_delegate_property_name, implied_by => $property_name }
1697             )
1698             }
1699             }
1700             }
1701             }
1702             }
1703              
1704 24690         28105 for my $pname (sort keys %{ $self->{has} }) {
  24690         62734  
1705 95383         2812151 my $property_name = $pname; # mutable
1706 95383         77821 my $accessor_name = $pname;
1707              
1708 95383         159299 my $property_data = $self->{has}{$property_name};
1709              
1710             # handle aliases
1711             # the underlying property_name and data will change, though the accessor will not
1712 95383         76485 my $n = 0;
1713 95383   100     241394 while ($property_data->{via} and $property_data->{via} eq '__self__') {
1714 6         16 $property_name = $property_data->{to};
1715 6         12 $property_data = $self->{has}{$property_name};
1716 6 50       17 unless ($property_data) {
1717 0         0 Carp::confess("Property $accessor_name is an alias for $property_name, which does not exist!")
1718             }
1719 6 50       22 if ($n > 100) {
1720 0         0 Carp::confess("Deep recursion in property aliases behind $accessor_name!");
1721             }
1722             }
1723              
1724 95383         89031 my $column_name = $property_data->{column_name};
1725 95383         92485 my $is_transient = $property_data->{is_transient};
1726 95383         82424 my $where = $property_data->{where};
1727              
1728 95383         67489 do {
1729             # Handle the case where the software module has an explicit
1730             # override for one of the accessors.
1731 266     266   1419 no strict 'refs';
  266         399  
  266         158247  
1732 95383         61625 my $isa = \@{ $class_name . "::ISA" };
  95383         190396  
1733 95383         125822 my @old_isa = @$isa;
1734 95383         1218333 @$isa = ();
1735 95383 100       438905 if ($class_name->can($accessor_name)) {
1736             #warn "property $class_name $accessor_name exists!";
1737 5321         43140 $accessor_name = "__$accessor_name";
1738             }
1739 95383         3597842 @$isa = @old_isa;
1740             };
1741              
1742 95383 50       269943 unless ($accessor_name) {
1743 0         0 Carp::croak("No accessor name for property '$property_name' of class $class_name");
1744             }
1745              
1746 95383         70500 my $accessor_type;
1747 95383         137348 my @calculation_fields = (qw/calculate calc_perl calc_sql calculate_from/);
1748 95383 100 100     577705 if (my $id_by = $property_data->{id_by}) {
    100 66        
    100          
    100          
    100          
    100          
    100          
1749 4684         8025 my $r_class_name = $property_data->{data_type};
1750             #$self->mk_id_based_object_accessor($class_name, $accessor_name, $id_by, $r_class_name,$where);
1751 4684         8303 my $id_class_by = $property_data->{id_class_by};
1752 4684 50 33     18779 if ($property_data->{access_as} and $property_data->{access_as} eq 'auto') {
1753 0         0 $self->mk_id_based_flex_accessor($class_name, $accessor_name, $id_by, $r_class_name,$where, $id_class_by);
1754 0 0       0 $self->mk_id_based_object_accessor($class_name, $accessor_name . ($property_data->{is_many} ? '_objs' : '_obj'), $id_by, $r_class_name,$where, $id_class_by);
1755             }
1756             else {
1757 4684         24166 $self->mk_id_based_object_accessor($class_name, $accessor_name, $id_by, $r_class_name,$where, $id_class_by);
1758             }
1759             }
1760             elsif ($property_data->{'is_calculated'} and ! $property_data->{'is_mutable'}) {# and $property_data->{'column_name'}) {
1761             # For calculated + immutable properties, their calculation function is called
1762             # by UR::Context->create_entity(), which then stores the value in the object's
1763             # hash. So, the accessor just needs to pull the data like a regular r/o accessor
1764             #$self->mk_ro_accessor($class_name, $accessor_name, $property_data->{'column_name'});
1765             $self->mk_calculation_accessor(
1766             $class_name,
1767             $accessor_name,
1768             $property_data->{'calculate'},
1769             $property_data->{calculate_from},
1770             $property_data->{calculate_params},
1771             1, # the value should be cached
1772 4         37 $property_data->{'column_name'},
1773             );
1774             }
1775             elsif (my $via = $property_data->{via}) {
1776 18019   33     35549 my $to = $property_data->{to} || $property_data->{property_name};
1777 18019 50       30557 if ($via eq '__self__') {
1778 0         0 Carp::croak "aliases should be caught above!";
1779              
1780             }
1781 18019 100       24682 if ($property_data->{is_mutable}) {
1782 14         19 my $singular_name;
1783 14 100       42 if ($property_data->{'is_many'}) {
1784 8         37 require Lingua::EN::Inflect;
1785 8         28 $singular_name = Lingua::EN::Inflect::PL_V($accessor_name);
1786             }
1787 14   66     810 $self->mk_indirect_rw_accessor($class_name,$accessor_name,$via,$to,$where,$property_data->{'is_many'} && $singular_name, $property_name);
1788             }
1789             else {
1790 18005         39093 $self->mk_indirect_ro_accessor($class_name,$accessor_name,$via,$to,$where);
1791             }
1792             }
1793             elsif (my $calculate = $property_data->{calculate}) {
1794             $self->mk_calculation_accessor(
1795             $class_name,
1796             $accessor_name,
1797             $property_data->{calculate},
1798             $property_data->{calculate_from},
1799             $property_data->{calculate_params},
1800             $property_data->{is_constant},
1801             $property_data->{column_name},
1802 1891         13797 );
1803             }
1804             elsif (my $calculate_sql = $property_data->{'calculate_sql'}) {
1805             # The data gets filled in by the object loader behind the scenes.
1806             # To the user, it's a read-only property
1807 2         7 $self->mk_ro_accessor($class_name, $accessor_name, $calculate_sql);
1808              
1809             }
1810             elsif ($property_data->{is_many} or $property_data->{reverse_as}){
1811 2736         4443 my $reverse_as = $property_data->{reverse_as};
1812 2736         4131 my $r_class_name = $property_data->{data_type};
1813 2736         3127 my $singular_name;
1814             my $plural_name;
1815 2736 100       5129 if ($property_data->{is_many}) {
1816 2734         3304 $plural_name = $accessor_name;
1817 2734         12317 $singular_name = $self->singular_accessor_name_for_is_many_accessor($accessor_name);
1818             }
1819             else {
1820 2         3 $singular_name = $accessor_name;
1821             }
1822 2736         11467 $self->mk_object_set_accessors($class_name, $singular_name, $plural_name, $reverse_as, $r_class_name, $where);
1823             }
1824             elsif ($property_data->{'is_classwide'}) {
1825             my($value, $column_name, $is_transient, $calc_default)
1826 183         708 = @$property_data{'default_value','column_name','is_transient', 'calculated_default'};
1827 183 100       638 if ($property_data->{'is_constant'}) {
1828 180         1488 $self->mk_ro_class_accessor($class_name,$accessor_name,$column_name,$value, $calc_default);
1829             } else {
1830 3         19 $self->mk_rw_class_accessor($class_name,$accessor_name,$column_name,$is_transient,$value, $calc_default);
1831             }
1832             }
1833             else {
1834             # Just use key/value pairs in the hash for normal
1835             # table stuff, and also non-database stuff.
1836              
1837             #if ($column_name) {
1838             # push @$props, $property_name;
1839             # push @$cols, $column_name;
1840             #}
1841              
1842 67864         48624 my $maker;
1843 67864 100 100     209230 if ($id_property_names{$property_name} or not $property_data->{is_mutable}) {
1844 7904         9023 $maker = 'mk_ro_accessor';
1845             }
1846             else {
1847 59960         51190 $maker = 'mk_rw_accessor';
1848             }
1849 67864         164051 $self->$maker($class_name, $accessor_name, $column_name, $property_name,$is_transient);
1850             }
1851             }
1852              
1853             # right now we just stomp on the default accessors constructed above where they are:
1854             # 1. the fk behind a dimensional relationships
1855             # 2. the indirect properties created for the dimensional relationship
1856 24690         267396 for my $dimension_id (keys %dimensions_by_fk) {
1857 0         0 my $dimension_class_name = $dimensions_by_fk{$dimension_id};
1858 0         0 my $ref_class_meta = $dimension_class_name->__meta__;
1859 0         0 my %remote_id_properties = map { $_ => 1 } $ref_class_meta->id_property_names;
  0         0  
1860 0         0 my @non_id_properties = grep { not $remote_id_properties{$_} } $ref_class_meta->all_property_names;
  0         0  
1861 0         0 for my $added_property_name (@non_id_properties) {
1862 0         0 $self->mk_dimension_delegate_accessors($dimension_id,$dimension_class_name, \@non_id_properties, $added_property_name);
1863             }
1864 0         0 $self->mk_dimension_identifying_accessor($dimension_id,$dimension_class_name, \@non_id_properties);
1865             }
1866              
1867 24690         49072 return 1;
1868             }
1869              
1870              
1871             1;
1872              
1873             =pod
1874              
1875             =head1 NAME
1876              
1877             UR::Object::Type::AccessorWriter - Helper module for UR::Object::Type responsible for creating accessors for properties
1878              
1879             =head1 DESCRIPTION
1880              
1881             Subroutines within this module actually live in the UR::Object::Type
1882             namespace; this module is just a convienent place to collect them. The
1883             class initializer uses these subroutines when it's time to create accessor
1884             methods for a newly defined class. Each accessor is implemented by a closure
1885             that is then assigned a name by Sub::Name and inserted into the defined
1886             class's namespace by Sub::Install.
1887              
1888             =head1 METHODS
1889              
1890             =over 4
1891              
1892             =item initialize_direct_accessors
1893              
1894             $classobj->initialize_direct_accessors();
1895              
1896             This is the entry point into the accessor writing system. It inspects each
1897             item in the 'has' key of the class object's hashref, and creates methods for
1898             each property.
1899              
1900             =item mk_rw_accessor
1901              
1902             $classobj->mk_rw_accessor($class_name, $accessor_name, $column_name, $property_name, $is_transient);
1903              
1904             Creates a mutable accessor named $accessor_name which stores its value in
1905             the $property_name key of the object's hashref.
1906              
1907             =item mk_ro_accessor
1908              
1909             $classobj->mk_ro_accessor($class_name, $accessor_name, $column_name, $property_name);
1910              
1911             Creates a read-only accessor named $accessor_name which retrieves its value
1912             in the $property_name key of the object's hashref. If the method is used
1913             as a mutator by passing in a value to the method, it will throw an exception
1914             with Carp::croak.
1915              
1916             =item mk_id_based_object_accessor
1917              
1918             $classobj->mk_id_based_object_accessor($class_name, $accessor_name, $id_by,
1919             $r_class_name, $where);
1920              
1921             Creates an object accessor named $accessor_name. It returns objects of type
1922             $r_class_name, id-ed by the parameters named in the $id_by arrayref. $where
1923             is an optional listref of additional filters to apply when retrieving
1924             objects.
1925              
1926             The behavior of the created accessor depends on the number of parameters
1927             passed to it. For 0 params, it retrieves the object pointed to by
1928             $r_class_name and $id_by. For 1 param, it looks up the ID param values
1929             of the passed-in object-parameter, and reassigns value stored in the $id_by
1930             properties of the acted-upon object, effectively acting as a mutator.
1931              
1932             For more than 1 param, the additional parameters are taken as
1933             properties/values to filter the returned objects on
1934              
1935             =item mk_indirect_ro_accessor
1936              
1937             $classobj->mk_indirect_ro_accessor($class_name, $accessor_name, $via, $to, $where);
1938              
1939             Creates a read-only via accessor named $accessor_name. Its value is
1940             obtained by calling the object accessor named $via, and then calling
1941             the method $to on that object. The optional $where listref is used
1942             as additional filters when calling $via.
1943              
1944             =item mk_indirect_rw_accessor
1945              
1946             $classobj->mk_indirect_rw_accessor($class_name, $accessor_name, $via, $to,
1947             $where, $singular_name);
1948              
1949             Creates a via accessor named $accessor_name that is able to change the
1950             property it points to with $to when called as a mutator. If the $to property
1951             on the remote object is an ID property of its class, it deletes the refered-to
1952             object and creates a new one with the appropriate properties. Otherwise, it
1953             updates the $to property on the refered-to object.
1954              
1955             =item mk_calculation_accessor
1956              
1957             $classobj->mk_calculation_accessor($class_name, $accessor_name, $calculation_src,
1958             $calculate_from, $params, $is_constant, $column_name);
1959              
1960             Creates a calculated accessor called $accessor_name. If the $is_constant
1961             flag is true, then the accessor runs the calculation once, caches the result,
1962             and returns that result for subsequent calls to the accessor.
1963              
1964             $calculation_src can be one of: coderef, string containing Perl code, or
1965             the name of a module under UR::Object::Type::AccessorWriter which has a
1966             method called C. If $calculation_src is empty, then $accessor_name
1967             must be the name of an already-existing subroutine in the class's namespace.
1968              
1969             =item mk_dimension_delegate_accessors
1970              
1971             =item mk_dimension_identifying_accessor
1972              
1973             These create accessors for dealing with dimension tables in OLAP-type schemas.
1974             They need more documentation.
1975              
1976             =item mk_rw_class_accessor
1977              
1978             $classobj->mk_rw_class_accessor($class_name, $accessor_name, $column_name, $is_transient, $variable_value);
1979              
1980             Creates a read-write accessor called $accessor_name which stores its value
1981             in a scalar captured by the accessor's closure. Since the closure is
1982             inserted into the class's namespace, all instances of the class share the
1983             same closure (and therefore the same scalar), and the property effectively
1984             acts as a class-wide property.
1985              
1986             =item mk_ro_class_accessor
1987              
1988             $classobj->mk_ro_class_accessor($class_name, $accessor_name, $column_name, $variable_value);
1989              
1990             Creates a read-only accessor called $accessor_name which retrieves its value
1991             from a scalar captured by the accessor's closure. The value is initialized
1992             to $variable_value. If called as a mutator, it throws an exception through
1993             Carp::croak
1994              
1995             =back
1996              
1997             =head1 SEE ALSO
1998              
1999             UR::Object::Type::AccessorWriter, UR::Object::Type
2000              
2001             =cut