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
@@ -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
|