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.
Files changed (61) hide show
  1. data/History.md +48 -0
  2. data/lib/caruby/cli/command.rb +2 -1
  3. data/lib/caruby/csv/csv_mapper.rb +8 -8
  4. data/lib/caruby/database/persistable.rb +44 -65
  5. data/lib/caruby/database/persistence_service.rb +12 -9
  6. data/lib/caruby/database/persistifier.rb +14 -14
  7. data/lib/caruby/database/reader.rb +53 -51
  8. data/lib/caruby/database/search_template_builder.rb +9 -10
  9. data/lib/caruby/database/store_template_builder.rb +58 -58
  10. data/lib/caruby/database/writer.rb +96 -96
  11. data/lib/caruby/database.rb +19 -19
  12. data/lib/caruby/domain/attribute.rb +581 -0
  13. data/lib/caruby/domain/attributes.rb +615 -0
  14. data/lib/caruby/domain/dependency.rb +240 -0
  15. data/lib/caruby/domain/importer.rb +183 -0
  16. data/lib/caruby/domain/introspection.rb +176 -0
  17. data/lib/caruby/domain/inverse.rb +173 -0
  18. data/lib/caruby/domain/inversible.rb +1 -2
  19. data/lib/caruby/domain/java_attribute.rb +173 -0
  20. data/lib/caruby/domain/merge.rb +13 -10
  21. data/lib/caruby/domain/metadata.rb +141 -0
  22. data/lib/caruby/domain/mixin.rb +35 -0
  23. data/lib/caruby/domain/reference_visitor.rb +5 -3
  24. data/lib/caruby/domain.rb +340 -0
  25. data/lib/caruby/import/java.rb +29 -25
  26. data/lib/caruby/migration/migratable.rb +5 -5
  27. data/lib/caruby/migration/migrator.rb +19 -15
  28. data/lib/caruby/migration/resource_module.rb +1 -1
  29. data/lib/caruby/resource.rb +39 -30
  30. data/lib/caruby/util/collection.rb +94 -33
  31. data/lib/caruby/util/coordinate.rb +28 -2
  32. data/lib/caruby/util/log.rb +4 -4
  33. data/lib/caruby/util/module.rb +12 -28
  34. data/lib/caruby/util/partial_order.rb +9 -10
  35. data/lib/caruby/util/pretty_print.rb +46 -26
  36. data/lib/caruby/util/topological_sync_enumerator.rb +10 -4
  37. data/lib/caruby/util/transitive_closure.rb +2 -2
  38. data/lib/caruby/util/visitor.rb +1 -1
  39. data/lib/caruby/version.rb +1 -1
  40. data/test/lib/caruby/database/persistable_test.rb +1 -1
  41. data/test/lib/caruby/domain/domain_test.rb +14 -28
  42. data/test/lib/caruby/domain/inversible_test.rb +1 -1
  43. data/test/lib/caruby/import/java_test.rb +5 -0
  44. data/test/lib/caruby/migration/test_case.rb +0 -1
  45. data/test/lib/caruby/test_case.rb +9 -10
  46. data/test/lib/caruby/util/collection_test.rb +23 -5
  47. data/test/lib/caruby/util/module_test.rb +10 -14
  48. data/test/lib/caruby/util/partial_order_test.rb +16 -15
  49. data/test/lib/caruby/util/visitor_test.rb +1 -1
  50. data/test/lib/examples/galena/clinical_trials/migration/test_case.rb +1 -1
  51. metadata +16 -15
  52. data/History.txt +0 -44
  53. data/lib/caruby/domain/attribute_metadata.rb +0 -551
  54. data/lib/caruby/domain/java_attribute_metadata.rb +0 -183
  55. data/lib/caruby/domain/resource_attributes.rb +0 -565
  56. data/lib/caruby/domain/resource_dependency.rb +0 -217
  57. data/lib/caruby/domain/resource_introspection.rb +0 -160
  58. data/lib/caruby/domain/resource_inverse.rb +0 -151
  59. data/lib/caruby/domain/resource_metadata.rb +0 -155
  60. data/lib/caruby/domain/resource_module.rb +0 -370
  61. data/lib/caruby/yard/resource_metadata_handler.rb +0 -8
