| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
package UR::Object; |
|
2
|
|
|
|
|
|
|
|
|
3
|
266
|
|
|
266
|
|
918
|
use warnings; |
|
|
266
|
|
|
|
|
422
|
|
|
|
266
|
|
|
|
|
7683
|
|
|
4
|
266
|
|
|
266
|
|
1024
|
use strict; |
|
|
266
|
|
|
|
|
286
|
|
|
|
266
|
|
|
|
|
6442
|
|
|
5
|
|
|
|
|
|
|
|
|
6
|
|
|
|
|
|
|
require UR; |
|
7
|
|
|
|
|
|
|
|
|
8
|
266
|
|
|
266
|
|
846
|
use Scalar::Util qw(looks_like_number refaddr isweak); |
|
|
266
|
|
|
|
|
300
|
|
|
|
266
|
|
|
|
|
21145
|
|
|
9
|
266
|
|
|
266
|
|
119916
|
use List::MoreUtils qw(any); |
|
|
266
|
|
|
|
|
1974847
|
|
|
|
266
|
|
|
|
|
1351
|
|
|
10
|
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
our @ISA = ('UR::ModuleBase'); |
|
12
|
|
|
|
|
|
|
our $VERSION = "0.46"; # UR $VERSION;; |
|
13
|
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
# Base object API |
|
15
|
|
|
|
|
|
|
|
|
16
|
35773
|
100
|
|
35773
|
1
|
134480
|
sub class { ref($_[0]) || $_[0] } |
|
17
|
|
|
|
|
|
|
|
|
18
|
10887457
|
|
|
10887457
|
1
|
19547617
|
sub id { $_[0]->{id} } |
|
19
|
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
sub create { |
|
21
|
3580
|
|
|
3580
|
1
|
86697
|
$UR::Context::current->create_entity(@_); |
|
22
|
|
|
|
|
|
|
} |
|
23
|
|
|
|
|
|
|
|
|
24
|
|
|
|
|
|
|
sub get { |
|
25
|
246656
|
|
|
246656
|
1
|
967009
|
$UR::Context::current->query(@_); |
|
26
|
|
|
|
|
|
|
} |
|
27
|
|
|
|
|
|
|
|
|
28
|
|
|
|
|
|
|
sub delete { |
|
29
|
672
|
|
|
672
|
1
|
11094
|
$UR::Context::current->delete_entity(@_); |
|
30
|
|
|
|
|
|
|
} |
|
31
|
|
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
sub copy { |
|
33
|
3
|
|
|
3
|
1
|
14
|
my $self = shift; |
|
34
|
3
|
|
|
|
|
9
|
my %override = @_; |
|
35
|
|
|
|
|
|
|
|
|
36
|
3
|
|
|
|
|
13
|
my $meta = $self->__meta__; |
|
37
|
|
|
|
|
|
|
my @copyable_properties = |
|
38
|
3
|
|
100
|
|
|
92
|
grep { !$_->is_delegated && !$_->is_id } |
|
|
8
|
|
|
|
|
18
|
|
|
39
|
|
|
|
|
|
|
$meta->properties; |
|
40
|
|
|
|
|
|
|
|
|
41
|
3
|
|
|
|
|
7
|
my %params; |
|
42
|
3
|
|
|
|
|
6
|
for my $p (@copyable_properties) { |
|
43
|
3
|
|
|
|
|
11
|
my $name = $p->property_name; |
|
44
|
3
|
100
|
|
|
|
10
|
if ($p->is_many) { |
|
45
|
1
|
50
|
|
|
|
4
|
if (my @value = $self->$name) { |
|
46
|
1
|
|
|
|
|
4
|
$params{$name} = \@value; |
|
47
|
|
|
|
|
|
|
} |
|
48
|
|
|
|
|
|
|
} |
|
49
|
|
|
|
|
|
|
else { |
|
50
|
2
|
50
|
|
|
|
8
|
if (defined(my $value = $self->$name)) { |
|
51
|
2
|
|
|
|
|
5
|
$params{$name} = $value; |
|
52
|
|
|
|
|
|
|
} |
|
53
|
|
|
|
|
|
|
} |
|
54
|
|
|
|
|
|
|
} |
|
55
|
|
|
|
|
|
|
|
|
56
|
3
|
|
|
|
|
14
|
return $self->class->create(%params, %override); |
|
57
|
|
|
|
|
|
|
} |
|
58
|
|
|
|
|
|
|
|
|
59
|
|
|
|
|
|
|
|
|
60
|
|
|
|
|
|
|
# Meta API |
|
61
|
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
sub __context__ { |
|
63
|
|
|
|
|
|
|
# In UR, a "context" handles inter-object references so they can cross |
|
64
|
|
|
|
|
|
|
# process boundaries, and interact with persistence systems automatically. |
|
65
|
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
# For efficiency, all context switches update a package-level value. |
|
67
|
|
|
|
|
|
|
|
|
68
|
|
|
|
|
|
|
# We will ultimately need to support objects recording their context explicitly |
|
69
|
|
|
|
|
|
|
# for things such as data maintenance operations. This shouldn't happen |
|
70
|
|
|
|
|
|
|
# during "business logic". |
|
71
|
|
|
|
|
|
|
|
|
72
|
0
|
|
|
0
|
|
0
|
return $UR::Context::current; |
|
73
|
|
|
|
|
|
|
} |
|
74
|
|
|
|
|
|
|
|
|
75
|
|
|
|
|
|
|
sub __meta__ { |
|
76
|
|
|
|
|
|
|
# the class meta object |
|
77
|
|
|
|
|
|
|
# subclasses set this specifically for efficiency upon construction |
|
78
|
|
|
|
|
|
|
# the base class has a generic implementation for boostrapping |
|
79
|
|
|
|
|
|
|
Carp::cluck("using the default __meta__!"); |
|
80
|
|
|
|
|
|
|
my $class_name = shift; |
|
81
|
|
|
|
|
|
|
return $UR::Context::all_objects_loaded->{"UR::Object::Type"}{$class_name}; |
|
82
|
|
|
|
|
|
|
} |
|
83
|
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
# The identity operation. Not particularly useful by itself, but makes |
|
85
|
|
|
|
|
|
|
# things like mapping operations easier and calculate_from metadata able |
|
86
|
|
|
|
|
|
|
# to include the object as function args to calculated properties |
|
87
|
|
|
|
|
|
|
sub __self__ { |
|
88
|
24
|
50
|
|
24
|
|
94
|
return $_[0] if @_ == 1; |
|
89
|
0
|
|
|
|
|
0
|
my $self = shift; |
|
90
|
0
|
|
|
|
|
0
|
my $bx = $self->class->define_boolexpr(@_); |
|
91
|
0
|
0
|
|
|
|
0
|
if ($bx->evaluate($self)) { |
|
92
|
0
|
|
|
|
|
0
|
return $self; |
|
93
|
|
|
|
|
|
|
} |
|
94
|
|
|
|
|
|
|
else { |
|
95
|
0
|
|
|
|
|
0
|
return; |
|
96
|
|
|
|
|
|
|
} |
|
97
|
|
|
|
|
|
|
} |
|
98
|
|
|
|
|
|
|
|
|
99
|
|
|
|
|
|
|
sub does { |
|
100
|
7
|
|
|
7
|
0
|
1852
|
my($self, $role_name) = @_; |
|
101
|
|
|
|
|
|
|
|
|
102
|
7
|
|
|
|
|
20
|
my @roles = map { @{ $_->roles } } |
|
|
18
|
|
|
|
|
16
|
|
|
|
18
|
|
|
|
|
46
|
|
|
103
|
|
|
|
|
|
|
$self->__meta__->all_class_metas(); |
|
104
|
|
|
|
|
|
|
|
|
105
|
7
|
|
|
9
|
|
40
|
any { $role_name eq $_->role_name } @roles; |
|
|
9
|
|
|
|
|
18
|
|
|
106
|
|
|
|
|
|
|
} |
|
107
|
|
|
|
|
|
|
|
|
108
|
|
|
|
|
|
|
|
|
109
|
|
|
|
|
|
|
# Used to traverse n levels of indirect properties, even if the total |
|
110
|
|
|
|
|
|
|
# indirection is not defined on the primary ofhect this is called on. |
|
111
|
|
|
|
|
|
|
# For example: $obj->__get_attr__('a.b.c'); |
|
112
|
|
|
|
|
|
|
# gets $obj's 'a' value, calls 'b' on that, and calls 'c' on the last thing |
|
113
|
|
|
|
|
|
|
sub __get_attr__ { |
|
114
|
2126
|
|
|
2126
|
|
2193
|
my ($self, $property_name) = @_; |
|
115
|
2126
|
|
|
|
|
1667
|
my @property_values; |
|
116
|
2126
|
100
|
|
|
|
3972
|
if (index($property_name,'.') == -1) { |
|
117
|
2096
|
|
|
|
|
5216
|
@property_values = $self->$property_name; |
|
118
|
|
|
|
|
|
|
} |
|
119
|
|
|
|
|
|
|
else { |
|
120
|
30
|
|
|
|
|
92
|
my @links = split(/\./,$property_name); |
|
121
|
30
|
|
|
|
|
57
|
@property_values = ($self); |
|
122
|
30
|
|
|
|
|
90
|
for my $full_link (@links) { |
|
123
|
60
|
|
|
|
|
102
|
my $pos = index($full_link,'-'); |
|
124
|
60
|
50
|
|
|
|
121
|
my $link = ($pos == -1 ? $full_link : substr($full_link,0,$pos) ); |
|
125
|
60
|
50
|
|
|
|
86
|
@property_values = map { defined($_) ? $_->$link : undef } @property_values; |
|
|
63
|
|
|
|
|
258
|
|
|
126
|
|
|
|
|
|
|
} |
|
127
|
|
|
|
|
|
|
} |
|
128
|
2126
|
50
|
|
|
|
3664
|
return if not defined wantarray; |
|
129
|
2126
|
50
|
|
|
|
5982
|
return @property_values if wantarray; |
|
130
|
0
|
0
|
|
|
|
0
|
if (@property_values > 1) { |
|
131
|
0
|
|
|
|
|
0
|
my $class_name = $self->__meta__->class_name; |
|
132
|
0
|
|
|
|
|
0
|
Carp::confess("Multiple values returned for $class_name $property_name in scalar context!"); |
|
133
|
|
|
|
|
|
|
} |
|
134
|
0
|
|
|
|
|
0
|
return $property_values[0]; |
|
135
|
|
|
|
|
|
|
} |
|
136
|
|
|
|
|
|
|
|
|
137
|
|
|
|
|
|
|
sub __label_name__ { |
|
138
|
|
|
|
|
|
|
# override to provide default labeling of the object |
|
139
|
0
|
|
|
0
|
|
0
|
my $self = $_[0]; |
|
140
|
0
|
|
0
|
|
|
0
|
my $class = ref($self) || $self; |
|
141
|
0
|
|
|
|
|
0
|
my ($label) = ($class =~ /([^:]+)$/); |
|
142
|
0
|
|
|
|
|
0
|
$label =~ s/([a-z])([A-Z])/$1 $2/g; |
|
143
|
0
|
|
|
|
|
0
|
$label =~ s/([A-Z])([A-Z]([a-z]|\s|$))/$1 $2/g; |
|
144
|
0
|
0
|
|
|
|
0
|
$label = uc($label) if $label =~ /_id$/i; |
|
145
|
0
|
|
|
|
|
0
|
return $label; |
|
146
|
|
|
|
|
|
|
} |
|
147
|
|
|
|
|
|
|
|
|
148
|
|
|
|
|
|
|
sub __display_name__ { |
|
149
|
|
|
|
|
|
|
# default stringification (does override "" unless you specifically choose to) |
|
150
|
5
|
|
|
5
|
|
7
|
my $self = shift; |
|
151
|
5
|
|
|
|
|
9
|
my $in_context_of_related_object = shift; |
|
152
|
|
|
|
|
|
|
|
|
153
|
5
|
|
|
|
|
9
|
my $name = $self->id; |
|
154
|
5
|
|
|
|
|
11
|
$name =~ s/\t/ /g; |
|
155
|
5
|
|
|
|
|
249
|
return $name; |
|
156
|
|
|
|
|
|
|
|
|
157
|
0
|
0
|
|
|
|
0
|
if (not $in_context_of_related_object) { |
|
|
|
0
|
|
|
|
|
|
|
158
|
|
|
|
|
|
|
# no in_context_of_related_object. |
|
159
|
|
|
|
|
|
|
# the object is identified globally |
|
160
|
0
|
|
|
|
|
0
|
return $self->label_name . ' ' . $name; |
|
161
|
|
|
|
|
|
|
} |
|
162
|
|
|
|
|
|
|
elsif ($in_context_of_related_object eq ref($self)) { |
|
163
|
|
|
|
|
|
|
# the class is completely known |
|
164
|
|
|
|
|
|
|
# show only the core display name |
|
165
|
|
|
|
|
|
|
# -> less text, more in_context_of_related_object |
|
166
|
0
|
|
|
|
|
0
|
return $name |
|
167
|
|
|
|
|
|
|
} |
|
168
|
|
|
|
|
|
|
else { |
|
169
|
|
|
|
|
|
|
# some intermediate base class is known, |
|
170
|
|
|
|
|
|
|
# TODO: make this smarter |
|
171
|
|
|
|
|
|
|
# For now, just show the whole class name with the ID |
|
172
|
0
|
|
|
|
|
0
|
return $self->label_name . ' ' . $name; |
|
173
|
|
|
|
|
|
|
} |
|
174
|
|
|
|
|
|
|
} |
|
175
|
|
|
|
|
|
|
|
|
176
|
|
|
|
|
|
|
sub __errors__ { |
|
177
|
|
|
|
|
|
|
# This is the basis for software constraint checking. |
|
178
|
|
|
|
|
|
|
# Return a list of values describing the problems on the object. |
|
179
|
|
|
|
|
|
|
|
|
180
|
2995
|
|
|
2995
|
|
5158
|
my ($self,@property_names) = @_; |
|
181
|
|
|
|
|
|
|
|
|
182
|
2995
|
|
|
|
|
7455
|
my $class_object = $self->__meta__; |
|
183
|
|
|
|
|
|
|
|
|
184
|
2995
|
50
|
|
|
|
6058
|
unless (scalar @property_names) { |
|
185
|
2995
|
|
|
|
|
11474
|
@property_names = $class_object->all_property_names; |
|
186
|
|
|
|
|
|
|
} |
|
187
|
|
|
|
|
|
|
|
|
188
|
|
|
|
|
|
|
my @properties = map { |
|
189
|
2995
|
|
|
|
|
5230
|
$class_object->property_meta_for_name($_); |
|
|
18850
|
|
|
|
|
27086
|
|
|
190
|
|
|
|
|
|
|
} @property_names; |
|
191
|
|
|
|
|
|
|
|
|
192
|
2995
|
|
|
|
|
3790
|
my @tags; |
|
193
|
2995
|
|
|
|
|
4433
|
for my $property_metadata (@properties) { |
|
194
|
|
|
|
|
|
|
# For now we don't validate these. |
|
195
|
|
|
|
|
|
|
# Ultimately, we should delegate to the property metadata object for value validation. |
|
196
|
|
|
|
|
|
|
my($is_delegated, $is_calculated, $property_name, $is_optional, $generic_data_type, $data_length) |
|
197
|
18850
|
|
|
|
|
35434
|
= @$property_metadata{'is_delegated','is_calculated','property_name','is_optional', 'data_type','data_length'}; |
|
198
|
|
|
|
|
|
|
|
|
199
|
18850
|
100
|
100
|
|
|
51132
|
next if $is_delegated || $is_calculated; |
|
200
|
|
|
|
|
|
|
|
|
201
|
|
|
|
|
|
|
# TODO: is this making commits slow by calling lots of indirect accessors? |
|
202
|
17946
|
|
|
|
|
43416
|
my @values = $self->$property_name; |
|
203
|
17946
|
100
|
|
|
|
25739
|
next if @values > 1; |
|
204
|
|
|
|
|
|
|
|
|
205
|
17944
|
|
|
|
|
14644
|
my $value = $values[0]; |
|
206
|
|
|
|
|
|
|
|
|
207
|
|
|
|
|
|
|
# account for minus sign in dummy ID |
|
208
|
17944
|
0
|
33
|
|
|
29550
|
if ($ENV{UR_USE_DUMMY_AUTOGENERATED_IDS} and $property_metadata->is_id and defined($value) and index($value, '-') == 0 and defined $data_length) { |
|
|
|
|
33
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
209
|
0
|
|
|
|
|
0
|
$data_length++; |
|
210
|
|
|
|
|
|
|
} |
|
211
|
|
|
|
|
|
|
|
|
212
|
17944
|
100
|
100
|
|
|
37359
|
if (! ($is_optional or defined($value))) { |
|
213
|
95
|
|
|
|
|
630
|
push @tags, UR::Object::Tag->create( |
|
214
|
|
|
|
|
|
|
type => 'invalid', |
|
215
|
|
|
|
|
|
|
properties => [$property_name], |
|
216
|
|
|
|
|
|
|
desc => "No value specified for required property", |
|
217
|
|
|
|
|
|
|
); |
|
218
|
|
|
|
|
|
|
} |
|
219
|
|
|
|
|
|
|
|
|
220
|
|
|
|
|
|
|
# The tests below don't apply do undefined values. |
|
221
|
|
|
|
|
|
|
# Save the trouble and move on. |
|
222
|
17944
|
100
|
|
|
|
23800
|
next unless defined $value; |
|
223
|
|
|
|
|
|
|
|
|
224
|
|
|
|
|
|
|
# Check data type |
|
225
|
|
|
|
|
|
|
# TODO: delegate to the data type module for this |
|
226
|
13643
|
100
|
|
|
|
16780
|
$generic_data_type = '' unless (defined $generic_data_type); |
|
227
|
|
|
|
|
|
|
|
|
228
|
13643
|
100
|
100
|
|
|
43640
|
if ($generic_data_type eq 'Float' || $generic_data_type eq 'Integer') { |
|
|
|
50
|
|
|
|
|
|
|
229
|
1918
|
100
|
|
|
|
6437
|
if (looks_like_number($value)) { |
|
230
|
1914
|
|
|
|
|
2401
|
$value = $value + 0; |
|
231
|
|
|
|
|
|
|
} |
|
232
|
|
|
|
|
|
|
else{ |
|
233
|
4
|
|
|
|
|
20
|
push @tags, UR::Object::Tag->create ( |
|
234
|
|
|
|
|
|
|
type => 'invalid', |
|
235
|
|
|
|
|
|
|
properties => [$property_name], |
|
236
|
|
|
|
|
|
|
desc => "Invalid $generic_data_type value." |
|
237
|
|
|
|
|
|
|
); |
|
238
|
|
|
|
|
|
|
} |
|
239
|
|
|
|
|
|
|
} |
|
240
|
|
|
|
|
|
|
elsif ($generic_data_type eq 'DateTime') { |
|
241
|
|
|
|
|
|
|
# This check is currently disabled b/c of time format irrecularities |
|
242
|
|
|
|
|
|
|
# We rely on underlying database constraints for real invalidity checking. |
|
243
|
|
|
|
|
|
|
# TODO: fix me |
|
244
|
0
|
|
|
|
|
0
|
if (1) { |
|
245
|
|
|
|
|
|
|
|
|
246
|
|
|
|
|
|
|
} |
|
247
|
|
|
|
|
|
|
elsif ($value =~ /^\s*\d\d\d\d\-\d\d-\d\d\s*(\d\d:\d\d:\d\d|)\s*$/) { |
|
248
|
|
|
|
|
|
|
# TODO more validation here for a real date. |
|
249
|
|
|
|
|
|
|
} |
|
250
|
|
|
|
|
|
|
else { |
|
251
|
|
|
|
|
|
|
push @tags, UR::Object::Tag->create ( |
|
252
|
|
|
|
|
|
|
type => 'invalid', |
|
253
|
|
|
|
|
|
|
properties => [$property_name], |
|
254
|
|
|
|
|
|
|
desc => 'Invalid date string.' |
|
255
|
|
|
|
|
|
|
); |
|
256
|
|
|
|
|
|
|
} |
|
257
|
|
|
|
|
|
|
} |
|
258
|
|
|
|
|
|
|
|
|
259
|
|
|
|
|
|
|
# Check size |
|
260
|
13643
|
50
|
|
|
|
17787
|
if ($generic_data_type ne 'DateTime') { |
|
261
|
13643
|
50
|
66
|
|
|
20788
|
if ( defined($data_length) and ($data_length < length($value)) ) { |
|
262
|
0
|
|
|
|
|
0
|
push @tags, |
|
263
|
|
|
|
|
|
|
UR::Object::Tag->create( |
|
264
|
|
|
|
|
|
|
type => 'invalid', |
|
265
|
|
|
|
|
|
|
properties => [$property_name], |
|
266
|
|
|
|
|
|
|
desc => sprintf('Value too long (%s of %s has length of %d and should be <= %d).', |
|
267
|
|
|
|
|
|
|
$property_name, |
|
268
|
|
|
|
|
|
|
$self->$property_name, |
|
269
|
|
|
|
|
|
|
length($value), |
|
270
|
|
|
|
|
|
|
$data_length) |
|
271
|
|
|
|
|
|
|
); |
|
272
|
|
|
|
|
|
|
} |
|
273
|
|
|
|
|
|
|
} |
|
274
|
|
|
|
|
|
|
|
|
275
|
|
|
|
|
|
|
# Check valid values if there is an explicit list |
|
276
|
13643
|
100
|
|
|
|
22299
|
if (my $constraints = $property_metadata->valid_values) { |
|
277
|
1073
|
|
|
|
|
1101
|
my $valid = 0; |
|
278
|
1073
|
|
|
|
|
1729
|
for my $valid_value (@$constraints) { |
|
279
|
266
|
|
|
266
|
|
409413
|
no warnings; # undef == '' |
|
|
266
|
|
|
|
|
446
|
|
|
|
266
|
|
|
|
|
280241
|
|
|
280
|
1213
|
100
|
|
|
|
2175
|
if ($value eq $valid_value) { |
|
281
|
1067
|
|
|
|
|
970
|
$valid = 1; |
|
282
|
1067
|
|
|
|
|
1245
|
last; |
|
283
|
|
|
|
|
|
|
} |
|
284
|
|
|
|
|
|
|
} |
|
285
|
1073
|
100
|
|
|
|
1805
|
unless ($valid) { |
|
286
|
|
|
|
|
|
|
# undef is a valid value in the constraints list |
|
287
|
6
|
50
|
|
|
|
7
|
my $value_list = join(', ',map { defined($_) ? $_ : '' } @$constraints); |
|
|
24
|
|
|
|
|
39
|
|
|
288
|
6
|
|
|
|
|
40
|
push @tags, |
|
289
|
|
|
|
|
|
|
UR::Object::Tag->create( |
|
290
|
|
|
|
|
|
|
type => 'invalid', |
|
291
|
|
|
|
|
|
|
properties => [$property_name], |
|
292
|
|
|
|
|
|
|
desc => sprintf( |
|
293
|
|
|
|
|
|
|
'The value %s is not in the list of valid values for %s. Valid values are: %s', |
|
294
|
|
|
|
|
|
|
$value, |
|
295
|
|
|
|
|
|
|
$property_name, |
|
296
|
|
|
|
|
|
|
$value_list |
|
297
|
|
|
|
|
|
|
) |
|
298
|
|
|
|
|
|
|
); |
|
299
|
|
|
|
|
|
|
} |
|
300
|
|
|
|
|
|
|
} |
|
301
|
|
|
|
|
|
|
|
|
302
|
|
|
|
|
|
|
# Check FK if it is easy to do. |
|
303
|
|
|
|
|
|
|
# TODO: This is a heavy weight check, and is disabled for performance reasons. |
|
304
|
|
|
|
|
|
|
# Ideally we'd check a foreign key value _if_ it was changed only, since |
|
305
|
|
|
|
|
|
|
# saved foreign keys presumably could not have been save if they were invalid. |
|
306
|
13643
|
|
|
|
|
17153
|
if (0) { |
|
307
|
|
|
|
|
|
|
my $r_class; |
|
308
|
|
|
|
|
|
|
unless ($r_class->get(id => $value)) { |
|
309
|
|
|
|
|
|
|
push @tags, UR::Object::Tag->create ( |
|
310
|
|
|
|
|
|
|
type => 'invalid', |
|
311
|
|
|
|
|
|
|
properties => [$property_name], |
|
312
|
|
|
|
|
|
|
desc => "$value does not reference a valid " . $r_class . '.' |
|
313
|
|
|
|
|
|
|
); |
|
314
|
|
|
|
|
|
|
} |
|
315
|
|
|
|
|
|
|
} |
|
316
|
|
|
|
|
|
|
} |
|
317
|
|
|
|
|
|
|
|
|
318
|
2995
|
|
|
|
|
8546
|
return @tags; |
|
319
|
|
|
|
|
|
|
} |
|
320
|
|
|
|
|
|
|
|
|
321
|
|
|
|
|
|
|
# Standard API for working with UR fixtures |
|
322
|
|
|
|
|
|
|
# boolean expressions |
|
323
|
|
|
|
|
|
|
# sets |
|
324
|
|
|
|
|
|
|
# iterators |
|
325
|
|
|
|
|
|
|
# views |
|
326
|
|
|
|
|
|
|
# mock objects |
|
327
|
|
|
|
|
|
|
|
|
328
|
|
|
|
|
|
|
sub define_boolexpr { |
|
329
|
1582
|
50
|
|
1582
|
1
|
44586
|
if (ref($_[0])) { |
|
330
|
0
|
|
|
|
|
0
|
my $class = ref(shift); |
|
331
|
0
|
|
|
|
|
0
|
return UR::BoolExpr->resolve($class,@_); |
|
332
|
|
|
|
|
|
|
} |
|
333
|
|
|
|
|
|
|
else { |
|
334
|
1582
|
|
|
|
|
5305
|
return UR::BoolExpr->resolve(@_); |
|
335
|
|
|
|
|
|
|
} |
|
336
|
|
|
|
|
|
|
} |
|
337
|
|
|
|
|
|
|
|
|
338
|
|
|
|
|
|
|
sub define_set { |
|
339
|
26
|
|
|
26
|
1
|
4585
|
my $class = shift; |
|
340
|
26
|
|
33
|
|
|
134
|
$class = ref($class) || $class; |
|
341
|
26
|
|
|
|
|
135
|
my $rule = UR::BoolExpr->resolve_normalized($class,@_); |
|
342
|
26
|
|
|
|
|
93
|
my $flattened_rule = $rule->flatten_hard_refs(); |
|
343
|
26
|
|
|
|
|
68
|
my $set_class = $class . "::Set"; |
|
344
|
26
|
|
|
|
|
59
|
return $set_class->get($flattened_rule->id); |
|
345
|
|
|
|
|
|
|
} |
|
346
|
|
|
|
|
|
|
|
|
347
|
|
|
|
|
|
|
sub add_observer { |
|
348
|
108
|
|
|
108
|
1
|
15233
|
my $self = shift; |
|
349
|
108
|
|
|
|
|
311
|
my %params = @_; |
|
350
|
|
|
|
|
|
|
|
|
351
|
108
|
100
|
|
|
|
311
|
if (ref($self)) { |
|
352
|
60
|
|
|
|
|
208
|
$params{subject_id} = $self->id; |
|
353
|
|
|
|
|
|
|
} |
|
354
|
108
|
|
|
|
|
383
|
my $observer = UR::Observer->create( |
|
355
|
|
|
|
|
|
|
subject_class_name => $self->class, |
|
356
|
|
|
|
|
|
|
%params, |
|
357
|
|
|
|
|
|
|
); |
|
358
|
108
|
50
|
|
|
|
297
|
unless ($observer) { |
|
359
|
0
|
|
|
|
|
0
|
$self->error_message( |
|
360
|
|
|
|
|
|
|
"Failed to create observer: " |
|
361
|
|
|
|
|
|
|
. UR::Observer->error_message |
|
362
|
|
|
|
|
|
|
); |
|
363
|
0
|
|
|
|
|
0
|
return; |
|
364
|
|
|
|
|
|
|
} |
|
365
|
108
|
|
|
|
|
448
|
return $observer; |
|
366
|
|
|
|
|
|
|
} |
|
367
|
|
|
|
|
|
|
|
|
368
|
|
|
|
|
|
|
sub remove_observers { |
|
369
|
1
|
|
|
1
|
0
|
279
|
my $self = shift; |
|
370
|
1
|
|
|
|
|
2
|
my %params = @_; |
|
371
|
|
|
|
|
|
|
|
|
372
|
1
|
|
|
|
|
2
|
my $aspect = delete $params{'aspect'}; |
|
373
|
1
|
|
|
|
|
3
|
my $callback = delete $params{'callback'}; |
|
374
|
1
|
50
|
|
|
|
3
|
if (%params) { |
|
375
|
0
|
|
|
|
|
0
|
Carp::croak('Unrecognized parameters for observer removal: ' |
|
376
|
|
|
|
|
|
|
. Data::Dumper::Dumper(\%params) |
|
377
|
|
|
|
|
|
|
. "Expected 'aspect' and 'callback'"); |
|
378
|
|
|
|
|
|
|
} |
|
379
|
|
|
|
|
|
|
|
|
380
|
1
|
|
|
|
|
4
|
my %args = ( subject_class_name => $self->class ); |
|
381
|
1
|
50
|
|
|
|
5
|
$args{'subject_id'} = $self->id if (ref $self); |
|
382
|
1
|
50
|
|
|
|
3
|
$args{'aspect'} = $aspect if (defined $aspect); |
|
383
|
1
|
50
|
|
|
|
2
|
$args{'callback'} = $callback if (defined $callback); |
|
384
|
1
|
|
|
|
|
4
|
my @observers = UR::Observer->get(%args); |
|
385
|
|
|
|
|
|
|
|
|
386
|
1
|
|
|
|
|
6
|
$_->delete foreach @observers; |
|
387
|
1
|
|
|
|
|
6
|
return @observers; |
|
388
|
|
|
|
|
|
|
} |
|
389
|
|
|
|
|
|
|
|
|
390
|
|
|
|
|
|
|
sub create_iterator { |
|
391
|
65
|
|
|
65
|
1
|
28221
|
my $class = shift; |
|
392
|
|
|
|
|
|
|
|
|
393
|
|
|
|
|
|
|
# old syntax = create_iterator(where => [param_a => A, param_b => B]) |
|
394
|
65
|
100
|
|
|
|
216
|
if (@_ > 1) { |
|
395
|
42
|
|
|
|
|
133
|
my %params = @_; |
|
396
|
42
|
50
|
|
|
|
158
|
if (exists $params{'where'}) { |
|
397
|
0
|
|
|
|
|
0
|
Carp::carp('create_iterator called with old syntax create_iterator(where => \@params) should be called as create_iterator(@params)'); |
|
398
|
0
|
|
|
|
|
0
|
@_ = $params{'where'}; |
|
399
|
|
|
|
|
|
|
} |
|
400
|
|
|
|
|
|
|
} |
|
401
|
|
|
|
|
|
|
|
|
402
|
|
|
|
|
|
|
# new syntax, same as get() = create_iterator($bx) or create_iterator(param_a => A, param_b => B) |
|
403
|
65
|
|
|
|
|
91
|
my $filter; |
|
404
|
65
|
100
|
66
|
|
|
463
|
if (Scalar::Util::blessed($_[0]) && $_[0]->isa('UR::BoolExpr')) { |
|
405
|
15
|
|
|
|
|
28
|
$filter = $_[0]; |
|
406
|
|
|
|
|
|
|
} else { |
|
407
|
50
|
|
|
|
|
226
|
$filter = UR::BoolExpr->resolve($class, @_) |
|
408
|
|
|
|
|
|
|
} |
|
409
|
|
|
|
|
|
|
|
|
410
|
65
|
|
|
|
|
398
|
my $iterator = UR::Object::Iterator->create_for_filter_rule($filter); |
|
411
|
65
|
50
|
|
|
|
304
|
unless ($iterator) { |
|
412
|
0
|
|
|
|
|
0
|
$class->error_message(UR::Object::Iterator->error_message); |
|
413
|
0
|
|
|
|
|
0
|
return; |
|
414
|
|
|
|
|
|
|
} |
|
415
|
|
|
|
|
|
|
|
|
416
|
65
|
|
|
|
|
214
|
return $iterator; |
|
417
|
|
|
|
|
|
|
} |
|
418
|
|
|
|
|
|
|
|
|
419
|
|
|
|
|
|
|
sub create_view { |
|
420
|
51
|
|
|
51
|
0
|
2021
|
my $self = shift; |
|
421
|
51
|
|
|
|
|
160
|
my $class = $self->class; |
|
422
|
|
|
|
|
|
|
# this will auto-subclass into ${class}::View::${perspective}::${toolkit}, |
|
423
|
|
|
|
|
|
|
# using $class or some parent class of $class |
|
424
|
51
|
|
|
|
|
305
|
my $view = UR::Object::View->create( |
|
425
|
|
|
|
|
|
|
subject_class_name => $class, |
|
426
|
|
|
|
|
|
|
perspective => "default", |
|
427
|
|
|
|
|
|
|
@_ |
|
428
|
|
|
|
|
|
|
); |
|
429
|
|
|
|
|
|
|
|
|
430
|
51
|
50
|
|
|
|
164
|
unless ($view) { |
|
431
|
0
|
|
|
|
|
0
|
$self->error_message("Error creating view: " . UR::Object::View->error_message); |
|
432
|
0
|
|
|
|
|
0
|
return; |
|
433
|
|
|
|
|
|
|
} |
|
434
|
|
|
|
|
|
|
|
|
435
|
51
|
100
|
|
|
|
135
|
if (ref($self)) { |
|
436
|
7
|
|
|
|
|
47
|
$view->subject($self); |
|
437
|
|
|
|
|
|
|
} |
|
438
|
|
|
|
|
|
|
|
|
439
|
51
|
|
|
|
|
183
|
return $view; |
|
440
|
|
|
|
|
|
|
} |
|
441
|
|
|
|
|
|
|
|
|
442
|
|
|
|
|
|
|
sub create_mock { |
|
443
|
0
|
|
|
0
|
1
|
0
|
my $class = shift; |
|
444
|
0
|
|
|
|
|
0
|
my %params = @_; |
|
445
|
|
|
|
|
|
|
|
|
446
|
0
|
|
|
|
|
0
|
require Test::MockObject; |
|
447
|
|
|
|
|
|
|
|
|
448
|
0
|
|
|
|
|
0
|
my $self = Test::MockObject->new(); |
|
449
|
0
|
|
|
|
|
0
|
my $subject_class_object = $class->__meta__; |
|
450
|
0
|
|
|
|
|
0
|
for my $class_object ($subject_class_object,$subject_class_object->ancestry_class_metas) { |
|
451
|
0
|
|
|
|
|
0
|
for my $property ($class_object->direct_property_metas) { |
|
452
|
0
|
|
|
|
|
0
|
my $property_name = $property->property_name; |
|
453
|
0
|
0
|
0
|
|
|
0
|
if (($property->is_delegated || $property->is_optional) && !exists($params{$property_name})) { |
|
|
|
|
0
|
|
|
|
|
|
454
|
0
|
|
|
|
|
0
|
next; |
|
455
|
|
|
|
|
|
|
} |
|
456
|
0
|
0
|
0
|
|
|
0
|
if ($property->is_mutable || $property->is_calculated || $property->is_delegated) { |
|
|
|
|
0
|
|
|
|
|
|
457
|
|
|
|
|
|
|
my $sub = sub { |
|
458
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
|
459
|
0
|
0
|
|
|
|
0
|
if (@_) { |
|
460
|
0
|
0
|
|
|
|
0
|
if ($property->is_many) { |
|
461
|
0
|
|
|
|
|
0
|
$self->{'_'. $property_name} = @_; |
|
462
|
|
|
|
|
|
|
} else { |
|
463
|
0
|
|
|
|
|
0
|
$self->{'_'. $property_name} = shift; |
|
464
|
|
|
|
|
|
|
} |
|
465
|
|
|
|
|
|
|
} |
|
466
|
0
|
|
|
|
|
0
|
return $self->{'_'. $property_name}; |
|
467
|
0
|
|
|
|
|
0
|
}; |
|
468
|
0
|
|
|
|
|
0
|
$self->mock($property_name, $sub); |
|
469
|
0
|
0
|
|
|
|
0
|
if ($property->is_optional) { |
|
470
|
0
|
0
|
|
|
|
0
|
if (exists($params{$property_name})) { |
|
471
|
0
|
|
|
|
|
0
|
$self->$property_name($params{$property_name}); |
|
472
|
|
|
|
|
|
|
} |
|
473
|
|
|
|
|
|
|
} else { |
|
474
|
0
|
0
|
|
|
|
0
|
unless (exists($params{$property_name})) { |
|
475
|
0
|
0
|
|
|
|
0
|
if (defined($property->default_value)) { |
|
476
|
0
|
|
|
|
|
0
|
$params{$property_name} = $property->default_value; |
|
477
|
|
|
|
|
|
|
} else { |
|
478
|
0
|
0
|
|
|
|
0
|
unless ($property->is_calculated) { |
|
479
|
0
|
|
|
|
|
0
|
Carp::croak("Failed to provide value for required mutable property '$property_name'"); |
|
480
|
|
|
|
|
|
|
} |
|
481
|
|
|
|
|
|
|
} |
|
482
|
|
|
|
|
|
|
} |
|
483
|
0
|
|
|
|
|
0
|
$self->$property_name($params{$property_name}); |
|
484
|
|
|
|
|
|
|
} |
|
485
|
|
|
|
|
|
|
} else { |
|
486
|
0
|
0
|
|
|
|
0
|
unless (exists($params{$property_name})) { |
|
487
|
0
|
0
|
|
|
|
0
|
if (defined($property->default_value)) { |
|
488
|
0
|
|
|
|
|
0
|
$params{$property_name} = $property->default_value; |
|
489
|
|
|
|
|
|
|
} else { |
|
490
|
0
|
|
|
|
|
0
|
Carp::croak("Failed to provide value for required property '$property_name'"); |
|
491
|
|
|
|
|
|
|
} |
|
492
|
|
|
|
|
|
|
} |
|
493
|
0
|
0
|
|
|
|
0
|
if ($property->is_many) { |
|
494
|
0
|
|
|
|
|
0
|
$self->set_list($property_name,$params{$property_name}); |
|
495
|
|
|
|
|
|
|
} else { |
|
496
|
0
|
|
|
|
|
0
|
$self->set_always($property_name,$params{$property_name}); |
|
497
|
|
|
|
|
|
|
} |
|
498
|
|
|
|
|
|
|
} |
|
499
|
|
|
|
|
|
|
} |
|
500
|
|
|
|
|
|
|
} |
|
501
|
0
|
|
|
|
|
0
|
my @classes = ($class, $subject_class_object->ancestry_class_names); |
|
502
|
0
|
|
|
|
|
0
|
$self->set_isa(@classes); |
|
503
|
0
|
|
|
|
|
0
|
$UR::Context::all_objects_loaded->{$class}->{$self->id} = $self; |
|
504
|
0
|
|
|
|
|
0
|
return $self; |
|
505
|
|
|
|
|
|
|
} |
|
506
|
|
|
|
|
|
|
|
|
507
|
|
|
|
|
|
|
# Typically only used internally by UR except when debugging. |
|
508
|
|
|
|
|
|
|
|
|
509
|
|
|
|
|
|
|
sub __changes__ { |
|
510
|
|
|
|
|
|
|
# Return a list of changes present on the object _directly_. |
|
511
|
|
|
|
|
|
|
# This is really only useful internally because the boundary of the object |
|
512
|
|
|
|
|
|
|
# is internal/subjective. |
|
513
|
|
|
|
|
|
|
|
|
514
|
242068
|
|
|
242068
|
|
165077
|
my $self = shift; |
|
515
|
|
|
|
|
|
|
|
|
516
|
|
|
|
|
|
|
# performance optimization |
|
517
|
242068
|
100
|
|
|
|
621662
|
return unless $self->{_change_count}; |
|
518
|
|
|
|
|
|
|
|
|
519
|
927
|
|
|
|
|
1976
|
my $meta = $self->__meta__; |
|
520
|
927
|
50
|
|
|
|
1753
|
if (ref($meta) eq 'UR::DeletedRef') { |
|
521
|
0
|
|
|
|
|
0
|
print Data::Dumper::Dumper($self,$meta); |
|
522
|
0
|
|
|
|
|
0
|
Carp::confess("Meta is deleted for object requesting changes: $self\n"); |
|
523
|
|
|
|
|
|
|
} |
|
524
|
927
|
50
|
66
|
|
|
2289
|
if (!$meta->is_transactional and !$meta->is_meta_meta) { |
|
525
|
209
|
|
|
|
|
325
|
return; |
|
526
|
|
|
|
|
|
|
} |
|
527
|
|
|
|
|
|
|
|
|
528
|
718
|
|
66
|
|
|
1992
|
my $orig = $self->{db_saved_uncommitted} || $self->{db_committed}; |
|
529
|
|
|
|
|
|
|
|
|
530
|
718
|
|
|
|
|
582
|
my %prop_metas; |
|
531
|
|
|
|
|
|
|
my $prop_is_changed = sub { |
|
532
|
345
|
|
|
345
|
|
308
|
my $prop_name = shift; |
|
533
|
345
|
|
66
|
|
|
1144
|
my $property_meta = $prop_metas{$prop_name} ||= $meta->property_meta_for_name($prop_name); |
|
534
|
266
|
|
|
266
|
|
1478
|
no warnings 'uninitialized'; |
|
|
266
|
|
|
|
|
434
|
|
|
|
266
|
|
|
|
|
32159
|
|
|
535
|
345
|
|
66
|
|
|
1281
|
return ($orig->{$prop_name} ne $self->{$prop_name}) |
|
536
|
|
|
|
|
|
|
&& |
|
537
|
|
|
|
|
|
|
($self->can($prop_name) and ! UR::Object->can($prop_name)) |
|
538
|
|
|
|
|
|
|
&& |
|
539
|
|
|
|
|
|
|
defined($property_meta) |
|
540
|
|
|
|
|
|
|
&& |
|
541
|
|
|
|
|
|
|
(! $property_meta->is_transient) |
|
542
|
|
|
|
|
|
|
; |
|
543
|
718
|
|
|
|
|
2272
|
}; |
|
544
|
|
|
|
|
|
|
|
|
545
|
718
|
100
|
|
|
|
1200
|
unless (wantarray) { |
|
546
|
|
|
|
|
|
|
# scalar context only cares if there are any changes or not |
|
547
|
617
|
100
|
|
|
|
850
|
if (@_) { |
|
548
|
24
|
|
|
|
|
41
|
foreach (@_) { |
|
549
|
26
|
100
|
|
|
|
46
|
return 1 if $prop_is_changed->($_); |
|
550
|
|
|
|
|
|
|
} |
|
551
|
3
|
|
|
|
|
28
|
return ''; |
|
552
|
|
|
|
|
|
|
} else { |
|
553
|
|
|
|
|
|
|
return ($self->{__defined} and $self->{_change_count} == 1) |
|
554
|
|
|
|
|
|
|
? '' |
|
555
|
593
|
100
|
100
|
|
|
3360
|
: $self->{_change_count}; |
|
556
|
|
|
|
|
|
|
} |
|
557
|
|
|
|
|
|
|
} |
|
558
|
|
|
|
|
|
|
|
|
559
|
266
|
|
|
266
|
|
1207
|
no warnings; |
|
|
266
|
|
|
|
|
457
|
|
|
|
266
|
|
|
|
|
322848
|
|
|
560
|
101
|
|
|
|
|
122
|
my @changed; |
|
561
|
101
|
50
|
|
|
|
194
|
if ($orig) { |
|
562
|
101
|
|
|
|
|
272
|
my $class_name = $meta->class_name; |
|
563
|
|
|
|
|
|
|
@changed = |
|
564
|
319
|
|
|
|
|
601
|
grep { $prop_is_changed->($_) } |
|
565
|
101
|
50
|
|
|
|
356
|
grep { $_ } |
|
|
319
|
|
|
|
|
349
|
|
|
566
|
|
|
|
|
|
|
@_ ? (@_) : keys(%$orig); |
|
567
|
|
|
|
|
|
|
} |
|
568
|
|
|
|
|
|
|
else { |
|
569
|
0
|
|
|
|
|
0
|
@changed = $meta->all_property_names |
|
570
|
|
|
|
|
|
|
} |
|
571
|
|
|
|
|
|
|
|
|
572
|
|
|
|
|
|
|
return map { |
|
573
|
101
|
|
|
|
|
336
|
UR::Object::Tag->create |
|
|
75
|
|
|
|
|
360
|
|
|
574
|
|
|
|
|
|
|
( |
|
575
|
|
|
|
|
|
|
type => 'changed', |
|
576
|
|
|
|
|
|
|
properties => [$_] |
|
577
|
|
|
|
|
|
|
) |
|
578
|
|
|
|
|
|
|
} @changed; |
|
579
|
|
|
|
|
|
|
} |
|
580
|
|
|
|
|
|
|
|
|
581
|
|
|
|
|
|
|
|
|
582
|
|
|
|
|
|
|
sub _changed_property_names { |
|
583
|
47
|
|
|
47
|
|
72
|
my $self = shift; |
|
584
|
|
|
|
|
|
|
|
|
585
|
47
|
|
|
|
|
79
|
my @changes = $self->__changes__; |
|
586
|
47
|
|
|
|
|
90
|
my %changed_properties; |
|
587
|
47
|
|
|
|
|
88
|
foreach my $change ( @changes ) { |
|
588
|
50
|
50
|
|
|
|
1036
|
next unless ($change->type eq 'changed'); |
|
589
|
50
|
|
|
|
|
689
|
$changed_properties{$_} = 1 foreach $change->properties; |
|
590
|
|
|
|
|
|
|
} |
|
591
|
47
|
|
|
|
|
220
|
return keys %changed_properties; |
|
592
|
|
|
|
|
|
|
} |
|
593
|
|
|
|
|
|
|
|
|
594
|
|
|
|
|
|
|
sub __signal_change__ { |
|
595
|
|
|
|
|
|
|
# all mutable property accessors ("setters") call this method to tell the |
|
596
|
|
|
|
|
|
|
# current context about a state change. |
|
597
|
232556
|
|
|
232556
|
|
554263
|
$UR::Context::current->add_change_to_transaction_log(@_); |
|
598
|
232556
|
|
|
|
|
424840
|
$UR::Context::current->send_notification_to_observers(@_); |
|
599
|
|
|
|
|
|
|
} |
|
600
|
|
|
|
|
|
|
|
|
601
|
|
|
|
|
|
|
# send notifications that aren't state changes to observers |
|
602
|
|
|
|
|
|
|
sub __signal_observers__ { |
|
603
|
67391
|
|
|
67391
|
|
129686
|
$UR::Context::current->send_notification_to_observers(@_); |
|
604
|
|
|
|
|
|
|
} |
|
605
|
|
|
|
|
|
|
|
|
606
|
|
|
|
|
|
|
sub __define__ { |
|
607
|
|
|
|
|
|
|
# This is used internally to "virtually load" things. |
|
608
|
|
|
|
|
|
|
|
|
609
|
|
|
|
|
|
|
# Simply assert they already existed externally, and act as though they were just loaded... |
|
610
|
|
|
|
|
|
|
# It is used for classes defined in the source code (which is the default) by the "class {}" magic |
|
611
|
|
|
|
|
|
|
# instead of in some database, as we'd do for regular objects. It is also used by some test cases. |
|
612
|
95883
|
100
|
100
|
95883
|
|
294738
|
if ($UR::initialized and $_[0] ne 'UR::Object::Property') { |
|
613
|
|
|
|
|
|
|
# the nornal implementation has all create() features |
|
614
|
504
|
|
|
|
|
641
|
my $self; |
|
615
|
504
|
|
|
|
|
566
|
do { |
|
616
|
504
|
|
|
|
|
842
|
local $UR::Context::construction_method = '__define__'; |
|
617
|
504
|
|
|
|
|
2207
|
$self = $UR::Context::current->create_entity(@_); |
|
618
|
|
|
|
|
|
|
}; |
|
619
|
504
|
50
|
|
|
|
1340
|
return unless $self; |
|
620
|
504
|
|
|
|
|
2651
|
$self->{db_committed} = { %$self }; |
|
621
|
504
|
|
|
|
|
981
|
$self->{'__defined'} = 1; |
|
622
|
504
|
|
|
|
|
1777
|
$self->__signal_change__("load"); |
|
623
|
504
|
|
|
|
|
1258
|
return $self; |
|
624
|
|
|
|
|
|
|
} |
|
625
|
|
|
|
|
|
|
else { |
|
626
|
|
|
|
|
|
|
# used during boostrapping |
|
627
|
95379
|
|
|
|
|
88086
|
my $class = shift; |
|
628
|
95379
|
|
|
|
|
201128
|
my $class_meta = $class->__meta__; |
|
629
|
95379
|
50
|
|
|
|
217939
|
if (my $method_name = $class_meta->sub_classification_method_name) { |
|
630
|
0
|
|
|
|
|
0
|
my($rule, %extra) = UR::BoolExpr->resolve_normalized($class, @_); |
|
631
|
0
|
|
|
|
|
0
|
my $sub_class_name = $class->$method_name(@_); |
|
632
|
0
|
0
|
|
|
|
0
|
if ($sub_class_name ne $class) { |
|
633
|
|
|
|
|
|
|
# delegate to the sub-class to create the object |
|
634
|
0
|
|
|
|
|
0
|
return $sub_class_name->__define__(@_); |
|
635
|
|
|
|
|
|
|
} |
|
636
|
|
|
|
|
|
|
} |
|
637
|
|
|
|
|
|
|
|
|
638
|
95379
|
|
|
|
|
262094
|
my $self = $UR::Context::current->_construct_object($class, @_); |
|
639
|
95379
|
50
|
|
|
|
148709
|
return unless $self; |
|
640
|
95379
|
|
|
|
|
838218
|
$self->{db_committed} = { %$self }; |
|
641
|
95379
|
|
|
|
|
212341
|
$self->__signal_change__("load"); |
|
642
|
95379
|
|
|
|
|
187103
|
return $self; |
|
643
|
|
|
|
|
|
|
} |
|
644
|
|
|
|
|
|
|
} |
|
645
|
|
|
|
|
|
|
|
|
646
|
|
|
|
|
|
|
sub __extend_namespace__ { |
|
647
|
|
|
|
|
|
|
# A class Foo can implement this method to have a chance to auto-define Foo::Bar |
|
648
|
|
|
|
|
|
|
# TODO: make a Class::Autouse::ExtendNamespace Foo => sub { } to handle this. |
|
649
|
|
|
|
|
|
|
# Right now, UR::ModuleLoader will try it after "use". |
|
650
|
2065
|
|
|
2065
|
|
2672
|
my $class = shift; |
|
651
|
2065
|
|
|
|
|
2292
|
my $ext = shift; |
|
652
|
2065
|
|
|
|
|
4230
|
my $class_meta = $class->__meta__; |
|
653
|
2065
|
|
|
|
|
10769
|
return $class_meta->generate_support_class_for_extension($ext); |
|
654
|
|
|
|
|
|
|
} |
|
655
|
|
|
|
|
|
|
|
|
656
|
|
|
|
|
|
|
# Handling of references within the current process |
|
657
|
|
|
|
|
|
|
|
|
658
|
|
|
|
|
|
|
sub is_weakened { |
|
659
|
4370
|
|
|
4370
|
1
|
2693
|
my $self = shift; |
|
660
|
4370
|
|
66
|
|
|
11041
|
return (exists $self->{__weakened} && $self->{__weakened}); |
|
661
|
|
|
|
|
|
|
} |
|
662
|
|
|
|
|
|
|
|
|
663
|
|
|
|
|
|
|
sub __weaken__ { |
|
664
|
|
|
|
|
|
|
# Mark this object as unloadable by the object cache pruner. |
|
665
|
|
|
|
|
|
|
# If the class has a data source, then a weakened object is dropped |
|
666
|
|
|
|
|
|
|
# at the first opportunity, reguardless of its __get_serial number. |
|
667
|
|
|
|
|
|
|
# For classes without a data source, then it will be dropped according to |
|
668
|
|
|
|
|
|
|
# the normal rules w/r/t the __get_serial (classes without data sources |
|
669
|
|
|
|
|
|
|
# normally are never dropped by the pruner) |
|
670
|
12
|
|
|
12
|
|
17
|
my $self = $_[0]; |
|
671
|
12
|
|
|
|
|
22
|
delete $self->{'__strengthened'}; |
|
672
|
12
|
|
|
|
|
44
|
$self->{'__weakened'} = 1; |
|
673
|
|
|
|
|
|
|
} |
|
674
|
|
|
|
|
|
|
|
|
675
|
|
|
|
|
|
|
sub is_strengthened { |
|
676
|
2197
|
|
|
2197
|
1
|
1300
|
my $self = shift; |
|
677
|
2197
|
|
33
|
|
|
4421
|
return (exists $self->{__strengthened} && $self->{__strengthened}); |
|
678
|
|
|
|
|
|
|
} |
|
679
|
|
|
|
|
|
|
|
|
680
|
|
|
|
|
|
|
sub __strengthen__ { |
|
681
|
|
|
|
|
|
|
# Indicate this object should never be unloaded by the object cache pruner |
|
682
|
|
|
|
|
|
|
# or AutoUnloadPool |
|
683
|
0
|
|
|
0
|
|
0
|
my $self = $_[0]; |
|
684
|
0
|
|
|
|
|
0
|
delete $self->{'__weakened'}; |
|
685
|
0
|
|
|
|
|
0
|
$self->{'__strengthened'} = 1; |
|
686
|
|
|
|
|
|
|
} |
|
687
|
|
|
|
|
|
|
|
|
688
|
|
|
|
|
|
|
sub is_prunable { |
|
689
|
2197
|
|
|
2197
|
0
|
1416
|
my $self = shift; |
|
690
|
2197
|
50
|
|
|
|
2157
|
return 0 if $self->is_strengthened; |
|
691
|
2197
|
50
|
|
|
|
2395
|
return 1 if $self->is_weakened; |
|
692
|
2197
|
100
|
|
|
|
3272
|
return 0 if $self->__meta__->is_meta; |
|
693
|
2188
|
50
|
66
|
|
|
4394
|
return 0 if $self->{__get_serial} && $self->__changes__ && @{[$self->__changes__]}; |
|
|
0
|
|
33
|
|
|
0
|
|
|
694
|
2188
|
|
|
|
|
8278
|
return 1; |
|
695
|
|
|
|
|
|
|
} |
|
696
|
|
|
|
|
|
|
|
|
697
|
|
|
|
|
|
|
|
|
698
|
|
|
|
|
|
|
sub __rollback__ { |
|
699
|
967
|
|
|
967
|
|
961
|
my $self = shift; |
|
700
|
|
|
|
|
|
|
|
|
701
|
967
|
|
66
|
|
|
2935
|
my $saved = $self->{db_saved_uncommitted} || $self->{db_committed}; |
|
702
|
967
|
100
|
|
|
|
1643
|
unless ($saved) { |
|
703
|
27
|
|
|
|
|
59
|
return UR::Object::delete($self); |
|
704
|
|
|
|
|
|
|
} |
|
705
|
|
|
|
|
|
|
|
|
706
|
940
|
|
|
|
|
2961
|
my $meta = $self->__meta__; |
|
707
|
|
|
|
|
|
|
|
|
708
|
|
|
|
|
|
|
my $should_rollback = sub { |
|
709
|
46834
|
|
|
46834
|
|
29313
|
my $property_meta = shift; |
|
710
|
|
|
|
|
|
|
return ! ( |
|
711
|
46834
|
|
66
|
|
|
57950
|
defined $property_meta->is_id |
|
712
|
|
|
|
|
|
|
|| ! defined $property_meta->column_name |
|
713
|
|
|
|
|
|
|
|| $property_meta->is_delegated |
|
714
|
|
|
|
|
|
|
|| $property_meta->is_legacy_eav |
|
715
|
|
|
|
|
|
|
|| ! $property_meta->is_mutable |
|
716
|
|
|
|
|
|
|
|| $property_meta->is_transient |
|
717
|
|
|
|
|
|
|
|| $property_meta->is_constant |
|
718
|
|
|
|
|
|
|
); |
|
719
|
940
|
|
|
|
|
3390
|
}; |
|
720
|
|
|
|
|
|
|
my @rollback_property_names = |
|
721
|
329
|
|
|
|
|
554
|
map { $_->property_name } |
|
722
|
46834
|
|
|
|
|
40358
|
grep { $should_rollback->($_) } |
|
723
|
940
|
|
|
|
|
2978
|
map { $meta->property_meta_for_name($_) } |
|
|
46834
|
|
|
|
|
70936
|
|
|
724
|
|
|
|
|
|
|
$meta->all_property_names; |
|
725
|
|
|
|
|
|
|
|
|
726
|
|
|
|
|
|
|
# Existing object. Undo all changes since last sync, or since load |
|
727
|
|
|
|
|
|
|
# occurred when there have been no syncs. |
|
728
|
940
|
|
|
|
|
5094
|
foreach my $property_name ( @rollback_property_names ) { |
|
729
|
329
|
|
|
|
|
524
|
$self->__rollback_property__($property_name); |
|
730
|
|
|
|
|
|
|
} |
|
731
|
|
|
|
|
|
|
|
|
732
|
940
|
|
|
|
|
1566
|
delete $self->{'_change_count'}; |
|
733
|
|
|
|
|
|
|
|
|
734
|
940
|
|
|
|
|
4728
|
return $self; |
|
735
|
|
|
|
|
|
|
} |
|
736
|
|
|
|
|
|
|
|
|
737
|
|
|
|
|
|
|
|
|
738
|
|
|
|
|
|
|
sub __rollback_property__ { |
|
739
|
329
|
|
|
329
|
|
300
|
my ($self, $property_name) = @_; |
|
740
|
329
|
|
66
|
|
|
594
|
my $saved = $self->{db_saved_uncommitted} || $self->{db_committed}; |
|
741
|
329
|
50
|
|
|
|
451
|
unless ($saved) { |
|
742
|
0
|
|
|
|
|
0
|
Carp::croak(qq(Cannot rollback property '$property_name' because it has no saved state)); |
|
743
|
|
|
|
|
|
|
} |
|
744
|
329
|
|
|
|
|
666
|
my $saved_value = UR::Context->current->value_for_object_property_in_underlying_context($self, $property_name); |
|
745
|
329
|
|
|
|
|
857
|
return $self->$property_name($saved_value); |
|
746
|
|
|
|
|
|
|
} |
|
747
|
|
|
|
|
|
|
|
|
748
|
|
|
|
|
|
|
|
|
749
|
|
|
|
|
|
|
sub DESTROY { |
|
750
|
|
|
|
|
|
|
# Handle weak references in the object cache. |
|
751
|
411
|
|
|
411
|
|
6480
|
my $obj = shift; |
|
752
|
|
|
|
|
|
|
|
|
753
|
|
|
|
|
|
|
# objects_may_go_out_of_scope will be true if either light_cache is on, or |
|
754
|
|
|
|
|
|
|
# the cache_size_highwater mark is a valid value |
|
755
|
411
|
|
|
|
|
724
|
my($class, $id) = (ref($obj), $obj->{id}); |
|
756
|
|
|
|
|
|
|
|
|
757
|
411
|
100
|
66
|
|
|
2399
|
if (isweak($UR::Context::all_objects_loaded->{$class}{$id}) |
|
|
|
50
|
|
|
|
|
|
|
758
|
|
|
|
|
|
|
and |
|
759
|
|
|
|
|
|
|
refaddr($UR::Context::all_objects_loaded->{$class}{$id}) == refaddr($obj) |
|
760
|
|
|
|
|
|
|
) { |
|
761
|
|
|
|
|
|
|
# This object was dropped by the cache pruner or an AutoUnloadPool |
|
762
|
295
|
50
|
|
|
|
470
|
if (() = $obj->__changes__) { |
|
763
|
0
|
0
|
|
|
|
0
|
print STDERR "MEM DESTROY keeping changed object $class id $id\n" if $ENV{'UR_DEBUG_OBJECT_RELEASE'}; |
|
764
|
0
|
|
|
|
|
0
|
$obj->_save_object_from_destruction(); |
|
765
|
0
|
|
|
|
|
0
|
return; |
|
766
|
|
|
|
|
|
|
} else { |
|
767
|
295
|
50
|
|
|
|
460
|
print STDERR "MEM DESTROY object $obj class $class if $id\n" if $ENV{'UR_DEBUG_OBJECT_RELEASE'}; |
|
768
|
295
|
|
|
|
|
591
|
$obj->unload(); |
|
769
|
295
|
|
|
|
|
679
|
return $obj->SUPER::DESTROY(); |
|
770
|
|
|
|
|
|
|
} |
|
771
|
|
|
|
|
|
|
} |
|
772
|
|
|
|
|
|
|
elsif (UR::Context::objects_may_go_out_of_scope()) { |
|
773
|
0
|
|
|
|
|
0
|
my $obj_from_cache = delete $UR::Context::all_objects_loaded->{$class}{$id}; |
|
774
|
0
|
0
|
0
|
|
|
0
|
if ($obj->__meta__->is_meta_meta or @{[$obj->__changes__]}) { |
|
|
0
|
|
|
|
|
0
|
|
|
775
|
0
|
0
|
|
|
|
0
|
die "Object found in all_objects_loaded does not match destroyed ref/id! $obj/$id!" unless refaddr($obj) == refaddr($obj_from_cache); |
|
776
|
0
|
|
|
|
|
0
|
$obj->_save_object_from_destruction(); |
|
777
|
0
|
0
|
|
|
|
0
|
print "MEM DESTROY Keeping infrastructure/changed object $obj class $class if $id\n" if $ENV{'UR_DEBUG_OBJECT_RELEASE'}; |
|
778
|
0
|
|
|
|
|
0
|
return; |
|
779
|
|
|
|
|
|
|
} |
|
780
|
|
|
|
|
|
|
else { |
|
781
|
0
|
0
|
|
|
|
0
|
if ($ENV{'UR_DEBUG_OBJECT_RELEASE'}) { |
|
782
|
0
|
|
|
|
|
0
|
print STDERR "MEM DESTROY object $obj class $class id $id\n"; |
|
783
|
|
|
|
|
|
|
} |
|
784
|
0
|
|
|
|
|
0
|
$obj->unload(); |
|
785
|
0
|
|
|
|
|
0
|
return $obj->SUPER::DESTROY(); |
|
786
|
|
|
|
|
|
|
} |
|
787
|
|
|
|
|
|
|
} |
|
788
|
|
|
|
|
|
|
else { |
|
789
|
116
|
50
|
|
|
|
301
|
if ($ENV{'UR_DEBUG_OBJECT_RELEASE'}) { |
|
790
|
0
|
|
|
|
|
0
|
print STDERR "MEM DESTROY object $obj class $class id $id\n"; |
|
791
|
|
|
|
|
|
|
} |
|
792
|
116
|
|
|
|
|
580
|
$obj->SUPER::DESTROY(); |
|
793
|
|
|
|
|
|
|
} |
|
794
|
|
|
|
|
|
|
}; |
|
795
|
|
|
|
|
|
|
|
|
796
|
|
|
|
|
|
|
sub _save_object_from_destruction { |
|
797
|
0
|
|
|
0
|
|
|
my $obj = shift; |
|
798
|
0
|
|
|
|
|
|
my($class, $id) = (ref($obj), $obj->{id}); |
|
799
|
0
|
|
|
|
|
|
$UR::Context::all_objects_loaded->{$class}{$id} = $obj; |
|
800
|
|
|
|
|
|
|
} |
|
801
|
|
|
|
|
|
|
|
|
802
|
|
|
|
|
|
|
END { |
|
803
|
|
|
|
|
|
|
# Turn off monitoring of the DESTROY handler at application exit. |
|
804
|
|
|
|
|
|
|
# setting the typeglob to undef does not work. -sms |
|
805
|
266
|
|
|
266
|
|
1718506
|
delete $UR::Object::{DESTROY}; |
|
806
|
|
|
|
|
|
|
}; |
|
807
|
|
|
|
|
|
|
|
|
808
|
|
|
|
|
|
|
# This module implements the deprecated parts of the UR::Object API |
|
809
|
|
|
|
|
|
|
require UR::ObjectDeprecated; |
|
810
|
|
|
|
|
|
|
|
|
811
|
|
|
|
|
|
|
1; |
|
812
|
|
|
|
|
|
|
|
|
813
|
|
|
|
|
|
|
=pod |
|
814
|
|
|
|
|
|
|
|
|
815
|
|
|
|
|
|
|
=head1 NAME |
|
816
|
|
|
|
|
|
|
|
|
817
|
|
|
|
|
|
|
UR::Object - transactional, queryable, process-independent entities |
|
818
|
|
|
|
|
|
|
|
|
819
|
|
|
|
|
|
|
=head1 SYNOPSIS |
|
820
|
|
|
|
|
|
|
|
|
821
|
|
|
|
|
|
|
Create a new object in the current context, and return it: |
|
822
|
|
|
|
|
|
|
|
|
823
|
|
|
|
|
|
|
$elmo = Acme::Puppet->create( |
|
824
|
|
|
|
|
|
|
name => 'Elmo', |
|
825
|
|
|
|
|
|
|
father => $ernie, |
|
826
|
|
|
|
|
|
|
mother => $bigbird, |
|
827
|
|
|
|
|
|
|
jobs => [$dance, $sing], |
|
828
|
|
|
|
|
|
|
favorite_color => 'red', |
|
829
|
|
|
|
|
|
|
); |
|
830
|
|
|
|
|
|
|
|
|
831
|
|
|
|
|
|
|
Plain accessors work in the typial fashion: |
|
832
|
|
|
|
|
|
|
|
|
833
|
|
|
|
|
|
|
$color = $elmo->favorite_color(); |
|
834
|
|
|
|
|
|
|
|
|
835
|
|
|
|
|
|
|
Changes occur in a transaction in the current context: |
|
836
|
|
|
|
|
|
|
|
|
837
|
|
|
|
|
|
|
$elmo->favorite_color('blue'); |
|
838
|
|
|
|
|
|
|
|
|
839
|
|
|
|
|
|
|
Non-scalar (has_many) properties have a variety of accessors: |
|
840
|
|
|
|
|
|
|
|
|
841
|
|
|
|
|
|
|
@jobs = $elmo->jobs(); |
|
842
|
|
|
|
|
|
|
$jobs = $elmo->job_arrayref(); |
|
843
|
|
|
|
|
|
|
$set = $elmo->job_set(); |
|
844
|
|
|
|
|
|
|
$iter = $elmo->job_iterator(); |
|
845
|
|
|
|
|
|
|
$job = $elmo->add_job($snore); |
|
846
|
|
|
|
|
|
|
$success = $elmo->remove_job($sing); |
|
847
|
|
|
|
|
|
|
|
|
848
|
|
|
|
|
|
|
Query the current context to find objects: |
|
849
|
|
|
|
|
|
|
|
|
850
|
|
|
|
|
|
|
$existing_obj = Acme::Puppet->get(name => 'Elmo'); |
|
851
|
|
|
|
|
|
|
# same reference as $existing_obj |
|
852
|
|
|
|
|
|
|
|
|
853
|
|
|
|
|
|
|
@existing_objs = Acme::Puppet->get( |
|
854
|
|
|
|
|
|
|
favorite_color => ['red','yellow'], |
|
855
|
|
|
|
|
|
|
); |
|
856
|
|
|
|
|
|
|
# this will not get elmo because his favorite color is now blue |
|
857
|
|
|
|
|
|
|
|
|
858
|
|
|
|
|
|
|
@existing_objs = Acme::Puppet->get(job => $snore); |
|
859
|
|
|
|
|
|
|
# this will return $elmo along with other puppets that snore, |
|
860
|
|
|
|
|
|
|
# though we haven't saved the change yet.. |
|
861
|
|
|
|
|
|
|
|
|
862
|
|
|
|
|
|
|
Save our changes: |
|
863
|
|
|
|
|
|
|
|
|
864
|
|
|
|
|
|
|
UR::Context->current->commit; |
|
865
|
|
|
|
|
|
|
|
|
866
|
|
|
|
|
|
|
Too many puppets...: |
|
867
|
|
|
|
|
|
|
|
|
868
|
|
|
|
|
|
|
$elmo->delete; |
|
869
|
|
|
|
|
|
|
|
|
870
|
|
|
|
|
|
|
$elmo->play; # this will throw an exception now |
|
871
|
|
|
|
|
|
|
|
|
872
|
|
|
|
|
|
|
$elmo = Acme::Puppet->get(name => 'Elmo'); # this returns nothing now |
|
873
|
|
|
|
|
|
|
|
|
874
|
|
|
|
|
|
|
Just kidding: |
|
875
|
|
|
|
|
|
|
|
|
876
|
|
|
|
|
|
|
UR::Context->current->rollback; # not a database rollback, an in-memory undo |
|
877
|
|
|
|
|
|
|
|
|
878
|
|
|
|
|
|
|
All is well: |
|
879
|
|
|
|
|
|
|
|
|
880
|
|
|
|
|
|
|
$elmo = Acme::Puppet->get(name => 'Elmo'); # back again! |
|
881
|
|
|
|
|
|
|
|
|
882
|
|
|
|
|
|
|
=head1 DESCRIPTION |
|
883
|
|
|
|
|
|
|
|
|
884
|
|
|
|
|
|
|
UR::Objects are transactional, queryable, representations of entities, built to maintain |
|
885
|
|
|
|
|
|
|
separation between the physical reference in a program, and the logical entity the |
|
886
|
|
|
|
|
|
|
reference represents, using a well-defined interface. |
|
887
|
|
|
|
|
|
|
|
|
888
|
|
|
|
|
|
|
UR uses that separation to automatically handle I/O. It provides a query API, |
|
889
|
|
|
|
|
|
|
and manages the difference between the state of entities in the application, |
|
890
|
|
|
|
|
|
|
and their state in external persistence systems. It aims to do so transparently, |
|
891
|
|
|
|
|
|
|
keeping I/O logic orthogonally to "business logic", and hopefully making code |
|
892
|
|
|
|
|
|
|
around I/O unnecessary to write at all for most programs. |
|
893
|
|
|
|
|
|
|
|
|
894
|
|
|
|
|
|
|
Rather than explicitly constructing and serializing/deserializing objects, the |
|
895
|
|
|
|
|
|
|
application layer just requests objects from the current "context", according to |
|
896
|
|
|
|
|
|
|
their characteristics. The context manages database connections, object state |
|
897
|
|
|
|
|
|
|
changes, references, relationships, in-memory transactions, queries and caching in |
|
898
|
|
|
|
|
|
|
tunable ways. |
|
899
|
|
|
|
|
|
|
|
|
900
|
|
|
|
|
|
|
Accessors dynamically fabricate references lazily, as needed through the same |
|
901
|
|
|
|
|
|
|
query API, so objects work as the developer would traditionally expect in |
|
902
|
|
|
|
|
|
|
most cases. The goal of UR::Object is that your application doesn't have to do |
|
903
|
|
|
|
|
|
|
data management. Just ask for what you want, use it, and let it go. |
|
904
|
|
|
|
|
|
|
|
|
905
|
|
|
|
|
|
|
UR::Objects support full reflection and meta-programming. Its meta-object |
|
906
|
|
|
|
|
|
|
layer is fully self-bootstrapping (most classes of which UR is composed are |
|
907
|
|
|
|
|
|
|
themselves UR::Objects), so the class data can introspect itself, |
|
908
|
|
|
|
|
|
|
such that even classes can be created within transactions and discarded. |
|
909
|
|
|
|
|
|
|
|
|
910
|
|
|
|
|
|
|
=head1 INHERITANCE |
|
911
|
|
|
|
|
|
|
|
|
912
|
|
|
|
|
|
|
UR::ModuleBase Basic error, warning, and status messages for modules in UR. |
|
913
|
|
|
|
|
|
|
UR::Object This class - general OO transactional OO features |
|
914
|
|
|
|
|
|
|
|
|
915
|
|
|
|
|
|
|
=head1 WRITING CLASSES |
|
916
|
|
|
|
|
|
|
|
|
917
|
|
|
|
|
|
|
See L for a narrative explanation of how to write clases. |
|
918
|
|
|
|
|
|
|
|
|
919
|
|
|
|
|
|
|
For a complete reference see L. |
|
920
|
|
|
|
|
|
|
|
|
921
|
|
|
|
|
|
|
For the meta-object API see L. |
|
922
|
|
|
|
|
|
|
|
|
923
|
|
|
|
|
|
|
A simple example, declaring the class used above: |
|
924
|
|
|
|
|
|
|
|
|
925
|
|
|
|
|
|
|
class Acme::Puppet { |
|
926
|
|
|
|
|
|
|
id_by => 'name', |
|
927
|
|
|
|
|
|
|
has_optional => [ |
|
928
|
|
|
|
|
|
|
father => { is => 'Acme::Puppet' }, |
|
929
|
|
|
|
|
|
|
mother => { is => 'Acme::Puppet' }, |
|
930
|
|
|
|
|
|
|
jobs => { is => 'Acme::Job', is_many => 1 }, |
|
931
|
|
|
|
|
|
|
] |
|
932
|
|
|
|
|
|
|
}; |
|
933
|
|
|
|
|
|
|
|
|
934
|
|
|
|
|
|
|
You can also declare the same API, but specifying additional internal details to make |
|
935
|
|
|
|
|
|
|
database mapping occur the way you'd like: |
|
936
|
|
|
|
|
|
|
|
|
937
|
|
|
|
|
|
|
class Acme::Puppet { |
|
938
|
|
|
|
|
|
|
id_by => 'name', |
|
939
|
|
|
|
|
|
|
has_optional => [ |
|
940
|
|
|
|
|
|
|
father => { is => 'Acme::Puppet', id_by => 'father_id' }, |
|
941
|
|
|
|
|
|
|
mother => { is => 'Acme::Puppet', id_by => 'mother_id' }, |
|
942
|
|
|
|
|
|
|
}, |
|
943
|
|
|
|
|
|
|
has_many_optional => [ |
|
944
|
|
|
|
|
|
|
job_assignments => { is => 'Acme::PuppetJob', im_its => 'puppet' }, |
|
945
|
|
|
|
|
|
|
jobs => { is => 'Acme::Job', via => 'job_assignments', to => 'job' }, |
|
946
|
|
|
|
|
|
|
] |
|
947
|
|
|
|
|
|
|
}; |
|
948
|
|
|
|
|
|
|
|
|
949
|
|
|
|
|
|
|
|
|
950
|
|
|
|
|
|
|
=head1 CONSTRUCTING OBJECTS |
|
951
|
|
|
|
|
|
|
|
|
952
|
|
|
|
|
|
|
New objects are returned by create() and get(), which delegate to the current |
|
953
|
|
|
|
|
|
|
context for all object construction. |
|
954
|
|
|
|
|
|
|
|
|
955
|
|
|
|
|
|
|
The create() method will always create something new or will return undef if |
|
956
|
|
|
|
|
|
|
the identity is already known to be in use. |
|
957
|
|
|
|
|
|
|
|
|
958
|
|
|
|
|
|
|
The get() method lets the context internally decide whether to return a cached |
|
959
|
|
|
|
|
|
|
reference for the specified logical entities or to construct new objects |
|
960
|
|
|
|
|
|
|
by loading data from the outside. |
|
961
|
|
|
|
|
|
|
|
|
962
|
|
|
|
|
|
|
=head1 METHODS |
|
963
|
|
|
|
|
|
|
|
|
964
|
|
|
|
|
|
|
The examples below use $obj where an actual object reference is required, |
|
965
|
|
|
|
|
|
|
and SomeClass where the class name can be used. In some cases the |
|
966
|
|
|
|
|
|
|
example in the synopsisis is continued for deeper illustration. |
|
967
|
|
|
|
|
|
|
|
|
968
|
|
|
|
|
|
|
=head2 Base API |
|
969
|
|
|
|
|
|
|
|
|
970
|
|
|
|
|
|
|
=over 4 |
|
971
|
|
|
|
|
|
|
|
|
972
|
|
|
|
|
|
|
=item get |
|
973
|
|
|
|
|
|
|
|
|
974
|
|
|
|
|
|
|
$obj = SomeClass->get($id); |
|
975
|
|
|
|
|
|
|
$obj = SomeClass->get(property1 => value1, ...); |
|
976
|
|
|
|
|
|
|
@obj = SomeClass->get(property1 => value1, ...); |
|
977
|
|
|
|
|
|
|
@obj = SomeClass->get('property1 operator1' => value1, ...); |
|
978
|
|
|
|
|
|
|
|
|
979
|
|
|
|
|
|
|
Query the current context for objects. |
|
980
|
|
|
|
|
|
|
|
|
981
|
|
|
|
|
|
|
It turns the passed-in parameters into a L and returns all |
|
982
|
|
|
|
|
|
|
objects of the given class which match. The current context determines |
|
983
|
|
|
|
|
|
|
whether the request can be fulfilled without external queries. Data |
|
984
|
|
|
|
|
|
|
is loaded from underlying database(s) lazliy as needed to fulfuill the |
|
985
|
|
|
|
|
|
|
request. |
|
986
|
|
|
|
|
|
|
|
|
987
|
|
|
|
|
|
|
In the simplest case of requesting an object by id which is cached, the |
|
988
|
|
|
|
|
|
|
call to get() is an immediate hash lookup, and is very fast. |
|
989
|
|
|
|
|
|
|
|
|
990
|
|
|
|
|
|
|
See L, or look at L, L, |
|
991
|
|
|
|
|
|
|
and L for details. |
|
992
|
|
|
|
|
|
|
|
|
993
|
|
|
|
|
|
|
If called in scalar context and more than one object matches the given |
|
994
|
|
|
|
|
|
|
parameters, get() will raise an exception through C. |
|
995
|
|
|
|
|
|
|
|
|
996
|
|
|
|
|
|
|
=item create |
|
997
|
|
|
|
|
|
|
|
|
998
|
|
|
|
|
|
|
$obj = SomeClass->create( |
|
999
|
|
|
|
|
|
|
property1 => $value1, |
|
1000
|
|
|
|
|
|
|
properties2 => \@values2, |
|
1001
|
|
|
|
|
|
|
); |
|
1002
|
|
|
|
|
|
|
|
|
1003
|
|
|
|
|
|
|
Create a new entity in the current context, and return a reference to it. |
|
1004
|
|
|
|
|
|
|
|
|
1005
|
|
|
|
|
|
|
The only required property to create an object is the "id", |
|
1006
|
|
|
|
|
|
|
and that is only required for objects which do not autogenerate their |
|
1007
|
|
|
|
|
|
|
own ids. This requirement may be overridden in subclasses to be |
|
1008
|
|
|
|
|
|
|
more restrictive. |
|
1009
|
|
|
|
|
|
|
|
|
1010
|
|
|
|
|
|
|
If entities of this type persist in an underlying context, the entity will |
|
1011
|
|
|
|
|
|
|
not appear there until commit. (i.e. no insert is done until just before |
|
1012
|
|
|
|
|
|
|
a real database commit) The object in question does not need to pass its own |
|
1013
|
|
|
|
|
|
|
constraints when initially created, but must be fully valid before the |
|
1014
|
|
|
|
|
|
|
transaction which created it commits. |
|
1015
|
|
|
|
|
|
|
|
|
1016
|
|
|
|
|
|
|
=item delete |
|
1017
|
|
|
|
|
|
|
|
|
1018
|
|
|
|
|
|
|
$obj->delete |
|
1019
|
|
|
|
|
|
|
|
|
1020
|
|
|
|
|
|
|
Deletes an object in the current context. |
|
1021
|
|
|
|
|
|
|
|
|
1022
|
|
|
|
|
|
|
The $obj reference will be garbage collected at the discretion of the Perl interpreter as soon as possible. |
|
1023
|
|
|
|
|
|
|
Any attempt to use the reference after delete() is called will result in an exception. |
|
1024
|
|
|
|
|
|
|
|
|
1025
|
|
|
|
|
|
|
If the represented entity was loaded from the parent context (i.e. persistent database objects), |
|
1026
|
|
|
|
|
|
|
it will not be deleted from that context (the database) until commit is called. The commit call |
|
1027
|
|
|
|
|
|
|
will do both the delete and the commit, presuming the complete save works across all involved |
|
1028
|
|
|
|
|
|
|
data sources. |
|
1029
|
|
|
|
|
|
|
|
|
1030
|
|
|
|
|
|
|
Should the transaction roll-back, the deleted object will be re-created in the current context, |
|
1031
|
|
|
|
|
|
|
and a fresh reference will later be returnable by get(). See the documentation on L |
|
1032
|
|
|
|
|
|
|
for details on how deleted objects are rememberd and removed later from the database, and how |
|
1033
|
|
|
|
|
|
|
deleted objects are re-constructed on STM rollback. |
|
1034
|
|
|
|
|
|
|
|
|
1035
|
|
|
|
|
|
|
=item copy |
|
1036
|
|
|
|
|
|
|
|
|
1037
|
|
|
|
|
|
|
$obj->copy(%overrides) |
|
1038
|
|
|
|
|
|
|
|
|
1039
|
|
|
|
|
|
|
Copies the existing C<$obj> by copying the values of all direct properties, |
|
1040
|
|
|
|
|
|
|
except for ID properties, to a newly created object of the same type. A list |
|
1041
|
|
|
|
|
|
|
of params and values may be provided as overrides to the existing values or to |
|
1042
|
|
|
|
|
|
|
specify an ID. |
|
1043
|
|
|
|
|
|
|
|
|
1044
|
|
|
|
|
|
|
=item class |
|
1045
|
|
|
|
|
|
|
|
|
1046
|
|
|
|
|
|
|
$class_name = $obj->class; |
|
1047
|
|
|
|
|
|
|
$class_name = SomeClass->class; |
|
1048
|
|
|
|
|
|
|
|
|
1049
|
|
|
|
|
|
|
Returns the name of the class of the object in question. See __meta__ below |
|
1050
|
|
|
|
|
|
|
for the class meta-object. |
|
1051
|
|
|
|
|
|
|
|
|
1052
|
|
|
|
|
|
|
=item id |
|
1053
|
|
|
|
|
|
|
|
|
1054
|
|
|
|
|
|
|
$id = $obj->id; |
|
1055
|
|
|
|
|
|
|
|
|
1056
|
|
|
|
|
|
|
The unique identifier of the object within its class. |
|
1057
|
|
|
|
|
|
|
|
|
1058
|
|
|
|
|
|
|
For database-tracked entities this is the primary key value, or a composite |
|
1059
|
|
|
|
|
|
|
blob containing the primary key values for multi-column primary keys. |
|
1060
|
|
|
|
|
|
|
|
|
1061
|
|
|
|
|
|
|
For regular objects private to the process, the default id embeds the |
|
1062
|
|
|
|
|
|
|
hostname, process ID, and a timestamp to uniquely identify the |
|
1063
|
|
|
|
|
|
|
UR::Context::Process object which is its final home. |
|
1064
|
|
|
|
|
|
|
|
|
1065
|
|
|
|
|
|
|
When inheritance is involved beneath UR::Object, the 'id' may identify the object |
|
1066
|
|
|
|
|
|
|
within the super-class as well. It is also possible for an object to have a |
|
1067
|
|
|
|
|
|
|
different id upon sub-classification. |
|
1068
|
|
|
|
|
|
|
|
|
1069
|
|
|
|
|
|
|
|
|
1070
|
|
|
|
|
|
|
=back |
|
1071
|
|
|
|
|
|
|
|
|
1072
|
|
|
|
|
|
|
=head2 Accessors |
|
1073
|
|
|
|
|
|
|
|
|
1074
|
|
|
|
|
|
|
Every relationship declared in the class definition results in at least one |
|
1075
|
|
|
|
|
|
|
accesor being generated for the class in question. |
|
1076
|
|
|
|
|
|
|
|
|
1077
|
|
|
|
|
|
|
Identity properties are read-only, while non-identity properties are read-write |
|
1078
|
|
|
|
|
|
|
unless is_mutable is explicitly set to false. |
|
1079
|
|
|
|
|
|
|
|
|
1080
|
|
|
|
|
|
|
Assigning an invalid value is allowed temporarily, but the current transaction |
|
1081
|
|
|
|
|
|
|
will be in an invalid state until corrected, and will not be commitable. |
|
1082
|
|
|
|
|
|
|
|
|
1083
|
|
|
|
|
|
|
The return value of an the accessor when it mutates the object is |
|
1084
|
|
|
|
|
|
|
the value of the property after the mutation has occurred. |
|
1085
|
|
|
|
|
|
|
|
|
1086
|
|
|
|
|
|
|
|
|
1087
|
|
|
|
|
|
|
=head3 Single-value property accessors: |
|
1088
|
|
|
|
|
|
|
|
|
1089
|
|
|
|
|
|
|
By default, properties are expected to return a single value. |
|
1090
|
|
|
|
|
|
|
|
|
1091
|
|
|
|
|
|
|
=over 4 |
|
1092
|
|
|
|
|
|
|
|
|
1093
|
|
|
|
|
|
|
=item NAME |
|
1094
|
|
|
|
|
|
|
|
|
1095
|
|
|
|
|
|
|
Regular accessors have the same name as the property, as declared, and also work |
|
1096
|
|
|
|
|
|
|
as mutators as is commonly expected: |
|
1097
|
|
|
|
|
|
|
|
|
1098
|
|
|
|
|
|
|
$value = $obj->property_name; |
|
1099
|
|
|
|
|
|
|
$obj->property_name($new_value); |
|
1100
|
|
|
|
|
|
|
|
|
1101
|
|
|
|
|
|
|
When the property is declared with id_by instead of recording the refereince, it |
|
1102
|
|
|
|
|
|
|
records the id of the object automatically, such that both will return different |
|
1103
|
|
|
|
|
|
|
values after either changes. |
|
1104
|
|
|
|
|
|
|
|
|
1105
|
|
|
|
|
|
|
=back |
|
1106
|
|
|
|
|
|
|
|
|
1107
|
|
|
|
|
|
|
=head3 Muli-value property accessors: |
|
1108
|
|
|
|
|
|
|
|
|
1109
|
|
|
|
|
|
|
When a property is declared with the "is_many" flag, a variety of accessors are made |
|
1110
|
|
|
|
|
|
|
available on the object. See C for more details |
|
1111
|
|
|
|
|
|
|
on the ways to declare relationships between objects when writing classes. |
|
1112
|
|
|
|
|
|
|
|
|
1113
|
|
|
|
|
|
|
Using the example from the synopsis: |
|
1114
|
|
|
|
|
|
|
|
|
1115
|
|
|
|
|
|
|
=over 4 |
|
1116
|
|
|
|
|
|
|
|
|
1117
|
|
|
|
|
|
|
=item NAMEs (the property name pluralized) |
|
1118
|
|
|
|
|
|
|
|
|
1119
|
|
|
|
|
|
|
A "has_many" relationship is declared using the plural form of the relationship name. |
|
1120
|
|
|
|
|
|
|
An accessor returning the list of property values is generated for the class. It |
|
1121
|
|
|
|
|
|
|
is usable with or without additional filters: |
|
1122
|
|
|
|
|
|
|
|
|
1123
|
|
|
|
|
|
|
@jobs = $elmo->jobs(); |
|
1124
|
|
|
|
|
|
|
@fun_jobs = $elmo->jobs(is_fun => 1); |
|
1125
|
|
|
|
|
|
|
|
|
1126
|
|
|
|
|
|
|
The singular name is used for the remainder of the accessors... |
|
1127
|
|
|
|
|
|
|
|
|
1128
|
|
|
|
|
|
|
=item NAME (the property name in singular form) |
|
1129
|
|
|
|
|
|
|
|
|
1130
|
|
|
|
|
|
|
Returns one item from the group, which must be specified in parameters. If more |
|
1131
|
|
|
|
|
|
|
than one item is matched, an exception is thrown via die(): |
|
1132
|
|
|
|
|
|
|
|
|
1133
|
|
|
|
|
|
|
$job = $elmo->job(name => 'Sing'); |
|
1134
|
|
|
|
|
|
|
|
|
1135
|
|
|
|
|
|
|
$job = $elmo->job(is_fun => 1); |
|
1136
|
|
|
|
|
|
|
# die: too many things are fun for Elmo |
|
1137
|
|
|
|
|
|
|
|
|
1138
|
|
|
|
|
|
|
=item NAME_list |
|
1139
|
|
|
|
|
|
|
|
|
1140
|
|
|
|
|
|
|
The default accessor is available as *_list. Usable with or without additional filters: |
|
1141
|
|
|
|
|
|
|
|
|
1142
|
|
|
|
|
|
|
@jobs = $elmo->job_list(); |
|
1143
|
|
|
|
|
|
|
@fun_jobs = $elmo_>job_list(is_fun => 1); |
|
1144
|
|
|
|
|
|
|
|
|
1145
|
|
|
|
|
|
|
|
|
1146
|
|
|
|
|
|
|
=item NAME_set |
|
1147
|
|
|
|
|
|
|
|
|
1148
|
|
|
|
|
|
|
Return a L value representing the values with *_set: |
|
1149
|
|
|
|
|
|
|
|
|
1150
|
|
|
|
|
|
|
$set = $elmo->job_set(); |
|
1151
|
|
|
|
|
|
|
$set = $elmo->job_set(is_hard => 1); |
|
1152
|
|
|
|
|
|
|
|
|
1153
|
|
|
|
|
|
|
|
|
1154
|
|
|
|
|
|
|
=item NAME_iterator |
|
1155
|
|
|
|
|
|
|
|
|
1156
|
|
|
|
|
|
|
Create a new iterator for the set of property values with *_iterator: |
|
1157
|
|
|
|
|
|
|
|
|
1158
|
|
|
|
|
|
|
$iter = $elmo->job_iterator(); |
|
1159
|
|
|
|
|
|
|
$iter = $elmo->job_iterator(is_fun => 1, -order_by => ['name]); |
|
1160
|
|
|
|
|
|
|
while($obj = $iter->next()) { ... } |
|
1161
|
|
|
|
|
|
|
|
|
1162
|
|
|
|
|
|
|
=item add_NAME |
|
1163
|
|
|
|
|
|
|
|
|
1164
|
|
|
|
|
|
|
Add an item to the set of values with add_*: |
|
1165
|
|
|
|
|
|
|
|
|
1166
|
|
|
|
|
|
|
$added = $elmo->add_job($snore); |
|
1167
|
|
|
|
|
|
|
|
|
1168
|
|
|
|
|
|
|
A variation of the above will construt the item and add it at once. |
|
1169
|
|
|
|
|
|
|
This second form of add_* automatically would identify that the line items |
|
1170
|
|
|
|
|
|
|
also reference the order, and establish the correct converse relationship |
|
1171
|
|
|
|
|
|
|
automatically. |
|
1172
|
|
|
|
|
|
|
|
|
1173
|
|
|
|
|
|
|
@lines = $order->lines; |
|
1174
|
|
|
|
|
|
|
# 2 lines, for instance |
|
1175
|
|
|
|
|
|
|
|
|
1176
|
|
|
|
|
|
|
$line = $order->add_line( |
|
1177
|
|
|
|
|
|
|
product => $p, |
|
1178
|
|
|
|
|
|
|
quantity => $q, |
|
1179
|
|
|
|
|
|
|
); |
|
1180
|
|
|
|
|
|
|
print $line->num; |
|
1181
|
|
|
|
|
|
|
# 3, if the line item has a multi-column primary key with auto_increment on the 2nd column called num |
|
1182
|
|
|
|
|
|
|
|
|
1183
|
|
|
|
|
|
|
=item remove_NAME |
|
1184
|
|
|
|
|
|
|
|
|
1185
|
|
|
|
|
|
|
Items can be removed from the assigned group in a way symetrical with how they are added: |
|
1186
|
|
|
|
|
|
|
|
|
1187
|
|
|
|
|
|
|
$removed = $elmo->remove_job($sing); |
|
1188
|
|
|
|
|
|
|
|
|
1189
|
|
|
|
|
|
|
=back |
|
1190
|
|
|
|
|
|
|
|
|
1191
|
|
|
|
|
|
|
=head2 Extended API |
|
1192
|
|
|
|
|
|
|
|
|
1193
|
|
|
|
|
|
|
These methods are available on any class defined by UR. They |
|
1194
|
|
|
|
|
|
|
are convenience methods around L, L, |
|
1195
|
|
|
|
|
|
|
L, L, L |
|
1196
|
|
|
|
|
|
|
and L. |
|
1197
|
|
|
|
|
|
|
|
|
1198
|
|
|
|
|
|
|
=over 4 |
|
1199
|
|
|
|
|
|
|
|
|
1200
|
|
|
|
|
|
|
=item create_iterator |
|
1201
|
|
|
|
|
|
|
|
|
1202
|
|
|
|
|
|
|
$iter = SomeClass->create_iterator( |
|
1203
|
|
|
|
|
|
|
property1 => $explicit_value, |
|
1204
|
|
|
|
|
|
|
property2 => \@my_in_clause, |
|
1205
|
|
|
|
|
|
|
'property3 like' => 'some_pattern_with_%_as_wildcard', |
|
1206
|
|
|
|
|
|
|
'property4 between' => [$low,$high], |
|
1207
|
|
|
|
|
|
|
); |
|
1208
|
|
|
|
|
|
|
|
|
1209
|
|
|
|
|
|
|
while (my $obj = $iter->next) { |
|
1210
|
|
|
|
|
|
|
... |
|
1211
|
|
|
|
|
|
|
} |
|
1212
|
|
|
|
|
|
|
|
|
1213
|
|
|
|
|
|
|
Takes the same sort of parameters as get(), but returns a L |
|
1214
|
|
|
|
|
|
|
for the matching objects. |
|
1215
|
|
|
|
|
|
|
|
|
1216
|
|
|
|
|
|
|
The next() method will return one object from the resulting set each time it is |
|
1217
|
|
|
|
|
|
|
called, and undef when the results have been exhausted. |
|
1218
|
|
|
|
|
|
|
|
|
1219
|
|
|
|
|
|
|
C instances are normal object references in the current |
|
1220
|
|
|
|
|
|
|
process, not context-oriented UR::Objects. They vanish upon dereference, |
|
1221
|
|
|
|
|
|
|
and cannot be retrieved by querying the context. |
|
1222
|
|
|
|
|
|
|
|
|
1223
|
|
|
|
|
|
|
When using an iterator, the system attempts to return objects matching the params |
|
1224
|
|
|
|
|
|
|
at the time the iterator is created, even if those objects do not match the |
|
1225
|
|
|
|
|
|
|
params at the time they are returned from next(). Consider this case: |
|
1226
|
|
|
|
|
|
|
|
|
1227
|
|
|
|
|
|
|
# many objects in the DB match this |
|
1228
|
|
|
|
|
|
|
my $iter = SomeClass->create_iterator(job => 'cleaner'); |
|
1229
|
|
|
|
|
|
|
|
|
1230
|
|
|
|
|
|
|
my $an_obj = SomeClass->get(job => 'cleaner', id => 1); |
|
1231
|
|
|
|
|
|
|
$an_obj->job('messer-upper'); # This no longer matches the iterator's params |
|
1232
|
|
|
|
|
|
|
|
|
1233
|
|
|
|
|
|
|
my @iter_objs; |
|
1234
|
|
|
|
|
|
|
while (my $o = $iter->next) { |
|
1235
|
|
|
|
|
|
|
push @iter_objs, $o; |
|
1236
|
|
|
|
|
|
|
} |
|
1237
|
|
|
|
|
|
|
|
|
1238
|
|
|
|
|
|
|
At the end, @iter_objs will contain several objects, including the object with id 1, |
|
1239
|
|
|
|
|
|
|
even though its job is no longer 'cleaner'. However, if an object matching the |
|
1240
|
|
|
|
|
|
|
iterator's params is deleted between the time the iterator is created and the time |
|
1241
|
|
|
|
|
|
|
next() would return that object, then next() will throw an exception. |
|
1242
|
|
|
|
|
|
|
|
|
1243
|
|
|
|
|
|
|
=item define_set |
|
1244
|
|
|
|
|
|
|
|
|
1245
|
|
|
|
|
|
|
$set = SomeClass->define_set( |
|
1246
|
|
|
|
|
|
|
property1 => $explicit_value, |
|
1247
|
|
|
|
|
|
|
property2 => \@my_in_clause, |
|
1248
|
|
|
|
|
|
|
'property3 like' => 'some_pattern_with_%_as_wildcard', |
|
1249
|
|
|
|
|
|
|
'property4 between' => [$low,$high], |
|
1250
|
|
|
|
|
|
|
); |
|
1251
|
|
|
|
|
|
|
|
|
1252
|
|
|
|
|
|
|
@subsets = $set->group_by('property3','property4'); |
|
1253
|
|
|
|
|
|
|
|
|
1254
|
|
|
|
|
|
|
@some_members = $subsets[0]->members; |
|
1255
|
|
|
|
|
|
|
|
|
1256
|
|
|
|
|
|
|
Takes the same sort of parameters as get(), but returns a set object. |
|
1257
|
|
|
|
|
|
|
|
|
1258
|
|
|
|
|
|
|
Sets are lazy, and only query underlying databases as much as necessary. At any point |
|
1259
|
|
|
|
|
|
|
in time the members() method returns all matches to the specified parameters. |
|
1260
|
|
|
|
|
|
|
|
|
1261
|
|
|
|
|
|
|
See L for details. |
|
1262
|
|
|
|
|
|
|
|
|
1263
|
|
|
|
|
|
|
=item define_boolexpr |
|
1264
|
|
|
|
|
|
|
|
|
1265
|
|
|
|
|
|
|
$bx = SomeClass->define_boolexpr( |
|
1266
|
|
|
|
|
|
|
property1 => $explicit_value, |
|
1267
|
|
|
|
|
|
|
property2 => \@my_in_clause, |
|
1268
|
|
|
|
|
|
|
'property3 like' => 'some_pattern_with_%_as_wildcard', |
|
1269
|
|
|
|
|
|
|
'property4 between' => [$low,$high], |
|
1270
|
|
|
|
|
|
|
); |
|
1271
|
|
|
|
|
|
|
|
|
1272
|
|
|
|
|
|
|
$bx->evaluate($obj1); # true or false? |
|
1273
|
|
|
|
|
|
|
|
|
1274
|
|
|
|
|
|
|
Takes the same sort of parameters as get(), but returns a L object. |
|
1275
|
|
|
|
|
|
|
|
|
1276
|
|
|
|
|
|
|
The boolean expression can be used to evaluate other objects to see if they match |
|
1277
|
|
|
|
|
|
|
the given condition. The "id" of the object embeds the complete "where clause", |
|
1278
|
|
|
|
|
|
|
and as a semi-human-readable blob, such is reconstitutable from it. |
|
1279
|
|
|
|
|
|
|
|
|
1280
|
|
|
|
|
|
|
See L for details on how to use this to do advanced work on |
|
1281
|
|
|
|
|
|
|
defining sets, comparing objects, creating query templates, adding |
|
1282
|
|
|
|
|
|
|
object constraints, etc. |
|
1283
|
|
|
|
|
|
|
|
|
1284
|
|
|
|
|
|
|
=item add_observer |
|
1285
|
|
|
|
|
|
|
|
|
1286
|
|
|
|
|
|
|
$o = $obj1->add_observer( |
|
1287
|
|
|
|
|
|
|
aspect => 'someproperty' |
|
1288
|
|
|
|
|
|
|
callback => sub { print "change!\n" }, |
|
1289
|
|
|
|
|
|
|
); |
|
1290
|
|
|
|
|
|
|
|
|
1291
|
|
|
|
|
|
|
$obj1->property1('new value'); |
|
1292
|
|
|
|
|
|
|
|
|
1293
|
|
|
|
|
|
|
# observer callback fires.... |
|
1294
|
|
|
|
|
|
|
|
|
1295
|
|
|
|
|
|
|
$o->delete; |
|
1296
|
|
|
|
|
|
|
|
|
1297
|
|
|
|
|
|
|
Adds an observer to an object, monitoring one or more of its properties for changes. |
|
1298
|
|
|
|
|
|
|
|
|
1299
|
|
|
|
|
|
|
The specified callback is fired upon property changes which match the observation request. |
|
1300
|
|
|
|
|
|
|
|
|
1301
|
|
|
|
|
|
|
See L for details. |
|
1302
|
|
|
|
|
|
|
|
|
1303
|
|
|
|
|
|
|
=item create_mock |
|
1304
|
|
|
|
|
|
|
|
|
1305
|
|
|
|
|
|
|
$mock = SomeClass->create_mock( |
|
1306
|
|
|
|
|
|
|
property1 => $value, |
|
1307
|
|
|
|
|
|
|
method1 => $return_value, |
|
1308
|
|
|
|
|
|
|
); |
|
1309
|
|
|
|
|
|
|
|
|
1310
|
|
|
|
|
|
|
Creates a mock object using using the class meta-data for "SomeClass" via L. |
|
1311
|
|
|
|
|
|
|
|
|
1312
|
|
|
|
|
|
|
Useful for test cases. |
|
1313
|
|
|
|
|
|
|
|
|
1314
|
|
|
|
|
|
|
=back |
|
1315
|
|
|
|
|
|
|
|
|
1316
|
|
|
|
|
|
|
=head2 Meta API |
|
1317
|
|
|
|
|
|
|
|
|
1318
|
|
|
|
|
|
|
The following methods allow the application to interrogate UR for information |
|
1319
|
|
|
|
|
|
|
about the object in question. |
|
1320
|
|
|
|
|
|
|
|
|
1321
|
|
|
|
|
|
|
=over 4 |
|
1322
|
|
|
|
|
|
|
|
|
1323
|
|
|
|
|
|
|
=item __meta__ |
|
1324
|
|
|
|
|
|
|
|
|
1325
|
|
|
|
|
|
|
$class_obj = $obj->__meta__(); |
|
1326
|
|
|
|
|
|
|
|
|
1327
|
|
|
|
|
|
|
Returns the class metadata object for the given object's class. Class objects |
|
1328
|
|
|
|
|
|
|
are from the class L, and hold information about the class' |
|
1329
|
|
|
|
|
|
|
properties, data source, relationships to other classes, etc. |
|
1330
|
|
|
|
|
|
|
|
|
1331
|
|
|
|
|
|
|
=item __extend_namespace__ |
|
1332
|
|
|
|
|
|
|
|
|
1333
|
|
|
|
|
|
|
package Foo::Bar; |
|
1334
|
|
|
|
|
|
|
|
|
1335
|
|
|
|
|
|
|
class Foo::Bar { has => ['stuff','things'] }; |
|
1336
|
|
|
|
|
|
|
|
|
1337
|
|
|
|
|
|
|
sub __extend_namespace__ { |
|
1338
|
|
|
|
|
|
|
my $class = shift; |
|
1339
|
|
|
|
|
|
|
my $ext = shift; |
|
1340
|
|
|
|
|
|
|
return class {$class . '::' . $ext} { has => ['more'] }; |
|
1341
|
|
|
|
|
|
|
} |
|
1342
|
|
|
|
|
|
|
|
|
1343
|
|
|
|
|
|
|
Dynamically generate new classes under a given namespace. |
|
1344
|
|
|
|
|
|
|
This is called automatically by UR::ModuleLoader when an unidentified class name is used. |
|
1345
|
|
|
|
|
|
|
|
|
1346
|
|
|
|
|
|
|
If Foo::Bar::Baz is not a UR class, and this occurs: |
|
1347
|
|
|
|
|
|
|
|
|
1348
|
|
|
|
|
|
|
Foo::Bar::Baz->some_method() |
|
1349
|
|
|
|
|
|
|
|
|
1350
|
|
|
|
|
|
|
This is called: |
|
1351
|
|
|
|
|
|
|
|
|
1352
|
|
|
|
|
|
|
Foo::Bar->__extend_namespace__("Baz") |
|
1353
|
|
|
|
|
|
|
|
|
1354
|
|
|
|
|
|
|
If it returns a new class meta, the code will proceed on as though the class |
|
1355
|
|
|
|
|
|
|
had always existed. |
|
1356
|
|
|
|
|
|
|
|
|
1357
|
|
|
|
|
|
|
If Foo::Bar does not exist, the above will be called recursively: |
|
1358
|
|
|
|
|
|
|
|
|
1359
|
|
|
|
|
|
|
Foo->__extend_namespace__("Bar") |
|
1360
|
|
|
|
|
|
|
|
|
1361
|
|
|
|
|
|
|
If Foo::Bar, whether loaded or generated, cannot extend itself for "Baz", |
|
1362
|
|
|
|
|
|
|
the loader will go up the tree before giving up. This means a top-level |
|
1363
|
|
|
|
|
|
|
module could dynamically define classes for any given class name used |
|
1364
|
|
|
|
|
|
|
under it: |
|
1365
|
|
|
|
|
|
|
|
|
1366
|
|
|
|
|
|
|
Foo->__extend_namespace__("Bar::Baz") |
|
1367
|
|
|
|
|
|
|
|
|
1368
|
|
|
|
|
|
|
=item __errors__ |
|
1369
|
|
|
|
|
|
|
|
|
1370
|
|
|
|
|
|
|
@tags = $obj->__errors__() |
|
1371
|
|
|
|
|
|
|
|
|
1372
|
|
|
|
|
|
|
Return a list of L values describing the issues which would |
|
1373
|
|
|
|
|
|
|
prevent a commit in the current transaction. |
|
1374
|
|
|
|
|
|
|
|
|
1375
|
|
|
|
|
|
|
The base implementation check the validity of an object by applying any constraints |
|
1376
|
|
|
|
|
|
|
layed out in the class such as making sure any non-optional properties contain values, |
|
1377
|
|
|
|
|
|
|
numeric properties contain numeric data, and properties with enumerated values only |
|
1378
|
|
|
|
|
|
|
contain valid values. |
|
1379
|
|
|
|
|
|
|
|
|
1380
|
|
|
|
|
|
|
Sub-classes can override this method to add additional validity checking. |
|
1381
|
|
|
|
|
|
|
|
|
1382
|
|
|
|
|
|
|
=item __display_name__ |
|
1383
|
|
|
|
|
|
|
|
|
1384
|
|
|
|
|
|
|
$text = $obj->__display_name__; |
|
1385
|
|
|
|
|
|
|
# the class and id of $obj, by default |
|
1386
|
|
|
|
|
|
|
|
|
1387
|
|
|
|
|
|
|
$text = $line_item->__display_name__($order); |
|
1388
|
|
|
|
|
|
|
|
|
1389
|
|
|
|
|
|
|
Stringifies an object. Some classes may choose to actually overload the stringification operator |
|
1390
|
|
|
|
|
|
|
with this method. Even if they do not, this method will still attempt to identify this object in |
|
1391
|
|
|
|
|
|
|
text form. The default returns the class name and id value of the object within a string. |
|
1392
|
|
|
|
|
|
|
|
|
1393
|
|
|
|
|
|
|
It can be overridden to do a more nuanced job. The class might also choose to overload the |
|
1394
|
|
|
|
|
|
|
stringification operator itself with this method, but even if it doesn not the system will |
|
1395
|
|
|
|
|
|
|
presume this method can be called directly on an object for reasonable stringificaiton. |
|
1396
|
|
|
|
|
|
|
|
|
1397
|
|
|
|
|
|
|
=item __context__ |
|
1398
|
|
|
|
|
|
|
|
|
1399
|
|
|
|
|
|
|
$c = $self->__context__; |
|
1400
|
|
|
|
|
|
|
|
|
1401
|
|
|
|
|
|
|
Return the L for the object reference in question. |
|
1402
|
|
|
|
|
|
|
|
|
1403
|
|
|
|
|
|
|
In UR, a "context" handles connextions between objects, instead of relying |
|
1404
|
|
|
|
|
|
|
on having objects directly reference each other. This allows an object |
|
1405
|
|
|
|
|
|
|
to have a relationship with a large number of other logical entities, |
|
1406
|
|
|
|
|
|
|
without having a "physical" reference present within the process in question. |
|
1407
|
|
|
|
|
|
|
|
|
1408
|
|
|
|
|
|
|
All attempts to resolve non-primitive attribute access go through the context. |
|
1409
|
|
|
|
|
|
|
|
|
1410
|
|
|
|
|
|
|
=back |
|
1411
|
|
|
|
|
|
|
|
|
1412
|
|
|
|
|
|
|
=head2 Extension API |
|
1413
|
|
|
|
|
|
|
|
|
1414
|
|
|
|
|
|
|
These methods are primarily of interest for debugging, for test cases, and internal UR development. |
|
1415
|
|
|
|
|
|
|
|
|
1416
|
|
|
|
|
|
|
They are likely to change before the 1.0 release. |
|
1417
|
|
|
|
|
|
|
|
|
1418
|
|
|
|
|
|
|
=over 4 |
|
1419
|
|
|
|
|
|
|
|
|
1420
|
|
|
|
|
|
|
=item __signal_change__ |
|
1421
|
|
|
|
|
|
|
|
|
1422
|
|
|
|
|
|
|
Called by all mutators to tell the current context about a state change. |
|
1423
|
|
|
|
|
|
|
|
|
1424
|
|
|
|
|
|
|
=item __changes__ |
|
1425
|
|
|
|
|
|
|
|
|
1426
|
|
|
|
|
|
|
@tags = $obj->__changes__() |
|
1427
|
|
|
|
|
|
|
|
|
1428
|
|
|
|
|
|
|
@tags = $obj->__changes__('prop1', 'prop2', ...) |
|
1429
|
|
|
|
|
|
|
|
|
1430
|
|
|
|
|
|
|
Return a list of changes present on the object _directly_. This is really only |
|
1431
|
|
|
|
|
|
|
useful internally because the boundary of the object is internal/subjective. |
|
1432
|
|
|
|
|
|
|
Callers may also request only changes to particular properties. |
|
1433
|
|
|
|
|
|
|
|
|
1434
|
|
|
|
|
|
|
Changes to objects' properties are tracked by the system. If an object has been |
|
1435
|
|
|
|
|
|
|
changed since it was defined or loaded from its external data source, then changed() |
|
1436
|
|
|
|
|
|
|
will return a list of L objects describing which properties have been |
|
1437
|
|
|
|
|
|
|
changed. |
|
1438
|
|
|
|
|
|
|
|
|
1439
|
|
|
|
|
|
|
Work is in-progress on an API to request the portion of the changes in effect in the |
|
1440
|
|
|
|
|
|
|
current transaction which would impact the return value of a given list of properties. |
|
1441
|
|
|
|
|
|
|
This would be directly usable by a view/observer. |
|
1442
|
|
|
|
|
|
|
|
|
1443
|
|
|
|
|
|
|
=item __define__ |
|
1444
|
|
|
|
|
|
|
|
|
1445
|
|
|
|
|
|
|
This is used internally to "virtually load" things. Simply assert they already existed |
|
1446
|
|
|
|
|
|
|
externally, and act as though they were just loaded... It is used for classes defined in |
|
1447
|
|
|
|
|
|
|
the source code (which is the default) by the "class {}" magic instead of in some database, |
|
1448
|
|
|
|
|
|
|
as we'd do for regular objects. |
|
1449
|
|
|
|
|
|
|
|
|
1450
|
|
|
|
|
|
|
=item __strengthen__ |
|
1451
|
|
|
|
|
|
|
|
|
1452
|
|
|
|
|
|
|
$obj->__strengthen__(); |
|
1453
|
|
|
|
|
|
|
|
|
1454
|
|
|
|
|
|
|
Mark this object as unloadable by the object cache pruner. |
|
1455
|
|
|
|
|
|
|
|
|
1456
|
|
|
|
|
|
|
UR objects are normally tracked by the current Context for the life of the |
|
1457
|
|
|
|
|
|
|
application, but the programmer can specify a limit to cache size, in |
|
1458
|
|
|
|
|
|
|
which case old, unchanged objects are periodically pruned from the cache. |
|
1459
|
|
|
|
|
|
|
If strengthen() is called on an object, it will effectively be locked in |
|
1460
|
|
|
|
|
|
|
the cache, and will not be considered for pruning. |
|
1461
|
|
|
|
|
|
|
|
|
1462
|
|
|
|
|
|
|
See L for more information about the pruning mechanism. |
|
1463
|
|
|
|
|
|
|
|
|
1464
|
|
|
|
|
|
|
=item is_strengthened |
|
1465
|
|
|
|
|
|
|
|
|
1466
|
|
|
|
|
|
|
Check if an object has been stengthened, C<__stengthen__>. |
|
1467
|
|
|
|
|
|
|
|
|
1468
|
|
|
|
|
|
|
=item __weaken__ |
|
1469
|
|
|
|
|
|
|
|
|
1470
|
|
|
|
|
|
|
$obj->__weaken__(); |
|
1471
|
|
|
|
|
|
|
|
|
1472
|
|
|
|
|
|
|
Give a hint to the object cache pruner that this instance is not going to be used |
|
1473
|
|
|
|
|
|
|
in the application in the future, and should be removed with preference when |
|
1474
|
|
|
|
|
|
|
pruning the cache. |
|
1475
|
|
|
|
|
|
|
|
|
1476
|
|
|
|
|
|
|
=item is_weakened |
|
1477
|
|
|
|
|
|
|
|
|
1478
|
|
|
|
|
|
|
Check if an object has been weakened, C<__weaken__>. |
|
1479
|
|
|
|
|
|
|
|
|
1480
|
|
|
|
|
|
|
=item DESTROY |
|
1481
|
|
|
|
|
|
|
|
|
1482
|
|
|
|
|
|
|
Perl calls this method on any object before garbage collecting it. It |
|
1483
|
|
|
|
|
|
|
should never by called by your application explicitly. |
|
1484
|
|
|
|
|
|
|
|
|
1485
|
|
|
|
|
|
|
The DESTROY handler is overridden in UR::Object. If you override it in |
|
1486
|
|
|
|
|
|
|
a subclass, be sure to call $self->SUPER::DESTROY() before exiting your |
|
1487
|
|
|
|
|
|
|
override, or errors will occur. |
|
1488
|
|
|
|
|
|
|
|
|
1489
|
|
|
|
|
|
|
=back |
|
1490
|
|
|
|
|
|
|
|
|
1491
|
|
|
|
|
|
|
=head1 ERRORS, WARNINGS and STATUS MESSAGES |
|
1492
|
|
|
|
|
|
|
|
|
1493
|
|
|
|
|
|
|
When an error occurs which is "exceptional" the API will throw an exception via die(). |
|
1494
|
|
|
|
|
|
|
|
|
1495
|
|
|
|
|
|
|
In some cases, when the possibility of failure is "not-exceptional", the method will simply |
|
1496
|
|
|
|
|
|
|
return false. In scalar context this will be undef. In list context an empty list. |
|
1497
|
|
|
|
|
|
|
|
|
1498
|
|
|
|
|
|
|
When there is ambiguity as to whether this is an error or not (get() for instance, might |
|
1499
|
|
|
|
|
|
|
simply match zero items, ...or fail to understand your parameters), an exception is used. |
|
1500
|
|
|
|
|
|
|
|
|
1501
|
|
|
|
|
|
|
=over 4 |
|
1502
|
|
|
|
|
|
|
|
|
1503
|
|
|
|
|
|
|
=item error_message |
|
1504
|
|
|
|
|
|
|
|
|
1505
|
|
|
|
|
|
|
The standard way to convey the error which has occurred is to set ->error_message() on |
|
1506
|
|
|
|
|
|
|
the object. This will propagate to the class, and through its inheritance. This is |
|
1507
|
|
|
|
|
|
|
much like DBI's errstr method, which affects the handle on which it was called, its source |
|
1508
|
|
|
|
|
|
|
handle, and the DBI package itself. |
|
1509
|
|
|
|
|
|
|
|
|
1510
|
|
|
|
|
|
|
=item warning_message |
|
1511
|
|
|
|
|
|
|
|
|
1512
|
|
|
|
|
|
|
Calls to warning_message also record themselves on the object in question, and its class(es). |
|
1513
|
|
|
|
|
|
|
|
|
1514
|
|
|
|
|
|
|
They also emit a standard Perl warn(), which will invoke $SIG{__WARN__}; |
|
1515
|
|
|
|
|
|
|
|
|
1516
|
|
|
|
|
|
|
=item status_message |
|
1517
|
|
|
|
|
|
|
|
|
1518
|
|
|
|
|
|
|
Calls to status_message are also recorded on the object in question. They can be |
|
1519
|
|
|
|
|
|
|
monitored through hooks, as can the other messages. |
|
1520
|
|
|
|
|
|
|
|
|
1521
|
|
|
|
|
|
|
=back |
|
1522
|
|
|
|
|
|
|
|
|
1523
|
|
|
|
|
|
|
See L for more information. |
|
1524
|
|
|
|
|
|
|
|
|
1525
|
|
|
|
|
|
|
=head1 SEE ALSO |
|
1526
|
|
|
|
|
|
|
|
|
1527
|
|
|
|
|
|
|
L, L, L |
|
1528
|
|
|
|
|
|
|
|
|
1529
|
|
|
|
|
|
|
L, L, L, L |
|
1530
|
|
|
|
|
|
|
|
|
1531
|
|
|
|
|
|
|
L contains additional methods which are deprecated in the API. |
|
1532
|
|
|
|
|
|
|
|
|
1533
|
|
|
|
|
|
|
=cut |
|
1534
|
|
|
|
|
|
|
|