caruby-core 1.4.2 → 1.4.3

Sign up to get free protection for your applications and to get access to all the features.
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