@@ -1,565 +0,0 @@
1
- require 'caruby/util/collection'
2
- require 'caruby/domain/merge'
3
- require 'caruby/domain/attribute_metadata'
4
-
5
- module CaRuby
6
- # ResourceMetadata mix-in for attribute accessors.
7
- module ResourceAttributes
8
-
9
- attr_reader :attributes
10
-
11
- # @return [Hashable] the default attribute => value associations
12
- attr_reader :defaults
13
-
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
24
-
25
- # Adds the given attribute to this Class.
26
- # If attribute refers to a domain type, then the type argument is the referenced domain type.
27
- # Supported flags are listed in CaRuby::AttributeMetadata.
28
- def add_attribute(attribute, type=nil, *flags)
29
- add_attribute_metadata(AttributeMetadata.new(attribute, self, type, *flags))
30
- attribute
31
- end
32
-
33
- # Returns the +[:identifier]+ primary key attribute array.
34
- def primary_key_attributes
35
- IDENTIFIER_ATTR_ARRAY
36
- end
37
-
38
- # Returns this class's secondary key attribute array.
39
- # If this class's secondary key is not set, then the secondary key is the ResourceMetadata superclass
40
- # secondary key, if any.
41
- def secondary_key_attributes
42
- @scndy_key_attrs or superclass < Resource ? superclass.secondary_key_attributes : Array::EMPTY_ARRAY
43
- end
44
-
45
- # Returns this class's alternate key attribute array.
46
- # If this class's secondary key is not set, then the alternate key is the ResourceMetadata superclass
47
- # alternate key, if any.
48
- def alternate_key_attributes
49
- @alt_key_attrs or superclass < Resource ? superclass.alternate_key_attributes : Array::EMPTY_ARRAY
50
- end
51
-
52
- # @return the AttributeMetadata for the given attribute symbol or alias
53
- # @raise [NameError] if the attribute is not recognized
54
- def attribute_metadata(attribute)
55
- # simple and predominant case is that attribute is a standard attribute.
56
- # otherwise, resolve attribute to the standard symbol.
57
- attr_md = @attr_md_hash[attribute] || @attr_md_hash[standard_attribute(attribute)]
58
- # if not found, then delegate to handler which will either make the new attribute or raise a NameError
59
- attr_md || (attribute_missing(attribute) && @local_attr_md_hash[attribute])
60
- end
61
-
62
- # Returns the standard attribute symbol for the given name_or_alias.
63
- #
64
- # Raises NameError if the attribute is not found
65
- def standard_attribute(name_or_alias)
66
- @alias_std_attr_map[name_or_alias.to_sym] or raise NameError.new("#{self} attribute not found: #{name_or_alias}")
67
- end
68
-
69
- ## the built-in Metadata attribute filters ##
70
-
71
- # @return [<Symbol>] the domain attributes which wrap a java property
72
- # @see AttributeMetadata#java_property?
73
- def java_attributes
74
- @java_attrs ||= attribute_filter { |attr_md| attr_md.java_property? }
75
- end
76
-
77
- alias :printable_attributes :java_attributes
78
-
79
- # @return [<Symbol>] the domain attributes
80
- def domain_attributes
81
- @dom_attrs ||= attribute_filter { |attr_md| attr_md.domain? }
82
- end
83
-
84
- # @return [<Symbol>] the non-domain Java attributes
85
- def nondomain_attributes
86
- @nondom_attrs ||= attribute_filter { |attr_md| attr_md.java_property? and attr_md.nondomain? }
87
- end
88
-
89
- # @return [<Symbol>] the non-domain Java property wrapper attributes
90
- def nondomain_java_attributes
91
- @nondom_java_attrs ||= nondomain_attributes.compose { |attr_md| attr_md.java_property? }
92
- end
93
-
94
- # @return [<Symbol>] the standard attributes which can be merged into an instance of the subject class.
95
- # The default mergeable attributes consist of the {#nondomain_java_attributes}.
96
- # @see Mergeable#mergeable_attributes
97
- alias :mergeable_attributes :nondomain_java_attributes
98
-
99
- # @param [Boolean, nil] inc_super flag indicating whether to include dependents defined in the superclass
100
- # @return [<Symbol>] the dependent attributes
101
- def dependent_attributes(inc_super=true)
102
- if inc_super then
103
- @dep_attrs ||= attribute_filter { |attr_md| attr_md.dependent? }
104
- else
105
- @local_dep_attrs ||= dependent_attributes.compose { |attr_md| attr_md.declarer == self }
106
- end
107
- end
108
-
109
- # @return [<Symbol>] the dependent attributes
110
- def autogenerated_dependent_attributes
111
- @ag_dep_attrs ||= dependent_attributes.compose { |attr_md| attr_md.autogenerated? }
112
- end
113
-
114
- # @return [<Symbol>] the autogenerated logical dependent attributes
115
- # @see #logical_dependent_attributes
116
- # @see AttributeMetadata#autogenerated?
117
- def autogenerated_logical_dependent_attributes
118
- @ag_log_dep_attrs ||= dependent_attributes.compose { |attr_md| attr_md.autogenerated? and attr_md.logical? }
119
- end
120
-
121
- # @return [<Symbol>] the {AttributeMetadata#saved_fetch?} attributes
122
- def saved_fetch_attributes
123
- @svd_ftch_attrs ||= domain_attributes.compose { |attr_md| attr_md.saved_fetch? }
124
- end
125
-
126
- # @return [<Symbol>] the logical dependent attributes
127
- # @see AttributeMetadata#logical?
128
- def logical_dependent_attributes
129
- @log_dep_attrs ||= dependent_attributes.compose { |attr_md| attr_md.logical? }
130
- end
131
-
132
- # @return [<Symbol>] the unidirectional dependent attributes
133
- # @see AttributeMetadata#unidirectional?
134
- def unidirectional_dependent_attributes
135
- @uni_dep_attrs ||= dependent_attributes.compose { |attr_md| attr_md.unidirectional? }
136
- end
137
-
138
- # @return [<Symbol>] the auto-generated attributes
139
- # @see AttributeMetadata#autogenerated?
140
- def autogenerated_attributes
141
- @ag_attrs ||= attribute_filter { |attr_md| attr_md.autogenerated? }
142
- end
143
-
144
- # @return [<Symbol>] the auto-generated non-domain attributes
145
- # @see AttributeMetadata#nondomain?
146
- # @see AttributeMetadata#autogenerated?
147
- def autogenerated_nondomain_attributes
148
- @ag_nd_attrs ||= attribute_filter { |attr_md| attr_md.autogenerated? and attr_md.nondomain? }
149
- end
150
-
151
- # @return [<Symbol>] the {AttributeMetadata#volatile?} non-domain attributes
152
- def volatile_nondomain_attributes
153
- @unsvd_nd_attrs ||= attribute_filter { |attr_md| attr_md.volatile? and attr_md.nondomain? }
154
- end
155
-
156
- # @return [<Symbol>] the domain attributes which can serve as a query parameter
157
- # @see AttributeMetadata#searchable?
158
- def searchable_attributes
159
- @srchbl_attrs ||= attribute_filter { |attr_md| attr_md.searchable? }
160
- end
161
-
162
- # @return [<Symbol>] the create/update cascaded domain attributes
163
- # @see AttributeMetadata#cascaded?
164
- def cascaded_attributes
165
- @cscd_attrs ||= domain_attributes.compose { |attr_md| attr_md.cascaded? }
166
- end
167
-
168
- # @return [<Symbol>] the {#cascaded_attributes} which are saved with a proxy
169
- # using the dependent saver_proxy method
170
- def proxied_save_template_attributes
171
- @px_cscd_attrs ||= save_template_attributes.compose { |attr_md| attr_md.proxied_save? }
172
- end
173
-
174
- # @return [<Symbol>] the {#cascaded_attributes} which do not have a
175
- # #{AttributeMetadata#proxied_save?}
176
- def unproxied_save_template_attributes
177
- @unpx_sv_tmpl_attrs ||= save_template_attributes.compose { |attr_md| not attr_md.proxied_save? }
178
- end
179
-
180
- # @return [<Symbol>] the {#domain_attributes} to {AttributeMetadata#include_in_save_template?}
181
- def save_template_attributes
182
- @sv_tmpl_attrs ||= domain_attributes.compose { |attr_md| attr_md.include_in_save_template? }
183
- end
184
-
185
- # Returns the physical or auto-generated logical dependent attributes that can
186
- # be copied from a save result to the given save argument object.
187
- #
188
- # @return [<Symbol>] the attributes that can be copied from a save result to a
189
- # save argument object
190
- # @see AttributeMetadata#autogenerated?
191
- def copyable_saved_attributes
192
- @cp_sv_attrs ||= dependent_attributes.compose { |attr_md| attr_md.autogenerated? or not attr_md.logical? }
193
- end
194
-
195
- # Returns the subject class's required attributes, determined as follows:
196
- # * An attribute marked with the :mandatory flag is mandatory.
197
- # * An attribute marked with the :optional or :autogenerated flag is not mandatory.
198
- # * Otherwise, A secondary key or owner attribute is mandatory.
199
- def mandatory_attributes
200
- @mndtry_attrs ||= collect_mandatory_attributes
201
- end
202
-
203
- # @return [<Symbol>] the attributes which are {AttributeMetadata#creatable?}
204
- def creatable_attributes
205
- @cr_attrs ||= attribute_filter { |attr_md| attr_md.creatable? }
206
- end
207
-
208
- # @return [<Symbol>] the attributes which are {AttributeMetadata#updatable?}
209
- def updatable_attributes
210
- @upd_attrs ||= attribute_filter { |attr_md| attr_md.updatable? }
211
- end
212
-
213
- def fetched_dependent_attributes
214
- @ftchd_dep_attrs ||= (fetched_domain_attributes & dependent_attributes).to_a
215
- end
216
-
217
- # @return [<Symbol>] the independent saved attributes
218
- # @see AttributeMetadata#independent?
219
- # @see AttributeMetadata#saved?
220
- def saved_independent_attributes
221
- @svd_ind_attrs ||= attribute_filter { |attr_md| attr_md.independent? and attr_md.saved? }
222
- end
223
-
224
- # @return [<Symbol>] the domain {AttributeMetadata#saved?} attributes
225
- def saved_domain_attributes
226
- @svd_dom_attrs ||= domain_attributes.compose { |attr_md| attr_md.saved? }
227
- end
228
-
229
- # @return [<Symbol>] the non-domain {AttributeMetadata#saved?} attributes
230
- def saved_nondomain_attributes
231
- @svd_nondom_attrs ||= nondomain_attributes.compose { |attr_md| attr_md.saved? }
232
- end
233
-
234
- # @return [<Symbol>] the {AttributeMetadata#volatile?} {#nondomain_attributes}
235
- def volatile_nondomain_attributes
236
- @vlt_nondom_attrs ||= nondomain_attributes.compose { |attr_md| attr_md.volatile? }
237
- end
238
-
239
- # @return [<Symbol>] the domain {#creatable_attributes}
240
- def creatable_domain_attributes
241
- @cr_dom_attrs ||= domain_attributes.compose { |attr_md| attr_md.creatable? }
242
- end
243
-
244
- # @return [<Symbol>] the domain {#updatable_attributes}
245
- def updatable_domain_attributes
246
- @upd_dom_attrs ||= domain_attributes.compose { |attr_md| attr_md.updatable? }
247
- end
248
-
249
- # @return [<Symbol>] the domain attributes whose referents must exist before an instance of this
250
- # metadata's subject classcan be created
251
- # @see AttributeMetadata#storable_prerequisite?
252
- def storable_prerequisite_attributes
253
- @stbl_prereq_dom_attrs ||= attribute_filter { |attr_md| attr_md.storable_prerequisite? }
254
- end
255
-
256
- # @return [<Symbol>] the attributes which are populated from the database
257
- # @see AttributeMetadata#fetched?
258
- def fetched_attributes
259
- @ftchd_attrs ||= attribute_filter { |attr_md| attr_md.fetched? }
260
- end
261
-
262
- # Returns the domain attributes which are populated in a query on the given fetched instance of
263
- # this metadata's subject class. The domain attribute is fetched if it satisfies the following
264
- # conditions:
265
- # * the attribute is a dependent attribute or of abstract domain type
266
- # * the attribute is not specified as unfetched in the configuration
267
- #
268
- # @return [<Symbol>] the attributes which are {AttributeMetadata#fetched?}
269
- def fetched_domain_attributes
270
- @ftchd_dom_attrs ||= domain_attributes.compose { |attr_md| attr_md.fetched? }
271
- end
272
-
273
- #@return [<Symbol>] the #domain_attributes which are not #fetched_domain_attributes
274
- def unfetched_attributes
275
- @unftchd_attrs ||= domain_attributes.compose { |attr_md| not attr_md.fetched? }
276
- end
277
-
278
- alias :toxic_attributes :unfetched_attributes
279
-
280
- # @return [<Symbol>] the Java property non-abstract {#unfetched_attributes}
281
- def loadable_attributes
282
- @ldbl_attrs ||= unfetched_attributes.compose { |attr_md| attr_md.java_property? and not attr_md.type.abstract? }
283
- end
284
-
285
- # @param [Symbol] attribute the attribute to check
286
- # @return [Boolean] whether attribute return type is a domain object or collection thereof
287
- def domain_attribute?(attribute)
288
- attribute_metadata(attribute).domain?
289
- end
290
-
291
- # @param [Symbol] attribute the attribute to check
292
- # @return [Boolean] whether attribute is not a domain attribute
293
- def nondomain_attribute?(attribute)
294
- not domain_attribute?(attribute)
295
- end
296
-
297
- # @param [Symbol] attribute the attribute to check
298
- # @return [Boolean] whether attribute is an instance of a Java domain class
299
- def collection_attribute?(attribute)
300
- attribute_metadata(attribute).collection?
301
- end
302
-
303
- protected
304
-
305
- # @return [{Symbol => AttributeMetadata}] the attribute => metadata hash
306
- def attribute_metadata_hash
307
- @attr_md_hash
308
- end
309
-
310
- # @return [{Symbol => Symbol}] the attribute alias => standard hash
311
- def alias_standard_attribute_hash
312
- @alias_std_attr_map
313
- end
314
-
315
- private
316
-
317
- IDENTIFIER_ATTR_ARRAY = [:identifier]
318
-
319
- # A filter on the standard attribute symbol => metadata hash that yields
320
- # each attribute which satisfies the attribute metadata condition.
321
- class Filter
322
- include Enumerable
323
-
324
- # @param [{Symbol => AttributeMetadata}] hash the attribute symbol => metadata hash
325
- # @yield [attr_md] condition which determines whether the attribute is selected
326
- # @yieldparam [AttributeMetadata] the metadata for the standard attribute
327
- def initialize(klass, hash, &filter)
328
- raise ArgumentError.new("#{klass.qp} attribute filter missing hash argument") if hash.nil?
329
- raise ArgumentError.new("#{klass.qp} attribute filter missing filter block") unless block_given?
330
- @hash = hash
331
- @filter = filter
332
- end
333
-
334
- # @yield [attribute, attr_md] the block to apply to the filtered attribute metadata and attribute
335
- # @yieldparam [Symbol] attribute the attribute
336
- # @yieldparam [AttributeMetadata] attr_md the attribute metadata
337
- def each_pair
338
- @hash.each { |attr, attr_md| yield(attr, attr_md) if @filter.call(attr_md) }
339
- end
340
-
341
- # @yield [attribute] block to apply to each filtered attribute
342
- # @yieldparam [Symbol] the attribute which satisfies the filter condition
343
- def each_attribute(&block)
344
- each_pair { |attr, attr_md| yield(attr) }
345
- end
346
-
347
- alias :each :each_attribute
348
-
349
- # @yield [attr_md] the block to apply to the filtered attribute metadata
350
- # @yieldparam [AttributeMetadata] attr_md the attribute metadata
351
- def each_metadata
352
- each_pair { |attr, attr_md| yield(attr_md) }
353
- end
354
-
355
- # @yield [attribute] the block to apply to the attribute
356
- # @yieldparam [Symbol] attribute the attribute
357
- # @return [AttributeMetadata] the first attribute metadata satisfies the block
358
- def detect_metadata
359
- each_pair { |attr, attr_md| return attr_md if yield(attr) }
360
- nil
361
- end
362
-
363
- # @yield [attr_md] the block to apply to the attribute metadata
364
- # @yieldparam [AttributeMetadata] attr_md the attribute metadata
365
- # @return [Symbol] the first attribute whose metadata satisfies the block
366
- def detect_with_metadata
367
- each_pair { |attr, attr_md| return attr if yield(attr_md) }
368
- nil
369
- end
370
-
371
- # @yield [attr_md] the attribute selection filter
372
- # @yieldparam [AttributeMetadata] attr_md the candidate attribute metadata
373
- # @return [Filter] a new Filter which applies the filter block given to this
374
- # method with the AttributeMetadata enumerated by this filter
375
- def compose
376
- Filter.new(self, @hash) { |attr_md| @filter.call(attr_md) and yield(attr_md) }
377
- end
378
- end
379
-
380
- # Returns an Enumerable on this Resource class's attributes which iterates on each attribute whose
381
- # corresponding AttributeMetadata satisfies the given filter block.
382
- #
383
- # @yield [attr_md] the attribute selector
384
- # @yieldparam [AttributeMetadata] attr_md the candidate attribute
385
- def attribute_filter(&filter)
386
- Filter.new(self, @attr_md_hash, &filter)
387
- end
388
-
389
- # Initializes the attribute meta-data structures.
390
- def init_attributes
391
- @local_std_attr_hash = {}
392
- @alias_std_attr_map = append_ancestor_enum(@local_std_attr_hash) { |par| par.alias_standard_attribute_hash }
393
- @local_attr_md_hash = {}
394
- @attr_md_hash = append_ancestor_enum(@local_attr_md_hash) { |par| par.attribute_metadata_hash }
395
- @attributes = Enumerable::Enumerator.new(@attr_md_hash, :each_key)
396
- @local_mndty_attrs = Set.new
397
- @local_defaults = {}
398
- @defaults = append_ancestor_enum(@local_defaults) { |par| par.defaults }
399
- end
400
-
401
-
402
- # Creates the given attribute alias. Not that unlike {Class#alias_attribute}, this method creates a new
403
- # alias reader (writer) method which delegates to the attribute reader (writer, resp.) rather than aliasing
404
- # the existing reader or writer method. This allows the alias to pick up run-time redefinitions of the
405
- # aliased reader and writer.
406
- #
407
- # @param [Symbol] aliaz the attribute alias
408
- # @param [Symbol] attribute the attribute to alias
409
- def alias_attribute(aliaz, attribute)
410
- add_attribute_aliases(aliaz => attribute)
411
- end
412
-
413
- # Creates the given aliases to attributes.
414
- #
415
- # @param [{Symbol => Symbol}] hash the alias => attribute hash
416
- # @see #attribute_alias
417
- def add_attribute_aliases(hash)
418
- hash.each { |aliaz, attr| delegate_to_attribute(aliaz, attr) }
419
- end
420
-
421
- # Sets this class's secondary key attributes to the given attributes.
422
- # If attributes is set to nil, then the secondary key is cleared.
423
- def set_secondary_key_attributes(*attributes)
424
- attributes.compact!
425
- @scndy_key_attrs = attributes.map { |attr| standard_attribute(attr) }
426
- end
427
-
428
- # Sets this class's alternate key attributes to the given attributes.
429
- # If attributes is set to nil, then the alternate key is cleared.
430
- def set_alternate_key_attributes(*attributes)
431
- attributes.compact!
432
- @alt_key_attrs = attributes.map { |attr| standard_attribute(attr) }
433
- end
434
-
435
- # Sets the given attribute type to klass. If attribute is defined in a superclass,
436
- # then klass must be a subclass of the superclass attribute type.
437
- #
438
- # Raises ArgumentError if klass is incompatible with the current attribute type.
439
- def set_attribute_type(attribute, klass)
440
- attr_md = attribute_metadata(attribute)
441
- # If this class is the declarer, then simply set the attribute type.
442
- # Otherwise, if the attribute type is unspecified or is a superclass of the given class,
443
- # then make a new attribute metadata for this class.
444
- if attr_md.declarer == self then
445
- logger.debug { "Set #{qp}.#{attribute} type to #{klass.qp}." }
446
- attr_md.type = klass
447
- elsif attr_md.type.nil? or klass < attr_md.type then
448
- new_attr_md = attr_md.restrict_type(self, klass)
449
- logger.debug { "Restricted #{attr_md.declarer.qp}.#{attribute}(#{attr_md.type.qp}) to #{qp} with return type #{klass.qp}." }
450
- add_attribute_metadata(new_attr_md)
451
- elsif klass != attr_md.type then
452
- raise ArgumentError.new("Cannot reset #{qp}.#{attribute} type #{attr_md.type} to incompatible #{klass.qp}")
453
- end
454
- end
455
-
456
- def add_attribute_defaults(hash)
457
- hash.each { |attr, value| @local_defaults[standard_attribute(attr)] = value }
458
- end
459
-
460
- def add_mandatory_attributes(*attributes)
461
- attributes.each { |attr| @local_mndty_attrs << standard_attribute(attr) }
462
- end
463
-
464
- # Marks the given attribute with flags supported by {AttributeMetadata#qualify}.
465
- def qualify_attribute(attribute, *flags)
466
- attr_md = attribute_metadata(attribute)
467
- if attr_md.declarer == self then
468
- attr_md.qualify(*flags)
469
- else
470
- logger.debug { "Restricting #{attr_md.declarer.qp}.#{attribute} to #{qp} with additional flags #{flags.to_series}" }
471
- new_attr_md = attr_md.restrict_flags(self, *flags)
472
- add_attribute_metadata(new_attr_md)
473
- end
474
- end
475
-
476
- # Removes the given attribute from this Resource.
477
- # An attribute declared in a superclass Resource is hidden from this Resource but retained in
478
- # the declaring Resource.
479
- def remove_attribute(attribute)
480
- std_attr = standard_attribute(attribute)
481
- # if the attribute is local, then delete it, otherwise filter out the superclass attribute
482
- if @local_attr_md_hash.delete(std_attr) then
483
- @local_mndty_attrs.delete(std_attr)
484
- @local_std_attr_hash.delete_if { |aliaz, attr| attr == std_attr }
485
- else
486
- @attr_md_hash = @attr_md_hash.filter_on_key { |attr| attr != attribute }
487
- @attributes = Enumerable::Enumerator.new(@attr_md_hash, :each_key)
488
- @alias_std_attr_map = @alias_std_attr_map.filter_on_key { |attr| attr != attribute }
489
- end
490
- end
491
-
492
- def add_attribute_metadata(attr_md)
493
- symbol = attr_md.to_sym
494
- @local_attr_md_hash[symbol] = attr_md
495
- # map the attribute symbol to itself in the alias map
496
- @local_std_attr_hash[symbol] = symbol
497
- end
498
-
499
- # Records that the given aliaz aliases a standard attribute.
500
- def add_alias(aliaz, attribute)
501
- std_attr = standard_attribute(attribute)
502
- raise ArgumentError.new("#{self} attribute not found: #{attribute}") if std_attr.nil?
503
- @local_std_attr_hash[aliaz.to_sym] = std_attr
504
- end
505
-
506
- # Appends to the given enumerable the result of evaluating the block given to this method
507
- # on the superclass, if the superclass is a {Resource}.
508
- #
509
- # @param [Enumerable] enum the base collection
510
- # @return [Enumerable] the {Enumerable#union} of the base collection with the superclass
511
- # collection, if applicable
512
- def append_ancestor_enum(enum)
513
- return enum unless superclass < Resource
514
- anc_enum = yield superclass
515
- if anc_enum.nil? then raise MetadataError.new("#{qp} superclass #{superclass.qp} does not have required metadata") end
516
- enum.union(anc_enum)
517
- end
518
-
519
- def each_attribute_metadata(&block)
520
- @attr_md_hash.each_value(&block)
521
- end
522
-
523
- # Collects the {AttributeMetadata#fetched_dependent?} and {AttributeMetadata#fetched_independent?}
524
- # standard domain attributes.
525
- #
526
- # @return [<Symbol>] the fetched attributes
527
- def collect_default_fetched_domain_attributes
528
- attribute_filter do |attr_md|
529
- if attr_md.domain? then
530
- attr_md.dependent? ? fetched_dependent?(attr_md) : fetched_independent?(attr_md)
531
- end
532
- end
533
- end
534
-
535
- # Merges the secondary key, owner and additional mandatory attributes defined in the properties.
536
- #
537
- # @see #mandatory_attributes
538
- def collect_mandatory_attributes
539
- mandatory = Set.new
540
- # add the secondary key
541
- mandatory.merge(secondary_key_attributes)
542
- # add the owner attribute, if any
543
- oattr = mandatory_owner_attribute
544
- mandatory << oattr if oattr
545
- # remove autogenerated or optional attributes
546
- mandatory.delete_if { |attr| attribute_metadata(attr).autogenerated? or attribute_metadata(attr).optional? }
547
- @local_mndty_attrs.merge!(mandatory)
548
- append_ancestor_enum(@local_mndty_attrs) { |par| par.mandatory_attributes }
549
- end
550
-
551
- # @return [Symbol, nil] the unique non-self-referential owner attribute, if one exists
552
- def mandatory_owner_attribute
553
- attr = owner_attribute || return
554
- attr_md = attribute_metadata(attr)
555
- attr if attr_md.java_property? and attr_md.type != self
556
- end
557
-
558
- # Raises a NameError. Domain classes can override this method to dynamically create a new reference attribute.
559
- #
560
- # @raise [NameError] always
561
- def attribute_missing(attribute)
562
- raise NameError.new("#{name.demodulize} attribute not found: #{attribute}")
563
- end
564
- end
565
- end