caruby-core 1.5.5 → 2.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (126) hide show
  1. data/Gemfile +9 -0
  2. data/History.md +5 -1
  3. data/lib/caruby.rb +3 -5
  4. data/lib/caruby/caruby-src.tar.gz +0 -0
  5. data/lib/caruby/database.rb +53 -69
  6. data/lib/caruby/database/application_service.rb +25 -0
  7. data/lib/caruby/database/cache.rb +60 -0
  8. data/lib/caruby/database/fetched_matcher.rb +52 -38
  9. data/lib/caruby/database/lazy_loader.rb +4 -4
  10. data/lib/caruby/database/operation.rb +34 -0
  11. data/lib/caruby/database/persistable.rb +171 -86
  12. data/lib/caruby/database/persistence_service.rb +32 -34
  13. data/lib/caruby/database/persistifier.rb +100 -43
  14. data/lib/caruby/database/reader.rb +107 -85
  15. data/lib/caruby/database/reader_template_builder.rb +60 -0
  16. data/lib/caruby/database/saved_matcher.rb +3 -3
  17. data/lib/caruby/database/sql_executor.rb +88 -17
  18. data/lib/caruby/database/writer.rb +213 -177
  19. data/lib/caruby/database/writer_template_builder.rb +334 -0
  20. data/lib/caruby/{util → helpers}/controlled_value.rb +0 -0
  21. data/lib/caruby/{util → helpers}/coordinate.rb +4 -4
  22. data/lib/caruby/{util → helpers}/person.rb +3 -3
  23. data/lib/caruby/{util → helpers}/properties.rb +7 -9
  24. data/lib/caruby/{util → helpers}/roman.rb +2 -2
  25. data/lib/caruby/{util → helpers}/version.rb +1 -1
  26. data/lib/caruby/json/deserializer.rb +2 -2
  27. data/lib/caruby/json/serializer.rb +49 -7
  28. data/lib/caruby/metadata.rb +30 -0
  29. data/lib/caruby/metadata/java_property.rb +21 -0
  30. data/lib/caruby/metadata/propertied.rb +191 -0
  31. data/lib/caruby/metadata/property.rb +22 -0
  32. data/lib/caruby/metadata/property_characteristics.rb +201 -0
  33. data/lib/caruby/migration/migratable.rb +11 -182
  34. data/lib/caruby/rdbi/driver/jdbc.rb +446 -0
  35. data/lib/caruby/resource.rb +20 -823
  36. data/lib/caruby/version.rb +1 -1
  37. data/test/lib/caruby/database/cache_test.rb +54 -0
  38. data/test/lib/caruby/{util → helpers}/controlled_value_test.rb +3 -5
  39. data/test/lib/caruby/{util → helpers}/person_test.rb +4 -6
  40. data/test/lib/caruby/helpers/properties_test.rb +34 -0
  41. data/test/lib/caruby/{util → helpers}/roman_test.rb +2 -3
  42. data/test/lib/caruby/{util → helpers}/version_test.rb +2 -3
  43. data/test/lib/helper.rb +7 -0
  44. metadata +161 -214
  45. data/lib/caruby/cli/application.rb +0 -36
  46. data/lib/caruby/cli/command.rb +0 -202
  47. data/lib/caruby/csv/csv_mapper.rb +0 -159
  48. data/lib/caruby/csv/csvio.rb +0 -203
  49. data/lib/caruby/database/search_template_builder.rb +0 -56
  50. data/lib/caruby/database/store_template_builder.rb +0 -278
  51. data/lib/caruby/domain.rb +0 -193
  52. data/lib/caruby/domain/attribute.rb +0 -584
  53. data/lib/caruby/domain/attributes.rb +0 -628
  54. data/lib/caruby/domain/dependency.rb +0 -225
  55. data/lib/caruby/domain/id_alias.rb +0 -22
  56. data/lib/caruby/domain/importer.rb +0 -183
  57. data/lib/caruby/domain/introspection.rb +0 -176
  58. data/lib/caruby/domain/inverse.rb +0 -172
  59. data/lib/caruby/domain/inversible.rb +0 -90
  60. data/lib/caruby/domain/java_attribute.rb +0 -173
  61. data/lib/caruby/domain/merge.rb +0 -185
  62. data/lib/caruby/domain/metadata.rb +0 -142
  63. data/lib/caruby/domain/mixin.rb +0 -35
  64. data/lib/caruby/domain/properties.rb +0 -95
  65. data/lib/caruby/domain/reference_visitor.rb +0 -428
  66. data/lib/caruby/domain/uniquify.rb +0 -50
  67. data/lib/caruby/import/java.rb +0 -387
  68. data/lib/caruby/migration/migrator.rb +0 -918
  69. data/lib/caruby/migration/resource_module.rb +0 -9
  70. data/lib/caruby/migration/uniquify.rb +0 -17
  71. data/lib/caruby/util/attribute_path.rb +0 -44
  72. data/lib/caruby/util/cache.rb +0 -56
  73. data/lib/caruby/util/class.rb +0 -149
  74. data/lib/caruby/util/collection.rb +0 -1152
  75. data/lib/caruby/util/domain_extent.rb +0 -46
  76. data/lib/caruby/util/file_separator.rb +0 -65
  77. data/lib/caruby/util/inflector.rb +0 -27
  78. data/lib/caruby/util/log.rb +0 -95
  79. data/lib/caruby/util/math.rb +0 -12
  80. data/lib/caruby/util/merge.rb +0 -59
  81. data/lib/caruby/util/module.rb +0 -18
  82. data/lib/caruby/util/options.rb +0 -97
  83. data/lib/caruby/util/partial_order.rb +0 -35
  84. data/lib/caruby/util/pretty_print.rb +0 -204
  85. data/lib/caruby/util/stopwatch.rb +0 -74
  86. data/lib/caruby/util/topological_sync_enumerator.rb +0 -62
  87. data/lib/caruby/util/transitive_closure.rb +0 -55
  88. data/lib/caruby/util/tree.rb +0 -48
  89. data/lib/caruby/util/trie.rb +0 -37
  90. data/lib/caruby/util/uniquifier.rb +0 -30
  91. data/lib/caruby/util/validation.rb +0 -20
  92. data/lib/caruby/util/visitor.rb +0 -365
  93. data/lib/caruby/util/weak_hash.rb +0 -36
  94. data/test/lib/caruby/csv/csv_mapper_test.rb +0 -40
  95. data/test/lib/caruby/csv/csvio_test.rb +0 -69
  96. data/test/lib/caruby/database/persistable_test.rb +0 -92
  97. data/test/lib/caruby/domain/domain_test.rb +0 -112
  98. data/test/lib/caruby/domain/inversible_test.rb +0 -99
  99. data/test/lib/caruby/domain/reference_visitor_test.rb +0 -130
  100. data/test/lib/caruby/import/java_test.rb +0 -80
  101. data/test/lib/caruby/import/mixed_case_test.rb +0 -14
  102. data/test/lib/caruby/migration/test_case.rb +0 -102
  103. data/test/lib/caruby/test_case.rb +0 -230
  104. data/test/lib/caruby/util/cache_test.rb +0 -23
  105. data/test/lib/caruby/util/class_test.rb +0 -61
  106. data/test/lib/caruby/util/collection_test.rb +0 -398
  107. data/test/lib/caruby/util/command_test.rb +0 -55
  108. data/test/lib/caruby/util/domain_extent_test.rb +0 -60
  109. data/test/lib/caruby/util/file_separator_test.rb +0 -30
  110. data/test/lib/caruby/util/inflector_test.rb +0 -12
  111. data/test/lib/caruby/util/lazy_hash_test.rb +0 -34
  112. data/test/lib/caruby/util/merge_test.rb +0 -83
  113. data/test/lib/caruby/util/module_test.rb +0 -25
  114. data/test/lib/caruby/util/options_test.rb +0 -59
  115. data/test/lib/caruby/util/partial_order_test.rb +0 -42
  116. data/test/lib/caruby/util/pretty_print_test.rb +0 -85
  117. data/test/lib/caruby/util/properties_test.rb +0 -50
  118. data/test/lib/caruby/util/stopwatch_test.rb +0 -18
  119. data/test/lib/caruby/util/topological_sync_enumerator_test.rb +0 -69
  120. data/test/lib/caruby/util/transitive_closure_test.rb +0 -67
  121. data/test/lib/caruby/util/tree_test.rb +0 -23
  122. data/test/lib/caruby/util/trie_test.rb +0 -14
  123. data/test/lib/caruby/util/visitor_test.rb +0 -278
  124. data/test/lib/caruby/util/weak_hash_test.rb +0 -45
  125. data/test/lib/examples/clinical_trials/migration/migration_test.rb +0 -58
  126. data/test/lib/examples/clinical_trials/migration/test_case.rb +0 -38
