caruby-core 1.4.9 → 1.5.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
@@ -7,7 +7,7 @@ module CaRuby
|
|
7
7
|
# the template are determined by the block given to this initializer, filtered as follows:
|
8
8
|
# * If the save operation is a create, then exclude the auto-generated attributes.
|
9
9
|
# * If the visited object has an identifier, then include only those attributes
|
10
|
-
# which {
|
10
|
+
# which {Attribute#cascade_update_to_create?} or have an identifier.
|
11
11
|
#
|
12
12
|
# @param [Database] database the target database
|
13
13
|
# @yield [ref] the required selector block which determines which attributes are copied into the template
|
@@ -21,23 +21,23 @@ module CaRuby
|
|
21
21
|
# the mergeable attributes filter the given block with exclusions
|
22
22
|
@mergeable = Proc.new { |obj| mergeable_attributes(obj, yield(obj)) }
|
23
23
|
# the storable prerequisite reference visitor
|
24
|
-
@prereq_vstr = ReferenceVisitor.new(:prune_cycle) { |ref|
|
24
|
+
@prereq_vstr = ReferenceVisitor.new(:prune_cycle) { |ref| savable_template_attributes(ref) }
|
25
25
|
|
26
26
|
# the savable attributes filter the given block with exclusions
|
27
27
|
savable = Proc.new { |obj| savable_attributes(obj, yield(obj)) }
|
28
28
|
# the domain attributes to copy is determined by the constructor caller
|
29
|
-
# caTissue
|
29
|
+
# @quirk caTissue must copy all of the non-domain attributes rather than just the identifier,
|
30
30
|
# since caTissue auto-generated Specimen update requires the parent collection status. This
|
31
31
|
# is the only known occurrence of a referenced object required non-identifier attribute.
|
32
32
|
# The copy attributes are parameterized by the top-level save target.
|
33
33
|
copier = Proc.new do |src|
|
34
|
-
|
35
|
-
logger.debug { "Store template builder copied #{src.qp} into #{
|
36
|
-
copy_proxied_save_references(src,
|
37
|
-
|
34
|
+
tgt = src.copy
|
35
|
+
logger.debug { "Store template builder copied #{src.qp} into #{tgt}." }
|
36
|
+
copy_proxied_save_references(src, tgt)
|
37
|
+
tgt
|
38
38
|
end
|
39
39
|
# the template copier
|
40
|
-
@copy_vstr = CopyVisitor.new(:mergeable => savable, :copier => copier) { |ref|
|
40
|
+
@copy_vstr = CopyVisitor.new(:mergeable => savable, :copier => copier) { |ref| savable_template_attributes(ref) }
|
41
41
|
end
|
42
42
|
|
43
43
|
# Returns a new domain object which serves as the argument for obj create or update.
|
@@ -47,31 +47,31 @@ module CaRuby
|
|
47
47
|
# to store obj. The template object graph contains only those references which are
|
48
48
|
# essential to the store operation.
|
49
49
|
#
|
50
|
-
# caCORE
|
51
|
-
#
|
52
|
-
#
|
53
|
-
#
|
54
|
-
#
|
55
|
-
#
|
56
|
-
#
|
57
|
-
#
|
50
|
+
# @quirk caCORE +caCORE+ expects the store argument to be carefully prepared prior to
|
51
|
+
# the create or update. build_storable_template culls the target object with a template
|
52
|
+
# which includes only those references which are necessary for the store to succeed.
|
53
|
+
# This template builder ensures that mandatory independent references exist. Cascaded
|
54
|
+
# dependent references are included in the template but are not created before submission
|
55
|
+
# to +caCORE+. These reference attribute distinctions are implicit application rules which
|
56
|
+
# are explicated in the +caRuby+ application domain class definition using Metadata
|
57
|
+
# methods.
|
58
58
|
#
|
59
|
-
# caCORE
|
60
|
-
#
|
61
|
-
#
|
62
|
-
#
|
59
|
+
# @quirk caCORE +caCORE+ create issues an error if a create argument directly or
|
60
|
+
# indirectly references a non-cascaded domain object without an identifier, even if the
|
61
|
+
# reference is not relevant to the create. The template returned by this method elides
|
62
|
+
# all non-essential references.
|
63
63
|
#
|
64
|
-
# caCORE
|
65
|
-
#
|
66
|
-
#
|
67
|
-
#
|
68
|
-
#
|
69
|
-
#
|
70
|
-
#
|
71
|
-
#
|
72
|
-
#
|
73
|
-
#
|
74
|
-
#
|
64
|
+
# @quirk caCORE application business logic performs unnecessary verification
|
65
|
+
# of uncascaded references as if they were a cascaded create. This can result in
|
66
|
+
# an obscure ApplicationException. The server.log stack trace indicates the
|
67
|
+
# extraneous verification code. For example, +caTissue+ +NewSpecimenBizLogic.validateStorageContainer+
|
68
|
+
# is unnecessarily called on a SpecimenCollectionGroup (SCG) update. SCG does not
|
69
|
+
# cascade to Specimen, but caTissue considers the SCG update a Specimen create
|
70
|
+
# anyway if the SCG references a Specimen without an identifier. The Specimen
|
71
|
+
# business logic then raises an exception when it finds a StorageContainer
|
72
|
+
# without an identifier in the Specimen object graph. Therefore, an update must
|
73
|
+
# build a storable template which prunes the update object graph to exclude uncascaded
|
74
|
+
# objects. These uncascaded objects should be ignored by the application but aren't.
|
75
75
|
#
|
76
76
|
# @param [Resource] obj the domain object to save
|
77
77
|
# @return [Resource] the template to use as the caCORE argument
|
@@ -92,10 +92,10 @@ module CaRuby
|
|
92
92
|
# Ensure that the given domain object obj can be created or updated by setting the identifier for
|
93
93
|
# each independent reference in the create template object graph.
|
94
94
|
#
|
95
|
-
# caCORE
|
96
|
-
#
|
97
|
-
#
|
98
|
-
#
|
95
|
+
# @quirk caCORE +caCORE+ raises an ApplicationException if an independent reference in the create or
|
96
|
+
# update argument does not have an identifier. The +caCORE+ server log error is as follows:
|
97
|
+
# java.lang.IllegalArgumentException: id to load is required for loading
|
98
|
+
# The server log stack trace indicates a bizlogic line that offers a clue to the offending reference.
|
99
99
|
def ensure_storable(obj)
|
100
100
|
# Add defaults, which might introduce independent references. Enable the lazy loader to fetch
|
101
101
|
# create references from the database where needed to build defaults.
|
@@ -113,24 +113,24 @@ module CaRuby
|
|
113
113
|
|
114
114
|
# Returns the attributes to visit in building the template for the given
|
115
115
|
# domain object. The visitable attributes consist of the following:
|
116
|
-
# * The {
|
116
|
+
# * The {Attributes#unproxied_savable_template_attributes} filtered as follows:
|
117
117
|
# * If the database operation is a create, then exclude the cascaded attributes.
|
118
118
|
# * If the given object has an identifier, then exclude the attributes which
|
119
119
|
# have the the :no_cascade_update_to_create flag set.
|
120
|
-
# * The {
|
120
|
+
# * The {Attributes#proxied_savable_template_attributes} are included if and
|
121
121
|
# only if every referenced object has an identifier, and therefore does not
|
122
122
|
# need to be proxied.
|
123
123
|
#
|
124
|
-
# caTissue
|
125
|
-
#
|
126
|
-
#
|
127
|
-
#
|
124
|
+
# @quirk caTissue caTissue ignores some references, e.g. Participant CPR, and auto-generates
|
125
|
+
# the values instead. Therefore, the create template builder excludes these auto-generated
|
126
|
+
# attributes. After the create, the auto-generated references are merged into the created
|
127
|
+
# object graph and the references are updated if necessary.
|
128
128
|
#
|
129
129
|
# @param [Resource] obj the domain object copied to the update template
|
130
130
|
# @return [<Symbol>] the reference attributes to include in the update template
|
131
|
-
def
|
131
|
+
def savable_template_attributes(obj)
|
132
132
|
# The starting set of candidate attributes is the unproxied cascaded references.
|
133
|
-
unproxied = savable_attributes(obj, obj.class.
|
133
|
+
unproxied = savable_attributes(obj, obj.class.unproxied_savable_template_attributes)
|
134
134
|
# The proxied attributes to save.
|
135
135
|
proxied = savable_proxied_attributes(obj)
|
136
136
|
# The combined set of savable attributes
|
@@ -139,11 +139,11 @@ module CaRuby
|
|
139
139
|
|
140
140
|
# Filters the given attributes, if necessary, to exclude attributes as follows:
|
141
141
|
# * If the save operation is a create, then exclude the
|
142
|
-
# {
|
142
|
+
# {Attribute#autogenerated_on_create?} attributes.
|
143
143
|
#
|
144
144
|
# @param [Resource] obj the visited domain object
|
145
|
-
# @param [
|
146
|
-
# @return [
|
145
|
+
# @param [Attributes::Filter] the savable attribute filter
|
146
|
+
# @return [Attributes::Filter] the composed attribute filter
|
147
147
|
def mergeable_attributes(obj, attributes)
|
148
148
|
# If this is an update, then there is no filter on the given attributes.
|
149
149
|
return attributes if @subject.identifier
|
@@ -155,7 +155,7 @@ module CaRuby
|
|
155
155
|
# Composes the given attributes, if necessary, to exclude attributes as follows:
|
156
156
|
# * If the save operation is a create, then exclude the auto-generated attributes.
|
157
157
|
# * If the visited object has an identifier, then include only those attributes
|
158
|
-
# which {
|
158
|
+
# which {Attribute#cascade_update_to_create?} or have an identifier.
|
159
159
|
#
|
160
160
|
# @param (see #mergeable_attributes)
|
161
161
|
# @return (see #mergeable_attributes)
|
@@ -178,7 +178,7 @@ module CaRuby
|
|
178
178
|
def savable_proxied_attributes(obj)
|
179
179
|
# Include a proxied reference only if the proxied dependents have an identifier,
|
180
180
|
# since those without an identifer are created separately via the proxy.
|
181
|
-
obj.class.
|
181
|
+
obj.class.proxied_savable_template_attributes.reject do |attr|
|
182
182
|
ref = obj.send(attr)
|
183
183
|
case ref
|
184
184
|
when Enumerable then ref.any? { |dep| not dep.identifier }
|
@@ -189,18 +189,18 @@ module CaRuby
|
|
189
189
|
|
190
190
|
# Copies proxied references as needed.
|
191
191
|
#
|
192
|
-
# caTissue
|
193
|
-
#
|
194
|
-
#
|
195
|
-
#
|
196
|
-
#
|
197
|
-
#
|
198
|
-
#
|
199
|
-
#
|
200
|
-
#
|
192
|
+
# @quirk caTissue even though Specimen save cascades to SpecimenPosition,
|
193
|
+
# SpecimenPosition cannot be updated directly. Rather than simply not
|
194
|
+
# cascading to the SpecimenPosition, caTissue checks a Specimen save argument
|
195
|
+
# to ensure that the SpecimenPosition reflects the current database state
|
196
|
+
# rather than the desired cascaded state. Play along with this bizarre
|
197
|
+
# mechanism by adding our own bizarre work-around mechanism to copy a
|
198
|
+
# proxied reference only if it has an identifier. This works only because
|
199
|
+
# another work-around in the #{CaRuby::Database::Writer} updates proxied
|
200
|
+
# references via the proxy create before building the update template.
|
201
201
|
def copy_proxied_save_references(obj, template)
|
202
202
|
return unless obj.identifier
|
203
|
-
obj.class.
|
203
|
+
obj.class.proxied_savable_template_attributes.each do |attr|
|
204
204
|
# the proxy source
|
205
205
|
ref = obj.send(attr)
|
206
206
|
case ref
|
@@ -19,7 +19,7 @@ module CaRuby
|
|
19
19
|
# the save (result, argument) synchronization visitor
|
20
20
|
@svd_sync_vstr = MatchVisitor.new(:matcher => svd_mtchr) { |ref| ref.class.dependent_attributes }
|
21
21
|
# the attributes to merge from the save result
|
22
|
-
mgbl = Proc.new { |ref| ref.class.domain_attributes }
|
22
|
+
mgbl = Proc.new { |ref| ref.class.domain_attributes }
|
23
23
|
# the save result => argument merge visitor
|
24
24
|
@svd_mrg_vstr = MergeVisitor.new(:matcher => svd_mtchr, :mergeable => mgbl) { |ref| ref.class.dependent_attributes }
|
25
25
|
end
|
@@ -125,7 +125,7 @@ module CaRuby
|
|
125
125
|
# Note that some applications restrict or forbid delete operations. Check the specific application
|
126
126
|
# documentation to determine whether deletion is supported.
|
127
127
|
#
|
128
|
-
|
128
|
+
# @param [Resource] obj the domain object to delete
|
129
129
|
# @raise [DatabaseError] if the database operation fails
|
130
130
|
def delete(obj)
|
131
131
|
perform(:delete, obj) { delete_object(obj) }
|
@@ -133,9 +133,9 @@ module CaRuby
|
|
133
133
|
|
134
134
|
# Creates the domain object obj, if necessary.
|
135
135
|
#
|
136
|
-
#
|
137
|
-
#
|
138
|
-
#
|
136
|
+
# @param [Resource, <Resource>] obj the domain object or collection of domain objects to find or create
|
137
|
+
# @raise [ArgumentError] if obj is nil or empty
|
138
|
+
# @raise [DatabaseError] if obj could not be created
|
139
139
|
def ensure_exists(obj)
|
140
140
|
raise ArgumentError.new("Database ensure_exists is missing a domain object argument") if obj.nil_or_empty?
|
141
141
|
obj.enumerate { |ref| find(ref, :create) unless ref.identifier }
|
@@ -237,50 +237,49 @@ module CaRuby
|
|
237
237
|
# Creates obj by submitting a template to the persistence service. Ensures that the domain
|
238
238
|
# objects referenced by the created obj exist and are correctly stored.
|
239
239
|
#
|
240
|
-
# caCORE
|
241
|
-
#
|
242
|
-
#
|
243
|
-
#
|
244
|
-
#
|
245
|
-
#
|
246
|
-
#
|
247
|
-
#
|
248
|
-
#
|
249
|
-
# caCORE
|
250
|
-
#
|
251
|
-
#
|
252
|
-
#
|
253
|
-
#
|
254
|
-
#
|
255
|
-
#
|
256
|
-
#
|
257
|
-
#
|
258
|
-
#
|
259
|
-
#
|
260
|
-
#
|
261
|
-
#
|
262
|
-
#
|
263
|
-
#
|
264
|
-
#
|
265
|
-
#
|
266
|
-
#
|
267
|
-
#
|
268
|
-
#
|
269
|
-
#
|
270
|
-
#
|
271
|
-
#
|
272
|
-
#
|
273
|
-
#
|
274
|
-
#
|
275
|
-
#
|
240
|
+
# @quirk caCORE submitting the object directly for create runs into various caTissue bizlogic
|
241
|
+
# traps, e.g. Participant CPR is not cascaded but Participant bizlogic checks that each CPR
|
242
|
+
# referenced by Participant is ready to be created. It is treacherous to make assumptions
|
243
|
+
# about what caTissue bizlogic will or will not check. Therefore, the safer strategy is to
|
244
|
+
# build a template for submission that includes only the object cascaded and direct
|
245
|
+
# non-cascaded independent references. The independent references are created if necessary.
|
246
|
+
# The template thus includes only as much content as can safely pass through the caTissue
|
247
|
+
# bizlogic minefield.
|
248
|
+
#
|
249
|
+
# @quirk caCORE caCORE create does not update the submitted object to reflect the created
|
250
|
+
# content. The create result is a separate object, which in turn does not always reflect
|
251
|
+
# the created content, e.g. caTissue ignores auto-generated attributes such as Container
|
252
|
+
# name. Work-around is to merge the create result into the object being created, being
|
253
|
+
# careful to merge only the fetched content in order to avoid the dreaded out-of-session
|
254
|
+
# error message. The post-create cascaded dependent hierarchy is traversed to capture
|
255
|
+
# the created state for each created object.
|
256
|
+
#
|
257
|
+
# The ignored content is handled separately by fetching the ignored content from
|
258
|
+
# the database, comparing it to the desired content as reflected in the submitted
|
259
|
+
# create argument object, and submitting a post-create caCORE update as necessary to
|
260
|
+
# force caCORE to reflect the desired content. This is complicated by the various
|
261
|
+
# auto-generation schemes, e.g. in caTissue, that require a careful fetch, match and
|
262
|
+
# merge logic to make sense of how what was actually created corresponds to the desired
|
263
|
+
# content expressed in the create argument object graph.
|
264
|
+
#
|
265
|
+
# There are thus several objects involved in the create process:
|
266
|
+
# * the object to create
|
267
|
+
# * the template for caCORE createObject submission
|
268
|
+
# * the caCORE createObject result
|
269
|
+
# * the post-create fetched object that reflects the persistent content
|
270
|
+
# * the template for post-create caCORE updateObject submission
|
271
|
+
#
|
272
|
+
# This object menagerie is unfortunate but unavoidable if we are to navigate the treacherous
|
273
|
+
# caCORE create process and ensure that:
|
274
|
+
# 1. the database reflects the create argument.
|
275
|
+
# 2. the created object reflects the database content.
|
276
276
|
#
|
277
277
|
# @param (see #create)
|
278
278
|
# @return obj
|
279
279
|
def create_from_template(obj)
|
280
280
|
# The create template. Independent saved references are created as necessary.
|
281
|
-
tmpl = build_create_template(obj)
|
281
|
+
tmpl = build_create_template(obj)
|
282
282
|
save_with_template(obj, tmpl) { |svc| svc.create(tmpl) }
|
283
|
-
|
284
283
|
# If obj is a top-level create, then ensure that remaining references exist.
|
285
284
|
if @operations.first.subject == obj then
|
286
285
|
refs = obj.references.reject { |ref| ref.identifier }
|
@@ -297,7 +296,7 @@ module CaRuby
|
|
297
296
|
build_save_template(obj, @cr_tmpl_bldr)
|
298
297
|
end
|
299
298
|
#
|
300
|
-
# caCORE
|
299
|
+
# @quirk caCORE application create logic might ignore a non-domain attribute value,
|
301
300
|
# e.g. the caTissue StorageContainer auto-generated name attribute. In other cases, the application
|
302
301
|
# always ignores a non-domain attribute value, so the object should not be saved even if it differs
|
303
302
|
# from the stored result, e.g. the caTissue CollectionProtocolRegistration unsaved
|
@@ -306,7 +305,7 @@ module CaRuby
|
|
306
305
|
# to update the saved object.
|
307
306
|
#
|
308
307
|
# This method returns whether the saved obj differs from the stored source for any
|
309
|
-
# {
|
308
|
+
# {Attributes#autogenerated_nondomain_attributes}.
|
310
309
|
#
|
311
310
|
# @param [Resource] the created domain object
|
312
311
|
# @param [Resource] the stored database content source domain object
|
@@ -344,14 +343,14 @@ module CaRuby
|
|
344
343
|
|
345
344
|
# Returns whether the given creatable domain attribute with value obj satisfies
|
346
345
|
# each of the following conditions:
|
347
|
-
# * the attribute is {
|
348
|
-
# * the attribute is not an {
|
346
|
+
# * the attribute is {Attribute#independent?}
|
347
|
+
# * the attribute is not an {Attribute#owner?}
|
349
348
|
# * the obj value is unsaved
|
350
349
|
# * the attribute is not mandatory
|
351
350
|
# * the attribute references a {#pending_create?} save context.
|
352
351
|
#
|
353
352
|
# @param obj (see #create)
|
354
|
-
# @param [
|
353
|
+
# @param [Attribute] attr_md candidate attribute metadata
|
355
354
|
# @return [Boolean] whether the attribute should not be included in the create template
|
356
355
|
def exclude_pending_create_attribute?(obj, attr_md)
|
357
356
|
attr_md.independent? and
|
@@ -413,13 +412,10 @@ module CaRuby
|
|
413
412
|
|
414
413
|
# update a cascaded dependent by updating the owner
|
415
414
|
owner = cascaded_owner(obj)
|
416
|
-
|
417
|
-
# if not cascaded, then update directly with a template
|
418
|
-
result ||= create_from_template(obj)
|
415
|
+
if owner then return update_cascaded_dependent(owner, obj) end
|
419
416
|
|
420
|
-
# update using a template
|
417
|
+
# Not cascaded dependent; update using a template,
|
421
418
|
tmpl = build_update_template(obj)
|
422
|
-
|
423
419
|
# call the caCORE service with an obj update template
|
424
420
|
save_with_template(obj, tmpl) { |svc| svc.update(tmpl) }
|
425
421
|
# take a snapshot of the updated content
|
@@ -427,9 +423,9 @@ module CaRuby
|
|
427
423
|
end
|
428
424
|
|
429
425
|
# Returns the owner that can cascade update to the given object.
|
430
|
-
# The owner is the #{Resource#
|
431
|
-
# for which the owner attribute {
|
432
|
-
# is {
|
426
|
+
# The owner is the #{Resource#effective_owner_attribute} value
|
427
|
+
# for which the owner attribute {Attribute#inverse_metadata}
|
428
|
+
# is {Attribute#cascaded?}.
|
433
429
|
#
|
434
430
|
# @param [Resource] obj the domain object to update
|
435
431
|
# @return [Resource, nil] the owner which can cascade an update to the object, or nil if none
|
@@ -439,14 +435,14 @@ module CaRuby
|
|
439
435
|
# the owner attribute
|
440
436
|
oattr = obj.effective_owner_attribute
|
441
437
|
if oattr.nil? then raise DatabaseError.new("Dependent #{obj} does not have an owner") end
|
442
|
-
dep_md = obj.class.attribute_metadata(oattr).
|
438
|
+
dep_md = obj.class.attribute_metadata(oattr).inverse_metadata
|
443
439
|
if dep_md and dep_md.cascaded? then
|
444
440
|
obj.send(oattr)
|
445
441
|
end
|
446
442
|
end
|
447
443
|
|
448
|
-
def
|
449
|
-
logger.debug { "Updating #{obj} by
|
444
|
+
def update_cascaded_dependent(owner, obj)
|
445
|
+
logger.debug { "Updating cascaded dependent #{obj} by updating the owner #{owner}..." }
|
450
446
|
update(owner)
|
451
447
|
end
|
452
448
|
|
@@ -458,33 +454,33 @@ module CaRuby
|
|
458
454
|
obj.take_snapshot
|
459
455
|
end
|
460
456
|
|
461
|
-
# caTissue
|
462
|
-
#
|
463
|
-
#
|
464
|
-
#
|
465
|
-
#
|
466
|
-
#
|
467
|
-
#
|
468
|
-
#
|
469
|
-
#
|
470
|
-
#
|
471
|
-
#
|
472
|
-
#
|
473
|
-
#
|
474
|
-
#
|
475
|
-
#
|
476
|
-
#
|
477
|
-
#
|
478
|
-
#
|
479
|
-
#
|
480
|
-
#
|
481
|
-
#
|
457
|
+
# @quirk caTissue the conditions for when and how to include a proxied dependent are
|
458
|
+
# are intricate and treacherous. So far as can be determined, in the case of a
|
459
|
+
# SpecimenPosition proxied by a TransferEventParameters, the sequence is as follows:
|
460
|
+
# * If a Specimen without a previous position is updated with a position, then
|
461
|
+
# the update template should not include the target position. Subsequent to the
|
462
|
+
# Specimen update, the TransferEventParameters proxy is created.
|
463
|
+
# This creates a new position in the database as a server side-effect. caRuby
|
464
|
+
# then fetches the new position and merges it into the target position.
|
465
|
+
# * If a Specimen with a previous position is updated, then the update template
|
466
|
+
# must reflect the current datbase position state. Therefore, caRuby first
|
467
|
+
# creates the proxy to update the database state.
|
468
|
+
# * The TransferEventParameters create must reference a Specimen with the current
|
469
|
+
# database position state, not the new position state.
|
470
|
+
# * Update of a Specimen with a current database position must reference a
|
471
|
+
# position which reflects that database state. This is true even if the position
|
472
|
+
# has not changed. The position must be complete and consistent with the database
|
473
|
+
# state. E.g. omitting the position storage container is accepted by caTissue
|
474
|
+
# but corrupts the database side and has adverse delayed effects.
|
475
|
+
# * Specimen create (but not auto-generated update) cannot include a position
|
476
|
+
# (although that might have changed in the 1.1.2 release). The target position
|
477
|
+
# must be created via the proxy after the Specimen is created.
|
482
478
|
#
|
483
479
|
# @param (see #update)
|
484
|
-
# @return [<Resource>] the #{
|
480
|
+
# @return [<Resource>] the #{Attributes#proxied_savable_template_attributes} dependents
|
485
481
|
# which are #{Persistable#changed?}
|
486
482
|
def updatable_proxied_dependents(obj)
|
487
|
-
attrs = obj.class.
|
483
|
+
attrs = obj.class.proxied_savable_template_attributes
|
488
484
|
return Array::EMPTY_ARRAY if attrs.empty?
|
489
485
|
deps = []
|
490
486
|
attrs.each do |attr|
|
@@ -537,7 +533,7 @@ module CaRuby
|
|
537
533
|
def submit_save_template(obj, template)
|
538
534
|
svc = persistence_service(template.class)
|
539
535
|
result = template.identifier ? svc.update(template) : svc.create(template)
|
540
|
-
logger.debug { "
|
536
|
+
logger.debug { "Save #{obj.qp} with template #{template.qp} produced caCORE result: #{result}." }
|
541
537
|
result
|
542
538
|
end
|
543
539
|
|
@@ -557,7 +553,6 @@ module CaRuby
|
|
557
553
|
sync_saved_result_with_database(source, target)
|
558
554
|
# merge the source into the target
|
559
555
|
merge_saved(target, source)
|
560
|
-
|
561
556
|
# If saved must be updated, then update recursively.
|
562
557
|
# Otherwise, save dependents as needed.
|
563
558
|
if update_saved?(target, source) then
|
@@ -581,26 +576,30 @@ module CaRuby
|
|
581
576
|
# Merges the database content into the given saved domain object.
|
582
577
|
# Dependents are merged recursively.
|
583
578
|
#
|
584
|
-
# caTissue
|
585
|
-
#
|
586
|
-
#
|
587
|
-
#
|
588
|
-
#
|
589
|
-
#
|
579
|
+
# @quirk caTissue the auto-generated references are not necessarily valid, e.g. the auto-generated
|
580
|
+
# SpecimenRequirement characteristics tissue site is nil rather than the default 'Not Specified'.
|
581
|
+
# This results in an obscure downstream error when creating an CPR which auto-generates a SCG
|
582
|
+
# which auto-generates a Specimen which copies the invalid characteristics. The work-around for
|
583
|
+
# this bug is to add defaults to auto-generated references. Then, if the content differs from
|
584
|
+
# the database, the difference induces an update of the reference.
|
590
585
|
#
|
591
586
|
# @param (see #sync_saved)
|
592
587
|
# @return [Resource] the merged target object
|
593
588
|
def merge_saved(target, source)
|
594
589
|
logger.debug { "Merging saved result #{source} into saved #{target.qp}..." }
|
595
|
-
# Update each saved reference snapshot to reflect the database state and add a lazy loader
|
590
|
+
# Update each saved reference snapshot to reflect the database state and add a lazy loader
|
591
|
+
# if necessary.
|
596
592
|
@svd_mrg_vstr.visit(source, target) do |src, tgt|
|
593
|
+
# Skip unsaved source objects. This occurs, e.g., if a transient source reference is generated
|
594
|
+
# on demand.
|
595
|
+
next if src.identifier.nil?
|
597
596
|
# capture the id
|
598
597
|
prev_id = tgt.identifier
|
598
|
+
# The target dependents will be visited, so persistify the target non-recursively.
|
599
599
|
persistify_object(tgt, src)
|
600
|
-
#
|
600
|
+
# If tgt is an auto-generated reference, then add defaults.
|
601
601
|
if target != tgt and prev_id.nil? then tgt.add_defaults end
|
602
602
|
end
|
603
|
-
logger.debug { "Merged saved result #{source} into saved #{target.qp}." }
|
604
603
|
end
|
605
604
|
|
606
605
|
# Synchronizes the given save result source object to reflect the database content, as follows:
|
@@ -621,7 +620,7 @@ module CaRuby
|
|
621
620
|
set_inverses(source)
|
622
621
|
end
|
623
622
|
|
624
|
-
# Refetches the given create result source if there are any {
|
623
|
+
# Refetches the given create result source if there are any {Attributes#autogenerated_nondomain_attributes}
|
625
624
|
# which must be fetched to reflect the database state.
|
626
625
|
#
|
627
626
|
# @param source (see #sync_saved)
|
@@ -654,7 +653,7 @@ module CaRuby
|
|
654
653
|
# Returns the saved target attributes which must be fetched to reflect the database content, consisting
|
655
654
|
# of the following:
|
656
655
|
# * {Persistable#saved_fetch_attributes}
|
657
|
-
# * {
|
656
|
+
# * {Attributes#domain_attributes} which include a source reference without an identifier
|
658
657
|
#
|
659
658
|
# @param (see #sync_saved)
|
660
659
|
# @return [<Symbol>] the attributes which must be fetched
|
@@ -687,7 +686,7 @@ module CaRuby
|
|
687
686
|
def save_changed_dependents(obj)
|
688
687
|
obj.class.dependent_attributes.each do |attr|
|
689
688
|
deps = obj.send(attr).to_enum
|
690
|
-
logger.debug { "Saving the #{obj} #{attr} dependents #{deps.qp} which have changed..." } unless deps.empty?
|
689
|
+
logger.debug { "Saving the #{obj} #{attr} dependents #{deps.qp(:single_line)} which have changed..." } unless deps.empty?
|
691
690
|
deps.each { |dep| save_dependent_if_changed(obj, attr, dep) }
|
692
691
|
end
|
693
692
|
end
|
@@ -704,6 +703,7 @@ module CaRuby
|
|
704
703
|
return create(dependent)
|
705
704
|
end
|
706
705
|
changes = dependent.changed_attributes
|
706
|
+
# If the dependent identifier is missing, then the identifier was probably created on demand.
|
707
707
|
logger.debug { "#{owner.qp} #{attribute} dependent #{dependent.qp} changed for attributes #{changes.to_series}." } unless changes.empty?
|
708
708
|
if changes.any? { |attr| not dependent.class.attribute_metadata(attr).dependent? } then
|
709
709
|
# the owner save operation
|
data/lib/caruby/database.rb
CHANGED
@@ -77,23 +77,23 @@ module CaRuby
|
|
77
77
|
|
78
78
|
# Creates a new Database with the specified service name and options.
|
79
79
|
#
|
80
|
-
# caCORE
|
81
|
-
#
|
82
|
-
#
|
83
|
-
#
|
84
|
-
#
|
80
|
+
# @quirk caCORE obtaining a caCORE session instance mysteriously depends on referencing the
|
81
|
+
# application service first. Therefore, the default persistence service appService method must
|
82
|
+
# be called after it is instantiated and before the session is instantiated. However, when
|
83
|
+
# the appService method is called just before a session is acquired, then this call corrupts
|
84
|
+
# the object state of existing objects.
|
85
85
|
#
|
86
|
-
#
|
87
|
-
#
|
88
|
-
#
|
89
|
-
#
|
90
|
-
#
|
91
|
-
#
|
86
|
+
# Specifically, when a CaTissue::CollectionProtocol is created which references a
|
87
|
+
# CaTissue::CollectionProtocolRegistration which in turn references a CaTissue::Participant,
|
88
|
+
# then the call to PersistenceService.appService replaces the CaTissue::Participant
|
89
|
+
# reference with a difference CaTissue::Participant instance. The work-around for
|
90
|
+
# this extremely bizarre bug is to call appService immediately after instantiating
|
91
|
+
# the default persistence service.
|
92
92
|
#
|
93
|
-
#
|
94
|
-
#
|
95
|
-
#
|
96
|
-
#
|
93
|
+
# This bug might be a low-level JRuby-Java-caCORE-Hibernate confusion where something in
|
94
|
+
# caCORE stomps on an existing JRuby object graph. To reproduce, move the appService call
|
95
|
+
# to the start_session method and run {PCBIN::MigrationTest#test_save} with all but the
|
96
|
+
# verify_save(:biopsy, BIOPSY_OPTS) line commented out.
|
97
97
|
#
|
98
98
|
# @param [String] service_name the name of the default {PersistenceService}
|
99
99
|
# @param [{Symbol => String}] opts access options
|
@@ -251,10 +251,10 @@ module CaRuby
|
|
251
251
|
|
252
252
|
# @return [Cache] a new object cache.
|
253
253
|
def create_cache
|
254
|
-
# JRuby
|
255
|
-
#
|
256
|
-
#
|
257
|
-
#
|
254
|
+
# @quirk JRuby identifier is not a stable object when fetched from the database, i.e.:
|
255
|
+
# obj.identifier.equal?(obj.identifier) #=> false
|
256
|
+
# This is probably an artifact of jRuby Numeric - Java Long conversion interaction
|
257
|
+
# combined with hash access use of the eql? method. Work-around is to make a Ruby Integer.
|
258
258
|
Cache.new do |obj|
|
259
259
|
raise ArgumentError.new("Can't cache object without identifier: #{obj}") unless obj.identifier
|
260
260
|
obj.identifier.to_s.to_i
|