caruby-core 1.4.7 → 1.4.9
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +11 -0
- data/README.md +1 -1
- data/lib/caruby/cli/command.rb +27 -3
- data/lib/caruby/csv/csv_mapper.rb +2 -0
- data/lib/caruby/csv/csvio.rb +187 -169
- data/lib/caruby/database.rb +33 -16
- data/lib/caruby/database/lazy_loader.rb +23 -23
- data/lib/caruby/database/persistable.rb +32 -18
- data/lib/caruby/database/persistence_service.rb +20 -7
- data/lib/caruby/database/reader.rb +22 -21
- data/lib/caruby/database/search_template_builder.rb +7 -9
- data/lib/caruby/database/sql_executor.rb +52 -27
- data/lib/caruby/database/store_template_builder.rb +18 -13
- data/lib/caruby/database/writer.rb +107 -44
- data/lib/caruby/domain/attribute_metadata.rb +35 -25
- data/lib/caruby/domain/java_attribute_metadata.rb +43 -20
- data/lib/caruby/domain/merge.rb +9 -5
- data/lib/caruby/domain/reference_visitor.rb +4 -3
- data/lib/caruby/domain/resource_attributes.rb +52 -12
- data/lib/caruby/domain/resource_dependency.rb +129 -42
- data/lib/caruby/domain/resource_introspection.rb +1 -1
- data/lib/caruby/domain/resource_inverse.rb +20 -3
- data/lib/caruby/domain/resource_metadata.rb +20 -4
- data/lib/caruby/domain/resource_module.rb +190 -124
- data/lib/caruby/import/java.rb +39 -19
- data/lib/caruby/migration/migratable.rb +31 -6
- data/lib/caruby/migration/migrator.rb +126 -40
- data/lib/caruby/migration/uniquify.rb +0 -1
- data/lib/caruby/resource.rb +28 -5
- data/lib/caruby/util/attribute_path.rb +0 -2
- data/lib/caruby/util/class.rb +8 -5
- data/lib/caruby/util/collection.rb +5 -3
- data/lib/caruby/util/domain_extent.rb +0 -3
- data/lib/caruby/util/options.rb +10 -9
- data/lib/caruby/util/person.rb +41 -12
- data/lib/caruby/util/pretty_print.rb +1 -1
- data/lib/caruby/util/validation.rb +0 -28
- data/lib/caruby/version.rb +1 -1
- data/test/lib/caruby/import/java_test.rb +26 -9
- data/test/lib/caruby/migration/test_case.rb +103 -0
- data/test/lib/caruby/test_case.rb +231 -0
- data/test/lib/caruby/util/class_test.rb +2 -2
- data/test/lib/caruby/util/visitor_test.rb +3 -2
- data/test/lib/examples/galena/clinical_trials/migration/participant_test.rb +28 -0
- data/test/lib/examples/galena/clinical_trials/migration/test_case.rb +40 -0
- metadata +195 -170
- data/lib/caruby/domain/attribute_initializer.rb +0 -16
- data/test/lib/caruby/util/validation_test.rb +0 -14
@@ -11,18 +11,18 @@ module CaRuby
|
|
11
11
|
# * reader method symbol
|
12
12
|
# * writer method symbol
|
13
13
|
class AttributeMetadata
|
14
|
-
# The supported attribute qualifier flags.
|
14
|
+
# The supported attribute qualifier flags. See the complementary methods for an explanation of
|
15
|
+
# the flag option, e.g. {#autogenerated?} for the +:autogenerated+ flag.
|
15
16
|
SUPPORTED_FLAGS = [
|
16
|
-
:autogenerated, :collection, :dependent, :derived, :logical, :disjoint,
|
17
|
-
:no_cascade_update_to_create, :saved, :unsaved, :optional, :fetched, :unfetched,
|
18
|
-
:create_only, :update_only, :unidirectional, :volatile].to_set
|
17
|
+
:autogenerated, :autogenerated_on_update, :collection, :dependent, :derived, :logical, :disjoint,
|
18
|
+
:owner, :cascaded, :no_cascade_update_to_create, :saved, :unsaved, :optional, :fetched, :unfetched,
|
19
|
+
:include_in_save_template, :saved_fetch, :create_only, :update_only, :unidirectional, :volatile].to_set
|
19
20
|
|
20
21
|
# @return [(Symbol, Symbol)] the standard attribute reader and writer methods
|
21
22
|
attr_reader :accessors
|
22
23
|
|
23
24
|
# @return [Class] the declaring class
|
24
25
|
attr_accessor :declarer
|
25
|
-
protected :declarer=
|
26
26
|
|
27
27
|
# @return [Class] the return type
|
28
28
|
attr_reader :type
|
@@ -88,7 +88,7 @@ module CaRuby
|
|
88
88
|
@type = klass
|
89
89
|
if @inv_md then
|
90
90
|
self.inverse = @inv_md.to_sym
|
91
|
-
logger.debug { "Reset #{@declarer.qp}.#{self} inverse from #{@inv_md.type}.#{@inv_md} to #{klass}#{inv_md}." }
|
91
|
+
logger.debug { "Reset #{@declarer.qp}.#{self} inverse from #{@inv_md.type}.#{@inv_md} to #{klass}#{@inv_md}." }
|
92
92
|
end
|
93
93
|
end
|
94
94
|
|
@@ -150,9 +150,8 @@ module CaRuby
|
|
150
150
|
# if this attribute is disjoint, then so is the inverse.
|
151
151
|
@inv_md.qualify(:disjoint) if disjoint?
|
152
152
|
end
|
153
|
-
|
154
153
|
# propagate to restrictions
|
155
|
-
if @restrictions then @restrictions.each { |attr_md| attr_md.restrict_inverse_type } end
|
154
|
+
if @restrictions then @restrictions.each { |attr_md| attr_md.restrict_inverse_type(@inv_md) } end
|
156
155
|
end
|
157
156
|
|
158
157
|
# @return [AttributeMetadata, nil] the metadata for the {#inverse} attribute, if any
|
@@ -233,11 +232,21 @@ module CaRuby
|
|
233
232
|
end
|
234
233
|
|
235
234
|
# Returns whether the subject attribute is a dependent whose value is automatically generated
|
236
|
-
# with place-holder domain objects when the parent is created.
|
235
|
+
# with place-holder domain objects when the parent is created. An attribute is auto-generated
|
236
|
+
# if the +:autogenerate+ or the +:autogenerated_on_update+ flag is set.
|
237
237
|
#
|
238
238
|
# @return [Boolean] whether the attribute is auto-generated
|
239
239
|
def autogenerated?
|
240
|
-
@flags.include?(:autogenerated)
|
240
|
+
@flags.include?(:autogenerated) or @flags.include?(:autogenerated_on_update)
|
241
|
+
end
|
242
|
+
|
243
|
+
# Returns whether the the subject attribute is #{autogenerated?} for create. An attribute is
|
244
|
+
# auto-generated for create if the +:autogenerate+ flag is set and the
|
245
|
+
# +:autogenerated_on_update+ flag is not set.
|
246
|
+
#
|
247
|
+
# @return [Boolean] whether the attribute is auto-generated on create
|
248
|
+
def autogenerated_on_create?
|
249
|
+
@flags.include?(:autogenerated) and not @flags.include?(:autogenerated_on_update)
|
241
250
|
end
|
242
251
|
|
243
252
|
# Returns whether this attribute must be fetched when a declarer instance is saved.
|
@@ -248,7 +257,7 @@ module CaRuby
|
|
248
257
|
# @return [Boolean] whether the subject attribute must be refetched in order to reflect
|
249
258
|
# the database content
|
250
259
|
def saved_fetch?
|
251
|
-
autogenerated? or (cascaded? and @flags.include?(:unfetched))
|
260
|
+
@flags.include?(:saved_fetch) or autogenerated? or (cascaded? and @flags.include?(:unfetched))
|
252
261
|
end
|
253
262
|
|
254
263
|
# Returns whether the subject attribute is a dependent whose owner does not automatically
|
@@ -300,23 +309,25 @@ module CaRuby
|
|
300
309
|
logical? or (dependent? and not creatable?)
|
301
310
|
end
|
302
311
|
|
303
|
-
# @return whether this attribute is saved in a update operation
|
304
|
-
#
|
305
312
|
# A Java attribute is updatable if all of the following conditions hold:
|
306
313
|
# * the attribute is {#saved?}
|
307
314
|
# * the attribute :create_only flag is not set
|
315
|
+
#
|
316
|
+
# @return [Boolean] whether this attribute is saved in a update operation
|
308
317
|
def updatable?
|
309
318
|
saved? and not @flags.include?(:create_only)
|
310
319
|
end
|
311
320
|
|
312
|
-
# @return whether
|
313
|
-
# A cascaded attribute determines where to prune a database create or update object graph.
|
314
|
-
#
|
315
|
-
# An attribute is cascaded if it is a physical dependent or the :cascaded flag is set.
|
321
|
+
# @return [Boolean] whether the attribute is a physical dependent or the +:cascaded+ flag is set
|
316
322
|
def cascaded?
|
317
323
|
(dependent? and not logical?) or @flags.include?(:cascaded)
|
318
324
|
end
|
319
325
|
|
326
|
+
# @return whether this attribute is {#cascaded?} or marked with the +:include_in_save_template+ flag
|
327
|
+
def include_in_save_template?
|
328
|
+
cascaded? or @flags.include?(:include_in_save_template)
|
329
|
+
end
|
330
|
+
|
320
331
|
# Returns whether this attribute is #{#cascaded} and cascades a parent update to a child
|
321
332
|
# create. This corresponds to the Hibernate +save-update+ cascade style but not the Hibernate
|
322
333
|
# +all+ cascade style.
|
@@ -327,6 +338,8 @@ module CaRuby
|
|
327
338
|
# Exception: gov.nih.nci.system.applicationservice.ApplicationException:
|
328
339
|
# The given object has a null identifier:
|
329
340
|
# followed by the attribute type name.
|
341
|
+
#
|
342
|
+
# @return [Boolean] whether the attribute cascades to crate when the owner is updated
|
330
343
|
def cascade_update_to_create?
|
331
344
|
cascaded? and not @flags.include?(:no_cascade_update_to_create)
|
332
345
|
end
|
@@ -412,12 +425,6 @@ module CaRuby
|
|
412
425
|
@flags.include?(:disjoint)
|
413
426
|
end
|
414
427
|
|
415
|
-
# @param [Symbol] attribute the attribute to check
|
416
|
-
# @return [Boolean] whether the attribute is part of a 1:1 non-dependency association
|
417
|
-
def one_to_one_bidirectional_independent?
|
418
|
-
independent? and not owner? and not collection? and @inv_md and not @inv_md.collection?
|
419
|
-
end
|
420
|
-
|
421
428
|
# @return [Boolean] whether this attribute is a dependent which does not have a Java inverse owner attribute
|
422
429
|
def unidirectional_java_dependent?
|
423
430
|
# TODO - can this be relaxed to java_unidirectional? i.e. eliminate dependent filter
|
@@ -453,8 +460,11 @@ module CaRuby
|
|
453
460
|
|
454
461
|
# If there is a current inverse which can be restricted to an attribute in the scope of
|
455
462
|
# this metadata's restricted type, then reset the inverse to that attribute.
|
456
|
-
|
457
|
-
|
463
|
+
#
|
464
|
+
# @param [AttributeMetadata, nil] inv_md the inverse attribute to restrict
|
465
|
+
def restrict_inverse_type(inv_md=nil)
|
466
|
+
# set the inverse, if necessary
|
467
|
+
@inv_md ||= inv_md || return
|
458
468
|
# the current inverse
|
459
469
|
attr = inverse
|
460
470
|
# the restricted type's metadata for the current inverse
|
@@ -55,6 +55,16 @@ module CaRuby
|
|
55
55
|
qualify(:collection) if collection_java_class?
|
56
56
|
end
|
57
57
|
|
58
|
+
# @return [Symbol] the JRuby wrapper method for the Java property reader
|
59
|
+
def property_reader
|
60
|
+
property_accessors.first
|
61
|
+
end
|
62
|
+
|
63
|
+
# @return [Symbol] the JRuby wrapper method for the Java property writer
|
64
|
+
def property_writer
|
65
|
+
property_accessors.last
|
66
|
+
end
|
67
|
+
|
58
68
|
def type
|
59
69
|
@type ||= infer_type
|
60
70
|
end
|
@@ -90,12 +100,15 @@ module CaRuby
|
|
90
100
|
end
|
91
101
|
end
|
92
102
|
|
93
|
-
#
|
103
|
+
# @return [Boolean] whether this property's Java type is +Iterable+
|
94
104
|
def collection_java_class?
|
95
|
-
|
105
|
+
# the Java property type
|
106
|
+
ptype = @property_descriptor.property_type
|
107
|
+
# Test whether the corresponding JRuby wrapper class or module is an Iterable.
|
108
|
+
Class.to_ruby(ptype) < Java::JavaLang::Iterable
|
96
109
|
end
|
97
110
|
|
98
|
-
#
|
111
|
+
# @return [Class] the type for the specified klass property descriptor pd as described in {#initialize}
|
99
112
|
def infer_type
|
100
113
|
collection_java_class? ? infer_collection_type : infer_non_collection_type
|
101
114
|
end
|
@@ -103,40 +116,47 @@ module CaRuby
|
|
103
116
|
# Returns the domain type for this attribute's Java Collection property descriptor.
|
104
117
|
# If the property type is parameterized by a single domain class, then that generic type argument is the domain type.
|
105
118
|
# Otherwise, the type is inferred from the property name as described in {#infer_collection_type_from_name}.
|
119
|
+
#
|
120
|
+
# @return [Class] this property's Ruby type
|
106
121
|
def infer_collection_type
|
107
122
|
generic_parameter_type or infer_collection_type_from_name or Java::JavaLang::Object
|
108
123
|
end
|
109
124
|
|
125
|
+
# @return [Class] this property's Ruby type
|
110
126
|
def infer_non_collection_type
|
111
|
-
|
112
|
-
if
|
113
|
-
Class.to_ruby(
|
127
|
+
jtype = @property_descriptor.property_type
|
128
|
+
if jtype.primitive then
|
129
|
+
Class.to_ruby(jtype)
|
114
130
|
else
|
115
|
-
@declarer.domain_module.domain_type_with_name(
|
131
|
+
@declarer.domain_module.domain_type_with_name(jtype.name) or Class.to_ruby(jtype)
|
116
132
|
end
|
117
133
|
end
|
118
134
|
|
135
|
+
# @return [Class, nil] the Ruby type as determined by the configuration, if any
|
119
136
|
def configured_type
|
120
137
|
name = @declarer.class.configuration.domain_type_name(to_sym) || return
|
121
138
|
@declarer.domain_module.domain_type_with_name(name) or java_to_ruby_class(name)
|
122
139
|
end
|
123
140
|
|
124
|
-
#
|
141
|
+
# @return [Class, nil] the domain type of this attribute's property descriptor Collection generic
|
142
|
+
# type argument, or nil if none
|
125
143
|
def generic_parameter_type
|
126
144
|
method = @property_descriptor.readMethod || return
|
127
|
-
|
128
|
-
return unless Java::JavaLangReflect::ParameterizedType ===
|
129
|
-
|
130
|
-
return unless
|
131
|
-
|
132
|
-
klass = java_to_ruby_class(
|
133
|
-
logger.debug { "Inferred #{declarer.qp} #{self} domain type #{klass.qp} from generic parameter #{
|
145
|
+
gtype = method.genericReturnType
|
146
|
+
return unless Java::JavaLangReflect::ParameterizedType === gtype
|
147
|
+
atypes = gtype.actualTypeArguments
|
148
|
+
return unless atypes.size == 1
|
149
|
+
atype = atypes[0]
|
150
|
+
klass = java_to_ruby_class(atype)
|
151
|
+
logger.debug { "Inferred #{declarer.qp} #{self} domain type #{klass.qp} from generic parameter #{atype.name}." } if klass
|
134
152
|
klass
|
135
153
|
end
|
136
154
|
|
137
|
-
|
138
|
-
|
139
|
-
|
155
|
+
# @param [Class, String] jtype the Java class or class name
|
156
|
+
# @return [Class] the corresponding Ruby type
|
157
|
+
def java_to_ruby_class(jtype)
|
158
|
+
name = String === jtype ? jtype : jtype.name
|
159
|
+
@declarer.domain_module.domain_type_with_name(name) or Class.to_ruby(name)
|
140
160
|
end
|
141
161
|
|
142
162
|
# Returns the domain type for this attribute's collection Java property descriptor name.
|
@@ -148,13 +168,16 @@ module CaRuby
|
|
148
168
|
# is inferred as +DistributionProtocol+ by stripping the +Collection+ suffix,
|
149
169
|
# capitalizing the prefix and looking for a class of that name in this classifier's
|
150
170
|
# domain_module.
|
171
|
+
#
|
172
|
+
# @return [Class] the collection item type
|
151
173
|
def infer_collection_type_from_name
|
152
174
|
prop_name = @property_descriptor.name
|
153
175
|
index = prop_name =~ /Collection$/
|
154
176
|
index ||= prop_name.length
|
155
177
|
prefix = prop_name[0...1].upcase + prop_name[1...index]
|
156
|
-
|
157
|
-
|
178
|
+
klass = @declarer.domain_module.domain_type_with_name(prefix)
|
179
|
+
if klass then logger.debug { "Inferred #{declarer.qp} #{self} collection domain type #{klass.qp} from the attribute name." } end
|
180
|
+
klass
|
158
181
|
end
|
159
182
|
end
|
160
183
|
end
|
data/lib/caruby/domain/merge.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'caruby/util/validation'
|
2
|
+
|
1
3
|
module CaRuby
|
2
4
|
# A Mergeable supports merging {Resource} attribute values.
|
3
5
|
module Mergeable
|
@@ -74,7 +76,7 @@ module CaRuby
|
|
74
76
|
# @return the merged attribute value
|
75
77
|
def merge_attribute(attribute, newval, matches=nil)
|
76
78
|
# the previous value
|
77
|
-
oldval = send(attribute)
|
79
|
+
oldval = send(attribute)
|
78
80
|
# If nothing to merge or a block can take over, then bail.
|
79
81
|
if newval.nil? or mergeable__equal?(oldval, newval) then
|
80
82
|
return oldval
|
@@ -132,10 +134,12 @@ module CaRuby
|
|
132
134
|
if matches && matches.has_key?(src) then
|
133
135
|
# the source match
|
134
136
|
tgt = matches[src]
|
135
|
-
if
|
136
|
-
|
137
|
-
|
138
|
-
|
137
|
+
if tgt then
|
138
|
+
if oldval.include?(tgt) then
|
139
|
+
tgt.merge_attributes(src)
|
140
|
+
else
|
141
|
+
adds << tgt
|
142
|
+
end
|
139
143
|
end
|
140
144
|
else
|
141
145
|
adds << src
|
@@ -2,6 +2,7 @@ require 'enumerator'
|
|
2
2
|
require 'generator'
|
3
3
|
require 'caruby/util/options'
|
4
4
|
require 'caruby/util/collection'
|
5
|
+
require 'caruby/util/validation'
|
5
6
|
require 'caruby/util/visitor'
|
6
7
|
require 'caruby/util/math'
|
7
8
|
|
@@ -184,13 +185,13 @@ module CaRuby
|
|
184
185
|
# @yieldparam [Resource] source the visited source domain object
|
185
186
|
# @yieldparam [Resource] target the domain object which matches the visited source
|
186
187
|
def visit_matched(source)
|
187
|
-
|
188
|
+
tgt = match_for_visited(source)
|
188
189
|
# match the matchable references, if any
|
189
190
|
if @matchable then
|
190
191
|
attrs = @matchable.call(source) - attributes_to_visit(source)
|
191
|
-
attrs.each { |attr| match_reference(source,
|
192
|
+
attrs.each { |attr| match_reference(source, tgt, attr) }
|
192
193
|
end
|
193
|
-
block_given? ? yield(source,
|
194
|
+
block_given? ? yield(source, tgt) : tgt
|
194
195
|
end
|
195
196
|
|
196
197
|
# @param source (see #match_visited)
|
@@ -129,6 +129,8 @@ module CaRuby
|
|
129
129
|
@log_dep_attrs ||= dependent_attributes.compose { |attr_md| attr_md.logical? }
|
130
130
|
end
|
131
131
|
|
132
|
+
# @return [<Symbol>] the unidirectional dependent attributes
|
133
|
+
# @see AttributeMetadata#unidirectional?
|
132
134
|
def unidirectional_dependent_attributes
|
133
135
|
@uni_dep_attrs ||= dependent_attributes.compose { |attr_md| attr_md.unidirectional? }
|
134
136
|
end
|
@@ -165,14 +167,19 @@ module CaRuby
|
|
165
167
|
|
166
168
|
# @return [<Symbol>] the {#cascaded_attributes} which are saved with a proxy
|
167
169
|
# using the dependent saver_proxy method
|
168
|
-
def
|
169
|
-
@px_cscd_attrs ||=
|
170
|
+
def proxied_save_template_attributes
|
171
|
+
@px_cscd_attrs ||= save_template_attributes.compose { |attr_md| attr_md.proxied_save? }
|
170
172
|
end
|
171
173
|
|
172
174
|
# @return [<Symbol>] the {#cascaded_attributes} which do not have a
|
173
175
|
# #{AttributeMetadata#proxied_save?}
|
174
|
-
def
|
175
|
-
@
|
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? }
|
176
183
|
end
|
177
184
|
|
178
185
|
# Returns the physical or auto-generated logical dependent attributes that can
|
@@ -317,9 +324,9 @@ module CaRuby
|
|
317
324
|
# @param [{Symbol => AttributeMetadata}] hash the attribute symbol => metadata hash
|
318
325
|
# @yield [attr_md] condition which determines whether the attribute is selected
|
319
326
|
# @yieldparam [AttributeMetadata] the metadata for the standard attribute
|
320
|
-
def initialize(hash, &filter)
|
321
|
-
raise ArgumentError.new("
|
322
|
-
raise ArgumentError.new("
|
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?
|
323
330
|
@hash = hash
|
324
331
|
@filter = filter
|
325
332
|
end
|
@@ -345,6 +352,14 @@ module CaRuby
|
|
345
352
|
each_pair { |attr, attr_md| yield(attr_md) }
|
346
353
|
end
|
347
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
|
+
|
348
363
|
# @yield [attr_md] the block to apply to the attribute metadata
|
349
364
|
# @yieldparam [AttributeMetadata] attr_md the attribute metadata
|
350
365
|
# @return [Symbol] the first attribute whose metadata satisfies the block
|
@@ -358,7 +373,7 @@ module CaRuby
|
|
358
373
|
# @return [Filter] a new Filter which applies the filter block given to this
|
359
374
|
# method with the AttributeMetadata enumerated by this filter
|
360
375
|
def compose
|
361
|
-
Filter.new(@hash) { |attr_md| @filter.call(attr_md) and yield(attr_md) }
|
376
|
+
Filter.new(self, @hash) { |attr_md| @filter.call(attr_md) and yield(attr_md) }
|
362
377
|
end
|
363
378
|
end
|
364
379
|
|
@@ -368,7 +383,7 @@ module CaRuby
|
|
368
383
|
# @yield [attr_md] the attribute selector
|
369
384
|
# @yieldparam [AttributeMetadata] attr_md the candidate attribute
|
370
385
|
def attribute_filter(&filter)
|
371
|
-
Filter.new(@attr_md_hash, &filter)
|
386
|
+
Filter.new(self, @attr_md_hash, &filter)
|
372
387
|
end
|
373
388
|
|
374
389
|
# Initializes the attribute meta-data structures.
|
@@ -382,10 +397,23 @@ module CaRuby
|
|
382
397
|
@local_defaults = {}
|
383
398
|
@defaults = append_ancestor_enum(@local_defaults) { |par| par.defaults }
|
384
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
|
385
412
|
|
386
413
|
# Creates the given aliases to attributes.
|
387
414
|
#
|
388
415
|
# @param [{Symbol => Symbol}] hash the alias => attribute hash
|
416
|
+
# @see #attribute_alias
|
389
417
|
def add_attribute_aliases(hash)
|
390
418
|
hash.each { |aliaz, attr| delegate_to_attribute(aliaz, attr) }
|
391
419
|
end
|
@@ -414,10 +442,11 @@ module CaRuby
|
|
414
442
|
# Otherwise, if the attribute type is unspecified or is a superclass of the given class,
|
415
443
|
# then make a new attribute metadata for this class.
|
416
444
|
if attr_md.declarer == self then
|
445
|
+
logger.debug { "Set #{qp}.#{attribute} type to #{klass.qp}." }
|
417
446
|
attr_md.type = klass
|
418
447
|
elsif attr_md.type.nil? or klass < attr_md.type then
|
419
|
-
logger.debug { "Restricting #{attr_md.declarer.qp}.#{attribute}(#{attr_md.type.qp}) to #{qp} with return type #{klass.qp}..." }
|
420
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}." }
|
421
450
|
add_attribute_metadata(new_attr_md)
|
422
451
|
elsif klass != attr_md.type then
|
423
452
|
raise ArgumentError.new("Cannot reset #{qp}.#{attribute} type #{attr_md.type} to incompatible #{klass.qp}")
|
@@ -481,7 +510,10 @@ module CaRuby
|
|
481
510
|
# @return [Enumerable] the {Enumerable#union} of the base collection with the superclass
|
482
511
|
# collection, if applicable
|
483
512
|
def append_ancestor_enum(enum)
|
484
|
-
|
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)
|
485
517
|
end
|
486
518
|
|
487
519
|
def each_attribute_metadata(&block)
|
@@ -508,12 +540,20 @@ module CaRuby
|
|
508
540
|
# add the secondary key
|
509
541
|
mandatory.merge(secondary_key_attributes)
|
510
542
|
# add the owner attribute, if any
|
511
|
-
|
543
|
+
oattr = mandatory_owner_attribute
|
544
|
+
mandatory << oattr if oattr
|
512
545
|
# remove autogenerated or optional attributes
|
513
546
|
mandatory.delete_if { |attr| attribute_metadata(attr).autogenerated? or attribute_metadata(attr).optional? }
|
514
547
|
@local_mndty_attrs.merge!(mandatory)
|
515
548
|
append_ancestor_enum(@local_mndty_attrs) { |par| par.mandatory_attributes }
|
516
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
|
517
557
|
|
518
558
|
# Raises a NameError. Domain classes can override this method to dynamically create a new reference attribute.
|
519
559
|
#
|