@@ -7,7 +7,7 @@ module CaRuby
7
7
  # Creates a new LazyLoader which calls the loader block on the subject.
8
8
  #
9
9
  # @yield [subject, attribute] fetches the given subject attribute value from the database
10
- # @yieldparam [Resource] subject the domain object whose attribute is to be loaded
10
+ # @yieldparam [Jinx::Resource] subject the domain object whose attribute is to be loaded
11
11
  # @yieldparam [Symbol] attribute the domain attribute to load
12
12
  def initialize(&loader)
13
13
  @loader = loader
@@ -16,14 +16,14 @@ module CaRuby
16
16
  @enabled = true
17
17
  end
18
18
 
19
- # @param [Resource] subject the domain object whose attribute is to be loaded
19
+ # @param [Jinx::Resource] subject the domain object whose attribute is to be loaded
20
20
  # @param [Symbol] the domain attribute to load
21
21
  # @yield (see #initialize)
22
22
  # @yieldparam (see #initialize)
23
23
  # @return the attribute value loaded from the database
24
24
  # @raise [RuntimeError] if this loader is disabled
25
25
  def load(subject, attribute)
26
- if disabled? then raise RuntimeError.new("#{subject.qp} lazy load called on disabled loader") end
26
+ if disabled? then Jinx.fail(RuntimeError, "#{subject.qp} lazy load called on disabled loader") end
27
27
  logger.debug { "Lazy-loading #{subject.qp} #{attribute}..." }
