caruby-core 1.4.7 → 1.4.9
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.
- 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
|