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