28
28
  # the current value
29
29
  oldval = subject.send(attribute)
@@ -33,7 +33,7 @@ module CaRuby
33
33
  return oldval if fetched.nil_or_empty?
34
34
  # merge the fetched into the attribute
35
35
  logger.debug { "Merging #{subject.qp} fetched #{attribute} value #{fetched.qp}#{' into ' + oldval.qp if oldval}..." }
36
- matches = @matcher.match(fetched.to_enum, oldval.to_enum)
36
+ matches = @matcher.match(fetched.to_enum, oldval.to_enum, subject, attribute)
37
37
  subject.merge_attribute(attribute, fetched, matches)
38
38
  end
39
39
 
@@ -0,0 +1,34 @@
1
+ module CaRuby
2
+ class Database
3
+ # Database CRUD operation.
4
+ class Operation
5
+ attr_reader :type, :subject, :attribute
6
+
7
+ # @param [:find, :query, :create, :update, :delete] type the database operation type
8
+ # @param [Persistable] subject the domain object on which the operation is performed
9
+ # @param [{Symbol => Object}, Symbol, nil] the operation characteristics
10
+ # @option opts [Symbol] :attribute the query attribute
11
+ # @option opts [Boolean] :autogenerated whether this is an auto-generated subject update
12
+ def initialize(type, subject, opts=nil)
13
+ @type = type
14
+ @subject = subject
15
+ @attribute = Options.get(:attribute, opts)
16
+ @autogenerated = Options.get(:autogenerated, opts, false)
17
+ end
18
+
19
+ # @return [Boolean] whether this is a create or update
20
+ def save?
21
+ @type == :create or @type == :update
22
+ end
23
+
24
+ # @return [Boolean] whether this operation is an update of an auto-generated subject
25
+ def autogenerated?
26
+ @autogenerated
27
+ end
28
+
29
+ def to_s
30
+ "#{@subject.qp} #{attribute} #{type}"
31
+ end
32
+ end
33
+ end
34
+ end
@@ -1,8 +1,7 @@
1
- require 'caruby/util/log'
2
- require 'caruby/util/pretty_print'
3
- require 'caruby/util/inflector'
4
- require 'caruby/util/collection'
5
- require 'caruby/util/validation'
1
+ require 'jinx/helpers/pretty_print'
2
+ require 'jinx/helpers/inflector'
3
+ require 'jinx/helpers/collection'
4
+ require 'jinx/helpers/validation'
6
5
 
7
6
  module CaRuby
8
7
  # The Persistable mixin adds persistance capability. Every instance which includes Persistable
@@ -11,29 +10,31 @@ module CaRuby
11
10
  # @return [{Symbol => Object}] the content value hash at the point of the last snapshot
12
11
  attr_reader :snapshot
13
12
 
14
- # @param [Resource, <Resource>, nil] obj the object(s) to check
15
- # @return [Boolean] whether the given object(s) have an identifier, or the object is nil or empty
13
+ # @param [Jinx::Resource, <Jinx::Resource>, nil] obj the object(s) to check
14
+ # @return [Boolean] whether the given object(s) have an identifier
16
15
  def self.saved?(obj)
