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