caruby-core 1.4.7 → 1.4.9
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +11 -0
- data/README.md +1 -1
- data/lib/caruby/cli/command.rb +27 -3
- data/lib/caruby/csv/csv_mapper.rb +2 -0
- data/lib/caruby/csv/csvio.rb +187 -169
- data/lib/caruby/database.rb +33 -16
- data/lib/caruby/database/lazy_loader.rb +23 -23
- data/lib/caruby/database/persistable.rb +32 -18
- data/lib/caruby/database/persistence_service.rb +20 -7
- data/lib/caruby/database/reader.rb +22 -21
- data/lib/caruby/database/search_template_builder.rb +7 -9
- data/lib/caruby/database/sql_executor.rb +52 -27
- data/lib/caruby/database/store_template_builder.rb +18 -13
- data/lib/caruby/database/writer.rb +107 -44
- data/lib/caruby/domain/attribute_metadata.rb +35 -25
- data/lib/caruby/domain/java_attribute_metadata.rb +43 -20
- data/lib/caruby/domain/merge.rb +9 -5
- data/lib/caruby/domain/reference_visitor.rb +4 -3
- data/lib/caruby/domain/resource_attributes.rb +52 -12
- data/lib/caruby/domain/resource_dependency.rb +129 -42
- data/lib/caruby/domain/resource_introspection.rb +1 -1
- data/lib/caruby/domain/resource_inverse.rb +20 -3
- data/lib/caruby/domain/resource_metadata.rb +20 -4
- data/lib/caruby/domain/resource_module.rb +190 -124
- data/lib/caruby/import/java.rb +39 -19
- data/lib/caruby/migration/migratable.rb +31 -6
- data/lib/caruby/migration/migrator.rb +126 -40
- data/lib/caruby/migration/uniquify.rb +0 -1
- data/lib/caruby/resource.rb +28 -5
- data/lib/caruby/util/attribute_path.rb +0 -2
- data/lib/caruby/util/class.rb +8 -5
- data/lib/caruby/util/collection.rb +5 -3
- data/lib/caruby/util/domain_extent.rb +0 -3
- data/lib/caruby/util/options.rb +10 -9
- data/lib/caruby/util/person.rb +41 -12
- data/lib/caruby/util/pretty_print.rb +1 -1
- data/lib/caruby/util/validation.rb +0 -28
- data/lib/caruby/version.rb +1 -1
- data/test/lib/caruby/import/java_test.rb +26 -9
- data/test/lib/caruby/migration/test_case.rb +103 -0
- data/test/lib/caruby/test_case.rb +231 -0
- data/test/lib/caruby/util/class_test.rb +2 -2
- data/test/lib/caruby/util/visitor_test.rb +3 -2
- data/test/lib/examples/galena/clinical_trials/migration/participant_test.rb +28 -0
- data/test/lib/examples/galena/clinical_trials/migration/test_case.rb +40 -0
- metadata +195 -170
- data/lib/caruby/domain/attribute_initializer.rb +0 -16
- data/test/lib/caruby/util/validation_test.rb +0 -14
@@ -4,10 +4,6 @@ require 'caruby/util/pretty_print'
|
|
4
4
|
module CaRuby
|
5
5
|
# SearchTemplateBuilder builds a template suitable for a caCORE saarch database operation.
|
6
6
|
class SearchTemplateBuilder
|
7
|
-
def initialize(database)
|
8
|
-
@database = database
|
9
|
-
end
|
10
|
-
|
11
7
|
# Returns a template for matching the domain object obj and the optional hash values.
|
12
8
|
# The default hash attributes are the {ResourceAttributes#searchable_attributes}.
|
13
9
|
# The template includes only the non-domain attributes of the hash references.
|
@@ -42,16 +38,18 @@ module CaRuby
|
|
42
38
|
|
43
39
|
private
|
44
40
|
|
45
|
-
# Sets the template attribute to a new search reference object created from
|
46
|
-
# The reference contains only the source identifier
|
47
|
-
#
|
41
|
+
# Sets the template attribute to a new search reference object created from the given
|
42
|
+
# source domain object. The reference contains only the source identifier, if it exists,
|
43
|
+
# or the source non-domain attributes otherwise.
|
44
|
+
#
|
45
|
+
# @return [Resource] the search reference
|
48
46
|
def add_search_template_reference(template, source, attribute)
|
49
47
|
ref = source.identifier ? source.copy(:identifier) : source.copy
|
50
48
|
# Disable inverse integrity, since the template attribute assignment might have added a reference
|
51
49
|
# from ref to template, which introduces a template => ref => template cycle that causes a caCORE
|
52
50
|
# search infinite loop. Use the Java property writer instead.
|
53
|
-
|
54
|
-
template.send(
|
51
|
+
wtr = template.class.attribute_metadata(attribute).property_writer
|
52
|
+
template.send(wtr, ref)
|
55
53
|
logger.debug { "Search reference parameter #{attribute} for #{template.qp} set to #{ref} copied from #{source.qp}" }
|
56
54
|
ref
|
57
55
|
end
|
@@ -13,53 +13,78 @@ module CaRuby
|
|
13
13
|
class SQLExecutor
|
14
14
|
# Creates a new SQLExecutor with the given options.
|
15
15
|
#
|
16
|
-
# The default
|
17
|
-
# defaults to
|
16
|
+
# The default database host is the application :host property value, which in turn
|
17
|
+
# defaults to +localhost+.
|
18
18
|
#
|
19
|
-
# The default
|
19
|
+
# The default database type is +mysql+. The optional :database_port property overrides
|
20
20
|
# the default port for the database type.
|
21
21
|
#
|
22
|
-
# The default
|
22
|
+
# The default database driver is +jdbc:mysql+ for MySQL, +Oracle+ for Oracle.
|
23
|
+
# The default database driver class is +com.mysql.jdbc.Driver+ for MySQL,
|
24
|
+
# +oracle.jdbc.OracleDriver+ for Oracle.
|
23
25
|
#
|
24
|
-
# @
|
25
|
-
# @option
|
26
|
-
# @option
|
27
|
-
# @option
|
28
|
-
# @option
|
29
|
-
# @option
|
30
|
-
# @option
|
31
|
-
#
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
26
|
+
# @param [Hash] opts the connect options
|
27
|
+
# @option opts [String] :database the mandatory database name
|
28
|
+
# @option opts [String] :database_user the mandatory database username (not the application login name)
|
29
|
+
# @option opts [String] :database_password the optional database password (not the application login password)
|
30
|
+
# @option opts [String] :database_host the optional database host
|
31
|
+
# @option opts [Integer] :database_port the optional database port number
|
32
|
+
# @option opts [String] :database_type the optional DBI database type, e.g. +mysql+
|
33
|
+
# @option opts [String] :database_driver the optional DBI connect driver string, e.g. +jdbc:mysql+
|
34
|
+
# @option opts [String] :database_driver_class the optional DBI connect driver class name
|
35
|
+
# @raise [CaRuby::ConfigurationError] if an option is invalid
|
36
|
+
def initialize(opts)
|
37
|
+
app_host = Options.get(:host, opts, 'localhost')
|
38
|
+
db_host = Options.get(:database_host, opts, app_host)
|
39
|
+
db_type = Options.get(:database_type, opts, 'mysql')
|
40
|
+
db_driver = Options.get(:database_driver, opts) { default_driver_string(db_type) }
|
41
|
+
db_port = Options.get(:database_port, opts) { default_port(db_type) }
|
42
|
+
db_name = Options.get(:database, opts) { raise_missing_option_exception(:database) }
|
39
43
|
@address = "dbi:#{db_driver}://#{db_host}:#{db_port}/#{db_name}"
|
40
|
-
@username = Options.get(:database_user,
|
41
|
-
@password = Options.get(:database_password,
|
44
|
+
@username = Options.get(:database_user, opts) { raise_missing_option_exception(:database_user) }
|
45
|
+
@password = Options.get(:database_password, opts)
|
46
|
+
@driver_class = Options.get(:database_driver_class, opts, default_driver_class(db_type))
|
47
|
+
# The effective connection options.
|
48
|
+
eff_opts = {
|
49
|
+
:database => db_name,
|
50
|
+
:database_host => db_host,
|
51
|
+
:database_user => @username,
|
52
|
+
:database_type => db_type,
|
53
|
+
:database_port => db_port,
|
54
|
+
:database_driver => db_driver,
|
55
|
+
:database_driver_class => @driver_class
|
56
|
+
}
|
57
|
+
logger.debug { "Database connection options (excluding password): #{eff_opts.qp}" }
|
42
58
|
end
|
43
59
|
|
44
60
|
# Connects to the database, yields the DBI handle to the given block and disconnects.
|
45
61
|
#
|
46
62
|
# @return [Array] the execution result
|
47
63
|
def execute
|
48
|
-
|
49
|
-
result = DBI.connect(@address, @username, @password, "driver"=>"com.mysql.jdbc.Driver") { |dbh| yield dbh }
|
50
|
-
logger.debug { "Disconnected from the database." }
|
51
|
-
result
|
64
|
+
DBI.connect(@address, @username, @password, 'driver'=> @driver_class) { |dbh| yield dbh }
|
52
65
|
end
|
53
66
|
|
54
67
|
private
|
68
|
+
|
69
|
+
MYSQL_DRIVER_CLASS_NAME = 'com.mysql.jdbc.Driver'
|
70
|
+
|
71
|
+
ORACLE_DRIVER_CLASS_NAME = 'oracle.jdbc.OracleDriver'
|
55
72
|
|
56
73
|
def default_driver_string(db_type)
|
57
74
|
case db_type.downcase
|
58
|
-
when 'mysql' then '
|
75
|
+
when 'mysql' then 'Jdbc:mysql'
|
59
76
|
when 'oracle' then 'Oracle'
|
60
77
|
else raise CaRuby::ConfigurationError.new("Default database connection driver string could not be determined for database type #{db_type}")
|
61
78
|
end
|
62
79
|
end
|
80
|
+
|
81
|
+
def default_driver_class(db_type)
|
82
|
+
case db_type.downcase
|
83
|
+
when 'mysql' then MYSQL_DRIVER_CLASS_NAME
|
84
|
+
when 'oracle' then ORACLE_DRIVER_CLASS_NAME
|
85
|
+
else raise CaRuby::ConfigurationError.new("Default database connection driver class could not be determined for database type #{db_type}")
|
86
|
+
end
|
87
|
+
end
|
63
88
|
|
64
89
|
def default_port(db_type)
|
65
90
|
case db_type.downcase
|
@@ -70,7 +95,7 @@ module CaRuby
|
|
70
95
|
end
|
71
96
|
|
72
97
|
def raise_missing_option_exception(option)
|
73
|
-
raise CaRuby::ConfigurationError.new("
|
98
|
+
raise CaRuby::ConfigurationError.new("Database connection property not found: #{option}")
|
74
99
|
end
|
75
100
|
end
|
76
101
|
end
|
@@ -113,11 +113,11 @@ 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#
|
116
|
+
# * The {ResourceAttributes#unproxied_save_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#
|
120
|
+
# * The {ResourceAttributes#proxied_save_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
|
#
|
@@ -130,15 +130,16 @@ module CaRuby
|
|
130
130
|
# @return [<Symbol>] the reference attributes to include in the update template
|
131
131
|
def savable_cascaded_attributes(obj)
|
132
132
|
# The starting set of candidate attributes is the unproxied cascaded references.
|
133
|
-
unproxied = savable_attributes(obj, obj.class.
|
133
|
+
unproxied = savable_attributes(obj, obj.class.unproxied_save_template_attributes)
|
134
134
|
# The proxied attributes to save.
|
135
135
|
proxied = savable_proxied_attributes(obj)
|
136
136
|
# The combined set of savable attributes
|
137
137
|
proxied.empty? ? unproxied : unproxied + proxied
|
138
138
|
end
|
139
139
|
|
140
|
-
#
|
141
|
-
# * If the save operation is a create, then exclude the
|
140
|
+
# Filters the given attributes, if necessary, to exclude attributes as follows:
|
141
|
+
# * If the save operation is a create, then exclude the
|
142
|
+
# {AttributeMetadata#autogenerated_on_create?} attributes.
|
142
143
|
#
|
143
144
|
# @param [Resource] obj the visited domain object
|
144
145
|
# @param [ResourceAttributes::Filter] the savable attribute filter
|
@@ -148,7 +149,7 @@ module CaRuby
|
|
148
149
|
return attributes if @subject.identifier
|
149
150
|
# This is a create: ignore the optional auto-generated attributes.
|
150
151
|
mas = obj.mandatory_attributes.to_set
|
151
|
-
attributes.compose { |attr_md| mas.include?(attr_md.to_sym) or not attr_md.
|
152
|
+
attributes.compose { |attr_md| mas.include?(attr_md.to_sym) or not attr_md.autogenerated_on_create? }
|
152
153
|
end
|
153
154
|
|
154
155
|
# Composes the given attributes, if necessary, to exclude attributes as follows:
|
@@ -177,7 +178,7 @@ module CaRuby
|
|
177
178
|
def savable_proxied_attributes(obj)
|
178
179
|
# Include a proxied reference only if the proxied dependents have an identifier,
|
179
180
|
# since those without an identifer are created separately via the proxy.
|
180
|
-
obj.class.
|
181
|
+
obj.class.proxied_save_template_attributes.reject do |attr|
|
181
182
|
ref = obj.send(attr)
|
182
183
|
case ref
|
183
184
|
when Enumerable then ref.any? { |dep| not dep.identifier }
|
@@ -199,7 +200,7 @@ module CaRuby
|
|
199
200
|
# references via the proxy create before building the update template.
|
200
201
|
def copy_proxied_save_references(obj, template)
|
201
202
|
return unless obj.identifier
|
202
|
-
obj.class.
|
203
|
+
obj.class.proxied_save_template_attributes.each do |attr|
|
203
204
|
# the proxy source
|
204
205
|
ref = obj.send(attr)
|
205
206
|
case ref
|
@@ -253,20 +254,24 @@ module CaRuby
|
|
253
254
|
# add qualified prerequisite attribute references
|
254
255
|
stbl.send(attr).enumerate do |ref|
|
255
256
|
# Add the prerequisite if it satisfies the prerequisite? condition.
|
256
|
-
prereqs << ref if prerequisite?(ref, obj)
|
257
|
+
prereqs << ref if prerequisite?(ref, obj, attr)
|
257
258
|
end
|
258
259
|
end
|
259
|
-
end
|
260
|
+
end
|
260
261
|
prereqs
|
261
262
|
end
|
262
263
|
|
263
|
-
# A referenced object is a target object save prerequisite if
|
264
|
-
#
|
264
|
+
# A referenced object is a target object save prerequisite if none of the follwing is true:
|
265
|
+
# * it is the target object
|
266
|
+
# * it was already created
|
267
|
+
# * it is in an immediate or recursive dependent of the target object
|
268
|
+
# * the current save operation is in the context of creating the referenced object
|
265
269
|
#
|
266
270
|
# @param [Resource] ref the reference to check
|
267
271
|
# @param [Resource] obj the object being stored
|
272
|
+
# @param [Symbol] attribute the reference attribute
|
268
273
|
# @return [Boolean] whether the reference should exist before storing the object
|
269
|
-
def prerequisite?(ref, obj)
|
274
|
+
def prerequisite?(ref, obj, attribute)
|
270
275
|
not (ref == obj or ref.identifier or ref.owner_ancestor?(obj))
|
271
276
|
end
|
272
277
|
end
|
@@ -113,7 +113,7 @@ module CaRuby
|
|
113
113
|
# @return [Resource] obj
|
114
114
|
# @raise [DatabaseError] if the database operation fails
|
115
115
|
def save(obj)
|
116
|
-
logger.debug { "
|
116
|
+
logger.debug { "Saving #{obj}..." }
|
117
117
|
# if obj exists then update it, otherwise create it
|
118
118
|
exists?(obj) ? update(obj) : create(obj)
|
119
119
|
end
|
@@ -140,8 +140,10 @@ module CaRuby
|
|
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 }
|
142
142
|
end
|
143
|
+
|
144
|
+
private
|
143
145
|
|
144
|
-
# Returns whether there is already the given
|
146
|
+
# Returns whether there is already the given object operation in progress that is not in the scope of
|
145
147
|
# an operation performed on a dependent obj owner, i.e. a second obj save operation of the same type
|
146
148
|
# is only allowed if the obj operation was delegated to an owner save which in turn saves the dependent
|
147
149
|
# obj.
|
@@ -153,9 +155,7 @@ module CaRuby
|
|
153
155
|
@operations.any? { |op| op.type == operation and op.subject == obj } and
|
154
156
|
not obj.owner_ancestor?(@operations.last.subject)
|
155
157
|
end
|
156
|
-
|
157
|
-
private
|
158
|
-
|
158
|
+
|
159
159
|
# Creates obj as follows:
|
160
160
|
# * if obj has an uncreated owner, then store the owner, which in turn will create a physical dependent
|
161
161
|
# * otherwise, create a storable template. The template is a copy of obj containing a recursive copy
|
@@ -170,11 +170,14 @@ module CaRuby
|
|
170
170
|
# add obj to the transients set
|
171
171
|
@transients << obj
|
172
172
|
begin
|
173
|
-
# A dependent
|
174
|
-
# Otherwise, create
|
175
|
-
|
176
|
-
|
173
|
+
# A dependent is created by saving the owner.
|
174
|
+
# Otherwise, create the object from a template.
|
175
|
+
owner = cascaded_owner(obj)
|
176
|
+
result = create_dependent(owner, obj) if owner
|
177
|
+
result ||= create_from_template(obj)
|
178
|
+
if result.nil? then
|
177
179
|
raise DatabaseError.new("#{obj.class.qp} is not creatable in context #{print_operations}")
|
180
|
+
end
|
178
181
|
ensure
|
179
182
|
# since obj now has an id, removed from transients set
|
180
183
|
@transients.delete(obj)
|
@@ -192,18 +195,16 @@ module CaRuby
|
|
192
195
|
#
|
193
196
|
#@param [Resource] dep the dependent domain object to create
|
194
197
|
# @return [Resource] dep
|
195
|
-
def
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
logger.debug { "Ensuring that dependent #{dep.qp} owner #{ownr.qp} exists..." }
|
201
|
-
ensure_exists(ownr)
|
198
|
+
def create_dependent(owner, dep)
|
199
|
+
if owner.identifier.nil? then
|
200
|
+
logger.debug { "Adding #{owner.qp} dependent #{dep.qp} defaults..." }
|
201
|
+
logger.debug { "Ensuring that dependent #{dep.qp} owner #{owner.qp} exists..." }
|
202
|
+
ensure_exists(owner)
|
202
203
|
end
|
203
204
|
|
204
205
|
# If the dependent was created as a side-effect of creating the owner, then we are done.
|
205
206
|
if dep.identifier then
|
206
|
-
logger.debug { "Created dependent #{dep.qp} by saving owner #{
|
207
|
+
logger.debug { "Created dependent #{dep.qp} by saving owner #{owner.qp}." }
|
207
208
|
return dep
|
208
209
|
end
|
209
210
|
|
@@ -279,6 +280,7 @@ module CaRuby
|
|
279
280
|
# The create template. Independent saved references are created as necessary.
|
280
281
|
tmpl = build_create_template(obj)
|
281
282
|
save_with_template(obj, tmpl) { |svc| svc.create(tmpl) }
|
283
|
+
|
282
284
|
# If obj is a top-level create, then ensure that remaining references exist.
|
283
285
|
if @operations.first.subject == obj then
|
284
286
|
refs = obj.references.reject { |ref| ref.identifier }
|
@@ -340,20 +342,34 @@ module CaRuby
|
|
340
342
|
end
|
341
343
|
end
|
342
344
|
|
343
|
-
# Returns whether the given creatable domain attribute with value obj
|
344
|
-
#
|
345
|
-
#
|
345
|
+
# Returns whether the given creatable domain attribute with value obj satisfies
|
346
|
+
# each of the following conditions:
|
347
|
+
# * the attribute is {AttributeMetadata#independent?}
|
348
|
+
# * the attribute is not an {AttributeMetadata#owner?}
|
349
|
+
# * the obj value is unsaved
|
350
|
+
# * the attribute is not mandatory
|
351
|
+
# * the attribute references a {#pending_create?} save context.
|
346
352
|
#
|
347
353
|
# @param obj (see #create)
|
348
354
|
# @param [AttributeMetadata] attr_md candidate attribute metadata
|
349
355
|
# @return [Boolean] whether the attribute should not be included in the create template
|
350
356
|
def exclude_pending_create_attribute?(obj, attr_md)
|
351
|
-
attr_md.
|
357
|
+
attr_md.independent? and
|
358
|
+
not attr_md.owner? and
|
352
359
|
obj.identifier.nil? and
|
353
360
|
not obj.mandatory_attributes.include?(attr_md.to_sym) and
|
354
|
-
|
355
|
-
|
356
|
-
|
361
|
+
exclude_pending_create_value?(obj.send(attr_md.to_sym))
|
362
|
+
end
|
363
|
+
|
364
|
+
# @param [Resource, <Resource>, nil] value the referenced value
|
365
|
+
# @return [Boolean] whether the value includes a {#pending_create?} save context object
|
366
|
+
def exclude_pending_create_value?(value)
|
367
|
+
return false if value.nil?
|
368
|
+
if Enumerable === value then
|
369
|
+
value.any? { |ref| exclude_pending_create_value?(ref) }
|
370
|
+
else
|
371
|
+
value.identifier.nil? and pending_create?(value)
|
372
|
+
end
|
357
373
|
end
|
358
374
|
|
359
375
|
# @param [Resource] obj the object to check
|
@@ -395,6 +411,12 @@ module CaRuby
|
|
395
411
|
proxied.each { |dep| update(dep) }
|
396
412
|
end
|
397
413
|
|
414
|
+
# update a cascaded dependent by updating the owner
|
415
|
+
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)
|
419
|
+
|
398
420
|
# update using a template
|
399
421
|
tmpl = build_update_template(obj)
|
400
422
|
|
@@ -404,6 +426,38 @@ module CaRuby
|
|
404
426
|
obj.take_snapshot
|
405
427
|
end
|
406
428
|
|
429
|
+
# 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?}.
|
433
|
+
#
|
434
|
+
# @param [Resource] obj the domain object to update
|
435
|
+
# @return [Resource, nil] the owner which can cascade an update to the object, or nil if none
|
436
|
+
# @raise [DatabaseError] if the domain object is a cascaded dependent but does not have an owner
|
437
|
+
def cascaded_owner(obj)
|
438
|
+
return unless obj.class.cascaded_dependent?
|
439
|
+
# the owner attribute
|
440
|
+
oattr = obj.effective_owner_attribute
|
441
|
+
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
|
443
|
+
if dep_md and dep_md.cascaded? then
|
444
|
+
obj.send(oattr)
|
445
|
+
end
|
446
|
+
end
|
447
|
+
|
448
|
+
def update_dependent(owner, obj)
|
449
|
+
logger.debug { "Updating #{obj} by saving the owner #{owner}..." }
|
450
|
+
update(owner)
|
451
|
+
end
|
452
|
+
|
453
|
+
def update_from_template(obj)
|
454
|
+
tmpl = build_update_template(obj)
|
455
|
+
# call the caCORE service with an obj update template
|
456
|
+
save_with_template(obj, tmpl) { |svc| svc.update(tmpl) }
|
457
|
+
# take a snapshot of the updated content
|
458
|
+
obj.take_snapshot
|
459
|
+
end
|
460
|
+
|
407
461
|
# caTissue alert - the conditions for when and how to include a proxied dependent are
|
408
462
|
# are intricate and treacherous. So far as can be determined, in the case of a
|
409
463
|
# SpecimenPosition proxied by a TransferEventParameters, the sequence is as follows:
|
@@ -427,10 +481,10 @@ module CaRuby
|
|
427
481
|
# must be created via the proxy after the Specimen is created.
|
428
482
|
#
|
429
483
|
# @param (see #update)
|
430
|
-
# @return [<Resource>] the #{ResourceAttributes#
|
484
|
+
# @return [<Resource>] the #{ResourceAttributes#proxied_save_template_attributes} dependents
|
431
485
|
# which are #{Persistable#changed?}
|
432
486
|
def updatable_proxied_dependents(obj)
|
433
|
-
attrs = obj.class.
|
487
|
+
attrs = obj.class.proxied_save_template_attributes
|
434
488
|
return Array::EMPTY_ARRAY if attrs.empty?
|
435
489
|
deps = []
|
436
490
|
attrs.each do |attr|
|
@@ -459,7 +513,7 @@ module CaRuby
|
|
459
513
|
if obj.identifier.nil? then
|
460
514
|
raise DatabaseError.new("Delete target is missing a database identifier: #{obj}")
|
461
515
|
end
|
462
|
-
persistence_service(obj).delete_object(obj)
|
516
|
+
persistence_service(obj.class).delete_object(obj)
|
463
517
|
end
|
464
518
|
|
465
519
|
# Saves the given template built from the given domain object obj. The persistence operation
|
@@ -471,15 +525,22 @@ module CaRuby
|
|
471
525
|
# @param [Resource] template the obj template to submit to caCORE
|
472
526
|
def save_with_template(obj, template)
|
473
527
|
logger.debug { "Saving #{obj.qp} from template:\n#{template.dump}" }
|
474
|
-
#
|
475
|
-
|
476
|
-
svc = persistence_service(template)
|
477
|
-
result = template.identifier ? svc.update(template) : svc.create(template)
|
478
|
-
logger.debug { "Store #{obj.qp} with template #{template.qp} produced caCORE result: #{result}." }
|
528
|
+
# dispatch to the application service
|
529
|
+
result = submit_save_template(obj, template)
|
479
530
|
# sync the result
|
480
531
|
sync_saved(obj, result)
|
481
532
|
end
|
482
533
|
|
534
|
+
# Dispatches the given template to the application service.
|
535
|
+
#
|
536
|
+
# @param (see #save_with_template)
|
537
|
+
def submit_save_template(obj, template)
|
538
|
+
svc = persistence_service(template.class)
|
539
|
+
result = template.identifier ? svc.update(template) : svc.create(template)
|
540
|
+
logger.debug { "Store #{obj.qp} with template #{template.qp} produced caCORE result: #{result}." }
|
541
|
+
result
|
542
|
+
end
|
543
|
+
|
483
544
|
# Synchronizes the content of the given saved domain object and the save result source as follows:
|
484
545
|
# 1. The save result source is first synchronized with the database content as necessary.
|
485
546
|
# 2. Then the source is merged into the target.
|
@@ -551,7 +612,6 @@ module CaRuby
|
|
551
612
|
def sync_save_result(source, target)
|
552
613
|
# Bail if the result is the same as the source, as occurs, e.g., with caTissue annotations.
|
553
614
|
return if source == target
|
554
|
-
logger.debug { "Synchronizing #{target} save result #{source} with the database..." }
|
555
615
|
# If the target was created, then refetch and merge the source if necessary to reflect auto-generated
|
556
616
|
# non-domain attribute values.
|
557
617
|
if target.identifier.nil? then sync_created_result_object(source) end
|
@@ -559,7 +619,6 @@ module CaRuby
|
|
559
619
|
sync_save_result_references(source, target)
|
560
620
|
# Set inverses consistently in the source object graph
|
561
621
|
set_inverses(source)
|
562
|
-
logger.debug { "Synchronized #{target} save result #{source} with the database." }
|
563
622
|
end
|
564
623
|
|
565
624
|
# Refetches the given create result source if there are any {ResourceAttributes#autogenerated_nondomain_attributes}
|
@@ -626,9 +685,6 @@ module CaRuby
|
|
626
685
|
#
|
627
686
|
# @param [Resource] obj the owner domain object
|
628
687
|
def save_changed_dependents(obj)
|
629
|
-
# JRuby alert - copy the Resource dependents call result to an array, since iteration based on
|
630
|
-
# Forwardable enum_for breaks here with an obscure Java ConcurrentModificationException
|
631
|
-
# in the CaTissue SCG save test case. TODO - isolate and fix at source.
|
632
688
|
obj.class.dependent_attributes.each do |attr|
|
633
689
|
deps = obj.send(attr).to_enum
|
634
690
|
logger.debug { "Saving the #{obj} #{attr} dependents #{deps.qp} which have changed..." } unless deps.empty?
|
@@ -639,7 +695,9 @@ module CaRuby
|
|
639
695
|
# Saves the given dependent domain object if necessary.
|
640
696
|
# Recursively saves the obj dependents as necessary.
|
641
697
|
#
|
642
|
-
# @param [Resource]
|
698
|
+
# @param [Resource] owner the dependent owner
|
699
|
+
# @param [Symbol] attribute the dependent attribute
|
700
|
+
# @param [Resource] dependent the dependent to save
|
643
701
|
def save_dependent_if_changed(owner, attribute, dependent)
|
644
702
|
if dependent.identifier.nil? then
|
645
703
|
logger.debug { "Creating #{owner.qp} #{attribute} dependent #{dependent.qp}..." }
|
@@ -652,19 +710,24 @@ module CaRuby
|
|
652
710
|
op = operations.last
|
653
711
|
# The dependent is auto-generated if the owner was created or auto-generated and
|
654
712
|
# the dependent attribute is auto-generated.
|
655
|
-
|
713
|
+
attr_md = owner.class.attribute_metadata(attribute)
|
714
|
+
ag = (op.type == :create or op.autogenerated?) && attr_md.autogenerated?
|
656
715
|
logger.debug { "Updating the changed #{owner.qp} #{attribute} dependent #{dependent.qp}..." }
|
657
|
-
|
716
|
+
if ag then
|
717
|
+
logger.debug { "Adding defaults to the auto-generated #{owner.qp} #{attribute} dependent #{dependent.qp}..." }
|
718
|
+
dependent.add_defaults_autogenerated
|
719
|
+
end
|
720
|
+
update_changed_dependent(owner, attribute, dependent, ag)
|
658
721
|
else
|
659
722
|
save_changed_dependents(dependent)
|
660
723
|
end
|
661
724
|
end
|
662
725
|
|
663
|
-
#
|
726
|
+
# Updates the given dependent.
|
664
727
|
#
|
665
|
-
# @param (see #
|
666
|
-
def
|
667
|
-
|
728
|
+
# @param (see #save_dependent_if_changed)
|
729
|
+
def update_changed_dependent(owner, attribute, dependent, autogenerated)
|
730
|
+
perform(:update, dependent, :autogenerated => autogenerated) { update_object(dependent) }
|
668
731
|
end
|
669
732
|
end
|
670
733
|
end
|