17
- if obj.collection? then
16
+ if obj.nil_or_empty? then
17
+ false
18
+ elsif obj.collection? then
18
19
  obj.all? { |ref| saved?(ref) }
19
20
  else
20
- obj.nil? or obj.identifier
21
+ !!obj.identifier
21
22
  end
22
23
  end
23
24
 
24
- # @param [Resource, <Resource>, nil] obj the object(s) to check
25
+ # @param [Jinx::Resource, <Jinx::Resource>, nil] obj the object(s) to check
25
26
  # @return [Boolean] whether at least one of the given object(s) does not have an identifier
26
27
  def self.unsaved?(obj)
27
- not saved?(obj)
28
+ not (obj.nil_or_empty? or saved?(obj))
28
29
  end
29
30
 
30
31
  # Returns the data access mediator for this domain object.
31
- # Application #{Resource} modules are required to override this method.
32
+ # Application #{Jinx::Resource} modules are required to override this method.
32
33
  #
33
34
  # @return [Database] the data access mediator for this Persistable, if any
34
35
  # @raise [DatabaseError] if the subclass does not override this method
35
36
  def database
36
- raise ValidationError.new("#{self} database is missing")
37
+ Jinx.fail(ValidationError, "#{self} database is missing")
37
38
  end
38
39
 
39
40
  # @return [PersistenceService] the database application service for this Persistable
@@ -126,25 +127,25 @@ module CaRuby
126
127
  end
127
128
 
128
129
  # @return [Boolean] whether this Persistable has a {#snapshot}
129
- def snapshot_taken?
130
- not @snapshot.nil?
130
+ def fetched?
131
+ !!@snapshot
131
132
  end
132
133
 
133
134
  # Merges the other domain object non-domain attribute values into this domain object's snapshot,
134
135
  # An existing snapshot value is replaced by the corresponding other attribute value.
135
136
  #
136
- # @param [Resource] other the source domain object
137
+ # @param [Jinx::Resource] other the source domain object
137
138
  # @raise [ValidationError] if this domain object does not have a snapshot
138
139
  def merge_into_snapshot(other)
139
- unless snapshot_taken? then
140
- raise ValidationError.new("Cannot merge #{other.qp} content into #{qp} snapshot, since #{qp} does not have a snapshot.")
140
+ if @snapshot.nil? then
141
+ Jinx.fail(ValidationError, "Cannot merge #{other.qp} content into #{qp} snapshot, since #{qp} does not have a snapshot.")
141
142
  end
142
143
  # the non-domain attribute => [target value, other value] difference hash
143
144
  delta = diff(other)
144
145
  # the difference attribute => other value hash, excluding nil other values
145
- dvh = delta.transform { |d| d.last }
146
+ dvh = delta.transform_value { |d| d.last }
146
147
  return if dvh.empty?
147
- logger.debug { "#{qp} differs from database content #{other.qp} as follows: #{delta.filter_on_key { |attr| dvh.has_key?(attr) }.qp}" }
148
+ logger.debug { "#{qp} differs from database content #{other.qp} as follows: #{delta.filter_on_key { |pa| dvh.has_key?(pa) }.qp}" }
148
149
  logger.debug { "Setting #{qp} snapshot values from other #{other.qp} values to reflect the database state: #{dvh.qp}..." }
149
150
  # update the snapshot from the other value to reflect the database state
150
151
  @snapshot.merge!(dvh)
@@ -152,18 +153,20 @@ module CaRuby
152
153
 
153
154
  # Returns whether this Persistable either doesn't have a snapshot or has changed since the last snapshot.
154
155
  # This is a conservative condition test that returns false if there is no snaphsot for this Persistable
155
- # and therefore no basis to determine whether the content changed.
156
+ # and therefore no basis to determine whether the content changed. If the attribute parameter is given,
157
+ # then only that attribute is checked for a change. Otherwise, all attributes are checked.
156
158
  #
159
+ # @param [Symbol, nil] attribute the optional attribute to check.
157
160
  # @return [Boolean] whether this Persistable's content differs from its snapshot
158
- def changed?
159
- @snapshot.nil? or not snapshot_equal_content?
161
+ def changed?(attribute=nil)
162
+ @snapshot.nil? or not snapshot_equal_content?(attribute)
160
163
  end
161
164
 
162
165
  # @return [<Symbol>] the attributes which differ between the {#snapshot} and current content
163
166
  def changed_attributes
