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.
Files changed (61) hide show
  1. data/History.md +48 -0
  2. data/lib/caruby/cli/command.rb +2 -1
  3. data/lib/caruby/csv/csv_mapper.rb +8 -8
  4. data/lib/caruby/database/persistable.rb +44 -65
  5. data/lib/caruby/database/persistence_service.rb +12 -9
  6. data/lib/caruby/database/persistifier.rb +14 -14
  7. data/lib/caruby/database/reader.rb +53 -51
  8. data/lib/caruby/database/search_template_builder.rb +9 -10
  9. data/lib/caruby/database/store_template_builder.rb +58 -58
  10. data/lib/caruby/database/writer.rb +96 -96
  11. data/lib/caruby/database.rb +19 -19
  12. data/lib/caruby/domain/attribute.rb +581 -0
  13. data/lib/caruby/domain/attributes.rb +615 -0
  14. data/lib/caruby/domain/dependency.rb +240 -0
  15. data/lib/caruby/domain/importer.rb +183 -0
  16. data/lib/caruby/domain/introspection.rb +176 -0
  17. data/lib/caruby/domain/inverse.rb +173 -0
  18. data/lib/caruby/domain/inversible.rb +1 -2
  19. data/lib/caruby/domain/java_attribute.rb +173 -0
  20. data/lib/caruby/domain/merge.rb +13 -10
  21. data/lib/caruby/domain/metadata.rb +141 -0
  22. data/lib/caruby/domain/mixin.rb +35 -0
  23. data/lib/caruby/domain/reference_visitor.rb +5 -3
  24. data/lib/caruby/domain.rb +340 -0
  25. data/lib/caruby/import/java.rb +29 -25
  26. data/lib/caruby/migration/migratable.rb +5 -5
  27. data/lib/caruby/migration/migrator.rb +19 -15
  28. data/lib/caruby/migration/resource_module.rb +1 -1
  29. data/lib/caruby/resource.rb +39 -30
  30. data/lib/caruby/util/collection.rb +94 -33
  31. data/lib/caruby/util/coordinate.rb +28 -2
  32. data/lib/caruby/util/log.rb +4 -4
  33. data/lib/caruby/util/module.rb +12 -28
  34. data/lib/caruby/util/partial_order.rb +9 -10
  35. data/lib/caruby/util/pretty_print.rb +46 -26
  36. data/lib/caruby/util/topological_sync_enumerator.rb +10 -4
  37. data/lib/caruby/util/transitive_closure.rb +2 -2
  38. data/lib/caruby/util/visitor.rb +1 -1
  39. data/lib/caruby/version.rb +1 -1
  40. data/test/lib/caruby/database/persistable_test.rb +1 -1
  41. data/test/lib/caruby/domain/domain_test.rb +14 -28
  42. data/test/lib/caruby/domain/inversible_test.rb +1 -1
  43. data/test/lib/caruby/import/java_test.rb +5 -0
  44. data/test/lib/caruby/migration/test_case.rb +0 -1
  45. data/test/lib/caruby/test_case.rb +9 -10
  46. data/test/lib/caruby/util/collection_test.rb +23 -5
  47. data/test/lib/caruby/util/module_test.rb +10 -14
  48. data/test/lib/caruby/util/partial_order_test.rb +16 -15
  49. data/test/lib/caruby/util/visitor_test.rb +1 -1
  50. data/test/lib/examples/galena/clinical_trials/migration/test_case.rb +1 -1
  51. metadata +16 -15
  52. data/History.txt +0 -44
  53. data/lib/caruby/domain/attribute_metadata.rb +0 -551
  54. data/lib/caruby/domain/java_attribute_metadata.rb +0 -183
  55. data/lib/caruby/domain/resource_attributes.rb +0 -565
  56. data/lib/caruby/domain/resource_dependency.rb +0 -217
  57. data/lib/caruby/domain/resource_introspection.rb +0 -160
  58. data/lib/caruby/domain/resource_inverse.rb +0 -151
  59. data/lib/caruby/domain/resource_metadata.rb +0 -155
  60. data/lib/caruby/domain/resource_module.rb +0 -370
  61. data/lib/caruby/yard/resource_metadata_handler.rb +0 -8
