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.
- data/Gemfile +9 -0
- data/History.md +5 -1
- data/lib/caruby.rb +3 -5
- data/lib/caruby/caruby-src.tar.gz +0 -0
- data/lib/caruby/database.rb +53 -69
- data/lib/caruby/database/application_service.rb +25 -0
- data/lib/caruby/database/cache.rb +60 -0
- data/lib/caruby/database/fetched_matcher.rb +52 -38
- data/lib/caruby/database/lazy_loader.rb +4 -4
- data/lib/caruby/database/operation.rb +34 -0
- data/lib/caruby/database/persistable.rb +171 -86
- data/lib/caruby/database/persistence_service.rb +32 -34
- data/lib/caruby/database/persistifier.rb +100 -43
- data/lib/caruby/database/reader.rb +107 -85
- data/lib/caruby/database/reader_template_builder.rb +60 -0
- data/lib/caruby/database/saved_matcher.rb +3 -3
- data/lib/caruby/database/sql_executor.rb +88 -17
- data/lib/caruby/database/writer.rb +213 -177
- data/lib/caruby/database/writer_template_builder.rb +334 -0
- data/lib/caruby/{util → helpers}/controlled_value.rb +0 -0
- data/lib/caruby/{util → helpers}/coordinate.rb +4 -4
- data/lib/caruby/{util → helpers}/person.rb +3 -3
- data/lib/caruby/{util → helpers}/properties.rb +7 -9
- data/lib/caruby/{util → helpers}/roman.rb +2 -2
- data/lib/caruby/{util → helpers}/version.rb +1 -1
- data/lib/caruby/json/deserializer.rb +2 -2
- data/lib/caruby/json/serializer.rb +49 -7
- data/lib/caruby/metadata.rb +30 -0
- data/lib/caruby/metadata/java_property.rb +21 -0
- data/lib/caruby/metadata/propertied.rb +191 -0
- data/lib/caruby/metadata/property.rb +22 -0
- data/lib/caruby/metadata/property_characteristics.rb +201 -0
- data/lib/caruby/migration/migratable.rb +11 -182
- data/lib/caruby/rdbi/driver/jdbc.rb +446 -0
- data/lib/caruby/resource.rb +20 -823
- data/lib/caruby/version.rb +1 -1
- data/test/lib/caruby/database/cache_test.rb +54 -0
- data/test/lib/caruby/{util → helpers}/controlled_value_test.rb +3 -5
- data/test/lib/caruby/{util → helpers}/person_test.rb +4 -6
- data/test/lib/caruby/helpers/properties_test.rb +34 -0
- data/test/lib/caruby/{util → helpers}/roman_test.rb +2 -3
- data/test/lib/caruby/{util → helpers}/version_test.rb +2 -3
- data/test/lib/helper.rb +7 -0
- metadata +161 -214
- data/lib/caruby/cli/application.rb +0 -36
- data/lib/caruby/cli/command.rb +0 -202
- data/lib/caruby/csv/csv_mapper.rb +0 -159
- data/lib/caruby/csv/csvio.rb +0 -203
- data/lib/caruby/database/search_template_builder.rb +0 -56
- data/lib/caruby/database/store_template_builder.rb +0 -278
- data/lib/caruby/domain.rb +0 -193
- data/lib/caruby/domain/attribute.rb +0 -584
- data/lib/caruby/domain/attributes.rb +0 -628
- data/lib/caruby/domain/dependency.rb +0 -225
- data/lib/caruby/domain/id_alias.rb +0 -22
- data/lib/caruby/domain/importer.rb +0 -183
- data/lib/caruby/domain/introspection.rb +0 -176
- data/lib/caruby/domain/inverse.rb +0 -172
- data/lib/caruby/domain/inversible.rb +0 -90
- data/lib/caruby/domain/java_attribute.rb +0 -173
- data/lib/caruby/domain/merge.rb +0 -185
- data/lib/caruby/domain/metadata.rb +0 -142
- data/lib/caruby/domain/mixin.rb +0 -35
- data/lib/caruby/domain/properties.rb +0 -95
- data/lib/caruby/domain/reference_visitor.rb +0 -428
- data/lib/caruby/domain/uniquify.rb +0 -50
- data/lib/caruby/import/java.rb +0 -387
- data/lib/caruby/migration/migrator.rb +0 -918
- data/lib/caruby/migration/resource_module.rb +0 -9
- data/lib/caruby/migration/uniquify.rb +0 -17
- data/lib/caruby/util/attribute_path.rb +0 -44
- data/lib/caruby/util/cache.rb +0 -56
- data/lib/caruby/util/class.rb +0 -149
- data/lib/caruby/util/collection.rb +0 -1152
- data/lib/caruby/util/domain_extent.rb +0 -46
- data/lib/caruby/util/file_separator.rb +0 -65
- data/lib/caruby/util/inflector.rb +0 -27
- data/lib/caruby/util/log.rb +0 -95
- data/lib/caruby/util/math.rb +0 -12
- data/lib/caruby/util/merge.rb +0 -59
- data/lib/caruby/util/module.rb +0 -18
- data/lib/caruby/util/options.rb +0 -97
- data/lib/caruby/util/partial_order.rb +0 -35
- data/lib/caruby/util/pretty_print.rb +0 -204
- data/lib/caruby/util/stopwatch.rb +0 -74
- data/lib/caruby/util/topological_sync_enumerator.rb +0 -62
- data/lib/caruby/util/transitive_closure.rb +0 -55
- data/lib/caruby/util/tree.rb +0 -48
- data/lib/caruby/util/trie.rb +0 -37
- data/lib/caruby/util/uniquifier.rb +0 -30
- data/lib/caruby/util/validation.rb +0 -20
- data/lib/caruby/util/visitor.rb +0 -365
- data/lib/caruby/util/weak_hash.rb +0 -36
- data/test/lib/caruby/csv/csv_mapper_test.rb +0 -40
- data/test/lib/caruby/csv/csvio_test.rb +0 -69
- data/test/lib/caruby/database/persistable_test.rb +0 -92
- data/test/lib/caruby/domain/domain_test.rb +0 -112
- data/test/lib/caruby/domain/inversible_test.rb +0 -99
- data/test/lib/caruby/domain/reference_visitor_test.rb +0 -130
- data/test/lib/caruby/import/java_test.rb +0 -80
- data/test/lib/caruby/import/mixed_case_test.rb +0 -14
- data/test/lib/caruby/migration/test_case.rb +0 -102
- data/test/lib/caruby/test_case.rb +0 -230
- data/test/lib/caruby/util/cache_test.rb +0 -23
- data/test/lib/caruby/util/class_test.rb +0 -61
- data/test/lib/caruby/util/collection_test.rb +0 -398
- data/test/lib/caruby/util/command_test.rb +0 -55
- data/test/lib/caruby/util/domain_extent_test.rb +0 -60
- data/test/lib/caruby/util/file_separator_test.rb +0 -30
- data/test/lib/caruby/util/inflector_test.rb +0 -12
- data/test/lib/caruby/util/lazy_hash_test.rb +0 -34
- data/test/lib/caruby/util/merge_test.rb +0 -83
- data/test/lib/caruby/util/module_test.rb +0 -25
- data/test/lib/caruby/util/options_test.rb +0 -59
- data/test/lib/caruby/util/partial_order_test.rb +0 -42
- data/test/lib/caruby/util/pretty_print_test.rb +0 -85
- data/test/lib/caruby/util/properties_test.rb +0 -50
- data/test/lib/caruby/util/stopwatch_test.rb +0 -18
- data/test/lib/caruby/util/topological_sync_enumerator_test.rb +0 -69
- data/test/lib/caruby/util/transitive_closure_test.rb +0 -67
- data/test/lib/caruby/util/tree_test.rb +0 -23
- data/test/lib/caruby/util/trie_test.rb +0 -14
- data/test/lib/caruby/util/visitor_test.rb +0 -278
- data/test/lib/caruby/util/weak_hash_test.rb +0 -45
- data/test/lib/examples/clinical_trials/migration/migration_test.rb +0 -58
- 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
|
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 '
|
2
|
-
require '
|
3
|
-
require '
|
4
|
-
require '
|
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
|
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.
|
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.
|
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
|
-
|
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
|
130
|
-
|
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
|
-
|
140
|
-
|
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.
|
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 { |
|
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) { |
|
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
|
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
|
-
|
196
|
-
logger.debug { "Lazy loader added to #{qp} attributes #{
|
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
|
-
# {
|
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
|
-
|
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? { |
|
211
|
-
|
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
|
-
|
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 { |
|
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.
|
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 {
|
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
|
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.
|
259
|
-
|
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 {#
|
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 #
|
268
|
-
# @return (see #
|
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 { |
|
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? { |
|
379
|
+
ag_attrs.any? { |pa| not send(pa).nil_or_empty? }
|
309
380
|
end
|
310
381
|
|
311
|
-
# Sets the {
|
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
|
-
|
317
|
-
return if
|
318
|
-
logger.debug { "Merging volatile attributes #{
|
319
|
-
|
320
|
-
val = send(
|
321
|
-
oval = other.send(
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
#
|
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
|
-
|
344
|
-
|
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
|
348
|
-
# This could be related to the problem of an abstract DomainObject not being added as a domain module class.
|
349
|
-
#
|
350
|
-
|
351
|
-
|
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
|
356
|
-
|
357
|
-
logger.debug { "#{qp} is missing snapshot #{
|
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
|
360
|
-
|
361
|
-
logger.debug { "#{qp} has snapshot #{
|
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
|
-
|
365
|
-
|
366
|
-
eq = Resource.value_equal?(
|
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} #{
|
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.
|
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
|
-
#
|
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
|
-
|
419
|
-
if
|
503
|
+
pa = self.class.property(attribute)
|
504
|
+
if pa.dependent? then
|
420
505
|
# the owner attribute
|
421
|
-
oattr =
|
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.
|
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
|