164
167
  if @snapshot then
165
168
  ovh = value_hash(self.class.updatable_attributes)
166
- diff = @snapshot.diff(ovh) { |attr, v, ov| Resource.value_equal?(v, ov) }
169
+ diff = @snapshot.diff(ovh) { |pa, v, ov| Jinx::Resource.value_equal?(v, ov) }
167
170
  diff.keys
168
171
  else
169
172
  self.class.updatable_attributes
@@ -187,30 +190,30 @@ module CaRuby
187
190
  # @param loader [LazyLoader] the lazy loader to add
188
191
  def add_lazy_loader(loader, attributes=nil)
189
192
  # guard against invalid call
190
- if identifier.nil? then raise ValidationError.new("Cannot add lazy loader to an unfetched domain object: #{self}") end
193
+ if identifier.nil? then Jinx.fail(ValidationError, "Cannot add lazy loader to an unfetched domain object: #{self}") end
191
194
  # the attributes to lazy-load
192
195
  attributes ||= loadable_attributes
193
196
  return if attributes.empty?
194
197
  # define the reader and writer method overrides for the missing attributes
195
- attrs = attributes.select { |attr| inject_lazy_loader(attr) }
196
- logger.debug { "Lazy loader added to #{qp} attributes #{attrs.to_series}." } unless attrs.empty?
198
+ pas = attributes.select { |pa| inject_lazy_loader(pa) }
199
+ logger.debug { "Lazy loader added to #{qp} attributes #{pas.to_series}." } unless pas.empty?
197
200
  end
198
201
 
199
202
  # Returns the attributes to load on demand. The base attribute list is given by the
200
- # {Domain::Attributes#loadable_attributes} whose value is nil or empty.
203
+ # {Propertied#loadable_attributes} whose value is nil or empty.
201
204
  # In addition, if this Persistable has more than one {Domain::Dependency#owner_attributes}
202
205
  # and one is non-nil, then none of the owner attributes are loaded on demand,
203
206
  # since there can be at most one owner and ownership cannot change.
204
207
  #
205
208
  # @return [<Symbol>] the attributes to load on demand
206
209
  def loadable_attributes
207
- attrs = self.class.loadable_attributes.select { |attr| send(attr).nil_or_empty? }
210
+ pas = self.class.loadable_attributes.select { |pa| send(pa).nil_or_empty? }
208
211
  ownr_attrs = self.class.owner_attributes
209
212
  # If there is an owner, then variant owners are not loaded.
210
- if ownr_attrs.size > 1 and ownr_attrs.any? { |attr| not send(attr).nil_or_empty? } then
211
- attrs - ownr_attrs
213
+ if ownr_attrs.size > 1 and ownr_attrs.any? { |pa| not send(pa).nil_or_empty? } then
214
+ pas - ownr_attrs
212
215
  else
213
- attrs
216
+ pas
214
217
  end
215
218
  end
216
219
 
@@ -221,18 +224,83 @@ module CaRuby
221
224
  # @param [Symbol] the attribute to remove from the load list, or nil if to remove all attributes
222
225
  def remove_lazy_loader(attribute=nil)
223
226
  if attribute.nil? then
224
- return self.class.domain_attributes.each { |attr| remove_lazy_loader(attr) }
227
+ return self.class.domain_attributes.each { |pa| remove_lazy_loader(pa) }
225
228
  end
226
229
  # the modified accessor method
227
- reader, writer = self.class.attribute_metadata(attribute).accessors
230
+ reader, writer = self.class.property(attribute).accessors
228
231
  # remove the reader override
229
232
  disable_singleton_method(reader)
230
233
  # remove the writer override
231
234
  disable_singleton_method(writer)
232
235
  end
233
236
 
