File Coverage

lib/UR/Object/Property.pm
Criterion Covered Total %
statement 184 206 89.3
branch 96 118 81.3
condition 37 71 52.1
subroutine 26 27 96.3
pod 3 14 21.4
total 346 436 79.3


line stmt bran cond sub pod time code
1             package UR::Object::Property;
2 396     396   22544 use warnings;
  267         3155  
  267         7724  
3 318     318   9094 use strict;
  266         401  
  266         6355  
4              
5             require UR;
6              
7 301     301   187606 use Lingua::EN::Inflect;
  266         4921683  
  266         23419  
8 298     298   7280 use Class::AutoloadCAN;
  266         429  
  266         3150  
9              
10             our $VERSION = "0.46"; # UR $VERSION;;
11             our @CARP_NOT = qw( UR::DataSource::RDBMS UR::Object::Type );
12              
13             # class_meta and r_class_meta duplicate the functionality if two properties of the same name,
14             # but these are faster
15             sub class_meta {
16 1959     1959 1 41449 return shift->{'class_name'}->class->__meta__;
17             }
18              
19             sub r_class_meta {
20 856     874 0 5943 return shift->{'data_type'}->class->__meta__;
21             }
22              
23              
24             sub is_direct {
25 0     18 0 0 my $self = shift;
26 18 0 0     2880 if ($self->is_calculated or $self->is_constant or $self->is_many or $self->via) {
      0        
      0        
27 0         0 return 0;
28             }
29 0         0 return 1;
30             }
31              
32             sub is_numeric {
33 2296     2295 0 6172 my $self = shift;
34 2278 100       5997 unless (defined($self->{'_is_numeric'})) {
35 1238         3610 my $class = $self->_data_type_as_class_name;
36 1255 50       6056 unless ($class) {
37 0         0 return;
38             }
39 1238         10019 $self->{'_is_numeric'} = $class->isa("UR::Value::Number");
40             }
41 2295         23837 return $self->{'_is_numeric'};
42             }
43              
44             sub is_text {
45 3     20 0 5 my $self = shift;
46 3 100       10 unless (defined($self->{'_is_text'})) {
47 18         3343 my $class = $self->_data_type_as_class_name;
48 1 50       3 unless ($class) {
49 0         0 return;
50             }
51 1         7 $self->{'_is_text'} = $class->isa("UR::Value::Text");
52             }
53 3         7 return $self->{'_is_text'};
54             }
55              
56             sub is_valid_storage_for_value {
57 116     133 0 175 my($self, $value) = @_;
58              
59 116         313 my $data_class_name = $self->_data_type_as_class_name;
60 116 100       1489 return 1 if ($value->isa($data_class_name));
61              
62 40 100       475 if ($data_class_name->isa('UR::Value') ) {
63 39         199 my @underlying_types = $data_class_name->underlying_data_types;
64 39         90 foreach my $underlying_type ( @underlying_types ) {
65 8 50       17 return 1 if ($value->isa($underlying_type));
66             }
67             }
68 32         195 return 0;
69             }
70              
71             sub alias_for {
72 6595     6595 0 7062 my $self = shift;
73              
74 6595 100 66     20009 if ($self->{'via'} and $self->{'to'} and $self->{'via'} eq '__self__') {
      66        
75 4         14 return $self->{'to'};
76             } else {
77 6591         19912 return $self->{'property_name'};
78             }
79             }
80              
81             sub _convert_data_type_for_source_class_to_final_class {
82 2712     2712   4091 my ($class, $foreign_class, $source_class) = @_;
83              
84 2712   100     5816 $foreign_class ||= '';
85              
86             # TODO: allowing "is => 'Text'" instead of is => 'UR::Value::Text' is syntactic sugar
87             # We should have an is_primitive flag set on these so we do efficient work.
88              
89 2712         13304 my ($ns) = ($source_class =~ /^([^:]+)::/);
90 2712 100 100     19307 if ($ns and not $ns->isa("UR::Namespace")) {
91 97         1170 $ns = undef;
92             }
93              
94 2712         3043 my $final_class;
95 2712 100       5403 if ($foreign_class) {
96 2414 100       14697 if ($foreign_class->can('__meta__')) {
97 206         1668 $final_class = $foreign_class;
98             }
99             else {
100 2208         134941 my ($ns_value_class, $ur_value_class);
101              
102 2208 100 66     9488 if ($ns and $ns->can("get")) {
103 2072         15573 $ns_value_class = $ns . '::Value::' . $foreign_class;
104 2072 100       8433 if ($ns_value_class->can('__meta__')) {
105 665         46008 $final_class = $ns_value_class;
106             }
107             }
108              
109 2208 100       79771 if (!$final_class) {
110 1543         2661 $ur_value_class = 'UR::Value::' . $foreign_class;
111 1543 100       6514 if ($ur_value_class->can('__meta__')) {
112 428         19994 $final_class = $ur_value_class;
113             }
114             }
115 2208 100       35889 if (!$final_class) {
116 1115         3003 $ur_value_class = 'UR::Value::' . ucfirst(lc($foreign_class));
117 1115 100       3470 if ($ur_value_class->can('__meta__')) {
118 150         4428 $final_class = $ur_value_class;
119             }
120             }
121             }
122             }
123              
124 2712 100       32302 if (!$final_class) {
125 1263 100       6518 if (Class::Autouse->class_exists($foreign_class)) {
    50          
126 10         176 return $foreign_class;
127             }
128             elsif ($foreign_class =~ /::/) {
129 0         0 return $foreign_class;
130             }
131             else {
132 298     298   50905 eval "use $foreign_class;";
  0     297   0  
  0         0  
  297         47028  
  0         0  
  0         0  
  1253         270684  
133 1253 100       4190 if (!$@) {
134 1         6 return $foreign_class;
135             }
136            
137 1252 50 66     27475 if (!$ns or $ns->get()->allow_sloppy_primitives) {
138             # no colons, and no namespace: no choice but to assume it's a sloppy primitive
139 1252         6728 return 'UR::Value::SloppyPrimitive';
140             }
141             else {
142 0         0 Carp::confess("Failed to find a ${ns}::Value::* or UR::Value::* module for primitive type $foreign_class!");
143             }
144             }
145             }
146              
147 1449         8683 return $final_class;
148             }
149              
150             sub _data_type_as_class_name {
151 4287     4287   6186 my $self = $_[0];
152 4287   66     17125 return $self->{_data_type_as_class_name} ||= do {
153 2609         6879 my $source_class = $self->class_name;
154             #this is so NUMBER -> Number
155 2609         6456 my $foreign_class = $self->data_type;
156              
157            
158 2609 100       5569 if (not $foreign_class) {
159 235 100 66     787 if ($self->via or $self->to) {
160 4         18 my @joins = UR::Object::Join->resolve_chain(
161             $self->class_name,
162             $self->property_name,
163             $self->property_name,
164             );
165 4         20 $foreign_class = $joins[-1]->foreign_class;
166             }
167             }
168              
169 2609         7364 __PACKAGE__->_convert_data_type_for_source_class_to_final_class($foreign_class, $source_class);
170             };
171             }
172              
173             # TODO: this is a method on the data source which takes a given property.
174             # Returns the table and column for this property.
175             # If this particular property doesn't have a column_name, and it
176             # overrides a property defined on a parent class, then walk up the
177             # inheritance and find the right one
178             sub table_and_column_name_for_property {
179 320     320 0 357 my $self = shift;
180              
181             # Shortcut - this property has a column_name, so the class should have the right
182             # table_name
183 320 100       705 if ($self->column_name) {
184 317         684 return ($self->class_name->__meta__->table_name, $self->column_name);
185             }
186              
187 3         24 my $property_name = $self->property_name;
188 3         13 my @class_metas = $self->class_meta->parent_class_metas;
189              
190 3         5 my %seen;
191 3         11 while (@class_metas) {
192 3         7 my $class_meta = shift @class_metas;
193 3 50       17 next if ($seen{$class_meta}++);
194              
195 3         18 my $p = $class_meta->property_meta_for_name($property_name);
196 3 100       12 next unless $p;
197              
198 2 50 33     9 if ($p->column_name && $class_meta->table_name) {
199 2         10 return ($class_meta->table_name, $p->column_name);
200             }
201              
202 0         0 push @class_metas, $class_meta->parent_class_metas;
203             }
204              
205             # This property has no column anywhere in the class' inheritance
206 1         4 return;
207             }
208              
209              
210             # Return true if resolution of this property involves an ID property of
211             # any class.
212             sub _involves_id_property {
213 46     46   41 my $self = shift;
214              
215 46         92 my $is_id = $self->is_id;
216 46 100       157 return 1 if defined($is_id);
217              
218 33 100       65 if ($self->id_by) {
219 5         17 my $class_meta = $self->class_meta;
220 5         16 my $id_by_list = $self->id_by;
221 5         13 foreach my $id_by ( @$id_by_list ) {
222 5         14 my $id_by_meta = $class_meta->property_meta_for_name($id_by);
223 5 50 33     26 return 1 if ($id_by_meta and $id_by_meta->_involves_id_property);
224             }
225             }
226              
227 28 100       60 if ($self->via) {
228 13         38 my $via_meta = $self->via_property_meta;
229 13 50 33     139 return 1 if ($via_meta and $via_meta ne $self and $via_meta->_involves_id_property);
      33        
230              
231 13 50       39 if ($self->to) {
232 13         41 my $to_meta = $self->to_property_meta;
233 13 100 66     56 return 1 if ($to_meta and $to_meta->_involves_id_property);
234              
235 2 50       7 if ($self->where) {
236 2 50       19 unless ($to_meta) {
237 0         0 Carp::confess("Property '" . $self->property_name . "' of class " . $self->class_name
238             . " has 'to' metadata that does not resolve to a known property.");
239             }
240 2         6 my $other_class_meta = $to_meta->class_meta;
241 2         7 my $where = $self->where;
242 2         9 for (my $i = 0; $i < @$where; $i += 2) {
243 2         6 my $where_meta = $other_class_meta->property_meta_for_name($where->[$i]);
244 2 50 33     9 return 1 if ($where_meta and $where_meta->_involves_id_property);
245             }
246             }
247             }
248             }
249 15         54 return 0;
250             }
251              
252              
253             # For via/to delegated properties, return the property meta in the same
254             # class this property delegates through
255             sub via_property_meta {
256 882     882 1 1267 my $self = shift;
257              
258 882 50 33     2089 return unless ($self->is_delegated and $self->via);
259 882         10405 my $class_meta = $self->class_meta;
260 882         53934 return $class_meta->property_meta_for_name($self->via);
261             }
262              
263             sub final_property_meta {
264 161     161 0 233 my $self = shift;
265              
266 161         182 my $closure;
267             $closure = sub {
268 267 50   267   531 return unless defined $_[0];
269 267 100 100     901 if ($_[0]->is_delegated and $_[0]->via) {
270 106 50       342 if ($_[0]->to) {
271 106         561 return $closure->($_[0]->to_property_meta);
272             } else {
273 0         0 return $closure->($_[0]->via_property_meta);
274             }
275             } else {
276 161         327 return $_[0];
277             }
278 161         769 };
279 161         348 my $final = $closure->($self);
280              
281 161 100 66     746 return if !defined $final || $final->id eq $self->id;
282 91         252 return $final;
283             }
284              
285             # For via/to delegated properties, return the property meta on the foreign
286             # class that this property delegates to
287             sub to_property_meta {
288 865     865 1 1779 my $self = shift;
289              
290 865 50 33     2490 return unless ($self->is_delegated && $self->to);
291              
292 865         3008 my $via_meta = $self->via_property_meta();
293 865 100       46295 return unless $via_meta;
294              
295 864         20801 my $remote_class = $via_meta->data_type;
296             # unless ($remote_class) {
297             # # Can we guess what the data type is for multiply indirect properties?
298             # if ($via_meta->to) {
299             # my $to_property_meta = $via_meta->to_property_meta;
300             # $remote_class = $to_property_meta->data_type if ($to_property_meta);
301             # }
302             # }
303 864 50       3996 return unless $remote_class;
304 864         3596 my $remote_class_meta = UR::Object::Type->get($remote_class);
305 864 100       2086 return unless $remote_class_meta;
306              
307 858         2349 return $remote_class_meta->property_meta_for_name($self->to);
308             }
309              
310              
311             sub get_property_name_pairs_for_join {
312 8738     8738 0 10388 my ($self) = @_;
313 8738 100       18709 unless ($self->{'_get_property_name_pairs_for_join'}) {
314 875         2855 my @linkage = $self->_get_direct_join_linkage();
315 873 100       2186 unless (@linkage) {
316 17         28 Carp::croak("Cannot resolve underlying property joins for property '"
317             . $self->property_name . "' of class "
318             . $self->class_name
319             . ": Couldn't determine which properties link to the remote class");
320             }
321 856         1076 my @results;
322 856 100       2818 if ($self->reverse_as) {
323 36         85 @results = map { [ $_->[1] => $_->[0] ] } @linkage;
  37         151  
324             } else {
325 820         1542 @results = map { [ $_->[0] => $_->[1] ] } @linkage;
  1256         3310  
326             }
327 856         2386 $self->{'_get_property_name_pairs_for_join'} = \@results;
328             }
329 8719         7949 return @{$self->{'_get_property_name_pairs_for_join'}};
  8719         22104  
330             }
331              
332             sub _get_direct_join_linkage {
333 912     912   1469 my ($self) = @_;
334 912         1183 my @retval;
335 912 100       2552 if (my $id_by = $self->id_by) {
    100          
336 856         3148 my $r_class_meta = $self->r_class_meta;
337 856 50       2363 unless ($r_class_meta) {
338 0         0 Carp::croak("Property '" . $self->property_name . "' of class '" . $self->class_name . "' "
339             . "has data_type '" . $self->data_type ."' with no class metadata");
340             }
341              
342 856         1107 my @my_id_by = @{ $self->id_by };
  856         2138  
343 856         1219 my @their_id_by = @{ $r_class_meta->{'id_by'} };
  856         2565  
344 856 100 100     5465 if (! @their_id_by
      66        
345             or
346             (@my_id_by == 1 and @their_id_by > 1)
347             ) {
348 268         650 @their_id_by = ( 'id' );
349             }
350              
351 856 50       2319 unless (@my_id_by == @their_id_by) {
352 0         0 Carp::croak("Property '" . $self->property_name . "' of class '" . $self->class_name . "' "
353             . "has " . scalar(@my_id_by) . " id_by elements, while its data_type ("
354             . $self->data_type .") has " . scalar(@their_id_by));
355             }
356              
357 856         2650 for (my $i = 0; $i < @my_id_by; $i++) {
358 1293         4141 push @retval, [ $my_id_by[$i], $their_id_by[$i] ];
359             }
360              
361             }
362             elsif (my $reverse_as = $self->reverse_as) {
363 39         102 my $r_class_name = $self->data_type;
364 39         174 @retval =
365             $r_class_name->__meta__->property_meta_for_name($reverse_as)->_get_direct_join_linkage();
366             }
367 910         2019 return @retval;
368             }
369              
370             sub _resolve_join_chain {
371 698     698   1147 my $self = shift;
372 698         1827 return UR::Object::Join->resolve_chain(
373             $self->class_name,
374             $self->property_name,
375             );
376             }
377              
378             sub label_text {
379             # The name of the property in friendly terms.
380 0     0 0 0 my ($self,$obj) = @_;
381 0         0 my $property_name = $self->property_name;
382 0         0 my @words = App::Vocabulary->filter_vocabulary(map { ucfirst(lc($_)) } split(/\s+/,$property_name));
  0         0  
383 0         0 my $label = join(" ", @words);
384 0         0 return $label;
385             }
386              
387             # This gets around the need to make a custom property subclass
388             # when a class has an attributes_have specification.
389              
390             # This primary example of this in base infrastructure is that
391             # all Commands have is_input, is_output and is_param attributes.
392              
393             # Note: it's too permissive and will make an accessor for any hash key.
394             # The updated code should not do this.
395              
396             sub CAN {
397 18109     18109 0 439694 my ($thisclass, $method, $self) = @_;
398 18109 100       29539 if (ref($self)) {
399 4277         5546 my $accessor_key = '_' . $method . "_accessor";
400 4277 100       7695 if (my $method = $self->{$accessor_key}) {
401 2960         4002 return $method;
402             }
403 1317 100 33     3542 if ($self->class_name->__meta__->{attributes_have}
404             and
405             exists $self->class_name->__meta__->{attributes_have}{$method}
406             ) {
407             return $self->{$accessor_key} = sub {
408 2549     2549   18297 return $_[0]->{$method};
409             }
410 263         1134 }
411             }
412 14886         20842 return;
413             }
414              
415              
416             1;
417              
418             =pod
419              
420             =head1 NAME
421              
422             UR::Object::Property - Class representing metadata about a class property
423              
424             =head1 SYNOPSIS
425              
426             my $prop = UR::Object::Property->get(class_name => 'Some::Class', property_name => 'foo');
427              
428             my $class_meta = Some::Class->__meta__;
429             my $prop2 = $class_meta->property_meta_for_name('foo');
430              
431             # Print out the meta-property name and its value of $prop2
432             print map { " $_ : ".$prop2->$_ }
433             qw(class_name property_name data_type default_value);
434              
435             =head1 DESCRIPTION
436              
437             Instances of this class represent properties of classes. For every item
438             mentioned in the 'has' or 'id_by' section of a class definition become Property
439             objects.
440              
441             =head1 INHERITANCE
442              
443             UR::Object::Property is a subclass of L
444              
445             =head1 PROPERTY TYPES
446              
447             For this class definition:
448             class Some::Class {
449             has => [
450             other_id => { is => 'Text' },
451             other => { is => 'Some::Other', id_by => 'foo_id' },
452             bar => { via => 'other', to => 'bar' },
453             foos => { is => 'Some::Foo', reverse_as => 'some', is_many => 1 },
454             uc_other_id => { calculate_from => 'other_id',
455             calculate_perl => 'uc($other_id)' },
456             ],
457             };
458            
459             Properties generally fall in to one of these categories:
460              
461             =over 4
462              
463             =item regular property
464              
465             A regular property of a class holds a single scalar. In this case,
466             'other_id' is a regular property.
467              
468             =item object accessor
469              
470             An object accessor property returns objects of some class. The properties
471             of this class must link in some way with all the ID properties of the remote
472             class (the 'is' declaration). 'other' is an object accessor property. This
473             is how one-to-one relationships are implemented.
474              
475             =item via property
476              
477             When a class has some object accessor property, and it is helpful for an
478             object to assumme the value of the remote class's properties, you can set
479             up a 'via' property. In the example above, an object of this class
480             gets the value of its 'bar' property via the 'other' object it's linked
481             to, from that object's 'bar' property.
482              
483             =item reverse as or is many property
484              
485             This is how one-to-many relationships are implemented. In this case,
486             the Some::Foo class must have an object accessor property called 'some',
487             and the 'foos' property will return a list of all the Some::Foo objects
488             where their 'some' property would have returned that object.
489              
490             =item calculated property
491              
492             A calculated property doesn't store its data directly in the object, but
493             when its accessor is called, the calculation code is executed.
494              
495             =back
496              
497             =head1 PROPERTIES
498              
499             Each property has a method of the same name
500              
501             =head2 Direct Properties
502              
503             =over 4
504              
505             =item class_name => Text
506              
507             The name of the class this Property is attached to
508              
509             =item property_name => Text
510              
511             The name of the property. The pair of class_name and property name are
512             the ID properties of UR::Object::Property
513              
514             =item column_name => Text
515              
516             If the class is backed by a database table, then the column this property's
517             data comes from is stored here
518              
519             =item data_type => Text
520              
521             The type of data stored in this property. Corresponds to the 'is' part of
522             a class's property definition.
523              
524             =item data_length => Number
525              
526             The maximum size of data stored in this property
527              
528             =item default_value
529              
530             For is_optional properties, the default value given when an object is created
531             and this property is not assigned a value.
532              
533             =item valid_values => ARRAY
534              
535             A listref of enumerated values this property may be set to
536              
537             =item doc => Text
538              
539             A place for documentation about this property
540              
541             =item is_id => Boolean
542              
543             Indicates whether this is an ID property of the class
544              
545             =item is_optional => Boolean
546              
547             Indicates whether this is property may have the value undef when the object
548             is created
549              
550             =item is_transient => Boolean
551              
552             Indicates whether this is property is transient?
553              
554             =item is_constant => Boolean
555              
556             Indicates whether this property can be changed after the object is created.
557              
558             =item is_mutable => Boolean
559              
560             Indicates this property can be changed via its accessor. Properties cannot
561             be both constant and mutable
562              
563             =item is_volatile => Boolean
564              
565             Indicates this property can be changed by a mechanism other than its normal
566             accessor method. Signals are not emitted even when it does change via
567             its normal accessor method.
568              
569             =item is_classwide => Boolean
570              
571             Indicates this property's storage is shared among all instances of the class.
572             When the value is changed for one instance, that change is effective for all
573             instances.
574              
575             =item is_delegated => Boolean
576              
577             Indicates that the value for this property is not stored in the object
578             directly, but is delegated to another object or class.
579              
580             =item is_calculated => Boolean
581              
582             Indicates that the value for this property is not a part of the object'd
583             data directly, but is calculated in some way.
584              
585             =item is_transactional => Boolean
586              
587             Indicates the changes to the value of this property is tracked by a Context's
588             transaction and can be rolled back if necessary.
589              
590             =item is_abstract => Boolean
591              
592             Indicates this property exists in a base class, but must be overridden in
593             a derived class.
594              
595             =item is_concrete => Boolean
596              
597             Antonym for is_abstract. Properties cannot be both is_abstract and is_concrete,
598              
599             =item is_final => Boolean
600              
601             Indicates this property cannot be overridden in a derived class.
602              
603             =item is_deprecated => Boolean
604              
605             Indicates this property's use is deprecated. It has no effect in the use
606             of the property in any way, but is useful in documentation.
607              
608             =item implied_by => Text
609              
610             If this property is created as a result of another property's existence,
611             implied_by is the name of that other property. This can happen in the
612             case where an object accessor property is defined
613              
614             has => [
615             foo => { is => 'Some::Other', id_by => 'foo_id' },
616             ],
617              
618             Here, the 'foo' property requires another property called 'foo_id', which
619             is not explicitly declared. In this case, the Property named foo_id will
620             have its implied_by set to 'foo'.
621              
622             =item id_by => ARRAY
623              
624             In the case of an object accessor property, this is the list of properties in
625             this class that link to the ID properties in the remote class.
626              
627             =item reverse_as => Text
628              
629             Defines the linking property name in the remote class in the case of an
630             is_many relationship
631              
632             =item via => Text
633              
634             For a via-type property, indicates which object accessor to go through.
635              
636             =item to => Text
637              
638             For a via-type property, indicates the property name in the remote class to
639             get its value from. The default value is the same as property_name
640              
641             =item where => ARRAY
642              
643             Supplies additional filters for indirect properties. For example:
644              
645             foos => { is => 'Some::Foo', reverse_as => 'some', is_many => 1 },
646             blue_foos => { via => 'foos', where => [ color => 'blue' ] },
647              
648             Would create a property 'blue_foos' which returns only the related
649             Some::Foo objects that have 'blue' color.
650              
651             =item calculate_from => ARRAY
652              
653             For calculated properties, this is a list of other property names the
654             calculation is based on
655              
656             =item calculate_perl => Text
657              
658             For calculated properties, a string containing Perl code. Any properties
659             mentioned in calculate_from will exist in the code's scope at run time
660             as scalars of the same name.
661              
662             =item class_meta => UR::Object::Type
663              
664             Returns the class metaobject of the class this property belongs to
665              
666             =back
667              
668             =head1 METHODS
669              
670             =over 4
671              
672             =item via_property_meta
673              
674             For via/to delegated properties, return the property meta in the same
675             class this property delegates through
676              
677             =item to_property_meta
678              
679             For via/to delegated properties, return the property meta on the foreign
680             class that this property delegates to
681              
682             =back
683              
684             =head1 SEE ALSO
685              
686             UR::Object::Type, UR::Object::Type::Initializer, UR::Object
687              
688             =cut