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.
Files changed (52) hide show
  1. data/History.txt +10 -0
  2. data/lib/caruby/cli/command.rb +10 -8
  3. data/lib/caruby/database/fetched_matcher.rb +28 -39
  4. data/lib/caruby/database/lazy_loader.rb +101 -0
  5. data/lib/caruby/database/persistable.rb +190 -167
  6. data/lib/caruby/database/persistence_service.rb +21 -7
  7. data/lib/caruby/database/persistifier.rb +185 -0
  8. data/lib/caruby/database/reader.rb +106 -176
  9. data/lib/caruby/database/saved_matcher.rb +56 -0
  10. data/lib/caruby/database/search_template_builder.rb +1 -1
  11. data/lib/caruby/database/sql_executor.rb +8 -7
  12. data/lib/caruby/database/store_template_builder.rb +134 -61
  13. data/lib/caruby/database/writer.rb +252 -52
  14. data/lib/caruby/database.rb +88 -67
  15. data/lib/caruby/domain/attribute_initializer.rb +16 -0
  16. data/lib/caruby/domain/attribute_metadata.rb +161 -72
  17. data/lib/caruby/domain/id_alias.rb +22 -0
  18. data/lib/caruby/domain/inversible.rb +91 -0
  19. data/lib/caruby/domain/merge.rb +116 -35
  20. data/lib/caruby/domain/properties.rb +1 -1
  21. data/lib/caruby/domain/reference_visitor.rb +207 -71
  22. data/lib/caruby/domain/resource_attributes.rb +93 -80
  23. data/lib/caruby/domain/resource_dependency.rb +22 -97
  24. data/lib/caruby/domain/resource_introspection.rb +21 -28
  25. data/lib/caruby/domain/resource_inverse.rb +134 -0
  26. data/lib/caruby/domain/resource_metadata.rb +41 -19
  27. data/lib/caruby/domain/resource_module.rb +42 -33
  28. data/lib/caruby/import/java.rb +8 -9
  29. data/lib/caruby/migration/migrator.rb +20 -7
  30. data/lib/caruby/migration/resource_module.rb +0 -2
  31. data/lib/caruby/resource.rb +132 -351
  32. data/lib/caruby/util/cache.rb +4 -1
  33. data/lib/caruby/util/class.rb +48 -1
  34. data/lib/caruby/util/collection.rb +54 -18
  35. data/lib/caruby/util/inflector.rb +7 -0
  36. data/lib/caruby/util/options.rb +35 -31
  37. data/lib/caruby/util/partial_order.rb +1 -1
  38. data/lib/caruby/util/properties.rb +2 -2
  39. data/lib/caruby/util/stopwatch.rb +16 -8
  40. data/lib/caruby/util/transitive_closure.rb +1 -1
  41. data/lib/caruby/util/visitor.rb +342 -328
  42. data/lib/caruby/version.rb +1 -1
  43. data/lib/caruby/yard/resource_metadata_handler.rb +8 -0
  44. data/lib/caruby.rb +2 -0
  45. metadata +10 -9
  46. data/lib/caruby/database/saved_merger.rb +0 -131
  47. data/lib/caruby/domain/annotatable.rb +0 -25
  48. data/lib/caruby/domain/annotation.rb +0 -23
  49. data/lib/caruby/import/annotatable_class.rb +0 -28
  50. data/lib/caruby/import/annotation_class.rb +0 -27
  51. data/lib/caruby/import/annotation_module.rb +0 -67
  52. 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, :defaults
9
+ attr_reader :attributes
10
+
11
+ # @return [Hashable] the default attribute => value associations
12
+ attr_reader :defaults
10
13
 
11
- # Returns the subject class's required attributes, determined as follows:
12
- # * An attribute marked with the :mandatory flag is mandatory.
13
- # * An attribute marked with the :optional or :autogenerated flag is not mandatory.
14
- # * Otherwise, A secondary key or owner attribute is mandatory.
15
- attr_reader :mandatory_attributes
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 = attribute_metadata_hash[attribute] || attribute_metadata_hash[standard_attribute(attribute)]
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
- alias_standard_attribute_hash[name_or_alias.to_sym] or raise NameError.new("#{qp} attribute not found: #{name_or_alias}")
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
- @dep_attrs ||= attribute_filter { |attr_md| attr_md.dependent? }
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 autogenerated {AttributeMetadata#saved_mergeable?} dependent attributes
119
- def mergeable_saved_autogenerated_attributes
120
- @mgbl_sv_unftchd_ag_attrs ||= autogenerated_dependent_attributes.compose { |attr_md| attr_md.saved_mergeable? }
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 unfetched_domain_attributes
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 {#unfetched_domain_attributes}
271
+ # @return [<Symbol>] the Java property non-abstract {#unfetched_attributes}
265
272
  def loadable_attributes
266
- @unftchd_attrs ||= unfetched_domain_attributes.compose { |attr_md| attr_md.java_property? and not attr_md.type.abstract? }
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] enumerates each filtered 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 each(&block)
330
- @hash.each { |k, v| yield(k) if @filter.call(v) }
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
- new_attr_md = attr_md.restrict(self, klass)
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).qualify(*flags)
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 = attribute_metadata_hash.filter_on_key { |attr| attr != attribute }
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 = alias_standard_attribute_hash.filter_on_key { |attr| attr != attribute }
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
- attribute_metadata_hash.each_value(&block)
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, :parent, :children)
41
- inv_type.add_owner(self, inverse, attribute)
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
- # Returns the attribute which references the dependent type, or nil if none.
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
- # Returns the sole owner attribute of this class, or nil if there is not exactly one owner.
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
- # If attribute is nil, then the owner attribute is inferred as follows:
100
- # * If there is exactly one reference attribute from this dependent to the owner klass, then that
101
- # attribute is the owner attribute.
102
- # * Otherwise, if this dependent class has a default attribute name given by the demodulized,
103
- # underscored owner class name and that attribute references the owner klass, then that attribute
104
- # is the owner attribute.
105
- # * Otherwise, there is no owner attribute.
106
- def add_owner(klass, attribute=nil, inverse=nil)
107
- logger.debug { "Adding #{qp} owner #{klass.qp}..." }
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 ||= detect_owner_attribute(klass, inverse)
113
+ @local_owner_attr_hash[klass] = attribute ||= detect_inverse_attribute(klass)
113
114
 
114
- # augment the owner writer method
115
+ # set the inverse
115
116
  if attribute then
116
- raise ValidationError.new("Owner #{klass.qp} missing dependent attribute for dependent #{qp}") if inverse.nil?
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
- # Returns this Resource class's owner type => attribute hash.
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