237
+ # Wrap +Resource.dump+ to disable the lazy-loader while printing.
238
+ def dump
239
+ do_without_lazy_loader { super }
240
+ end
241
+
242
+ # Executes the given block with the database lazy loader disabled, if any.
243
+ #
244
+ # @yield the block to execute
245
+ def do_without_lazy_loader(&block)
246
+ if database then
247
+ database.lazy_loader.disable(&block)
248
+ else
249
+ yield
250
+ end
251
+ end
252
+
253
+ # Validates this domain object and its #{Propertied#unproxied_savable_template_attributes}
254
+ # for consistency and completeness prior to a database create operation.
255
+ # An object without an identifer is valid if it contains a non-nil value for each mandatory attribute.
256
+ # Objects which have an identifier or have already been validated are skipped.
257
+ #
258
+ # A Persistable class should not override this method, but override the private {#validate_local}
259
+ # method instead.
260
+ #
261
+ # @return [Persistable] this domain object
262
+ # @raise [Jinx::ValidationError] if the object state is invalid
263
+ def validate
264
+ if identifier.nil? and not @validated then
265
+ validate_local
266
+ @validated = true
267
+ end
268
+ self.class.unproxied_savable_template_attributes.each do |pa|
269
+ send(pa).enumerate { |dep| dep.validate }
270
+ end
271
+ self
272
+ end
273
+
274
+ # Sets the default attribute values for this auto-generated domain object.
275
+ def add_defaults_autogenerated
276
+ add_defaults_recursive
277
+ end
278
+
279
+ # @return [Boolean] whether this domain object has {#searchable_attributes}
280
+ def searchable?
281
+ not searchable_attributes.nil?
282
+ end
283
+
284
+ # Returns the attributes to use for a search using this domain object as a template, determined
285
+ # as follows:
286
+ # * If this domain object has a non-nil primary key, then the primary key is the search criterion.
287
+ # * Otherwise, if this domain object has a secondary key and each key attribute value is not nil,
288
+ # then the secondary key is the search criterion.
289
+ # * Otherwise, if this domain object has an alternate key and each key attribute value is not nil,
290
+ # then the aklternate key is the search criterion.
291
+ #
292
+ # @return [<Symbol>] the attributes to use for a search on this domain object
293
+ def searchable_attributes
294
+ key_props = self.class.primary_key_attributes
295
+ return key_props if key_searchable?(key_props)
296
+ key_props = self.class.secondary_key_attributes
297
+ return key_props if key_searchable?(key_props)
298
+ key_props = self.class.alternate_key_attributes
299
+ return key_props if key_searchable?(key_props)
300
+ end
301
+
234
302
  # Returns this domain object's attributes which must be fetched to reflect the database state.
235
- # This default implementation returns the {Domain::Attributes#autogenerated_logical_dependent_attributes}
303
+ # This default implementation returns the {Propertied#autogenerated_logical_dependent_attributes}
236
304
  # if this domain object does not have an identifier, or an empty array otherwise.
237
305
  # Subclasses can override to relax or restrict the condition.
238
306
  #
@@ -251,21 +319,21 @@ module CaRuby
251
319
  #
252
320
  # @param [Database::Operation] the save operation
253
321
  # @return [<Symbol>] whether this domain object must be fetched to reflect the database state
254
- def saved_fetch_attributes(operation)
322
+ def saved_attributes_to_fetch(operation)
255
323
  # only fetch a create, not an update (note that subclasses can override this condition)
256
324
  if operation.type == :create or operation.autogenerated? then
257
325
  # Filter the class saved fetch attributes for content.
258
- self.class.saved_fetch_attributes.select { |attr| not send(attr).nil_or_empty? }
259
- else
326
+ self.class.saved_attributes_to_fetch.select { |pa| not send(pa).nil_or_empty? }
327
+ else
260
328
  Array::EMPTY_ARRAY
261
329
  end
262
330
  end
263
331
 
264
- # Relaxes the {#saved_fetch_attributes} condition for a SCG as follows:
332
+ # Relaxes the {#saved_attributes_to_fetch} condition for a SCG as follows:
265
333
  # * If the SCG status was updated from +Pending+ to +Collected+, then fetch the saved SCG event parameters.
266
334
  #
267
- # @param (see #saved_fetch_attributes)
268
- # @return (see #saved_fetch_attributes)
335
+ # @param (see #saved_attributes_to_fetch)
336
+ # @return (see #saved_attributes_to_fetch)
269
337
  def autogenerated?(operation)
270
338
  operation == :update && status_changed_to_complete? ? EVENT_PARAM_ATTRS : super
271
339
  end
@@ -275,7 +343,7 @@ module CaRuby
275
343
  operation == :update
276
344
  # Check for an attribute with a value that might need to be changed in order to
277
345
  # reflect the auto-generated database content.
278
- self.class.autogenerated_logical_dependent_attributes.select { |attr| not send(attr).nil_or_empty? }
346
+ self.class.autogenerated_logical_dependent_attributes.select { |pa| not send(pa).nil_or_empty? }
279
347
  end
280
348
 
281
349
  # Returns whether this domain object must be fetched to reflect the database state.
@@ -297,6 +365,9 @@ module CaRuby
297
365
  # @quirk caCORE A saved attribute which is cascaded but not fetched must be fetched in
