caruby-core 1.4.9 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/History.md +48 -0
- data/lib/caruby/cli/command.rb +2 -1
- data/lib/caruby/csv/csv_mapper.rb +8 -8
- data/lib/caruby/database/persistable.rb +44 -65
- data/lib/caruby/database/persistence_service.rb +12 -9
- data/lib/caruby/database/persistifier.rb +14 -14
- data/lib/caruby/database/reader.rb +53 -51
- data/lib/caruby/database/search_template_builder.rb +9 -10
- data/lib/caruby/database/store_template_builder.rb +58 -58
- data/lib/caruby/database/writer.rb +96 -96
- data/lib/caruby/database.rb +19 -19
- data/lib/caruby/domain/attribute.rb +581 -0
- data/lib/caruby/domain/attributes.rb +615 -0
- data/lib/caruby/domain/dependency.rb +240 -0
- data/lib/caruby/domain/importer.rb +183 -0
- data/lib/caruby/domain/introspection.rb +176 -0
- data/lib/caruby/domain/inverse.rb +173 -0
- data/lib/caruby/domain/inversible.rb +1 -2
- data/lib/caruby/domain/java_attribute.rb +173 -0
- data/lib/caruby/domain/merge.rb +13 -10
- data/lib/caruby/domain/metadata.rb +141 -0
- data/lib/caruby/domain/mixin.rb +35 -0
- data/lib/caruby/domain/reference_visitor.rb +5 -3
- data/lib/caruby/domain.rb +340 -0
- data/lib/caruby/import/java.rb +29 -25
- data/lib/caruby/migration/migratable.rb +5 -5
- data/lib/caruby/migration/migrator.rb +19 -15
- data/lib/caruby/migration/resource_module.rb +1 -1
- data/lib/caruby/resource.rb +39 -30
- data/lib/caruby/util/collection.rb +94 -33
- data/lib/caruby/util/coordinate.rb +28 -2
- data/lib/caruby/util/log.rb +4 -4
- data/lib/caruby/util/module.rb +12 -28
- data/lib/caruby/util/partial_order.rb +9 -10
- data/lib/caruby/util/pretty_print.rb +46 -26
- data/lib/caruby/util/topological_sync_enumerator.rb +10 -4
- data/lib/caruby/util/transitive_closure.rb +2 -2
- data/lib/caruby/util/visitor.rb +1 -1
- data/lib/caruby/version.rb +1 -1
- data/test/lib/caruby/database/persistable_test.rb +1 -1
- data/test/lib/caruby/domain/domain_test.rb +14 -28
- data/test/lib/caruby/domain/inversible_test.rb +1 -1
- data/test/lib/caruby/import/java_test.rb +5 -0
- data/test/lib/caruby/migration/test_case.rb +0 -1
- data/test/lib/caruby/test_case.rb +9 -10
- data/test/lib/caruby/util/collection_test.rb +23 -5
- data/test/lib/caruby/util/module_test.rb +10 -14
- data/test/lib/caruby/util/partial_order_test.rb +16 -15
- data/test/lib/caruby/util/visitor_test.rb +1 -1
- data/test/lib/examples/galena/clinical_trials/migration/test_case.rb +1 -1
- metadata +16 -15
- data/History.txt +0 -44
- data/lib/caruby/domain/attribute_metadata.rb +0 -551
- data/lib/caruby/domain/java_attribute_metadata.rb +0 -183
- data/lib/caruby/domain/resource_attributes.rb +0 -565
- data/lib/caruby/domain/resource_dependency.rb +0 -217
- data/lib/caruby/domain/resource_introspection.rb +0 -160
- data/lib/caruby/domain/resource_inverse.rb +0 -151
- data/lib/caruby/domain/resource_metadata.rb +0 -155
- data/lib/caruby/domain/resource_module.rb +0 -370
- data/lib/caruby/yard/resource_metadata_handler.rb +0 -8
@@ -0,0 +1,581 @@
|
|
1
|
+
require 'set'
|
2
|
+
require 'caruby/util/inflector'
|
3
|
+
require 'caruby/util/collection'
|
4
|
+
require 'caruby/util/validation'
|
5
|
+
require 'caruby/domain/java_attribute'
|
6
|
+
|
7
|
+
module CaRuby
|
8
|
+
module Domain
|
9
|
+
# An Attribute captures the following metadata about a domain class attribute:
|
10
|
+
# * attribute symbol
|
11
|
+
# * declarer type
|
12
|
+
# * return type
|
13
|
+
# * reader method symbol
|
14
|
+
# * writer method symbol
|
15
|
+
class Attribute
|
16
|
+
# The supported attribute qualifier flags. See the complementary methods for an explanation of
|
17
|
+
# the flag option, e.g. {#autogenerated?} for the +:autogenerated+ flag.
|
18
|
+
SUPPORTED_FLAGS = [
|
19
|
+
:autogenerated, :autogenerated_on_update, :collection, :dependent, :derived, :logical, :disjoint,
|
20
|
+
:owner, :cascaded, :no_cascade_update_to_create, :saved, :unsaved, :optional, :fetched, :unfetched,
|
21
|
+
:include_in_save_template, :saved_fetch, :create_only, :update_only, :unidirectional, :volatile].to_set
|
22
|
+
|
23
|
+
# @return [(Symbol, Symbol)] the standard attribute reader and writer methods
|
24
|
+
attr_reader :accessors
|
25
|
+
|
26
|
+
# @return [Class] the declaring class
|
27
|
+
attr_accessor :declarer
|
28
|
+
|
29
|
+
# @return [Class] the return type
|
30
|
+
attr_reader :type
|
31
|
+
|
32
|
+
# @return [<Symbol>] the qualifier flags
|
33
|
+
# @see SUPPORTED_FLAGS
|
34
|
+
attr_accessor :flags
|
35
|
+
|
36
|
+
# Creates a new Attribute from the given attribute.
|
37
|
+
#
|
38
|
+
# The return type is the referenced entity type. An attribute whose return type is a
|
39
|
+
# collection of domain objects is thus the domain object class rather than a collection class.
|
40
|
+
#
|
41
|
+
# @param [String,Symbol] attr the subject attribute
|
42
|
+
# @param [Class] declarer the declaring class
|
43
|
+
# @param [Class] type the return type
|
44
|
+
# @param [<Symbol>] flags the qualifying flags
|
45
|
+
# @option flags :dependent the attribute references a dependent
|
46
|
+
# @option flags :collection the attribute return type is a collection
|
47
|
+
# @option flags :owner the attribute references the owner of a dependent
|
48
|
+
# @option flags :cascaded database create/update/delete operation propagates to the attribute reference
|
49
|
+
def initialize(attribute, declarer, type=nil, *flags)
|
50
|
+
# the attribute symbol
|
51
|
+
@symbol = attribute.to_sym
|
52
|
+
# the declaring class
|
53
|
+
@declarer = declarer
|
54
|
+
# the Ruby class
|
55
|
+
@type = Class.to_ruby(type) if type
|
56
|
+
# the read and write methods
|
57
|
+
@accessors = [@symbol, "#{attribute}=".to_sym]
|
58
|
+
# the qualifier flags
|
59
|
+
@flags = Set.new
|
60
|
+
# identifier is always volatile
|
61
|
+
if @symbol == :identifier then flags << :volatile end
|
62
|
+
qualify(*flags)
|
63
|
+
end
|
64
|
+
|
65
|
+
# @return [Symbol] the reader method
|
66
|
+
def reader
|
67
|
+
accessors.first
|
68
|
+
end
|
69
|
+
|
70
|
+
# @return [Symbol] the writer method
|
71
|
+
def writer
|
72
|
+
accessors.last
|
73
|
+
end
|
74
|
+
|
75
|
+
# @return [Symbol, nil] the inverse of this attribute, if any
|
76
|
+
def inverse
|
77
|
+
@inv_md.to_sym if @inv_md
|
78
|
+
end
|
79
|
+
|
80
|
+
# An attribute is unidirectional if both of the following is true:
|
81
|
+
# * there is no distinct {#inverse} attribute
|
82
|
+
# * the attribute is not a {#dependent?} with more than one owner
|
83
|
+
#
|
84
|
+
# @return [Boolean] whether this attribute does not have an inverse
|
85
|
+
def unidirectional?
|
86
|
+
inverse.nil? and not (dependent? and type.owner_attributes.size > 1)
|
87
|
+
end
|
88
|
+
|
89
|
+
# @param [Class] the attribute return type
|
90
|
+
def type=(klass)
|
91
|
+
return if klass == @type
|
92
|
+
@type = klass
|
93
|
+
if @inv_md then
|
94
|
+
self.inverse = @inv_md.to_sym
|
95
|
+
logger.debug { "Reset #{@declarer.qp}.#{self} inverse from #{@inv_md.type}.#{@inv_md} to #{klass}#{@inv_md}." }
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# Creates a new declarer attribute which restricts this attribute {#type} to the given type.
|
100
|
+
#
|
101
|
+
# @param declarer (see #restrict)
|
102
|
+
# @param [Class] type the restricted subclass of this attribute's return type
|
103
|
+
# @return (see #restrict)
|
104
|
+
def restrict_type(declarer, type)
|
105
|
+
if self.type and not type < self.type then
|
106
|
+
raise ArgumentError.new("Cannot restrict #{self.declarer.qp}.#{self} to incompatible attribute type #{type.qp}")
|
107
|
+
end
|
108
|
+
rst = restrict(declarer)
|
109
|
+
rst.type = type
|
110
|
+
# specialize the inverse to the restricted type attribute, if necessary
|
111
|
+
rst.restrict_inverse_type
|
112
|
+
rst
|
113
|
+
end
|
114
|
+
|
115
|
+
# Creates a new declarer attribute which qualifies this attribute for the given declarer.
|
116
|
+
#
|
117
|
+
# @param declarer (see #restrict)
|
118
|
+
# @param [<Symbol>] flags the additional flags for the restricted attribute
|
119
|
+
# @return (see #restrict)
|
120
|
+
def restrict_flags(declarer, *flags)
|
121
|
+
copy = restrict(declarer)
|
122
|
+
copy.qualify(*flags)
|
123
|
+
copy
|
124
|
+
end
|
125
|
+
|
126
|
+
# Sets the inverse of the subject attribute to the given attribute.
|
127
|
+
# The inverse relation is symmetric, i.e. the inverse of the referenced Attribute
|
128
|
+
# is set to this Attribute's subject attribute.
|
129
|
+
#
|
130
|
+
# @param attribute the inverse attribute
|
131
|
+
def inverse=(attribute)
|
132
|
+
return if inverse == attribute
|
133
|
+
# if no attribute, then the clear the existing inverse, if any
|
134
|
+
return clear_inverse if attribute.nil?
|
135
|
+
|
136
|
+
# the inverse attribute meta-data
|
137
|
+
begin
|
138
|
+
@inv_md = type.attribute_metadata(attribute)
|
139
|
+
rescue NameError
|
140
|
+
raise MetadataError.new("#{@declarer.qp}.#{self} inverse attribute #{type.qp}.#{attribute} not found - #{$!}")
|
141
|
+
end
|
142
|
+
# the inverse of the inverse
|
143
|
+
inv_inv_md = @inv_md.inverse_metadata
|
144
|
+
# If the inverse of the inverse is already set to a different attribute, then raise an exception.
|
145
|
+
# Otherwise, it there is an inverse, then set the inverse of the inverse to this attribute.
|
146
|
+
return if inv_inv_md == self
|
147
|
+
if inv_inv_md and not inv_inv_md.restriction?(self) then
|
148
|
+
raise MetadataError.new("Cannot set #{type.qp}.#{@inv_md} inverse attribute to #{@declarer.qp}.#{self} since it conflicts with existing inverse #{inv_inv_md.declarer.qp}.#{inv_inv_md}")
|
149
|
+
end
|
150
|
+
# set the inverse of the inverse
|
151
|
+
@inv_md.inverse = @symbol
|
152
|
+
# If this attribute is disjoint, then so is the inverse.
|
153
|
+
@inv_md.qualify(:disjoint) if disjoint?
|
154
|
+
|
155
|
+
# propagate to restrictions
|
156
|
+
# if @restrictions then
|
157
|
+
# @restrictions.each { |attr_md| attr_md.restrict_inverse_type(@inv_md) }
|
158
|
+
# end
|
159
|
+
logger.debug { "Set #{@declarer.qp}.#{self} inverse to #{type.qp}.#{attribute}." }
|
160
|
+
end
|
161
|
+
|
162
|
+
# @return [Attribute, nil] the metadata for the {#inverse} attribute, if any
|
163
|
+
def inverse_metadata
|
164
|
+
@inv_md
|
165
|
+
end
|
166
|
+
|
167
|
+
# Qualifies this attribute with the given flags. Supported flags are listed in {SUPPORTED_FLAGS}.
|
168
|
+
#
|
169
|
+
# @param [<Symbol>] the flags to add
|
170
|
+
# @raise [ArgumentError] if the flag is not supported
|
171
|
+
def qualify(*flags)
|
172
|
+
flags.each { |flag| set_flag(flag) }
|
173
|
+
# propagate to restrictions
|
174
|
+
if @restrictions then @restrictions.each { |attr_md| attr_md.qualify(*flags) } end
|
175
|
+
end
|
176
|
+
|
177
|
+
# @return whether the subject attribute encapsulates a Java property
|
178
|
+
def java_property?
|
179
|
+
JavaAttribute === self
|
180
|
+
end
|
181
|
+
|
182
|
+
# @return whether the subject attribute returns a domain object or collection of domain objects
|
183
|
+
def domain?
|
184
|
+
# the type must be a Ruby class rather than a Java Class, and include the Domain mix-in
|
185
|
+
Class === type and type < Resource
|
186
|
+
end
|
187
|
+
|
188
|
+
# @return whether the subject attribute is not a domain object attribute
|
189
|
+
def nondomain?
|
190
|
+
not domain?
|
191
|
+
end
|
192
|
+
|
193
|
+
# Returns whether the subject attribute is fetched, determined as follows:
|
194
|
+
# * An attribute marked with the :fetched flag is fetched.
|
195
|
+
# * An attribute marked with the :unfetched flag is not fetched.
|
196
|
+
# Otherwise, a non-domain attribute is fetched, and a domain attribute is
|
197
|
+
# fetched if one of the following conditions hold:
|
198
|
+
# * A dependent domain attribute is fetched if it is not logical.
|
199
|
+
# * An owner domain attribute is fetched by default.
|
200
|
+
# * An independent domain attribute is fetched if it is abstract and not derived.
|
201
|
+
#
|
202
|
+
# @return [Boolean] whether the attribute is fetched
|
203
|
+
def fetched?
|
204
|
+
return true if @flags.include?(:fetched)
|
205
|
+
return false if @flags.include?(:unfetched)
|
206
|
+
nondomain? or dependent? ? fetched_dependent? : fetched_independent?
|
207
|
+
end
|
208
|
+
|
209
|
+
# @return whether the subject attribute return type is a collection
|
210
|
+
def collection?
|
211
|
+
@flags.include?(:collection)
|
212
|
+
end
|
213
|
+
|
214
|
+
# Returns whether the subject attribute is a dependent on a parent. See the caRuby configuration
|
215
|
+
# documentation for a dependency description.
|
216
|
+
#
|
217
|
+
# @return [Boolean] whether the attribute references a dependent
|
218
|
+
def dependent?
|
219
|
+
@flags.include?(:dependent)
|
220
|
+
end
|
221
|
+
|
222
|
+
# Returns whether the subject attribute is marked as optional in a create.
|
223
|
+
# This method returns true only if the :optional flag is explicitly set.
|
224
|
+
# Other attributes are optional by default.
|
225
|
+
#
|
226
|
+
# @return [Boolean] whether the attribute is optional
|
227
|
+
# @see Attributes#mandatory_attributes.
|
228
|
+
def optional?
|
229
|
+
@flags.include?(:optional)
|
230
|
+
end
|
231
|
+
|
232
|
+
# Returns whether the subject attribute is not saved.
|
233
|
+
#
|
234
|
+
# @return [Boolean] whether the attribute is unsaved
|
235
|
+
def unsaved?
|
236
|
+
@flags.include?(:unsaved)
|
237
|
+
end
|
238
|
+
|
239
|
+
# Returns whether the subject attribute is a dependent whose value is automatically generated
|
240
|
+
# with place-holder domain objects when the parent is created. An attribute is auto-generated
|
241
|
+
# if the +:autogenerate+ or the +:autogenerated_on_update+ flag is set.
|
242
|
+
#
|
243
|
+
# @return [Boolean] whether the attribute is auto-generated
|
244
|
+
def autogenerated?
|
245
|
+
@flags.include?(:autogenerated) or @flags.include?(:autogenerated_on_update)
|
246
|
+
end
|
247
|
+
|
248
|
+
# Returns whether the the subject attribute is #{autogenerated?} for create. An attribute is
|
249
|
+
# auto-generated for create if the +:autogenerate+ flag is set and the
|
250
|
+
# +:autogenerated_on_update+ flag is not set.
|
251
|
+
#
|
252
|
+
# @return [Boolean] whether the attribute is auto-generated on create
|
253
|
+
def autogenerated_on_create?
|
254
|
+
@flags.include?(:autogenerated) and not @flags.include?(:autogenerated_on_update)
|
255
|
+
end
|
256
|
+
|
257
|
+
# Returns whether this attribute must be fetched when a declarer instance is saved.
|
258
|
+
# An attribute is a saved fetch attribute if either of the following conditions hold:
|
259
|
+
# * it is {#autogenerated?}
|
260
|
+
# * it is {#cascaded?} and marked with the +:unfetched+ flag.
|
261
|
+
#
|
262
|
+
# @return [Boolean] whether the subject attribute must be refetched in order to reflect
|
263
|
+
# the database content
|
264
|
+
def saved_fetch?
|
265
|
+
@flags.include?(:saved_fetch) or autogenerated? or (cascaded? and @flags.include?(:unfetched))
|
266
|
+
end
|
267
|
+
|
268
|
+
# Returns whether the subject attribute is a dependent whose owner does not automatically
|
269
|
+
# cascade application service creation or update to the dependent. It is incumbent upon
|
270
|
+
# CaRuby::Database to cascade the changes.
|
271
|
+
#
|
272
|
+
# @return [Boolean] whether the attribute is an uncascaded dependent
|
273
|
+
def logical?
|
274
|
+
@flags.include?(:logical)
|
275
|
+
end
|
276
|
+
|
277
|
+
# An attribute is derived if the attribute value is set by setting another attribute, e.g. if this
|
278
|
+
# attribute is the inverse of a dependent owner attribute.
|
279
|
+
#
|
280
|
+
# @return [Boolean] whether this attribute is derived from another attribute
|
281
|
+
def derived?
|
282
|
+
@flags.include?(:derived) or (dependent? and not inverse.nil?)
|
283
|
+
end
|
284
|
+
|
285
|
+
# @return [Boolean] this attribute's inverse attribute if the inverse is a derived attribute, or nil otherwise
|
286
|
+
def derived_inverse
|
287
|
+
@inv_md.to_sym if @inv_md and @inv_md.derived?
|
288
|
+
end
|
289
|
+
|
290
|
+
# An independent attribute is a reference to one or more non-dependent Resource objects.
|
291
|
+
# An {#owner?} attribute is independent.
|
292
|
+
#
|
293
|
+
# @return [Boolean] whether the subject attribute is a non-dependent domain attribute
|
294
|
+
def independent?
|
295
|
+
domain? and not dependent?
|
296
|
+
end
|
297
|
+
|
298
|
+
# A Java attribute is creatable if all of the following conditions hold:
|
299
|
+
# * the attribute is {#saved?}
|
300
|
+
# * the attribute is not a {#proxied_save?}
|
301
|
+
# * the attribute :update_only flag is not set
|
302
|
+
#
|
303
|
+
# @return [Boolean] whether this attribute is saved in a create operation
|
304
|
+
def creatable?
|
305
|
+
saved? and not @flags.include?(:update_only)
|
306
|
+
end
|
307
|
+
|
308
|
+
# A Java attribute is an uncreated dependent if any of the following conditions hold:
|
309
|
+
# * the attribute is a {#logical?} dependent
|
310
|
+
# * the attribute is a #dependent? which is not {#creatable?}
|
311
|
+
#
|
312
|
+
# @return [Boolean] whether this attribute is saved in a create operation
|
313
|
+
def uncreated_dependent?
|
314
|
+
logical? or (dependent? and not creatable?)
|
315
|
+
end
|
316
|
+
|
317
|
+
# A Java attribute is updatable if all of the following conditions hold:
|
318
|
+
# * the attribute is {#saved?}
|
319
|
+
# * the attribute :create_only flag is not set
|
320
|
+
#
|
321
|
+
# @return [Boolean] whether this attribute is saved in a update operation
|
322
|
+
def updatable?
|
323
|
+
saved? and not @flags.include?(:create_only)
|
324
|
+
end
|
325
|
+
|
326
|
+
# @return [Boolean] whether the attribute is a physical dependent or the +:cascaded+ flag is set
|
327
|
+
def cascaded?
|
328
|
+
(dependent? and not logical?) or @flags.include?(:cascaded)
|
329
|
+
end
|
330
|
+
|
331
|
+
# @return whether this attribute is {#cascaded?} or marked with the +:include_in_save_template+ flag
|
332
|
+
def include_in_save_template?
|
333
|
+
cascaded? or @flags.include?(:include_in_save_template)
|
334
|
+
end
|
335
|
+
|
336
|
+
# Returns whether this attribute is #{#cascaded} and cascades a parent update to a child
|
337
|
+
# create. This corresponds to the Hibernate +save-update+ cascade style but not the Hibernate
|
338
|
+
# +all+ cascade style.
|
339
|
+
#
|
340
|
+
# This method returns true if this attribute is cascaded and the +:no_cascade_update_to_create+
|
341
|
+
# flag is not set. Set this flag if the Hibernate mapping specifies the +all+ cascade style.
|
342
|
+
# Failure to set this flag will result in the caTissue Hibernate error:
|
343
|
+
# Exception: gov.nih.nci.system.applicationservice.ApplicationException:
|
344
|
+
# The given object has a null identifier:
|
345
|
+
# followed by the attribute type name.
|
346
|
+
#
|
347
|
+
# @return [Boolean] whether the attribute cascades to crate when the owner is updated
|
348
|
+
def cascade_update_to_create?
|
349
|
+
cascaded? and not @flags.include?(:no_cascade_update_to_create)
|
350
|
+
end
|
351
|
+
|
352
|
+
# A Java property attribute is saved if none of the following conditions hold:
|
353
|
+
# * the attribute :unsaved flag is set
|
354
|
+
# * the attribute is {#proxied_save?}
|
355
|
+
# and any of the following conditions hold:
|
356
|
+
# * the attibute is {#nondomain?}
|
357
|
+
# * the attribute is cascaded
|
358
|
+
# * the attribute value is not a collection
|
359
|
+
# * the attribute does not have an inverse
|
360
|
+
# * the attribute :saved flag is set
|
361
|
+
#
|
362
|
+
# @return [Boolean] whether this attribute is saved in a create or update operation
|
363
|
+
def saved?
|
364
|
+
@flags.include?(:saved) or
|
365
|
+
(java_property? and not @flags.include?(:unsaved) and not proxied_save? and
|
366
|
+
(nondomain? or cascaded? or not collection? or inverse.nil? or unidirectional_java_dependent?))
|
367
|
+
end
|
368
|
+
|
369
|
+
# @return [Boolean] whether this attribute is not {#saved?}
|
370
|
+
def unsaved?
|
371
|
+
not saved?
|
372
|
+
end
|
373
|
+
|
374
|
+
# @return [Boolean] whether the attribute return {#type} is a Resource class which
|
375
|
+
# implements the saver_proxy method
|
376
|
+
def proxied_save?
|
377
|
+
domain? and type.method_defined?(:saver_proxy)
|
378
|
+
end
|
379
|
+
|
380
|
+
# Returns whether this attribute's referents must exist before an instance of the
|
381
|
+
# declarer class can be created. An attribute is a storable prerequisite if it is
|
382
|
+
# either:
|
383
|
+
# * a {#cascaded?} dependent which does not #{#cascade_update_to_create?}, or
|
384
|
+
# * a {#saved?} {#independent?} 1:M or M:N association.
|
385
|
+
#
|
386
|
+
# @return [Boolean] whether this attribute is a create prerequisite
|
387
|
+
def storable_prerequisite?
|
388
|
+
return true if cascaded? and @flags.include?(:no_cascade_update_to_create)
|
389
|
+
return false unless independent? and saved?
|
390
|
+
return true unless collection?
|
391
|
+
inv_md = inverse_metadata
|
392
|
+
inv_md.nil? or inv_md.collection?
|
393
|
+
end
|
394
|
+
|
395
|
+
# @return [Boolean] whether this attribute is a collection with a collection inverse
|
396
|
+
def many_to_many?
|
397
|
+
return false unless collection?
|
398
|
+
inv_md = inverse_metadata
|
399
|
+
inv_md and inv_md.collection?
|
400
|
+
end
|
401
|
+
|
402
|
+
# @return [Boolean] whether the subject attribute is not saved
|
403
|
+
def transient?
|
404
|
+
not saved?
|
405
|
+
end
|
406
|
+
|
407
|
+
# Returns whether this attribute is set on the server as a side-effect
|
408
|
+
# of a change to the declarer object. The volatile attributes include
|
409
|
+
# those which are {#unsaved?} and those which are saved but marked
|
410
|
+
# with the +:volatile+ flag.
|
411
|
+
#
|
412
|
+
# @return [Boolean] whether this attribute's value is determined by the server
|
413
|
+
def volatile?
|
414
|
+
unsaved? or @flags.include?(:volatile)
|
415
|
+
end
|
416
|
+
|
417
|
+
# @return [Boolean] whether this is a non-collection Java attribute
|
418
|
+
def searchable?
|
419
|
+
java_property? and not collection?
|
420
|
+
end
|
421
|
+
|
422
|
+
# @return [Boolean] whether the subject attribute is a dependency owner
|
423
|
+
def owner?
|
424
|
+
@flags.include?(:owner)
|
425
|
+
end
|
426
|
+
|
427
|
+
# @return [Boolean] whether this is a dependent attribute which has exactly one owner value chosen from
|
428
|
+
# several owner attributes.
|
429
|
+
def disjoint?
|
430
|
+
@flags.include?(:disjoint)
|
431
|
+
end
|
432
|
+
|
433
|
+
# @return [Boolean] whether this attribute is a dependent which does not have a Java inverse owner attribute
|
434
|
+
def unidirectional_java_dependent?
|
435
|
+
dependent? and java_property? and not bidirectional_java_association?
|
436
|
+
end
|
437
|
+
|
438
|
+
# @return [Boolean] whether this is a Java attribute which has a Java inverse
|
439
|
+
def bidirectional_java_association?
|
440
|
+
inverse and java_property? and inverse_metadata.java_property?
|
441
|
+
end
|
442
|
+
|
443
|
+
def to_sym
|
444
|
+
@symbol
|
445
|
+
end
|
446
|
+
|
447
|
+
def to_s
|
448
|
+
@symbol.to_s
|
449
|
+
end
|
450
|
+
|
451
|
+
alias :inspect :to_s
|
452
|
+
|
453
|
+
alias :qp :to_s
|
454
|
+
|
455
|
+
protected
|
456
|
+
|
457
|
+
# Duplicates the mutable content as part of a {#deep_copy}.
|
458
|
+
def dup_content
|
459
|
+
# keep the copied flags but don't share them
|
460
|
+
@flags = @flags.dup
|
461
|
+
# restrictions are neither shared nor copied
|
462
|
+
@restrictions = nil
|
463
|
+
end
|
464
|
+
|
465
|
+
# Restricts this attribute's inverse to an attribute declared by this
|
466
|
+
# attribute's type. For example, if:
|
467
|
+
# * +AbstractProtocol.coordinator+ has inverse +Administrator.protocol+
|
468
|
+
# * +AbstractProtocol+ has subclass +StudyProtocol+
|
469
|
+
# * +StudyProtocol.coordinator+ returns a +StudyCoordinator+
|
470
|
+
# * +AbstractProtocol.coordinator+ is restricted to +StudyProtocol+
|
471
|
+
# then calling this method on the +StudyProtocol.coordinator+ restriction
|
472
|
+
# sets the +StudyProtocol.coordinator+ inverse to +StudyCoordinator.coordinator+.
|
473
|
+
#
|
474
|
+
# @param [Attribute, nil] inv_md the inverse attribute to restrict
|
475
|
+
# (default is the current inverse)
|
476
|
+
def restrict_inverse_type(inv_md=nil)
|
477
|
+
# default inverse is the current inverse
|
478
|
+
inv_md ||= @inv_md || return
|
479
|
+
# the current inverse
|
480
|
+
attr = inv_md.to_sym
|
481
|
+
# If the restricted type delegates to the current inverse metadata,
|
482
|
+
# then no change is needed.
|
483
|
+
return if @type.attribute_metadata(attr) == inv_md
|
484
|
+
# clear the current inverse
|
485
|
+
@inv_md = nil
|
486
|
+
# set the inverse to the restricted attribute
|
487
|
+
self.inverse = attr
|
488
|
+
end
|
489
|
+
|
490
|
+
# @param [Attribute] other the other attribute to check
|
491
|
+
# @return [Boolean] whether the other attribute restricts this attribute
|
492
|
+
def restriction?(other)
|
493
|
+
@restrictions.include?(other)
|
494
|
+
end
|
495
|
+
private
|
496
|
+
|
497
|
+
# Creates a copy of this metadata which does not share mutable content.
|
498
|
+
def deep_copy
|
499
|
+
other = dup
|
500
|
+
other.dup_content
|
501
|
+
other
|
502
|
+
end
|
503
|
+
|
504
|
+
# Creates a new declarer attribute which restricts this attribute type or flags.
|
505
|
+
#
|
506
|
+
# @param [Class] klass the declarer class for which the restriction holds
|
507
|
+
# @return [Attribute] the metadata for the new declarer attribute
|
508
|
+
def restrict(klass)
|
509
|
+
unless klass < @declarer then
|
510
|
+
raise ArgumentError.new("Cannot restrict #{@declarer.qp}.#{self} to incompatible declarer type #{klass.qp}")
|
511
|
+
end
|
512
|
+
rst = deep_copy
|
513
|
+
# specialize the copy declarer and type
|
514
|
+
rst.declarer = klass
|
515
|
+
# Capture the restriction to propagate modifications to this metadata, esp.
|
516
|
+
# adding an inverse.
|
517
|
+
@restrictions ||= []
|
518
|
+
@restrictions << rst
|
519
|
+
rst
|
520
|
+
end
|
521
|
+
|
522
|
+
def clear_inverse
|
523
|
+
return unless @inv_md
|
524
|
+
logger.debug { "Clearing #{@declarer.qp}.#{self} inverse #{type.qp}.#{inverse}..." }
|
525
|
+
inv_inv_md = @inv_md.inverse_metadata
|
526
|
+
@inv_md = nil
|
527
|
+
if inv_inv_md then inv_inv_md.inverse = nil end
|
528
|
+
logger.debug { "Cleared #{@declarer.qp}.#{self} inverse." }
|
529
|
+
end
|
530
|
+
|
531
|
+
# @param [Symbol] the flag to set
|
532
|
+
# @raise [ArgumentError] if flag is not supported
|
533
|
+
def set_flag(flag)
|
534
|
+
return if @flags.include?(flag)
|
535
|
+
raise ArgumentError.new("Attribute flag not supported: #{flag}") unless SUPPORTED_FLAGS.include?(flag)
|
536
|
+
@flags << flag
|
537
|
+
case flag
|
538
|
+
when :owner then owner_flag_set
|
539
|
+
when :dependent then dependent_flag_set
|
540
|
+
end
|
541
|
+
end
|
542
|
+
|
543
|
+
# This method is called when the owner flag is set.
|
544
|
+
# The inverse is inferred as the referenced owner type's dependent attribute which references
|
545
|
+
# this attribute's type.
|
546
|
+
#
|
547
|
+
# @raise [MetadataError] if this attribute is dependent or an inverse could not be inferred
|
548
|
+
def owner_flag_set
|
549
|
+
if dependent? then
|
550
|
+
raise MetadataError.new("#{declarer.qp}.#{self} cannot be set as a #{type.qp} owner since it is already defined as a #{type.qp} dependent")
|
551
|
+
end
|
552
|
+
inv_attr = type.dependent_attribute(@declarer)
|
553
|
+
if inv_attr.nil? then
|
554
|
+
raise MetadataError.new("#{@declarer.qp} owner attribute #{self} does not have a #{type.qp} dependent inverse")
|
555
|
+
end
|
556
|
+
self.inverse = type.dependent_attribute(@declarer)
|
557
|
+
if inverse_metadata.logical? then @flags << :logical end
|
558
|
+
end
|
559
|
+
|
560
|
+
# Validates that this is not an owner attribute.
|
561
|
+
#
|
562
|
+
# @raise [MetadataError] if this is an owner attribute
|
563
|
+
def dependent_flag_set
|
564
|
+
if owner? then
|
565
|
+
raise MetadataError.new("#{declarer.qp}.#{self} cannot be set as a #{type.qp} dependent since it is already defined as a #{type.qp} owner")
|
566
|
+
end
|
567
|
+
end
|
568
|
+
|
569
|
+
# @return [Boolean] whether this dependent attribute is fetched. Only physical dependents are fetched by default.
|
570
|
+
def fetched_dependent?
|
571
|
+
not (logical? or @flags.include?(:unfetched))
|
572
|
+
end
|
573
|
+
|
574
|
+
# @return [Boolean] whether this independent attribute is fetched. Only abstract, non-derived independent
|
575
|
+
# references are fetched by default.
|
576
|
+
def fetched_independent?
|
577
|
+
type.abstract? and not (derived? or @flags.include?(:unfetched))
|
578
|
+
end
|
579
|
+
end
|
580
|
+
end
|
581
|
+
end
|