caruby-core 1.4.2 → 1.4.3
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.txt +10 -0
- data/lib/caruby/cli/command.rb +10 -8
- data/lib/caruby/database/fetched_matcher.rb +28 -39
- data/lib/caruby/database/lazy_loader.rb +101 -0
- data/lib/caruby/database/persistable.rb +190 -167
- data/lib/caruby/database/persistence_service.rb +21 -7
- data/lib/caruby/database/persistifier.rb +185 -0
- data/lib/caruby/database/reader.rb +106 -176
- data/lib/caruby/database/saved_matcher.rb +56 -0
- data/lib/caruby/database/search_template_builder.rb +1 -1
- data/lib/caruby/database/sql_executor.rb +8 -7
- data/lib/caruby/database/store_template_builder.rb +134 -61
- data/lib/caruby/database/writer.rb +252 -52
- data/lib/caruby/database.rb +88 -67
- data/lib/caruby/domain/attribute_initializer.rb +16 -0
- data/lib/caruby/domain/attribute_metadata.rb +161 -72
- data/lib/caruby/domain/id_alias.rb +22 -0
- data/lib/caruby/domain/inversible.rb +91 -0
- data/lib/caruby/domain/merge.rb +116 -35
- data/lib/caruby/domain/properties.rb +1 -1
- data/lib/caruby/domain/reference_visitor.rb +207 -71
- data/lib/caruby/domain/resource_attributes.rb +93 -80
- data/lib/caruby/domain/resource_dependency.rb +22 -97
- data/lib/caruby/domain/resource_introspection.rb +21 -28
- data/lib/caruby/domain/resource_inverse.rb +134 -0
- data/lib/caruby/domain/resource_metadata.rb +41 -19
- data/lib/caruby/domain/resource_module.rb +42 -33
- data/lib/caruby/import/java.rb +8 -9
- data/lib/caruby/migration/migrator.rb +20 -7
- data/lib/caruby/migration/resource_module.rb +0 -2
- data/lib/caruby/resource.rb +132 -351
- data/lib/caruby/util/cache.rb +4 -1
- data/lib/caruby/util/class.rb +48 -1
- data/lib/caruby/util/collection.rb +54 -18
- data/lib/caruby/util/inflector.rb +7 -0
- data/lib/caruby/util/options.rb +35 -31
- data/lib/caruby/util/partial_order.rb +1 -1
- data/lib/caruby/util/properties.rb +2 -2
- data/lib/caruby/util/stopwatch.rb +16 -8
- data/lib/caruby/util/transitive_closure.rb +1 -1
- data/lib/caruby/util/visitor.rb +342 -328
- data/lib/caruby/version.rb +1 -1
- data/lib/caruby/yard/resource_metadata_handler.rb +8 -0
- data/lib/caruby.rb +2 -0
- metadata +10 -9
- data/lib/caruby/database/saved_merger.rb +0 -131
- data/lib/caruby/domain/annotatable.rb +0 -25
- data/lib/caruby/domain/annotation.rb +0 -23
- data/lib/caruby/import/annotatable_class.rb +0 -28
- data/lib/caruby/import/annotation_class.rb +0 -27
- data/lib/caruby/import/annotation_module.rb +0 -67
- data/lib/caruby/migration/resource.rb +0 -8
@@ -6,13 +6,21 @@ module CaRuby
|
|
6
6
|
# ResourceMetadata mix-in for attribute accessors.
|
7
7
|
module ResourceAttributes
|
8
8
|
|
9
|
-
attr_reader :attributes
|
9
|
+
attr_reader :attributes
|
10
|
+
|
11
|
+
# @return [Hashable] the default attribute => value associations
|
12
|
+
attr_reader :defaults
|
10
13
|
|
11
|
-
# Returns
|
12
|
-
#
|
13
|
-
#
|
14
|
-
#
|
15
|
-
|
14
|
+
# Returns whether this class has an attribute with the given symbol.
|
15
|
+
#
|
16
|
+
# @param [Symbol] symbol the potential attribute
|
17
|
+
# @return [Boolean] whether there is a corresponding attribute
|
18
|
+
def attribute_defined?(symbol)
|
19
|
+
unless Symbol === symbol then
|
20
|
+
raise ArgumentError.new("Attribute argument #{symbol.qp} of type #{symbol.class.qp} is not a symbol")
|
21
|
+
end
|
22
|
+
@attr_md_hash.has_key?(symbol)
|
23
|
+
end
|
16
24
|
|
17
25
|
# Adds the given attribute to this Class.
|
18
26
|
# If attribute refers to a domain type, then the type argument is the referenced domain type.
|
@@ -46,7 +54,7 @@ module CaRuby
|
|
46
54
|
def attribute_metadata(attribute)
|
47
55
|
# simple and predominant case is that attribute is a standard attribute.
|
48
56
|
# otherwise, resolve attribute to the standard symbol.
|
49
|
-
attr_md =
|
57
|
+
attr_md = @attr_md_hash[attribute] || @attr_md_hash[standard_attribute(attribute)]
|
50
58
|
# if not found, then delegate to handler which will either make the new attribute or raise a NameError
|
51
59
|
attr_md || (attribute_missing(attribute) && @local_attr_md_hash[attribute])
|
52
60
|
end
|
@@ -55,13 +63,7 @@ module CaRuby
|
|
55
63
|
#
|
56
64
|
# Raises NameError if the attribute is not found
|
57
65
|
def standard_attribute(name_or_alias)
|
58
|
-
|
59
|
-
end
|
60
|
-
|
61
|
-
# Returns an Enumerable on this Metadata's attributes which iterates on each attribute whose
|
62
|
-
# corresponding AttributeMetadata satisfies the given filter block.
|
63
|
-
def attribute_filter(&filter) # :yields: attribute_metadata
|
64
|
-
Filter.new(attribute_metadata_hash, &filter)
|
66
|
+
@alias_std_attr_map[name_or_alias.to_sym] or raise NameError.new("#{self} attribute not found: #{name_or_alias}")
|
65
67
|
end
|
66
68
|
|
67
69
|
## the built-in Metadata attribute filters ##
|
@@ -92,9 +94,14 @@ module CaRuby
|
|
92
94
|
# @see Mergeable#mergeable_attributes
|
93
95
|
alias :mergeable_attributes :nondomain_java_attributes
|
94
96
|
|
97
|
+
# @param [Boolean, nil] inc_super flag indicating whether to include dependents defined in the superclass
|
95
98
|
# @return [<Symbol>] the dependent attributes
|
96
|
-
def dependent_attributes
|
97
|
-
|
99
|
+
def dependent_attributes(inc_super=true)
|
100
|
+
if inc_super then
|
101
|
+
@dep_attrs ||= attribute_filter { |attr_md| attr_md.dependent? }
|
102
|
+
else
|
103
|
+
@local_dep_attrs ||= dependent_attributes.compose { |attr_md| attr_md.declarer == self }
|
104
|
+
end
|
98
105
|
end
|
99
106
|
|
100
107
|
# @return [<Symbol>] the dependent attributes
|
@@ -102,12 +109,6 @@ module CaRuby
|
|
102
109
|
@ag_dep_attrs ||= dependent_attributes.compose { |attr_md| attr_md.autogenerated? }
|
103
110
|
end
|
104
111
|
|
105
|
-
# @return [<Symbol>] the dependent attributes which are created but not fetched
|
106
|
-
# @see AttributeMetadata#unfetched_created?
|
107
|
-
def unfetched_created_attributes
|
108
|
-
@uc_attrs ||= attribute_filter { |attr_md| attr_md.unfetched_created? }
|
109
|
-
end
|
110
|
-
|
111
112
|
# @return [<Symbol>] the autogenerated logical dependent attributes
|
112
113
|
# @see #logical_dependent_attributes
|
113
114
|
# @see AttributeMetadata#autogenerated?
|
@@ -115,9 +116,9 @@ module CaRuby
|
|
115
116
|
@ag_log_dep_attrs ||= dependent_attributes.compose { |attr_md| attr_md.autogenerated? and attr_md.logical? }
|
116
117
|
end
|
117
118
|
|
118
|
-
# @return [<Symbol>] the
|
119
|
-
def
|
120
|
-
@
|
119
|
+
# @return [<Symbol>] the {AttributeMetadata#saved_fetch?} attributes
|
120
|
+
def saved_fetch_attributes
|
121
|
+
@svd_ftch_attrs ||= domain_attributes.compose { |attr_md| attr_md.saved_fetch? }
|
121
122
|
end
|
122
123
|
|
123
124
|
# @return [<Symbol>] the logical dependent attributes
|
@@ -182,6 +183,10 @@ module CaRuby
|
|
182
183
|
@cp_sv_attrs ||= dependent_attributes.compose { |attr_md| attr_md.autogenerated? or not attr_md.logical? }
|
183
184
|
end
|
184
185
|
|
186
|
+
# Returns the subject class's required attributes, determined as follows:
|
187
|
+
# * An attribute marked with the :mandatory flag is mandatory.
|
188
|
+
# * An attribute marked with the :optional or :autogenerated flag is not mandatory.
|
189
|
+
# * Otherwise, A secondary key or owner attribute is mandatory.
|
185
190
|
def mandatory_attributes
|
186
191
|
@mndtry_attrs ||= collect_mandatory_attributes
|
187
192
|
end
|
@@ -257,18 +262,15 @@ module CaRuby
|
|
257
262
|
end
|
258
263
|
|
259
264
|
#@return [<Symbol>] the #domain_attributes which are not #fetched_domain_attributes
|
260
|
-
def
|
265
|
+
def unfetched_attributes
|
261
266
|
@unftchd_attrs ||= domain_attributes.compose { |attr_md| not attr_md.fetched? }
|
262
267
|
end
|
268
|
+
|
269
|
+
alias :toxic_attributes :unfetched_attributes
|
263
270
|
|
264
|
-
# @return [<Symbol>] the Java property non-abstract {#
|
271
|
+
# @return [<Symbol>] the Java property non-abstract {#unfetched_attributes}
|
265
272
|
def loadable_attributes
|
266
|
-
@
|
267
|
-
end
|
268
|
-
|
269
|
-
# @return [<Symbol>] the {#loadable_attributes} which are not {AttributeMetadata#autogenerated?}
|
270
|
-
def non_autogenerated_loadable_attributes
|
271
|
-
@unftchd_attrs ||= unfetched_domain_attributes.compose { |attr_md| not attr_md.type.autogenerated? }
|
273
|
+
@ldbl_attrs ||= unfetched_attributes.compose { |attr_md| attr_md.java_property? and not attr_md.type.abstract? }
|
272
274
|
end
|
273
275
|
|
274
276
|
# @param [Symbol] attribute the attribute to check
|
@@ -290,18 +292,14 @@ module CaRuby
|
|
290
292
|
end
|
291
293
|
|
292
294
|
protected
|
293
|
-
|
295
|
+
|
294
296
|
# @return [{Symbol => AttributeMetadata}] the attribute => metadata hash
|
295
297
|
def attribute_metadata_hash
|
296
|
-
# initialize the meta-data if necessary
|
297
|
-
superclass.introspect_subclass(self) if @attr_md_hash.nil?
|
298
298
|
@attr_md_hash
|
299
299
|
end
|
300
300
|
|
301
301
|
# @return [{Symbol => Symbol}] the attribute alias => standard hash
|
302
302
|
def alias_standard_attribute_hash
|
303
|
-
# initialize the meta-data if necessary
|
304
|
-
superclass.introspect_subclass(self) if @alias_std_attr_map.nil?
|
305
303
|
@alias_std_attr_map
|
306
304
|
end
|
307
305
|
|
@@ -324,18 +322,52 @@ module CaRuby
|
|
324
322
|
@filter = filter
|
325
323
|
end
|
326
324
|
|
327
|
-
# @yield [attribute]
|
325
|
+
# @yield [attribute, attr_md] the block to apply to the filtered attribute metadata and attribute
|
326
|
+
# @yieldparam [Symbol] attribute the attribute
|
327
|
+
# @yieldparam [AttributeMetadata] attr_md the attribute metadata
|
328
|
+
def each_pair
|
329
|
+
@hash.each { |attr, attr_md| yield(attr, attr_md) if @filter.call(attr_md) }
|
330
|
+
end
|
331
|
+
|
332
|
+
# @yield [attribute] block to apply to each filtered attribute
|
328
333
|
# @yieldparam [Symbol] the attribute which satisfies the filter condition
|
329
|
-
def
|
330
|
-
|
334
|
+
def each_attribute(&block)
|
335
|
+
each_pair { |attr, attr_md| yield(attr) }
|
336
|
+
end
|
337
|
+
|
338
|
+
alias :each :each_attribute
|
339
|
+
|
340
|
+
# @yield [attr_md] the block to apply to the filtered attribute metadata
|
341
|
+
# @yieldparam [AttributeMetadata] attr_md the attribute metadata
|
342
|
+
def each_metadata
|
343
|
+
each_pair { |attr, attr_md| yield(attr_md) }
|
344
|
+
end
|
345
|
+
|
346
|
+
# @yield [attr_md] the block to apply to the attribute metadata
|
347
|
+
# @yieldparam [AttributeMetadata] attr_md the attribute metadata
|
348
|
+
# @return [Symbol] the first attribute whose metadata satisfies the block
|
349
|
+
def detect_with_metadata
|
350
|
+
each_pair { |attr, attr_md| return attr if yield(attr_md) }
|
351
|
+
nil
|
331
352
|
end
|
332
353
|
|
354
|
+
# @yield [attr_md] the attribute selection filter
|
355
|
+
# @yieldparam [AttributeMetadata] attr_md the candidate attribute metadata
|
333
356
|
# @return [Filter] a new Filter which applies the filter block given to this
|
334
|
-
# method with the AttributeMetadata enumerated by this filter
|
357
|
+
# method with the AttributeMetadata enumerated by this filter
|
335
358
|
def compose
|
336
359
|
Filter.new(@hash) { |attr_md| @filter.call(attr_md) and yield(attr_md) }
|
337
360
|
end
|
338
361
|
end
|
362
|
+
|
363
|
+
# Returns an Enumerable on this Resource class's attributes which iterates on each attribute whose
|
364
|
+
# corresponding AttributeMetadata satisfies the given filter block.
|
365
|
+
#
|
366
|
+
# @yield [attr_md] the attribute selector
|
367
|
+
# @yieldparam [AttributeMetadata] attr_md the candidate attribute
|
368
|
+
def attribute_filter(&filter)
|
369
|
+
Filter.new(@attr_md_hash, &filter)
|
370
|
+
end
|
339
371
|
|
340
372
|
# Initializes the attribute meta-data structures.
|
341
373
|
def init_attributes
|
@@ -349,6 +381,9 @@ module CaRuby
|
|
349
381
|
@defaults = append_parent_enum(@local_defaults) { |par| par.defaults }
|
350
382
|
end
|
351
383
|
|
384
|
+
# Creates the given aliases to attributes.
|
385
|
+
#
|
386
|
+
# @param [{Symbol => Symbol}] hash the alias => attribute hash
|
352
387
|
def add_attribute_aliases(hash)
|
353
388
|
hash.each { |aliaz, attr| delegate_to_attribute(aliaz, attr) }
|
354
389
|
end
|
@@ -373,49 +408,20 @@ module CaRuby
|
|
373
408
|
# Raises ArgumentError if klass is incompatible with the current attribute type.
|
374
409
|
def set_attribute_type(attribute, klass)
|
375
410
|
attr_md = attribute_metadata(attribute)
|
411
|
+
# If this class is the declarer, then simply set the attribute type.
|
412
|
+
# Otherwise, if the attribute type is unspecified or is a superclass of the given class,
|
413
|
+
# then make a new attribute metadata for this class.
|
376
414
|
if attr_md.declarer == self then
|
377
415
|
attr_md.type = klass
|
378
416
|
elsif attr_md.type.nil? or klass < attr_md.type then
|
379
|
-
|
417
|
+
logger.debug { "Restricting #{attr_md.declarer.qp}.#{attribute}(#{attr_md.type.qp}) to #{qp} with return type #{klass.qp}..." }
|
418
|
+
new_attr_md = attr_md.restrict_type(self, klass)
|
380
419
|
add_attribute_metadata(new_attr_md)
|
381
420
|
elsif klass != attr_md.type then
|
382
421
|
raise ArgumentError.new("Cannot reset #{qp}.#{attribute} type #{attr_md.type} to incompatible #{klass.qp}")
|
383
422
|
end
|
384
423
|
end
|
385
424
|
|
386
|
-
# Sets the given attribute inverse to the inverse symbol.
|
387
|
-
#
|
388
|
-
# Raises ArgumentError if the inverse type is incompatible with this Resource.
|
389
|
-
def set_attribute_inverse(attribute, inverse)
|
390
|
-
attr_md = attribute_metadata(attribute)
|
391
|
-
# return if inverse is already set
|
392
|
-
return if attr_md.inverse == inverse
|
393
|
-
# the inverse attribute meta-data
|
394
|
-
inv_md = attr_md.type.attribute_metadata(inverse)
|
395
|
-
# if the attribute is the many side of a 1:M relation, then delegate to the one side.
|
396
|
-
if attr_md.collection? and not inv_md.collection? then
|
397
|
-
return attr_md.type.set_attribute_inverse(inverse, attribute)
|
398
|
-
end
|
399
|
-
# this class must be the same as or a subclass of the inverse attribute type
|
400
|
-
unless self <= inv_md.type then
|
401
|
-
raise ArgumentError.new("Cannot set #{qp}.#{attribute} inverse to #{attr_md.type.qp}.#{attribute} with incompatible type #{inv_md.type.qp}")
|
402
|
-
end
|
403
|
-
|
404
|
-
# if the attribute is defined by this class, then set the inverse in the attribute metadata.
|
405
|
-
# otherwise, make a new attribute metadata specialized for this class.
|
406
|
-
unless attr_md.declarer == self then
|
407
|
-
attr_md = attr_md.dup
|
408
|
-
attr_md.declarer = self
|
409
|
-
add_attribute_metadata(attribute, inverse)
|
410
|
-
end
|
411
|
-
attr_md.inverse = inverse
|
412
|
-
|
413
|
-
# if attribute is the one side of a 1:M relation, then add the inverse updater.
|
414
|
-
unless attr_md.collection? then
|
415
|
-
add_inverse_updater(attribute, inverse)
|
416
|
-
end
|
417
|
-
end
|
418
|
-
|
419
425
|
def add_attribute_defaults(hash)
|
420
426
|
hash.each { |attr, value| @local_defaults[standard_attribute(attr)] = value }
|
421
427
|
end
|
@@ -426,7 +432,14 @@ module CaRuby
|
|
426
432
|
|
427
433
|
# Marks the given attribute with flags supported by {AttributeMetadata#qualify}.
|
428
434
|
def qualify_attribute(attribute, *flags)
|
429
|
-
attribute_metadata(attribute)
|
435
|
+
attr_md = attribute_metadata(attribute)
|
436
|
+
if attr_md.declarer == self then
|
437
|
+
attr_md.qualify(*flags)
|
438
|
+
else
|
439
|
+
logger.debug { "Restricting #{attr_md.declarer.qp}.#{attribute} to #{qp} with additional flags #{flags.to_series}" }
|
440
|
+
new_attr_md = attr_md.restrict_flags(self, *flags)
|
441
|
+
add_attribute_metadata(new_attr_md)
|
442
|
+
end
|
430
443
|
end
|
431
444
|
|
432
445
|
# Removes the given attribute from this Resource.
|
@@ -439,9 +452,9 @@ module CaRuby
|
|
439
452
|
@local_mndty_attrs.delete(std_attr)
|
440
453
|
@local_std_attr_hash.delete_if { |aliaz, attr| attr == std_attr }
|
441
454
|
else
|
442
|
-
@attr_md_hash =
|
455
|
+
@attr_md_hash = @attr_md_hash.filter_on_key { |attr| attr != attribute }
|
443
456
|
@attributes = Enumerable::Enumerator.new(@attr_md_hash, :each_key)
|
444
|
-
@alias_std_attr_map =
|
457
|
+
@alias_std_attr_map = @alias_std_attr_map.filter_on_key { |attr| attr != attribute }
|
445
458
|
end
|
446
459
|
end
|
447
460
|
|
@@ -466,7 +479,7 @@ module CaRuby
|
|
466
479
|
end
|
467
480
|
|
468
481
|
def each_attribute_metadata(&block)
|
469
|
-
|
482
|
+
@attr_md_hash.each_value(&block)
|
470
483
|
end
|
471
484
|
|
472
485
|
# Collects the {AttributeMetadata#fetched_dependent?} and {AttributeMetadata#fetched_independent?}
|
@@ -25,20 +25,16 @@ module CaRuby
|
|
25
25
|
# * _inverse_ is the owner inverse attribute defined in the dependent class
|
26
26
|
#
|
27
27
|
# @param [Symbol] attribute the dependent to add
|
28
|
-
# @param [Symbol] inverse the owner attribute defined in the dependent
|
29
28
|
# @param [<Symbol>] the attribute qualifier flags
|
30
29
|
def add_dependent_attribute(attribute, *flags)
|
31
30
|
attr_md = attribute_metadata(attribute)
|
32
|
-
unless attr_md.inverse_attribute_metadata.collection? then
|
33
|
-
delegate_writer_to_dependent(attribute)
|
34
|
-
end
|
35
31
|
flags << :dependent unless flags.include?(:dependent)
|
36
32
|
attr_md.qualify(*flags)
|
37
33
|
inverse = attr_md.inverse
|
38
34
|
inv_type = attr_md.type
|
39
35
|
# example: Parent.add_dependent_attribute(:children) with inverse :parent calls
|
40
|
-
# Child.add_owner(Parent, :
|
41
|
-
inv_type.add_owner(self,
|
36
|
+
# Child.add_owner(Parent, :children, :parent)
|
37
|
+
inv_type.add_owner(self, attribute, inverse)
|
42
38
|
end
|
43
39
|
|
44
40
|
# Returns whether this metadata's subject class depends on an owner.
|
@@ -51,18 +47,22 @@ module CaRuby
|
|
51
47
|
owners.detect { |owner| owner === other }
|
52
48
|
end
|
53
49
|
|
54
|
-
#
|
50
|
+
# @return [Symbol, nil] the attribute which references the dependent type,
|
51
|
+
# or nil if none
|
55
52
|
def dependent_attribute(dep_type)
|
56
53
|
type = dependent_attributes.detect { |attr| domain_type(attr) == dep_type }
|
57
54
|
return type if type
|
58
55
|
dependent_attribute(dep_type.superclass) if dep_type.superclass < Resource
|
59
56
|
end
|
60
57
|
|
61
|
-
#
|
58
|
+
# @return [Symbol, nil] the sole owner attribute of this class, or nil if there
|
59
|
+
# is not exactly one owner
|
62
60
|
def owner_attribute
|
63
61
|
if @local_owner_attr_hash then
|
62
|
+
# the sole attribute in the owner class => attribute hash
|
64
63
|
@local_owner_attr_hash.each_value { |attr| return attr } if @local_owner_attr_hash.size == 1
|
65
64
|
elsif superclass < Resource
|
65
|
+
# delegate to superclass
|
66
66
|
superclass.owner_attribute
|
67
67
|
end
|
68
68
|
end
|
@@ -96,24 +96,25 @@ module CaRuby
|
|
96
96
|
|
97
97
|
protected
|
98
98
|
|
99
|
-
#
|
100
|
-
#
|
101
|
-
#
|
102
|
-
#
|
103
|
-
#
|
104
|
-
#
|
105
|
-
#
|
106
|
-
|
107
|
-
|
99
|
+
# Adds the given owner class to this dependent class.
|
100
|
+
# This method must be called before any dependent attribute is accessed.
|
101
|
+
#
|
102
|
+
# @param [Class] the owner class
|
103
|
+
# @param [Symbol, nil] inverse the owner -> dependent attribute
|
104
|
+
# @param [Symbol, nil] attribute the dependent -> owner attribute, if known
|
105
|
+
# @raise [ValidationError] if there is no owner -> dependent inverse attribute
|
106
|
+
# @raise [MetadataError] if this method is called after a dependent attribute has been accessed
|
107
|
+
def add_owner(klass, inverse, attribute=nil)
|
108
|
+
logger.debug { "Adding #{qp} owner #{klass.qp}#{' attribute ' + attribute.to_s if attribute}#{' inverse ' + inverse.to_s if inverse}..." }
|
108
109
|
if @owner_attr_hash then
|
109
110
|
raise MetadataError.new("Can't add #{qp} owner #{klass.qp} after dependencies have been accessed")
|
110
111
|
end
|
111
112
|
@local_owner_attr_hash ||= {}
|
112
|
-
@local_owner_attr_hash[klass] = attribute ||=
|
113
|
+
@local_owner_attr_hash[klass] = attribute ||= detect_inverse_attribute(klass)
|
113
114
|
|
114
|
-
#
|
115
|
+
# set the inverse
|
115
116
|
if attribute then
|
116
|
-
raise ValidationError.new("Owner #{klass.qp} missing dependent attribute for dependent #{qp}")
|
117
|
+
if inverse.nil? then raise ValidationError.new("Owner #{klass.qp} missing dependent attribute for dependent #{qp}") end
|
117
118
|
set_attribute_inverse(attribute, inverse)
|
118
119
|
attribute_metadata(attribute).qualify(:owner)
|
119
120
|
else
|
@@ -121,85 +122,9 @@ module CaRuby
|
|
121
122
|
end
|
122
123
|
end
|
123
124
|
|
124
|
-
#
|
125
|
+
# @return [{Class => Symbol}] this Resource class's owner type => attribute hash
|
125
126
|
def owner_attribute_hash
|
126
127
|
@local_owner_attr_hash or (superclass.owner_attribute_hash if superclass < Resource) or Hash::EMPTY_HASH
|
127
128
|
end
|
128
|
-
|
129
|
-
private
|
130
|
-
|
131
|
-
def domain_class?(klass)
|
132
|
-
Class === klass and klass.include?(CaRuby::Resource)
|
133
|
-
end
|
134
|
-
|
135
|
-
# Redefines the attribute writer method to delegate to its inverse writer.
|
136
|
-
#
|
137
|
-
# For an attribute +dep+ with setter +setDep+ and inverse +owner+ with setter +setOwner+,
|
138
|
-
# this is equivalent to the following:
|
139
|
-
# class Owner
|
140
|
-
# def dep=(d)
|
141
|
-
# d.setOwner(self) if d
|
142
|
-
# setDep(self)
|
143
|
-
# end
|
144
|
-
# end
|
145
|
-
def delegate_writer_to_dependent(attribute)
|
146
|
-
attr_md = attribute_metadata(attribute)
|
147
|
-
# nothing to do if no inverse
|
148
|
-
inv_attr_md = attr_md.inverse_attribute_metadata || return
|
149
|
-
logger.debug { "Delegating #{qp}.#{attribute} update to the inverse #{attr_md.type}.#{inv_attr_md}..." }
|
150
|
-
# redefine the write to set the dependent inverse
|
151
|
-
redefine_method(attr_md.writer) do |old_writer|
|
152
|
-
# delegate to the CaRuby::Resource set_exclusive_dependent method
|
153
|
-
lambda { |dep| set_exclusive_dependent(dep, old_writer, inv_attr_md.writer) }
|
154
|
-
end
|
155
|
-
end
|
156
|
-
|
157
|
-
# Returns the owner attribute for the given owner klass and inverse, or nil if no
|
158
|
-
# owner attribute was detected.
|
159
|
-
def detect_owner_attribute(klass, inverse=nil)
|
160
|
-
# example: Parent.add_dependent_attribute(:children) without inverse calls
|
161
|
-
# Child.add_owner(Parent, nil, :children) which calls
|
162
|
-
# Child.detect_owner_attribute(klass, :children)
|
163
|
-
|
164
|
-
# the candidate attributes which return the owner type
|
165
|
-
candidates = domain_attributes.map do |attr|
|
166
|
-
attr_md = attribute_metadata(attr)
|
167
|
-
# possible hit if there is a match on the type
|
168
|
-
attr_md if klass.equal?(attr_md.type) or klass <= attr_md.type
|
169
|
-
end
|
170
|
-
candidates.compact!
|
171
|
-
return if candidates.empty?
|
172
|
-
|
173
|
-
# there can be at most one owner attribute per owner.
|
174
|
-
return candidates.first.to_sym if candidates.size == 1
|
175
|
-
|
176
|
-
# we have a hit if there is a match on the inverse. in the above example,
|
177
|
-
# attribute :parent with inverse :children => :parent is the owner attribute
|
178
|
-
candidates.each { |attr_md| return attr_md.to_sym if attr_md.inverse == inverse }
|
179
|
-
|
180
|
-
# by convention, if more than one attribute references the owner type,
|
181
|
-
# then the attribute named after the owner type is the owner attribute
|
182
|
-
hit = klass.name[/\w+$/].downcase.to_sym
|
183
|
-
hit if candidates.detect { |attr_md| attr_md.to_sym == hit }
|
184
|
-
end
|
185
|
-
|
186
|
-
# Infers annotation dependent attributes based on whether a domain attribute satisfies the
|
187
|
-
# following criteria:
|
188
|
-
# 1. the referenced type has an attribute which refers back to this classifier's subject class
|
189
|
-
# 2. the referenced type is not an owner of this classifier's subject class
|
190
|
-
# Annotation dependencies are not specified in a configuration and follow the above convention.
|
191
|
-
def infer_annotation_dependent_attributes
|
192
|
-
dep_attrs = []
|
193
|
-
domain_attributes.each do |attr|
|
194
|
-
next if owner_attribute?(attr)
|
195
|
-
ref_md = domain_type(attr).metadata
|
196
|
-
owner_attr = ref_md.detect_owner_attribute(subject_class)
|
197
|
-
if owner_attr then
|
198
|
-
ref_md.add_owner(subject_class, owner_attr)
|
199
|
-
dep_attrs << attr
|
200
|
-
end
|
201
|
-
end
|
202
|
-
dep_attrs
|
203
|
-
end
|
204
129
|
end
|
205
130
|
end
|