298
366
  # order to reflect the database identifier in the saved object.
299
367
  #
368
+ # TODO - this method is no longeer used. Should it be? If not, remove here and in catissue
369
+ # subclasses.
370
+ #
300
371
  # @return [Boolean] whether this domain object must be fetched to reflect the database state
301
372
  def fetch_saved?
302
373
  # only fetch a create, not an update (note that subclasses can override this condition)
@@ -305,67 +376,81 @@ module CaRuby
305
376
  # reflect the auto-generated database content.
306
377
  ag_attrs = self.class.autogenerated_attributes
307
378
  return false if ag_attrs.empty?
308
- ag_attrs.any? { |attr| not send(attr).nil_or_empty? }
379
+ ag_attrs.any? { |pa| not send(pa).nil_or_empty? }
309
380
  end
310
381
 
311
- # Sets the {Domain::Attributes#volatile_nondomain_attributes} to the other fetched value,
382
+ # Sets the {Propertied#volatile_nondomain_attributes} to the other fetched value,
312
383
  # if different.
313
384
  #
314
- # @param [Resource] other the fetched domain object reflecting the database state
385
+ # @param [Jinx::Resource] other the fetched domain object reflecting the database state
315
386
  def copy_volatile_attributes(other)
316
- attrs = self.class.volatile_nondomain_attributes
317
- return if attrs.empty?
318
- logger.debug { "Merging volatile attributes #{attrs.to_series} from #{other.qp} into #{qp}..." }
319
- attrs.each do |attr|
320
- val = send(attr)
321
- oval = other.send(attr)
322
- # set the attribute to the other value if it differs from the current value
323
- unless oval == val then
324
- # if this error occurs, then there is a serious match-merge flaw
325
- if val and attr == :identifier then
326
- raise DatabaseError.new("Can't copy #{other} to #{self} with different identifier")
327
- end
328
- # overwrite the current attribute value
329
- set_attribute(attr, oval)
330
- logger.debug { "Set #{qp} volatile #{attr} to the fetched #{other.qp} database value #{oval.qp}." }
387
+ pas = self.class.volatile_nondomain_attributes
388
+ return if pas.empty?
389
+ logger.debug { "Merging volatile attributes #{pas.to_series} from #{other.qp} into #{qp}..." }
390
+ pas.each do |pa|
391
+ val = send(pa)
392
+ oval = other.send(pa)
393
+ if val.nil? then
394
+ # Overwrite the current attribute value.
395
+ set_property_value(pa, oval)
396
+ logger.debug { "Set #{qp} volatile #{pa} to the fetched #{other.qp} database value #{oval.qp}." }
397
+ elsif oval != val and pa == :identifier then
398
+ # If this error occurs, then there is a serious match-merge flaw.
399
+ Jinx.fail(DatabaseError, "Can't copy #{other} to #{self} with different identifier")
331
400
  end
332
401
  end
333
402
  end
334
403
 
335
404
  private
336
405
 
406
+ # @return [Boolean] whether the given key attributes is non-empty and each attribute in the key has a non-nil value
407
+ def key_searchable?(attributes)
408
+ not (attributes.empty? or attributes.any? { |pa| send(pa).nil? })
409
+ end
410
+
337
411
  # Returns whether the {#snapshot} and current content are equal.
338
412
  # The attribute values _v_ and _ov_ of the snapshot and current content, resp., are
339
- # compared with equality determined by {Resource.value_equal?}.
413
+ # compared with equality determined by {Jinx::Resource.value_equal?}.
340
414
  #
415
+ # @param (see #changed?)
341
416
  # @return [Boolean] whether the {#snapshot} and current content are equal
342
- def snapshot_equal_content?
343
- vh = @snapshot
344
- ovh = value_hash(self.class.updatable_attributes)
417
+ def snapshot_equal_content?(attribute=nil)
418
+ if attribute then
419
+ value = send(attribute)
420
+ ssval = @snapshot[attribute]
421
+ eq = Jinx::Resource.value_equal?(value, ssval)
422
+ unless eq then
423
+ logger.debug { "#{qp} #{attribute} snapshot value #{ssval.qp} differs from the current value #{value.qp}." }
424
+ end
425
+ return eq
426
+ end
427
+
428
+ vh = value_hash(self.class.updatable_attributes)
345
429
 