@@ -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 {AttributeMetadata#cascade_update_to_create?} or have an identifier.
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| savable_cascaded_attributes(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 alert - must copy all of the non-domain attributes rather than just the identifier,
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
- copy = src.copy
35
- logger.debug { "Store template builder copied #{src.qp} into #{copy}." }
36
- copy_proxied_save_references(src, copy)
37
- copy
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| savable_cascaded_attributes(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 alert - +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 ResourceMetadata
57
- # methods.
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 alert - +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.
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 alert - 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.
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 alert - +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.
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 {ResourceAttributes#unproxied_save_template_attributes} filtered as follows:
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 {ResourceAttributes#proxied_save_template_attributes} are included if and
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 alert - 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.
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 savable_cascaded_attributes(obj)
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.unproxied_save_template_attributes)
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
- # {AttributeMetadata#autogenerated_on_create?} attributes.
142
+ # {Attribute#autogenerated_on_create?} attributes.
143
143
  #
144
144
  # @param [Resource] obj the visited domain object
145
- # @param [ResourceAttributes::Filter] the savable attribute filter
146
- # @return [ResourceAttributes::Filter] the composed attribute filter
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 {AttributeMetadata#cascade_update_to_create?} or have an identifier.
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.proxied_save_template_attributes.reject do |attr|
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 alert - 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.
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.proxied_save_template_attributes.each do |attr|
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
- #@param [Resource] obj the domain object to delete
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
- # Raises ArgumentError if obj is nil or empty.
137
- # Raises DatabaseError if obj could not be created.
138
- # The return value is undefined.
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 alert - 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
- # caCORE alert - 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.
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 alert - application create logic might ignore a non-domain attribute value,
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
- # {ResourceAttributes#autogenerated_nondomain_attributes}.
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 {AttributeMetadata#independent?}
348
- # * the attribute is not an {AttributeMetadata#owner?}
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 [AttributeMetadata] attr_md candidate attribute metadata
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
- result = update_dependent(owner, obj) if owner
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#effective_owner_attribute_metadata} value
431
- # for which the owner attribute {AttributeMetadata#inverse_attribute_metadata}
432
- # is {AttributeMetadata#cascaded?}.
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).inverse_attribute_metadata
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 update_dependent(owner, obj)
449
- logger.debug { "Updating #{obj} by saving the owner #{owner}..." }
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 alert - the conditions for when and how to include a proxied dependent are
462
- # are intricate and treacherous. So far as can be determined, in the case of a
463
- # SpecimenPosition proxied by a TransferEventParameters, the sequence is as follows:
464
- # * If a Specimen without a previous position is updated with a position, then
465
- # the update template should not include the target position. Subsequent to the
466
- # Specimen update, the TransferEventParameters proxy is created.
467
- # This creates a new position in the database as a server side-effect. caRuby
468
- # then fetches the new position and merges it into the target position.
469
- # * If a Specimen with a previous position is updated, then the update template
470
- # must reflect the current datbase position state. Therefore, caRuby first
471
- # creates the proxy to update the database state.
472
- # * The TransferEventParameters create must reference a Specimen with the current
473
- # database position state, not the new position state.
474
- # * Update of a Specimen with a current database position must reference a
475
- # position which reflects that database state. This is true even if the position
476
- # has not changed. The position must be complete and consistent with the database
477
- # state. E.g. omitting the position storage container is accepted by caTissue
478
- # but corrupts the database side and has adverse delayed effects.
479
- # * Specimen create (but not auto-generated update) cannot include a position
480
- # (although that might have changed in the 1.1.2 release). The target position
481
- # must be created via the proxy after the Specimen is created.
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 #{ResourceAttributes#proxied_save_template_attributes} dependents
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.proxied_save_template_attributes
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 { "Store #{obj.qp} with template #{template.qp} produced caCORE result: #{result}." }
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 alert - the auto-generated references are not necessarily valid, e.g. the auto-generated
585
- # SpecimenRequirement characteristics tissue site is nil rather than the default 'Not Specified'.
586
- # This results in an obscure downstream error when creating an CPR which auto-generates a SCG
587
- # which auto-generates a Specimen which copies the invalid characteristics. The work-around for
588
- # this bug is to add defaults to auto-generated references. Then, if the content differs from
589
- # the database, the difference induces an update of the reference.
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 if necessary.
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
- # if tgt is an auto-generated reference, then add defaults
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 {ResourceAttributes#autogenerated_nondomain_attributes}
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
- # * {ResourceAttributes#domain_attributes} which include a source reference without an identifier
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
@@ -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 alert - 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.
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
- # 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.
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
- # 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.
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 alert - 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.
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