346
- # KLUDGE TODO - confirm this is still a problem and fix
347
- # In Galena frozen migration example, SpecimenPosition snapshot doesn't include identifier; work around this here
348
- # This could be related to the problem of an abstract DomainObject not being added as a domain module class. See the
349
- # ClinicalTrials::Resource for more info.
350
- if ovh[:identifier] and not @snapshot[:identifier] then
351
- @snapshot[:identifier] = ovh[:identifier]
430
+ # KLUDGE TODO - confirm this is still a problem and fix.
431
+ # In the Galena frozen migration example, the SpecimenPosition snapshot doesn't include the identifier.
432
+ # This bug could be related to the problem of an abstract DomainObject not being added as a domain module class.
433
+ # Work around this here by setting the snapshot identifier.
434
+ # See the ClinicalTrials::Jinx::Resource rubydoc for more info.
435
+ if vh[:identifier] and not @snapshot[:identifier] then
436
+ @snapshot[:identifier] = vh[:identifier]
352
437
  end
353
438
  # END OF KLUDGE
354
439
 
355
- if vh.size < ovh.size then
356
- attr, oval = ovh.detect { |a, v| not vh.has_key?(a) }
357
- logger.debug { "#{qp} is missing snapshot #{attr} compared to the current value #{oval.qp}." }
440
+ if @snapshot.size < vh.size then
441
+ pa, pv = vh.detect { |a, v| not @snapshot.has_key?(a) }
442
+ logger.debug { "#{qp} is missing snapshot #{pa} compared to the current value #{pv.qp}." }
358
443
  false
359
- elsif vh.size > ovh.size then
360
- attr, value = vh.detect { |a, v| not ovh.has_key?(a) }
361
- logger.debug { "#{qp} has snapshot #{attr} value #{value.qp} not found in current content." }
444
+ elsif @snapshot.size > vh.size then
445
+ pa, value = @snapshot.detect { |a, v| not vh.has_key?(a) }
446
+ logger.debug { "#{qp} has snapshot #{pa} value #{value.qp} not found in current content." }
362
447
  false
363
448
  else
364
- vh.all? do |attr, value|
365
- oval = ovh[attr]
366
- eq = Resource.value_equal?(oval, value)
449
+ @snapshot.all? do |pa, ssval|
450
+ pv = vh[pa]
451
+ eq = Jinx::Resource.value_equal?(pv, ssval)
367
452
  unless eq then
368
- logger.debug { "#{qp} #{attr} snapshot value #{value.qp} differs from the current value #{oval.qp}." }
453
+ logger.debug { "#{qp} #{pa} snapshot value #{ssval.qp} differs from the current value #{pv.qp}." }
369
454
  end
370
455
  eq
371
456
  end
@@ -381,7 +466,7 @@ module CaRuby
381
466
  # bail if there is already a value
382
467
  return false if attribute_loaded?(attribute)
383
468
  # the accessor methods to modify
384
- reader, writer = self.class.attribute_metadata(attribute).accessors
469
+ reader, writer = self.class.property(attribute).accessors
385
470
  # The singleton attribute reader method loads the reference once and thenceforth calls the
386
471
  # standard reader.
387
472
  instance_eval "def #{reader}; load_reference(:#{attribute}); end"
@@ -409,21 +494,21 @@ module CaRuby
409
494
  # bypass the singleton method and call the class instance method if the lazy loader is disabled
410
495
  return transient_value(attribute) unless ldr.enabled?
411
496
 
412
- # Disable lazy loading first for the attribute, since the reader method is called by the loader.
497
+ # First disable lazy loading for the attribute, since the reader method is called by the loader.
413
498
  remove_lazy_loader(attribute)
414
499
  # load the fetched value
415
500
  merged = ldr.load(self, attribute)
416
501
 
417
502
  # update dependent snapshots if necessary
418
- attr_md = self.class.attribute_metadata(attribute)
419
- if attr_md.dependent? then
503
+ pa = self.class.property(attribute)
504
+ if pa.dependent? then
420
505
  # the owner attribute
421
- oattr = attr_md.inverse
506
+ oattr = pa.inverse
422
507
  if oattr then
423
508
  # update dependent snapshot with the owner, since the owner snapshot is taken when fetched but the
424
509
  # owner might be set when the fetched dependent is merged into the owner dependent attribute.
425
510
  merged.enumerate do |dep|
426
- if dep.snapshot_taken? then
511
+ if dep.fetched? then
427
512
  dep.snapshot[oattr] = self
428
513
  logger.debug { "Updated the #{qp} fetched #{attribute} dependent #{dep.qp} snapshot with #{oattr} value #{qp}." }
429
514
  end