caruby-tissue 1.5.6 → 2.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +17 -0
- data/History.md +5 -1
- data/README.md +2 -2
- data/bin/crtdump +2 -8
- data/bin/crtexample +2 -5
- data/bin/crtmigrate +3 -6
- data/bin/crtsmoke +3 -8
- data/conf/wustl/{log4j.properties → linux/log4j.properties} +3 -3
- data/conf/wustl/windows/log4j.properties +40 -0
- data/examples/galena/Gemfile +16 -0
- data/examples/galena/Gemfile.lock +88 -0
- data/examples/galena/README.md +16 -16
- data/examples/galena/Rakefile +30 -0
- data/examples/galena/bin/seed +5 -11
- data/examples/galena/conf/annotation/defaults.yaml +2 -0
- data/examples/galena/conf/{migration/annotation_fields.yaml → annotation/fields.yaml} +2 -4
- data/examples/galena/conf/defaults.yaml +9 -0
- data/examples/galena/conf/{migration/filter_fields.yaml → filter/fields.yaml} +0 -1
- data/examples/galena/conf/filter/values.yaml +8 -0
- data/examples/galena/conf/{migration/frozen_defaults.yaml → frozen/defaults.yaml} +0 -0
- data/examples/galena/conf/{migration/frozen_fields.yaml → frozen/fields.yaml} +0 -2
- data/examples/galena/conf/{migration/general_fields.yaml → general/fields.yaml} +0 -24
- data/examples/galena/conf/registration/fields.yaml +6 -0
- data/examples/galena/conf/{migration/simple_fields.yaml → simple/fields.yaml} +1 -6
- data/examples/galena/data/annotation.csv +1 -1
- data/examples/galena/data/filter.csv +1 -1
- data/examples/galena/data/frozen.csv +1 -1
- data/examples/galena/data/general.csv +1 -1
- data/examples/galena/data/registration.csv +1 -1
- data/examples/galena/data/simple.csv +1 -1
- data/examples/galena/galena.gemspec +24 -0
- data/examples/galena/lib/galena/filter.rb +25 -0
- data/examples/galena/lib/galena/{tissue/migration/frozen_shims.rb → frozen.rb} +6 -10
- data/examples/galena/lib/galena/seed.rb +126 -0
- data/examples/galena/lib/galena/version.rb +3 -0
- data/examples/galena/lib/galena.rb +18 -7
- data/examples/galena/log/galena.log +37351 -0
- data/examples/galena/log/galena.log.0 +147830 -0
- data/examples/galena/spec/annotation_spec.rb +46 -0
- data/examples/galena/spec/filter_spec.rb +94 -0
- data/examples/galena/spec/frozen_spec.rb +39 -0
- data/examples/galena/spec/general_spec.rb +62 -0
- data/examples/galena/spec/registration_spec.rb +37 -0
- data/examples/galena/spec/seed.rb +107 -0
- data/examples/galena/spec/simple_spec.rb +58 -0
- data/examples/galena/spec/spec_helper.rb +11 -0
- data/examples/galena/spec/support/migration.rb +70 -0
- data/lib/catissue/annotation/annotatable.rb +10 -8
- data/lib/catissue/annotation/annotation.rb +7 -7
- data/lib/catissue/annotation/de_integration.rb +9 -20
- data/lib/catissue/annotation/importer.rb +148 -0
- data/lib/catissue/annotation/introspector.rb +32 -0
- data/lib/catissue/annotation/metadata.rb +422 -0
- data/lib/catissue/annotation/proxy.rb +2 -2
- data/lib/catissue/annotation/proxy_class.rb +45 -30
- data/lib/catissue/annotation/record_entry_proxy.rb +2 -2
- data/lib/catissue/cli/command.rb +14 -24
- data/lib/catissue/cli/example.rb +5 -3
- data/lib/catissue/cli/migrate.rb +45 -37
- data/lib/catissue/cli/smoke.rb +2 -3
- data/lib/catissue/database/annotation/annotation_service.rb +8 -17
- data/lib/catissue/database/annotation/entity_facade.rb +33 -30
- data/lib/catissue/database/annotation/id_generator.rb +1 -1
- data/lib/catissue/database/annotation/integration_service.rb +11 -4
- data/lib/catissue/database/annotation/reference_writer.rb +38 -38
- data/lib/catissue/database/controlled_value_finder.rb +13 -28
- data/lib/catissue/database/controlled_values.rb +73 -45
- data/lib/catissue/database.rb +637 -277
- data/lib/catissue/domain/abstract_domain_object.rb +5 -0
- data/lib/catissue/domain/abstract_position.rb +3 -5
- data/lib/catissue/domain/abstract_specimen.rb +79 -65
- data/lib/catissue/domain/abstract_specimen_collection_group.rb +3 -6
- data/lib/catissue/domain/address.rb +0 -2
- data/lib/catissue/domain/cancer_research_group.rb +0 -3
- data/lib/catissue/domain/capacity.rb +2 -4
- data/lib/catissue/domain/check_in_check_out_event_parameter.rb +0 -3
- data/lib/catissue/domain/collection_event_parameters.rb +2 -7
- data/lib/catissue/domain/collection_protocol.rb +11 -16
- data/lib/catissue/domain/collection_protocol_event.rb +19 -12
- data/lib/catissue/domain/collection_protocol_registration.rb +8 -12
- data/lib/catissue/domain/consent_tier_response.rb +0 -4
- data/lib/catissue/domain/consent_tier_status.rb +1 -4
- data/lib/catissue/domain/container.rb +10 -10
- data/lib/catissue/domain/container_position.rb +4 -7
- data/lib/catissue/domain/container_type.rb +4 -7
- data/lib/catissue/domain/department.rb +0 -3
- data/lib/catissue/domain/disposal_event_parameters.rb +5 -5
- data/lib/catissue/domain/embedded_event_parameters.rb +1 -4
- data/lib/catissue/domain/external_identifier.rb +0 -12
- data/lib/catissue/domain/frozen_event_parameters.rb +1 -4
- data/lib/catissue/domain/institution.rb +0 -3
- data/lib/catissue/domain/new_specimen_array_order_item.rb +0 -5
- data/lib/catissue/domain/order_details.rb +0 -2
- data/lib/catissue/domain/participant/clinical/chemotherapy.rb +1 -3
- data/lib/catissue/domain/participant/clinical/duration.rb +2 -4
- data/lib/catissue/domain/participant/clinical/radiation_therapy.rb +2 -4
- data/lib/catissue/domain/participant.rb +22 -24
- data/lib/catissue/domain/participant_medical_identifier.rb +0 -4
- data/lib/catissue/domain/password.rb +0 -4
- data/lib/catissue/domain/race.rb +0 -3
- data/lib/catissue/domain/received_event_parameters.rb +3 -6
- data/lib/catissue/domain/site.rb +1 -4
- data/lib/catissue/domain/specimen/pathology/additional_finding.rb +12 -0
- data/lib/catissue/domain/specimen/pathology/details.rb +12 -0
- data/lib/catissue/domain/specimen/pathology/gleason_score.rb +12 -0
- data/lib/catissue/domain/specimen/pathology/histologic_grade.rb +12 -0
- data/lib/catissue/domain/specimen/pathology/histologic_type.rb +19 -0
- data/lib/catissue/domain/specimen/pathology/histologic_variant_type.rb +12 -0
- data/lib/catissue/domain/specimen/pathology/invasion.rb +12 -0
- data/lib/catissue/domain/specimen/pathology/prostate_specimen_gleason_score.rb +5 -11
- data/lib/catissue/domain/specimen/pathology/prostate_specimen_pathology_annotation.rb +12 -12
- data/lib/catissue/domain/specimen/pathology/specimen_additional_finding.rb +5 -14
- data/lib/catissue/domain/specimen/pathology/specimen_base_solid_tissue_pathology_annotation.rb +6 -21
- data/lib/catissue/domain/specimen/pathology/specimen_details.rb +4 -10
- data/lib/catissue/domain/specimen/pathology/specimen_histologic_grade.rb +4 -10
- data/lib/catissue/domain/specimen/pathology/specimen_histologic_type.rb +6 -14
- data/lib/catissue/domain/specimen/pathology/specimen_histologic_variant_type.rb +5 -11
- data/lib/catissue/domain/specimen/pathology/specimen_invasion.rb +5 -11
- data/lib/catissue/domain/specimen.rb +113 -76
- data/lib/catissue/domain/specimen_array.rb +0 -3
- data/lib/catissue/domain/specimen_array_content.rb +1 -4
- data/lib/catissue/domain/specimen_array_type.rb +1 -4
- data/lib/catissue/domain/specimen_characteristics.rb +0 -3
- data/lib/catissue/domain/specimen_collection_group/pathology/base_pathology_annotation.rb +2 -4
- data/lib/catissue/domain/specimen_collection_group/pathology/base_solid_tissue_pathology_annotation.rb +2 -4
- data/lib/catissue/domain/specimen_collection_group.rb +43 -53
- data/lib/catissue/domain/specimen_event_parameters.rb +24 -32
- data/lib/catissue/domain/specimen_position.rb +8 -5
- data/lib/catissue/domain/specimen_protocol.rb +3 -6
- data/lib/catissue/domain/specimen_requirement.rb +22 -20
- data/lib/catissue/domain/storage_container.rb +9 -12
- data/lib/catissue/domain/storage_type.rb +6 -10
- data/lib/catissue/domain/transfer_event_parameters.rb +3 -6
- data/lib/catissue/domain/user.rb +22 -29
- data/lib/catissue/{util → helpers}/collectible.rb +23 -18
- data/lib/catissue/helpers/collectible_event_parameters.rb +68 -0
- data/lib/catissue/helpers/controlled_value.rb +35 -0
- data/lib/catissue/{domain → helpers}/hash_code.rb +0 -0
- data/lib/catissue/{util → helpers}/location.rb +6 -5
- data/lib/catissue/helpers/log.rb +4 -0
- data/lib/catissue/{util → helpers}/person.rb +1 -1
- data/lib/catissue/{util → helpers}/position.rb +10 -8
- data/lib/catissue/helpers/properties_loader.rb +143 -0
- data/lib/catissue/{util → helpers}/storable.rb +2 -1
- data/lib/catissue/{util → helpers}/storage_type_holder.rb +9 -3
- data/lib/catissue/{annotation/annotatable_class.rb → metadata.rb} +73 -95
- data/lib/catissue/migration/migratable.rb +93 -44
- data/lib/catissue/migration/migrator.rb +26 -42
- data/lib/catissue/migration/shims.rb +1 -1
- data/lib/catissue/migration/unique.rb +76 -0
- data/lib/catissue/resource.rb +16 -20
- data/lib/catissue/version.rb +1 -1
- data/lib/catissue/wustl/logger.rb +52 -32
- data/lib/catissue.rb +38 -20
- data/test/lib/catissue/database/controlled_values_test.rb +22 -27
- data/test/lib/catissue/database/database_test.rb +18 -0
- data/test/lib/catissue/domain/address_test.rb +9 -11
- data/test/lib/catissue/domain/ca_tissue_test_defaults_test.rb +5 -16
- data/test/lib/catissue/domain/capacity_test.rb +2 -2
- data/test/lib/catissue/domain/collection_event_parameters_test.rb +16 -8
- data/test/lib/catissue/domain/collection_protocol_event_test.rb +1 -1
- data/test/lib/catissue/domain/collection_protocol_registration_test.rb +26 -16
- data/test/lib/catissue/domain/collection_protocol_test.rb +2 -2
- data/test/lib/catissue/domain/container_position_test.rb +7 -4
- data/test/lib/catissue/domain/department_test.rb +3 -3
- data/test/lib/catissue/domain/disposal_event_parameters_test.rb +1 -1
- data/test/lib/catissue/domain/external_identifier_test.rb +5 -1
- data/test/lib/catissue/domain/location_test.rb +4 -4
- data/test/lib/catissue/domain/participant_medical_identifier_test.rb +3 -3
- data/test/lib/catissue/domain/participant_test.rb +33 -20
- data/test/lib/catissue/domain/received_event_parameters_test.rb +19 -0
- data/test/lib/catissue/domain/site_test.rb +2 -2
- data/test/lib/catissue/domain/specimen_array_test.rb +3 -3
- data/test/lib/catissue/domain/specimen_array_type_test.rb +6 -6
- data/test/lib/catissue/domain/specimen_characteristics_test.rb +1 -1
- data/test/lib/catissue/domain/specimen_collection_group_test.rb +49 -13
- data/test/lib/catissue/domain/specimen_event_parameters_test.rb +4 -4
- data/test/lib/catissue/domain/specimen_position_test.rb +1 -1
- data/test/lib/catissue/domain/specimen_requirement_test.rb +2 -2
- data/test/lib/catissue/domain/specimen_test.rb +58 -24
- data/test/lib/catissue/domain/storage_container_test.rb +3 -16
- data/test/lib/catissue/domain/storage_type_test.rb +3 -3
- data/test/lib/catissue/domain/transfer_event_parameters_test.rb +17 -17
- data/test/lib/catissue/domain/user_test.rb +32 -34
- data/test/lib/catissue/helpers/properties_loader_test.rb +19 -0
- data/test/lib/catissue/migration/{test_case.rb → helpers/test_case.rb} +30 -20
- data/test/lib/examples/galena/tissue/domain/examples_test.rb +28 -38
- data/test/lib/examples/galena/tissue/helpers/test_case.rb +24 -0
- metadata +175 -99
- data/bin/crtextract +0 -47
- data/examples/galena/conf/extract/simple_fields.yaml +0 -4
- data/examples/galena/conf/migration/annotation_defaults.yaml +0 -2
- data/examples/galena/conf/migration/filter_defaults.yaml +0 -1
- data/examples/galena/conf/migration/filter_values.yaml +0 -13
- data/examples/galena/conf/migration/participant_fields.yaml +0 -4
- data/examples/galena/conf/migration/registration_fields.yaml +0 -5
- data/examples/galena/data/participant.csv +0 -1
- data/examples/galena/lib/galena/tissue/migration/filter_shims.rb +0 -41
- data/examples/galena/lib/galena/tissue/seed/defaults.rb +0 -127
- data/examples/psbin/README.md +0 -45
- data/examples/psbin/conf/adjuvant_hormone_defaults.yaml +0 -2
- data/examples/psbin/conf/adjuvant_radiation_defaults.yaml +0 -3
- data/examples/psbin/conf/biopsy_defaults.yaml +0 -3
- data/examples/psbin/conf/biopsy_fields.yaml +0 -9
- data/examples/psbin/conf/neoadjuvant_hormone_defaults.yaml +0 -2
- data/examples/psbin/conf/neoadjuvant_radiation_defaults.yaml +0 -3
- data/examples/psbin/conf/patient_defaults.yaml +0 -3
- data/examples/psbin/conf/patient_fields.yaml +0 -5
- data/examples/psbin/conf/surgery_defaults.yaml +0 -4
- data/examples/psbin/conf/surgery_fields.yaml +0 -15
- data/examples/psbin/conf/t_stage_defaults.yaml +0 -1
- data/examples/psbin/conf/t_stage_fields.yaml +0 -4
- data/examples/psbin/conf/therapy_fields.yaml +0 -5
- data/examples/psbin/data/adjuvant_hormone.csv +0 -1
- data/examples/psbin/data/adjuvant_radiation.csv +0 -1
- data/examples/psbin/data/biopsy.csv +0 -1
- data/examples/psbin/data/neoadjuvant_hormone.csv +0 -1
- data/examples/psbin/data/neoadjuvant_radiation.csv +0 -1
- data/examples/psbin/data/patient.csv +0 -1
- data/examples/psbin/data/surgery.csv +0 -1
- data/examples/psbin/data/t_stage.csv +0 -1
- data/examples/psbin/lib/psbin/biopsy_shims.rb +0 -15
- data/examples/psbin/lib/psbin/surgery_shims.rb +0 -15
- data/lib/catissue/annotation/annotation_class.rb +0 -406
- data/lib/catissue/annotation/annotation_module.rb +0 -106
- data/lib/catissue/domain.rb +0 -26
- data/lib/catissue/extract/command.rb +0 -31
- data/lib/catissue/extract/delta.rb +0 -58
- data/lib/catissue/extract/extractor.rb +0 -99
- data/lib/catissue/migration/uniquify.rb +0 -2
- data/lib/catissue/util/collectible_event_parameters.rb +0 -71
- data/lib/catissue/util/controlled_value.rb +0 -29
- data/lib/catissue/util/uniquify.rb +0 -86
- data/test/fixtures/catissue/domain/conf/catissue_override.yaml +0 -9
- data/test/fixtures/catissue/extract/conf/scg_extract.yaml +0 -3
- data/test/fixtures/catissue/extract/conf/scg_fields.yaml +0 -3
- data/test/fixtures/catissue/extract/conf/spc_extract.yaml +0 -3
- data/test/fixtures/catissue/extract/conf/spc_fields.yaml +0 -4
- data/test/fixtures/lib/catissue/defaults_test_fixture.rb +0 -206
- data/test/fixtures/lib/examples/galena/migration/alt_key_shims.rb +0 -7
- data/test/lib/catissue/domain/base_haemotology_pathology_test.rb +0 -24
- data/test/lib/catissue/extract/delta_test.rb +0 -25
- data/test/lib/catissue/extract/extractor_test.rb +0 -43
- data/test/lib/catissue/import/importable_module_test.rb +0 -14
- data/test/lib/catissue/test_case.rb +0 -247
- data/test/lib/examples/galena/tissue/migration/annotation_test.rb +0 -29
- data/test/lib/examples/galena/tissue/migration/filter_test.rb +0 -29
- data/test/lib/examples/galena/tissue/migration/frozen_test.rb +0 -36
- data/test/lib/examples/galena/tissue/migration/general_test.rb +0 -56
- data/test/lib/examples/galena/tissue/migration/participant_test.rb +0 -61
- data/test/lib/examples/galena/tissue/migration/registration_test.rb +0 -17
- data/test/lib/examples/galena/tissue/migration/seedify.rb +0 -119
- data/test/lib/examples/galena/tissue/migration/simple_test.rb +0 -30
- data/test/lib/examples/galena/tissue/migration/test_case.rb +0 -72
- data/test/lib/examples/psbin/migration_test.rb +0 -153
data/lib/catissue/database.rb
CHANGED
@@ -1,30 +1,35 @@
|
|
1
1
|
require 'singleton'
|
2
|
-
require '
|
3
|
-
require '
|
4
|
-
require 'caruby/util/topological_sync_enumerator'
|
2
|
+
require 'jinx/helpers/validation'
|
3
|
+
require 'jinx/helpers/uniquifier'
|
5
4
|
require 'caruby/database'
|
6
5
|
require 'catissue/database/annotation/annotator'
|
7
|
-
require 'catissue/
|
6
|
+
require 'catissue/helpers/collectible_event_parameters'
|
7
|
+
require 'catissue/helpers/collectible'
|
8
8
|
|
9
9
|
module CaTissue
|
10
|
-
# Database mediates access to the caTissue application server.
|
11
|
-
# Superclass +CaRuby::Database+ functionality base class methods are overridden as necessary
|
12
|
-
# to enable caTissue-specific work-arounds and alternate search strategies.
|
13
10
|
# A CaTissue::Database mediates access to the caTissue database.
|
14
|
-
# The CaRuby::Database functionality is preserved
|
15
|
-
# several base class private methods to enable alternate CaTissue-specific search strategies
|
16
|
-
# around caTissue and caCORE bugs.
|
11
|
+
# The CaRuby::Database functionality is preserved, but this CaTissue::Database overrides
|
12
|
+
# several base class private methods to enable alternate CaTissue-specific search strategies
|
13
|
+
# and work around caTissue and caCORE bugs.
|
14
|
+
#
|
15
|
+
# This class contains a grab-bag of long and convoluted method implementations for the
|
16
|
+
# many caTissue API quirks that necessitate special attention.
|
17
17
|
class Database < CaRuby::Database
|
18
18
|
include Singleton
|
19
19
|
|
20
|
-
#
|
21
|
-
attr_reader :executor, :access_properties
|
22
|
-
|
23
|
-
# Creates a new Database with the +catissuecore+ service and {CaTissue.access_properties}.
|
20
|
+
# Creates a new Database with the +catissuecore+ service and {#access_properties}.
|
24
21
|
def initialize
|
25
|
-
|
26
|
-
|
27
|
-
|
22
|
+
super(SVC_NAME) { access_properties }
|
23
|
+
end
|
24
|
+
|
25
|
+
# @return (see CaRuby::Domain.properties)
|
26
|
+
def access_properties
|
27
|
+
CaTissue.properties
|
28
|
+
end
|
29
|
+
|
30
|
+
# return [CaRuby::SQLExecutor] a utility SQL executor
|
31
|
+
def executor
|
32
|
+
@executor ||= create_executor
|
28
33
|
end
|
29
34
|
|
30
35
|
# @return [Annotator] the annotator utility
|
@@ -32,7 +37,7 @@ module CaTissue
|
|
32
37
|
@annotator ||= Annotator.new(self)
|
33
38
|
end
|
34
39
|
|
35
|
-
# If the given domain object is an {Annotation}, then this method returns the
|
40
|
+
# If the given domain object is an {Annotation}, then this method returns the +CaRuby::AnnotationService+
|
36
41
|
# for the object {AnnotationModule}, otherwise this method returns the standard {CaTissue::Database}
|
37
42
|
# service.
|
38
43
|
#
|
@@ -42,7 +47,22 @@ module CaTissue
|
|
42
47
|
klass < Annotation ? klass.annotation_module.persistence_service : super
|
43
48
|
end
|
44
49
|
|
45
|
-
#
|
50
|
+
# @quirk caTissue caTissue Address create succeeds but does not set the result identifier, rendering
|
51
|
+
# the result useless. This leads to obscure cascading errors if the address is saved before
|
52
|
+
# creating and then updating the address owner. Throw an exception if the create argument is an
|
53
|
+
# address. An address can be created but only in the context of its owner, and special case logic
|
54
|
+
# is required to fetch the address by an inverted query on the owner address reference.
|
55
|
+
#
|
56
|
+
# @param [Resource] obj the dependent domain object to save
|
57
|
+
# @raise [CaRuby::DatabaseError] if the object is a {CaTissue::Address}
|
58
|
+
def create(obj)
|
59
|
+
if CaTissue::Address === obj then
|
60
|
+
Jinx.fail(CaRuby::DatabaseError, "Address creation is not supported, since a caTissue bug does not set the create result identifier property.")
|
61
|
+
end
|
62
|
+
super
|
63
|
+
end
|
64
|
+
|
65
|
+
# Augments +CaRuby::Database.ensure_exists} to ensure that an {Annotation::Proxy+ reference identifier
|
46
66
|
# reflects the hook identifier.
|
47
67
|
#
|
48
68
|
# @param (see CaRuby::Database::Writer#ensure_exists)
|
@@ -61,8 +81,26 @@ module CaTissue
|
|
61
81
|
UPD_EID_SQL = 'update catissue_external_identifier set name = ?, value = ?, specimen_id = ? where identifier = ?'
|
62
82
|
|
63
83
|
UPD_CTR_SQL = 'update catissue_consent_tier_response set response = ? where identifier = ?'
|
84
|
+
|
85
|
+
MAX_ADDR_ID_SQL = "select max(identifier) from catissue_address"
|
86
|
+
|
87
|
+
# return (see #executor)
|
88
|
+
def create_executor
|
89
|
+
# Augment the user-defined application properties with Hibernate properties.
|
90
|
+
props = access_properties
|
91
|
+
hprops = Java::edu.wustl.common.hibernate.HibernateUtil.configuration.properties.to_hash rescue nil
|
92
|
+
if hprops then
|
93
|
+
props[:database_user] ||= hprops['connection.username']
|
94
|
+
props[:database_password] ||= hprops['connection.password']
|
95
|
+
if not props.has_key?(:database_name) and not props.has_key?(:database_port) and hprops.has_key?('connection.url') then
|
96
|
+
props[:database_url] ||= hprops['connection.url']
|
97
|
+
end
|
98
|
+
props[:database_driver_class] ||= hprops['connection.driver_class']
|
99
|
+
end
|
100
|
+
CaRuby::SQLExecutor.new(props)
|
101
|
+
end
|
64
102
|
|
65
|
-
# Overrides
|
103
|
+
# Overrides #+CaRuby::Database::Writer.recursive_save?+ to support the update work-around
|
66
104
|
# described in {#update_object}. A recursive SCG update is allowed if the nested
|
67
105
|
# transaction sequence is:
|
68
106
|
# * Update SCG
|
@@ -72,7 +110,7 @@ module CaTissue
|
|
72
110
|
# @param (see CaRuby::Database::Writer#recursive_save?)
|
73
111
|
# @return (see CaRuby::Database::Writer#recursive_save?)
|
74
112
|
def recursive_save?(obj, operation)
|
75
|
-
super and not
|
113
|
+
super and not collectible_event_update_workaround?(obj, operation)
|
76
114
|
end
|
77
115
|
|
78
116
|
# Returns whether the given specimens are compatible. The target is compatible with the source
|
@@ -120,36 +158,86 @@ module CaTissue
|
|
120
158
|
parent = ref.send(attribute) || next
|
121
159
|
resolved = id_ref_hash[parent.identifier] || next
|
122
160
|
logger.debug { "Resetting #{ref.qp} #{attribute} from #{parent} to #{resolved} in order to fix a caCORE inconsistency..." }
|
123
|
-
ref.
|
161
|
+
ref.set_property_value(attribute, resolved)
|
124
162
|
end
|
125
163
|
end
|
126
164
|
|
127
165
|
# @quirk caTissue Bug #135: Update SCG SpecimenEventParameters raises AuditException.
|
128
166
|
# Work around is to update the SCG instead.
|
129
|
-
#
|
167
|
+
#
|
168
|
+
# @quirk caTissue CPR consent tier response update results in 'Access denied' error.
|
130
169
|
# Work-around is to update the response using a direct SQL call.
|
131
170
|
#
|
171
|
+
# @quirk caTissue The label is auto-generated by caTissue when the specimen is created.
|
172
|
+
# Label creation depends on an auto-generator. Therefore, the specimen must be refetched
|
173
|
+
# when collected in order to reflect the database label value.
|
174
|
+
#
|
175
|
+
# @quirk caTissue The Specimen label is auto-generated when the collection status is changed
|
176
|
+
# from Pending to Collected. However, the update argument label is not updated to reflect
|
177
|
+
# the auto-generated value. Note that the barcode is auto-generated on create, not update.
|
178
|
+
#
|
179
|
+
# @quirk caTissue Updating a User does not cascade update to the address.
|
180
|
+
# address is cascade none in Hibernate, but is created in caTissue, perhaps by biz logic.
|
181
|
+
# An address change is ignored by the User update. Since Address cannot be updated
|
182
|
+
# separately due to a different caTissue bug, the address update must be performed
|
183
|
+
# by a work-around here.
|
184
|
+
#
|
185
|
+
# @quirk caTissue Specimen update does not cascade to a referenced SpecimenEventParameters.
|
186
|
+
# Even though the reference property is marked cascade in Hibernate, the update does
|
187
|
+
# not propagate to the SpecimenEventParameters. Specimen create does cascade. SCG update
|
188
|
+
# cascades. Certain SpecimenEventParameters subclasses have variant Specimen cascade
|
189
|
+
# behavior, as described in other quirk documentation. The work-around is to update
|
190
|
+
# the Specimen SpecimenEventParameters directly rather than delegating the update
|
191
|
+
# to the Specimen owner.
|
192
|
+
#
|
193
|
+
# @quirk caTissue caTissue Specimen update ignores the available quantity. Unlike the
|
194
|
+
# auto-generated Specimen update, there is no known work-around
|
195
|
+
#
|
132
196
|
# @param (see CaRuby::Database#update_object)
|
133
|
-
# @return (see CaRuby::Database#update_object)
|
134
197
|
def update_object(obj)
|
135
|
-
|
136
|
-
|
198
|
+
# prep work
|
199
|
+
case obj
|
200
|
+
when CaTissue::SpecimenCollectionGroup then
|
201
|
+
if obj.collection_protocol_registration.nil? then
|
202
|
+
# add the extraneous SCG template CPR
|
203
|
+
logger.debug { "Work around caTissue bug by fetching extraneous #{obj} CPR..." }
|
204
|
+
obj.collection_protocol_registration = fetch_association(obj, :collection_protocol_registration)
|
205
|
+
end
|
206
|
+
if obj.collection_status.nil? and obj.collection_event_parameters then
|
207
|
+
obj.collection_status = 'Complete'
|
208
|
+
logger.debug { "Set #{obj} status to Complete since it has collection event parameters." }
|
209
|
+
end
|
210
|
+
when CaTissue::Specimen then
|
211
|
+
prepare_specimen_for_update(obj)
|
212
|
+
when CaTissue::StorageContainer then
|
213
|
+
if obj.storage_type.nil? then
|
214
|
+
logger.debug { "Fetching #{obj} storage type prior to update..." }
|
215
|
+
lazy_loader.enable { obj.storage_type }
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
# Delegate update special cases.
|
220
|
+
if CaTissue::User === obj then
|
221
|
+
if obj.address.identifier.nil? or obj.address.changed? then
|
222
|
+
return update_user_address(obj, obj.address)
|
223
|
+
end
|
224
|
+
end
|
225
|
+
if CollectibleEventParameters === obj then
|
226
|
+
if obj.specimen_collection_group then
|
227
|
+
return save_collectible_scg_event_parameters(obj)
|
228
|
+
elsif obj.specimen then
|
229
|
+
return update_from_template(obj)
|
230
|
+
end
|
231
|
+
elsif SpecimenEventParameters === obj then
|
232
|
+
return update_from_template(obj)
|
137
233
|
elsif CaTissue::ConsentTierResponse === obj then
|
138
|
-
update_consent_tier_response(obj)
|
234
|
+
return update_consent_tier_response(obj)
|
139
235
|
elsif Annotation === obj then
|
140
|
-
|
141
|
-
else
|
142
|
-
case obj
|
143
|
-
when CaTissue::Specimen then
|
144
|
-
# Specimen activity status is not always set to default; don't know why.
|
145
|
-
# TODO - isolate and fix at source
|
146
|
-
obj.activity_status ||= 'Active'
|
147
|
-
when CaTissue::StorageContainer then
|
148
|
-
logger.debug { "Fetching #{obj} storage type prior to update..." }
|
149
|
-
if obj.storage_type.nil? then lazy_loader.enable { obj.storage_type } end
|
150
|
-
end
|
151
|
-
super
|
236
|
+
Jinx.fail(CaRuby::DatabaseError, "Annotation update is not supported on #{obj}")
|
152
237
|
end
|
238
|
+
|
239
|
+
# Finally, the standard update.
|
240
|
+
super
|
153
241
|
end
|
154
242
|
|
155
243
|
# Updates the given dependent.
|
@@ -157,92 +245,112 @@ module CaTissue
|
|
157
245
|
# @quirk caTissue 1.2 user address update results in authorization error. Work-around is to
|
158
246
|
# create a new address record.
|
159
247
|
#
|
248
|
+
# @quirk caTissue Specimen update cascades to child update according to Hibernate, but
|
249
|
+
# caTissue somehow circumvents the child update. The child database content is not changed
|
250
|
+
# to reflect the update argument. Work-around is to update the child independently after
|
251
|
+
# the parent update.
|
252
|
+
#
|
253
|
+
# @quirk caTissue The aforementioned {#save_with_template} caTissue collectible event parameters
|
254
|
+
# dependent bug implies that the dependent must be saved directly rather than via a cascade
|
255
|
+
# from the Specimen or SCG owner to the referenced event parameters. The direct save avoids
|
256
|
+
# a tangled nest of obscure caTissue bugs described in the {#save_with_template} rubydoc.
|
257
|
+
#
|
160
258
|
# @param (see CaRuby::Writer#update_changed_dependent)
|
161
|
-
def update_changed_dependent(owner,
|
162
|
-
|
259
|
+
def update_changed_dependent(owner, property, dependent, autogenerated)
|
260
|
+
# Save the changed collectible event parameters directly rather than via a cascade.
|
261
|
+
if CollectibleEventParameters === dependent then
|
262
|
+
logger.debug { "Work around a caTissue bug by resaving the collected #{owner} #{dependent} directly rather than via a cascade..." }
|
263
|
+
return update_from_template(dependent)
|
264
|
+
end
|
265
|
+
if CaTissue::User === owner and property.attribute == :address then
|
163
266
|
update_user_address(owner, dependent)
|
267
|
+
elsif CaTissue::Specimen === owner and CaTissue::Specimen === dependent then
|
268
|
+
logger.debug { "Work around caTissue bug to update #{dependent} separately after the parent #{owner} update..." }
|
269
|
+
prepare_specimen_for_update(dependent)
|
270
|
+
update_from_template(dependent)
|
271
|
+
logger.debug { "Updated the #{owner} child #{dependent}." }
|
164
272
|
else
|
165
273
|
super
|
166
274
|
end
|
167
275
|
end
|
276
|
+
|
277
|
+
def prepare_specimen_for_update(obj)
|
278
|
+
if obj.collection_status.nil? and obj.collection_event_parameters then
|
279
|
+
obj.collection_status = 'Collected'
|
280
|
+
logger.debug { "Set #{obj} status to Collected since it has collection event parameters." }
|
281
|
+
end
|
282
|
+
if obj.characteristics.nil? then
|
283
|
+
logger.debug { "Fetching #{obj} characteristics for update..." }
|
284
|
+
fetched = fetch_association(obj, :specimen_characteristics)
|
285
|
+
Jinx.fail(CaRuby::DatabaseError, "#{obj} is missing characteristics") if fetched.nil?
|
286
|
+
logger.debug { "Set #{obj} characteristics to #{fetched}." }
|
287
|
+
obj.characteristics = fetched
|
288
|
+
elsif obj.characteristics.identifier.nil? then
|
289
|
+
logger.debug { "Fetching #{obj} characteristics identifier for update..." }
|
290
|
+
fetched = fetch_association(obj, :specimen_characteristics)
|
291
|
+
raise Jinx.fail(CaRuby::DatabaseError, "#{obj} is missing characteristics") if fetched.nil?
|
292
|
+
obj.characteristics.identifier = fetched.identifier
|
293
|
+
logger.debug { "Set #{obj} characteristics #{obj.characteristics} identifier." }
|
294
|
+
end
|
295
|
+
cep = obj.collection_event_parameters
|
296
|
+
rep = obj.received_event_parameters
|
297
|
+
if (cep and cep.identifier.nil?) or (rep and rep.identifier.nil?) then
|
298
|
+
logger.debug { "Fetching #{obj} collectible event parameters identifiers for update..." }
|
299
|
+
eps = fetch_association(obj, :specimen_event_parameters)
|
300
|
+
fcep = eps.detect { |ep| CaTissue::CollectionEventParameters === ep }
|
301
|
+
if fcep then
|
302
|
+
cep.merge(fcep)
|
303
|
+
logger.debug { "Set #{obj} #{cep} identifier." }
|
304
|
+
end
|
305
|
+
frep = eps.detect { |ep| CaTissue::ReceivedEventParameters === ep }
|
306
|
+
if frep then
|
307
|
+
rep.merge(frep)
|
308
|
+
logger.debug { "Set #{obj} #{rep} identifier." }
|
309
|
+
end
|
310
|
+
end
|
311
|
+
# A collected specimen requires a label.
|
312
|
+
if obj.collection_status == 'Collected' and obj.label.nil? then
|
313
|
+
obj.label = Jinx::Uniquifier.qualifier
|
314
|
+
logger.debug { "Worked around caTissue bug by setting a collected specimen label to #{obj.label}." }
|
315
|
+
end
|
316
|
+
end
|
168
317
|
|
169
318
|
# Updates the given user address.
|
170
319
|
#
|
171
|
-
# @param [CaTissue::User] the user
|
320
|
+
# @param [CaTissue::User] the user owner
|
172
321
|
# @param [CaTissue::Address] the address to update
|
322
|
+
# @return [CaTissue::User] the updated user
|
173
323
|
def update_user_address(user, address)
|
174
|
-
logger.debug { "Work around caTissue prohibition of #{user} address #{address} update by creating a new address record..." }
|
324
|
+
logger.debug { "Work around caTissue prohibition of #{user} address #{address} update by creating a new address record for a dummy user..." }
|
175
325
|
address.identifier = nil
|
176
|
-
create(address)
|
326
|
+
perform(:create, address) { create_object(address) }
|
327
|
+
logger.debug { "Worked around caTissue address update bug by swizzling the #{user} address #{address} identifier." }
|
177
328
|
perform(:update, user) { update_object(user) }
|
178
|
-
|
179
|
-
address.identifier = fetched.identifier
|
180
|
-
logger.debug { "#{user} address identifier reset to #{address.identifier}." }
|
329
|
+
user
|
181
330
|
end
|
182
331
|
|
183
|
-
# Returns whether operation is the second
|
184
|
-
def
|
185
|
-
# Is this an
|
186
|
-
return false unless
|
332
|
+
# Returns whether operation is the second Update described in {#recursive_save?}.
|
333
|
+
def collectible_event_update_workaround?(obj, operation)
|
334
|
+
# Is this an update?
|
335
|
+
return false unless Collectible === obj and operation == :update
|
187
336
|
last = @operations.last
|
188
|
-
# Is
|
189
|
-
return false unless last
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
337
|
+
# Is there a nesting operation?
|
338
|
+
return false unless last
|
339
|
+
# Is the nesting operation subject a CEP?
|
340
|
+
return false unless CaTissue::CollectibleEventParameters === last.subject
|
341
|
+
# Is the nesting operation subject owned by the current object?
|
342
|
+
return false unless last.subject.owner == obj
|
343
|
+
prev = penultimate_save_operation
|
344
|
+
# Is the outer save operation subject the current object?
|
345
|
+
prev and prev.subject == obj
|
196
346
|
end
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
# updatable, unless old status is Pending. The validation defect described in Bug #149 requires
|
203
|
-
# a work-around that is also used for a different reason described in the following paragraph.
|
204
|
-
#
|
205
|
-
# @quirk caTissue Update of a {CaTissue::Specimen} which references a position must include the former
|
206
|
-
# position in the caTissue service update argument. A Specimen position is altered as a side-effect
|
207
|
-
# by creating a proxy save {CaTissue::TransferEventParameters}. The changed position is not reflected
|
208
|
-
# in the Specimen position, which must be refetched to reflect the database state. This fetch is
|
209
|
-
# done automatically by {CaRuby::Database} as part of the save proxy mechanism. The Specimen update
|
210
|
-
# template must include a reference to the former position but not the changed position.
|
211
|
-
#
|
212
|
-
# However, the Specimen {CaRuby::Writer#update} argument will include the changed position, not the
|
213
|
-
# former position. The template built {CaRuby::Writer#update} for submission to the caTissue app
|
214
|
-
# does not include a position reference, since the position has a save proxy which handles position
|
215
|
-
# change as part of the {CaRuby::Writer} update dependent propagation.
|
216
|
-
#
|
217
|
-
# Thus, updating a Specimen which includes a position change is performed as follows:
|
218
|
-
# * reconstitute the former position from the Position snapshot taken as part of the
|
219
|
-
# {CaRuby::Persistable} change tracker.
|
220
|
-
# * add the former position to the template (which will now differ from the {CaRuby::Writer#update}
|
221
|
-
# argument).
|
222
|
-
# * submit the adjusted Specimen template to the caTissue app updateObject.
|
223
|
-
# * {CaRuby::Writer#update} will propagate the Specimen update to the changed position dependent,
|
224
|
-
# which in turn saves via the {CaTissue::TransferEventParameters} proxy.
|
225
|
-
# * The proxy save will in turn refetch the proxied Specimen position to obtain the identifier
|
226
|
-
# and merge this into the Specimen position.
|
227
|
-
# * The Specimen update template is used solely to satisfy the often arcane caTissue interaction
|
228
|
-
# requirements like this work-around, and is thrown away along with its aberrant state.
|
229
|
-
#
|
230
|
-
# This work-around is the only case of a save template modification to handle a caTissue special
|
231
|
-
# case. Note that the {CaTissue::SpecimenPosition} logic does not apply to a
|
232
|
-
# {CaTissue::ContainerPosition}, which can be updated directly.
|
233
|
-
#
|
234
|
-
# The additional complexity of this work-around is necessitated by the caTissue policy of update
|
235
|
-
# by indirect server-side side-effects that are not reflected back to the client. The caRuby
|
236
|
-
# policy of a declarative API that persists the save argument as given and reflects the
|
237
|
-
# changed database state requires this work-around.
|
238
|
-
#
|
239
|
-
# @param obj (see #store)
|
240
|
-
# @param [Resource] template the obj template to submit to caCORE
|
241
|
-
def save_with_template(obj, template)
|
242
|
-
if CaTissue::Specimen === obj and obj.position and obj.position.identifier then
|
243
|
-
add_position_to_specimen_template(obj, template)
|
347
|
+
|
348
|
+
def penultimate_save_operation
|
349
|
+
2.upto(@operations.size) do |index|
|
350
|
+
op = @operations[-index]
|
351
|
+
return op if op.save?
|
244
352
|
end
|
245
|
-
|
353
|
+
nil
|
246
354
|
end
|
247
355
|
|
248
356
|
# Adds the specimen position to its save template.
|
@@ -253,170 +361,256 @@ module CaTissue
|
|
253
361
|
def add_position_to_specimen_template(specimen, template)
|
254
362
|
pos = specimen.position
|
255
363
|
# the non-domain position attributes
|
256
|
-
|
364
|
+
pas = pos.class.nondomain_attributes
|
257
365
|
# the template position reflects the old values, if available
|
258
366
|
ss = pos.snapshot
|
259
367
|
# the attribute => value hash
|
260
|
-
vh = ss ?
|
368
|
+
vh = ss ? pas.to_compact_hash { |pas| ss[pas] } : pos.value_hash(pas)
|
261
369
|
vh[:specimen] = template
|
262
370
|
vh[:storage_container] = pos.storage_container.copy
|
263
371
|
# the template position reflects the old values
|
264
372
|
template.position = pos.class.new(vh)
|
265
373
|
logger.debug { "Work around #{specimen} update anomaly by copying position #{template.position.qp} to update template #{template.qp} as #{template.position.qp} with values #{vh.qp}..." }
|
266
374
|
end
|
267
|
-
|
268
|
-
# @return [Boolean] whether obj is a CollectibleEventParameters with a SCG owner
|
269
|
-
def collectible_event_parameters?(obj)
|
270
|
-
CollectibleEventParameters === obj and obj.specimen_collection_group
|
271
|
-
end
|
272
375
|
|
273
376
|
# @param [CollectibleEventParameters] ep the SCG event parameters to save
|
274
|
-
# @return (see
|
377
|
+
# @return (see #update_object)
|
275
378
|
# @see #create_object
|
276
379
|
# @see #update_object
|
277
|
-
def
|
380
|
+
def save_collectible_scg_event_parameters(ep)
|
278
381
|
scg = ep.specimen_collection_group
|
279
|
-
logger.debug { "Work around #{ep.qp} caTissue SCG
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
382
|
+
logger.debug { "Work around #{ep.qp} caTissue SCG event parameters save bug by updating the owner #{scg.qp} instead..." }
|
383
|
+
if ep.identifier.nil? then
|
384
|
+
ensure_exists(scg)
|
385
|
+
return ep if ep.identifier
|
386
|
+
end
|
387
|
+
update_from_template(scg)
|
388
|
+
# Last resort: straight create; probably will fail due to caTissue bug which
|
389
|
+
# expects a CEP create to reference a specimen rather than a SCG.
|
390
|
+
create_from_template(ep) if ep.identifier.nil?
|
284
391
|
ep
|
285
392
|
end
|
286
393
|
|
287
394
|
|
288
|
-
# @param [CaTissue::
|
395
|
+
# @param [CaTissue::ConsentTierResponse] ctr the response to update
|
289
396
|
# @see #update_object
|
290
397
|
def update_consent_tier_response(ctr)
|
291
398
|
# Call the SQL
|
292
399
|
logger.debug { "Work around caTissue #{ctr} update bug by submitting direct SQL call..." }
|
293
|
-
|
400
|
+
executor.transact(UPD_CTR_SQL, ctr.response, ctr.identifier)
|
294
401
|
logger.debug { "caTissue #{ctr} update work-around completed." }
|
295
402
|
end
|
296
403
|
|
297
|
-
# Overrides
|
298
|
-
#
|
404
|
+
# Overrides +CaRuby::Database::Writer.save_changed_dependents+ to handle the following anomaly:
|
405
|
+
#
|
406
|
+
# @quirk caTissue DisposalEventParameters must be created after all other Specimen SEPs. This
|
407
|
+
# use case arises when migrating a source biorepository discarded specimen for archival.
|
408
|
+
#
|
409
|
+
# The process for creating a discarded Specimen is as follows:
|
410
|
+
# * Create the Specimen with status Active.
|
411
|
+
# * Create the non-disposal events.
|
412
|
+
# * Create the DisposalEventParameters.
|
413
|
+
#
|
414
|
+
# {#save_changed_dependents} delegates to this method to handle the latter two steps.
|
415
|
+
#
|
416
|
+
# A DisposalEventParameters cannot be created for a closed Specimen. Conversely, caTissue closes
|
417
|
+
# the Specimen as a side-effect of creating a DisposalEventParameters. Therefore, even if the
|
418
|
+
# client submits a closed Specimen for create, this CaTissue::Database must first create the
|
419
|
+
# Specimen with status Active, then submit the DisposalEventParameters.
|
420
|
+
#
|
421
|
+
# This is a work-around on top of the {#create_unavailable_specimen} work-around. See that method
|
422
|
+
# for the subtle interaction required between these two work-arounds.
|
299
423
|
#
|
300
424
|
# @param (see CaRuby::Writer#save_dependents)
|
301
425
|
def save_changed_dependents(obj)
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
end
|
306
|
-
end
|
307
|
-
|
308
|
-
# Overrides {CaRuby::Database::Writer#save_changed_dependents} on a Specimen to correct the
|
309
|
-
# following problem:
|
310
|
-
#
|
311
|
-
# @quirk caTissue DisposalEventParameters must be created after all other Specimen SEPs.
|
312
|
-
#
|
313
|
-
# The process for migrating a discarded Specimen is as follows:
|
314
|
-
# * Create the Specimen with status Active.
|
315
|
-
# * Create the non-disposal events.
|
316
|
-
# * Create the DisposalEventParameters.
|
317
|
-
#
|
318
|
-
# A DisposalEventParameters cannot be created for a closed Specimen. Conversely, caTissue closes
|
319
|
-
# the Specimen as a side-effect of creating a DisposalEventParameters. Therefore, even if the
|
320
|
-
# client submits a closed Specimen for create, this CaTissue::Database must first create the
|
321
|
-
# Specimen with status Active, then submit the DisposalEventParameters.
|
322
|
-
#
|
323
|
-
# This is a work-around on top of the {#create_unavailable_specimen} work-around. See that method
|
324
|
-
# for the subtle interaction required between these two work-arounds.
|
325
|
-
#
|
326
|
-
# @param [CaTissue::Specimen] the specimen whose dependents are to be saved
|
327
|
-
# @yield [dependent] calls the base {CaRuby::Writer#save_changed_dependents}
|
328
|
-
# @yieldparam [Resource] dependent the dependent to save
|
329
|
-
def save_changed_specimen_dependents(specimen)
|
330
|
-
dsp = specimen.specimen_events.detect { |ep| CaTissue::DisposalEventParameters === ep }
|
426
|
+
if CaTissue::Specimen === obj then
|
427
|
+
dsp = obj.specimen_events.detect { |ep| CaTissue::DisposalEventParameters === ep }
|
428
|
+
end
|
331
429
|
if dsp then
|
332
|
-
|
333
|
-
|
430
|
+
obj.specimen_events.delete(dsp)
|
431
|
+
logger.debug { "Work around a caTissue #{obj.qp} event parameters save order dependency by deferring the #{dsp.qp} save..." }
|
432
|
+
obj.specimen_events.delete(dsp)
|
334
433
|
end
|
335
434
|
|
435
|
+
# Delegate to the standard save_changed_dependents.
|
336
436
|
begin
|
337
|
-
|
437
|
+
super
|
338
438
|
ensure
|
339
|
-
|
439
|
+
obj.specimen_events << dsp if dsp
|
340
440
|
end
|
341
441
|
|
342
|
-
#
|
442
|
+
# Save the deferred disposal, if any.
|
343
443
|
if dsp then
|
344
|
-
logger.debug { "Creating deferred #{
|
345
|
-
|
444
|
+
logger.debug { "Creating deferred #{obj.qp} dependent #{dsp.qp}..." }
|
445
|
+
save_dependent_if_changed(obj, :specimen_events, dsp)
|
446
|
+
if obj.activity_status != 'Closed' then
|
447
|
+
logger.debug { "Refetching the disposed #{obj.qp} to reflect the modified activity status..." }
|
448
|
+
obj.activity_status = nil
|
449
|
+
obj.find
|
450
|
+
end
|
346
451
|
end
|
347
452
|
end
|
348
453
|
|
349
|
-
# Overrides
|
454
|
+
# Overrides +CaRuby::Database.build_save_template+ to return obj itself if
|
350
455
|
# obj is an {Annotation}, since annotations do not employ a separate template.
|
351
456
|
#
|
352
457
|
# @param (see CaRuby::Database#build_save_template)
|
353
458
|
# @return (see CaRuby::Database#build_save_template)
|
354
459
|
def build_save_template(obj, builder)
|
355
|
-
Annotation === obj ?
|
460
|
+
Annotation === obj ? prepare_annotation_as_save_template(obj) : super
|
356
461
|
end
|
357
462
|
|
358
|
-
#
|
463
|
+
# Validates and completes the given annotation object prior to save.
|
464
|
+
# The annotation is submitted directly to the caTissue save rather
|
465
|
+
# than building a save template, since an annotation save does not
|
466
|
+
# recurse to references, unlike a standard save.
|
359
467
|
#
|
360
468
|
# @param [Annotation] annotation the object to create
|
361
469
|
# @return [Annotation] the annotation object
|
362
|
-
# @raise
|
363
|
-
def
|
470
|
+
# @raise (see #ensure_primary_annotation_has_hook)
|
471
|
+
def prepare_annotation_as_save_template(annotation)
|
472
|
+
ensure_primary_annotation_has_hook(annotation) if annotation.class.primary?
|
473
|
+
annotation
|
474
|
+
end
|
475
|
+
|
476
|
+
# Ensures that a primary annotation hook exists.
|
477
|
+
#
|
478
|
+
# @param (see #prepare_annotation_for_save)
|
479
|
+
# @raise [CaRuby::DatabaseError] if the annotation does not reference a hook entity
|
480
|
+
def ensure_primary_annotation_has_hook(annotation)
|
364
481
|
hook = annotation.hook
|
365
482
|
if hook.nil? then
|
366
|
-
|
483
|
+
Jinx.fail(CaRuby::DatabaseError, "Cannot save annotation #{annotation} since it does not reference a hook entity")
|
367
484
|
end
|
368
485
|
if hook.identifier.nil? then
|
369
486
|
logger.debug { "Ensuring that the annotation #{annotation.qp} hook entity #{hook.qp} exists in the database..." }
|
370
487
|
ensure_exists(hook)
|
371
488
|
end
|
372
|
-
annotation
|
373
489
|
end
|
374
490
|
|
375
|
-
#
|
376
|
-
#
|
377
|
-
#
|
378
|
-
#
|
379
|
-
#
|
380
|
-
#
|
491
|
+
# Augments +CaRuby::Database.save_with_template+ to work around the following caTissue anomalies:
|
492
|
+
#
|
493
|
+
# @quirk caTissue Bug #149: API update TissueSpecimen position validation incorrect.
|
494
|
+
# The Specimen update argument must reference the old position, even though the position is not
|
495
|
+
# updatable, unless old status is Pending. The validation defect described in Bug #149 requires
|
496
|
+
# a work-around that is also used for a different reason described in the following paragraph.
|
497
|
+
#
|
498
|
+
# @quirk caTissue Update of a {CaTissue::Specimen} which references a position must include the former
|
499
|
+
# position in the caTissue service update argument. A Specimen position is altered as a side-effect
|
500
|
+
# by creating a proxy save {CaTissue::TransferEventParameters}. The changed position is not reflected
|
501
|
+
# in the Specimen position, which must be refetched to reflect the database state. This fetch is
|
502
|
+
# done automatically by +CaRuby::Database+ as part of the save proxy mechanism. The Specimen update
|
503
|
+
# template must include a reference to the former position but not the changed position.
|
504
|
+
#
|
505
|
+
# However, the Specimen +CaRuby::Writer.update+ argument will include the changed position, not the
|
506
|
+
# former position. The template built by +CaRuby::Writer.update+ for submission to the caTissue app
|
507
|
+
# does not include a position reference, since the position has a save proxy which handles position
|
508
|
+
# change as part of the +CaRuby::Writer+ update dependent propagation.
|
509
|
+
#
|
510
|
+
# Thus, updating a Specimen which includes a position change is performed as follows:
|
511
|
+
# * reconstitute the former position from the Position snapshot taken as part of the
|
512
|
+
# +CaRuby::Persistable+ change tracker.
|
513
|
+
# * add the former position to the template (which will now differ from the +CaRuby::Writer.update+
|
514
|
+
# argument).
|
515
|
+
# * submit the adjusted Specimen template to the caTissue app updateObject.
|
516
|
+
# * +CaRuby::Writer.update+ will propagate the Specimen update to the changed position dependent,
|
517
|
+
# which in turn saves via the {CaTissue::TransferEventParameters} proxy.
|
518
|
+
# * The proxy save will in turn refetch the proxied Specimen position to obtain the identifier
|
519
|
+
# and merge this into the Specimen position.
|
520
|
+
# * The Specimen update template is used solely to satisfy the often arcane caTissue interaction
|
521
|
+
# requirements like this work-around, and is thrown away along with its aberrant state.
|
522
|
+
#
|
523
|
+
# This work-around is the only case of a save template modification to handle a position special
|
524
|
+
# case. Note that the {CaTissue::SpecimenPosition} logic does not apply to a
|
525
|
+
# {CaTissue::ContainerPosition}, which can be updated directly.
|
526
|
+
#
|
527
|
+
# The additional complexity of this work-around is necessitated by the caTissue policy of update
|
528
|
+
# by indirect server-side side-effects that are not reflected back to the client. The caRuby
|
529
|
+
# declarative API policy persists the save argument as given and reflects the changed database
|
530
|
+
# state. That policy requires this work-around.
|
531
|
+
#
|
532
|
+
# @quirk caTissue Bug #63: A SpecimenCollectionGroup update requires that the referenced
|
533
|
+
# CollectionProtocolRegistration hold extraneous content, including the CPR collection
|
534
|
+
# protocol and PPI.
|
535
|
+
#
|
536
|
+
# @quirk caTissue Bug: CollectionProtocolRegistration must cascade through the
|
537
|
+
# CollectionProtocol, but the CP events cannot cascade to SpecimenRequirement without
|
538
|
+
# raising an Exception. The work-around is to clear the template CP events.
|
539
|
+
#
|
540
|
+
# @quirk caTissue Update SpecimenCollectionGroup requires a collection and received event parameter,
|
541
|
+
# even if the collection status is pending. Work-around is to add default parameters.
|
542
|
+
#
|
381
543
|
# @quirk caTissue Create Specimen with nil label does not auto-generate the label.
|
382
544
|
# Work-around is to set the label to a unique value.
|
383
545
|
#
|
546
|
+
# @quirk caTissue When caTissue updates a Specimen referencing a child Specimen with an identifier which
|
547
|
+
# is setting the collection status to +Collected+ and has a received or collection event parameters
|
548
|
+
# without an identifier, then caTissue creates the referenced event parameters as well as spurious
|
549
|
+
# auto-generated received and collection event parameters. This behavior differs from the top-level
|
550
|
+
# Specimen, where the event parameters in the argument are simply ignored. The work-around is to
|
551
|
+
# recursively strip the derived received and collection event parameters, then fetch, match and resave
|
552
|
+
# the stripped event parameters. This behavior is not entirely confirmed, because the various forms
|
553
|
+
# of caTissue event parameters corruption are hard to isolate and catalog. The best recourse is to
|
554
|
+
# assume that caTissue will ignore or corrupt any received and collection event parameters references
|
555
|
+
# and strip, fetch, match and resave these event parameters separately.
|
556
|
+
#
|
557
|
+
# @quirk caTissue When caTissue updates a pending SCG to status Complete then a collection and
|
558
|
+
# received event parameters is added to each referenced top-level specimen, even though the
|
559
|
+
# specimen status is not updated from Pending to Collected. Event parameters are not added to
|
560
|
+
# child specimens.
|
561
|
+
#
|
562
|
+
# @param obj [Resource] obj the object to save
|
563
|
+
# @param [Resource] template the template to submit to caCORE
|
384
564
|
# @raise DatabaseError if the object to save is an {Annotation::Proxy}, which is not supported
|
385
565
|
def save_with_template(obj, template)
|
386
566
|
# special cases to work around caTissue bugs
|
387
567
|
if CaTissue::CollectionProtocolRegistration === obj and template.collection_protocol then
|
388
568
|
template.collection_protocol.collection_protocol_events.clear
|
389
569
|
elsif CaTissue::Specimen === obj then
|
390
|
-
if template.label.nil? then
|
391
|
-
|
392
|
-
|
393
|
-
end
|
394
|
-
|
395
|
-
|
396
|
-
result = submit_save_template(obj, template)
|
397
|
-
# if app service is not broken, then sync the result and return
|
398
|
-
if obj.identifier then
|
399
|
-
sync_saved(obj, result)
|
400
|
-
return
|
570
|
+
# if template.identifier.nil? and template.label.nil? then
|
571
|
+
# logger.debug { "Work around caTissue label bug by setting the #{obj.qp} create template #{template.qp} label to a unique value." }
|
572
|
+
# template.label = Jinx::Uniquifier.qualifier
|
573
|
+
# end
|
574
|
+
if obj.position and obj.position.identifier then
|
575
|
+
add_position_to_specimen_template(obj, template)
|
401
576
|
end
|
402
|
-
|
403
|
-
#
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
577
|
+
# Anticipate the caTissue disposed Specimen update side-effect by removing
|
578
|
+
# the consent tier statuses.
|
579
|
+
if obj.disposed? then
|
580
|
+
unless obj.consent_tier_statuses.empty? then
|
581
|
+
obj.consent_tier_statuses.clear
|
582
|
+
template.consent_tier_statuses.clear
|
583
|
+
logger.debug { "Anticipated a caTissue side-effect by clearing the disposed #{obj.qp} consent tier statuses prior to save." }
|
584
|
+
end
|
409
585
|
end
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
586
|
+
# TODO - is there a test case for this? Isn't EID create delegated to
|
587
|
+
# specimen create, which cascades to the EID?
|
588
|
+
# elsif obj.identifier.nil? and CaTissue::ExternalIdentifier === obj then
|
589
|
+
# # application service save
|
590
|
+
# result = submit_save_template(obj, template)
|
591
|
+
# # if app service is not broken, then sync the result and return
|
592
|
+
# if obj.identifier then
|
593
|
+
# sync_saved(obj, result)
|
594
|
+
# return
|
595
|
+
# end
|
596
|
+
# logger.debug { "Work around caTissue ExternalIdentifier create bug by updating the phantom caTissue auto-generated empty #{obj.specimen} EID directly with SQL..." }
|
597
|
+
# # app service is broken; fetch the identifier and set directly via SQL
|
598
|
+
# tmpl = obj.class.new
|
599
|
+
# tmpl.setSpecimen(obj.specimen)
|
600
|
+
# eids = query(tmpl).select { |eid| eid.name.nil? }
|
601
|
+
# if eids.size > 1 then
|
602
|
+
# Jinx.fail(DatabaseError, "#{spc} has more than external identifier without a name: #{eids}")
|
603
|
+
# end
|
604
|
+
# # Set the identifier.
|
605
|
+
# obj.identifier = eids.first.identifier
|
606
|
+
# # Call the SQL
|
607
|
+
# @executor.transact(UPD_EID_SQL, obj.name, obj.value, obj.specimen.identifier, obj.identifier)
|
608
|
+
# logger.debug { "caTissue #{obj} create work-around completed." }
|
609
|
+
# return
|
416
610
|
elsif obj.identifier and CaTissue::SpecimenEventParameters === obj then
|
417
|
-
# TODO - this case occurs in the simple_test migration; fix it there and remove this check
|
611
|
+
# TODO - this case occurs in the simple_test migration; fix it there and remove this check.
|
418
612
|
# TODO - KLUDGE!!!! FIX AT SOURCE AND REMOVE SEP KLUDGE AS WELL!!!!
|
419
|
-
#
|
613
|
+
# Fix this with an auto-gen add_defaults?
|
420
614
|
if template.user.nil? then template.user = query(template, :user).first end
|
421
615
|
if template.specimen.nil? and template.specimen_collection_group.nil? then
|
422
616
|
template.specimen = query(template, :specimen).first
|
@@ -425,45 +619,142 @@ module CaTissue
|
|
425
619
|
elsif obj.identifier and CaTissue::SpecimenCollectionGroup === obj then
|
426
620
|
# add the extraneous SCG template CPR protocol and PPI, if necessary
|
427
621
|
cpr = obj.collection_protocol_registration
|
428
|
-
if cpr.nil? then
|
622
|
+
if cpr.nil? then Jinx.fail(Jinx::ValidationError, "#{obj} cannot be updated since it is missing a CPR") end
|
429
623
|
tcpr = template.collection_protocol_registration
|
430
|
-
if tcpr.nil? then
|
624
|
+
if tcpr.nil? then Jinx.fail(Jinx::ValidationError, "#{obj} CPR #{cpr} was not copied to the update template #{tcpr}") end
|
431
625
|
if tcpr.collection_protocol.nil? then
|
432
626
|
pcl = lazy_loader.enable { cpr.collection_protocol }
|
433
|
-
if pcl.nil? then
|
627
|
+
if pcl.nil? then Jinx.fail(Jinx::ValidationError, "#{obj} cannot be updated since it is missing a referenced CPR #{cpr} protocol") end
|
434
628
|
tpcl = pcl.copy(:identifier)
|
435
629
|
logger.debug { "Work around caTissue bug by adding extraneous #{template} #{tcpr} protocol #{tpcl}..." }
|
436
630
|
tmpl.collection_protocol = tpcl
|
437
631
|
end
|
438
632
|
if tcpr.protocol_participant_identifier.nil? then
|
439
633
|
ppi = lazy_loader.enable { cpr.protocol_participant_identifier }
|
440
|
-
if ppi.nil? then
|
634
|
+
if ppi.nil? then
|
635
|
+
Jinx.fail(Jinx::ValidationError, "#{obj} cannot be updated since it is missing a referenced CPR #{cpr} PPI required to work around a caTissue SCG update bug")
|
636
|
+
end
|
441
637
|
tppi = ppi.copy(:identifier)
|
442
638
|
logger.debug { "Work around caTissue bug by adding extraneous #{template} #{tcpr} PPI #{tppi}..." }
|
443
639
|
tmpl.protocol_participant_identifier = tppi
|
444
640
|
end
|
445
|
-
|
446
|
-
# TODO - why aren't obj SEPs below added with add_defaults_autogenerated in writer.rb?
|
447
|
-
#
|
448
|
-
unless template.received? then
|
449
|
-
if obj.received? then raise DatabaseError.new("#{obj} is received, but the generated template #{template} is not.") end
|
641
|
+
unless obj.received? then
|
450
642
|
rep = obj.instance_eval { create_default_received_event_parameters }
|
451
|
-
if rep.nil? then
|
643
|
+
if rep.nil? then Jinx.fail(CaRuby::DatabaseError, "Default received event parameters were not added to #{obj}.") end
|
452
644
|
rep.copy.merge_attributes(:user => rep.user, :specimen_collection_group => template)
|
453
645
|
end
|
454
|
-
unless
|
646
|
+
unless obj.collected? then
|
455
647
|
cep = obj.instance_eval { create_default_collection_event_parameters }
|
456
|
-
if cep.nil? then
|
648
|
+
if cep.nil? then Jinx.fail(CaRuby::DatabaseError, "Default collection event parameters were not added to #{obj}.") end
|
457
649
|
cep.copy.merge_attributes(:user => cep.user, :specimen_collection_group => template)
|
458
650
|
end
|
459
651
|
elsif Annotation::Proxy === obj then
|
460
|
-
|
652
|
+
Jinx.fail(CaRuby::DatabaseError, "Annotation proxy direct database save is not supported: #{obj}")
|
461
653
|
elsif Annotation === obj and obj.class.primary? then
|
462
654
|
copy_annotation_proxy_owner_to_template(obj, template)
|
463
655
|
end
|
464
656
|
|
465
|
-
#
|
657
|
+
# Work around a caTissue bug by removing CollectibleEventParameters.
|
658
|
+
ceps = strip_collectible_event_parameters(obj, template) if Collectible === obj
|
659
|
+
|
660
|
+
# delegate to the standard save
|
466
661
|
super
|
662
|
+
|
663
|
+
# post-process the deferred CEPs
|
664
|
+
if ceps and not ceps.empty? then
|
665
|
+
# the owner => target CEP hash
|
666
|
+
hash = LazyHash.new { Array.new }
|
667
|
+
ceps.each { |cep| hash[cep.owner] << cep }
|
668
|
+
hash.each do |owner, teps|
|
669
|
+
logger.debug { "Refetch the #{owner} event parameters to work around a caTissue bug..." }
|
670
|
+
fetched = fetch_association(owner, :specimen_event_parameters)
|
671
|
+
teps.each do |tep|
|
672
|
+
match = fetched.detect { |fep| tep.class === fep }
|
673
|
+
if match then
|
674
|
+
logger.debug { "Matched the #{owner} event parameter #{tep} to the fetched #{fep}." }
|
675
|
+
tep.merge(fep)
|
676
|
+
else
|
677
|
+
logger.debug { "#{owner} event parameter #{tep} does not match a fetched event parameters object." }
|
678
|
+
end
|
679
|
+
end
|
680
|
+
end
|
681
|
+
end
|
682
|
+
end
|
683
|
+
|
684
|
+
# Removes the unsaved {CollectibleEventParameters} from the given template to work around the
|
685
|
+
# caTissue bug described in {#save_with_template}.
|
686
|
+
#
|
687
|
+
# The CollectibleEventParameters are required if and only if one of the following is true:
|
688
|
+
# * the operation is a SCG save and the collected status is not pending
|
689
|
+
# * the operation is an update to a previously collected Specimen
|
690
|
+
# In all other cases, the CollectibleEventParameters are removed.
|
691
|
+
#
|
692
|
+
# This method is applied recursively to Specimen children.
|
693
|
+
#
|
694
|
+
# @param [Collectible] the Specimen or SCG template
|
695
|
+
# @return [<CollectibleEventParameters>] the removed event parameters
|
696
|
+
def strip_collectible_event_parameters(obj, template)
|
697
|
+
if obj.collected? then
|
698
|
+
return if CaTissue::SpecimenCollectionGroup === obj
|
699
|
+
if obj.identifier then
|
700
|
+
if obj.changed?(:collection_status) then
|
701
|
+
fseps = fetch_association(obj, :specimen_event_parameters)
|
702
|
+
obj.collectible_event_parameters.each do |cep|
|
703
|
+
fcep = fseps.detect { |fsep| cep.class === fsep }
|
704
|
+
cep.merge(fcep) if fcep
|
705
|
+
end
|
706
|
+
template.collectible_event_parameters.each do |cep|
|
707
|
+
fcep = fseps.detect { |fsep| cep.class === fsep }
|
708
|
+
cep.merge(fcep) if fcep
|
709
|
+
end
|
710
|
+
end
|
711
|
+
return
|
712
|
+
end
|
713
|
+
end
|
714
|
+
ceps = template.specimen_event_parameters.select { |ep| CollectibleEventParameters === ep }
|
715
|
+
unless ceps.empty? then
|
716
|
+
ceps.each { |cep| template.specimen_event_parameters.delete(cep) }
|
717
|
+
logger.debug { "Worked around caTissue bug by stripping the following collectible event parameters from the #{template} template: #{ceps.pp_s}." }
|
718
|
+
end
|
719
|
+
if CaTissue::Specimen === template then
|
720
|
+
obj.children.each do |spc|
|
721
|
+
tmpl = spc.match_in(template.children)
|
722
|
+
ceps.concat(strip_collectible_event_parameters(spc, tmpl)) if tmpl
|
723
|
+
end
|
724
|
+
end
|
725
|
+
ceps
|
726
|
+
end
|
727
|
+
|
728
|
+
#
|
729
|
+
# @quirk caTissue When caTissue updates a pending Specimen to status Complete, an auto-generated
|
730
|
+
# collection and received event parameters is added to the Specimen even if the Specimen already
|
731
|
+
# has a collection and received event parameters. The redundant event parameters corrupts the
|
732
|
+
# database content and sporadically results in a GUI Severe Server Error with no trace-back.
|
733
|
+
# Work around this bug by deleting the extraneous CollectibleEventParameters.
|
734
|
+
# Hammer the database directly to back out this insidious caTissue-generated corruption.
|
735
|
+
def submit_save_template(obj, template)
|
736
|
+
result = super
|
737
|
+
|
738
|
+
if CaTissue::Specimen === obj and obj.identifier and obj.collected? and obj.changed?(:collection_status) then
|
739
|
+
fseps = query(obj.copy(:identifier), :specimen_event_parameters)
|
740
|
+
obj.collectible_event_parameters.each do |cep|
|
741
|
+
next if cep.identifier.nil?
|
742
|
+
bogus = fseps.detect { |fsep| cep.class === fsep and cep.identifier != fsep.identifier } || next
|
743
|
+
logger.debug { "Work around caTissue event parameters auto-corruption bug by deleting the bogus #{bogus}..." }
|
744
|
+
table = case bogus.class.name.demodulize
|
745
|
+
when 'CollectionEventParameters' then 'catissue_coll_event_param'
|
746
|
+
when 'ReceivedEventParameters' then 'catissue_received_event_param'
|
747
|
+
else Jinx.fail(CaRuby::DatabaseError, "Collectible event parameter class not recognized: #{bogus.class}")
|
748
|
+
end
|
749
|
+
sql = "delete from #{table} where identifier = ?"
|
750
|
+
executor.transact(sql, bogus.identifier)
|
751
|
+
sql = "delete from catissue_specimen_event_param where identifier = ?"
|
752
|
+
executor.transact(sql, bogus.identifier)
|
753
|
+
logger.debug { "Worked around caTissue event parameters auto-corruption bug by deleting the bogus #{bogus}." }
|
754
|
+
end
|
755
|
+
end
|
756
|
+
|
757
|
+
result
|
467
758
|
end
|
468
759
|
|
469
760
|
# The annotation proxy is not copied because the attribute redirects to the hook rather
|
@@ -473,51 +764,116 @@ module CaTissue
|
|
473
764
|
# @param [Annotation] obj the copy source
|
474
765
|
# @param [Annotation] template the copy target
|
475
766
|
def copy_annotation_proxy_owner_to_template(obj, template)
|
476
|
-
|
767
|
+
prop = obj.class.proxy_property
|
477
768
|
# Ignore the proxy attribute if it is defined by caRuby rather than caTissue.
|
478
|
-
return unless
|
479
|
-
rdr, wtr =
|
769
|
+
return unless prop and prop.java_property?
|
770
|
+
rdr, wtr = prop.property_accessors
|
480
771
|
pxy = obj.send(rdr)
|
481
772
|
logger.debug { "Setting #{obj.qp} template #{template.qp} proxy owner to #{pxy}..." }
|
482
773
|
template.send(wtr, pxy)
|
483
774
|
end
|
484
775
|
|
485
|
-
# Augment
|
776
|
+
# Augment +CaRuby::Database::Writer.create_object+ to work around caTissue bugs and pass through
|
777
|
+
# an {Annotation::Proxy} to the referenced annotations.
|
778
|
+
#
|
486
779
|
# @quirk caTissue Bug #124: SCG SpecimenEventParameters save fails validation.
|
487
780
|
# Work-around is to create the SEP by updating the SCG.
|
488
|
-
#
|
489
|
-
#
|
490
|
-
#
|
781
|
+
#
|
782
|
+
# @quirk caTissue If the save argument domain object is a CaTissue::Specimen with the +is_available+
|
783
|
+
# flag set to false, then work around the bug described in {#create_unavailable_specimen}.
|
784
|
+
#
|
785
|
+
# @quirk caTissue Bug #161: Specimen API disposal is not reflected in the saved result activity status.
|
491
786
|
# DisposalEventParameters create sets the owner Specimen activity_status to +Closed+ as a side-effect.
|
492
787
|
# Reflect this side-effect in the submitted DisposalEventParameters owner Specimen object.
|
493
|
-
#
|
788
|
+
#
|
789
|
+
# @quirk caTissue 1.2 An undocumented caTissue "feature" is that Specimen API disposal clears the
|
790
|
+
# Specimen consent tier statuses as a side-effect. Reflect this side-effect in the submitted
|
791
|
+
# DisposalEventParameters owner Specimen object.
|
792
|
+
#
|
793
|
+
# @quirk caTissue 1.2 Creating a specimen aliquot without a label or barcode resuts in a server
|
794
|
+
# uniqueness constraint failure SQL error. Work-around is to generate the aliquot and barcode
|
795
|
+
# on the fly.
|
796
|
+
#
|
797
|
+
# @quirk caTissue caTissue API create with an Address argument completes without an exception but
|
798
|
+
# silently ignores the argument and does not create the record. The create call has a result
|
799
|
+
# that is a copy of the argument, including the missing identifier.
|
800
|
+
# Unlike the {Database} {#create} method, this private {#create_object} method must allow an Address
|
801
|
+
# object in order to support the user address update caTissue bug work-around. However, the address
|
802
|
+
# update bug work-around encounters the Address create missing id caTissue bug. The
|
803
|
+
# work-around for this caTissue bug work-around bug is to bypass the caTissue API, hit the database
|
804
|
+
# directly with SQL, find the matching database record, and set the identifier to the matching record
|
805
|
+
# identifier. The match is complicated by the possibility that a different client might create an
|
806
|
+
# address after the SQL transaction but before the max id query. The work-around for this potential
|
807
|
+
# caTissue bug work-around bug work-around bug is to fetch addresses until one matches, then set the
|
808
|
+
# created address identifier to that fetched record identifier.
|
494
809
|
#
|
495
810
|
# @param [Resource] obj the dependent domain object to save
|
496
811
|
def create_object(obj)
|
497
|
-
|
498
|
-
|
812
|
+
if CaTissue::Address === obj then
|
813
|
+
return create_address(obj)
|
814
|
+
elsif CollectibleEventParameters === obj and obj.specimen_collection_group then
|
815
|
+
return save_collectible_scg_event_parameters(obj)
|
499
816
|
elsif CaTissue::Specimen === obj then
|
500
817
|
obj.add_defaults
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
818
|
+
# Work around aliquot bug
|
819
|
+
if obj.parent and obj.characteristics == obj.parent.characteristics then
|
820
|
+
if obj.label.nil? then
|
821
|
+
obj.label = obj.barcode = Jinx::Uniquifier.qualifier
|
822
|
+
logger.debug { "Worked around caTissue 1.2 bug by creating #{obj} aliquot label and barcode." }
|
823
|
+
end
|
824
|
+
obj.barcode = obj.label if obj.barcode.nil?
|
825
|
+
end
|
826
|
+
# Special case for an unavailable specimen.
|
827
|
+
# The obj.is_available == false test is required as opposed to obj.is_available?,
|
828
|
+
# since a nil is_available flag does not imply an unavailable specimen.
|
829
|
+
if obj.is_available == false or obj.available_quantity.zero? or obj.disposed? then
|
830
|
+
return create_unavailable_specimen(obj)
|
505
831
|
end
|
506
832
|
end
|
507
833
|
|
508
834
|
# standard create
|
509
835
|
super
|
510
|
-
|
836
|
+
|
511
837
|
# replicate caTissue create side-effects in the submitted object
|
512
838
|
if CaTissue::DisposalEventParameters === obj then
|
513
839
|
obj.specimen.activity_status = 'Closed'
|
514
840
|
logger.debug { "Set the created DisposalEventParameters #{obj.qp} owner #{obj.specimen.qp} activity status to Closed." }
|
841
|
+
unless obj.specimen.consent_tier_statuses.empty? then
|
842
|
+
obj.specimen.consent_tier_statuses.clear
|
843
|
+
logger.debug { "Cleared the created DisposalEventParameters #{obj.qp} owner #{obj.specimen.qp} consent tier statuses." }
|
844
|
+
end
|
515
845
|
end
|
516
846
|
|
517
847
|
obj
|
518
848
|
end
|
519
849
|
|
520
|
-
#
|
850
|
+
# The Address attribute => column map for those attributes whose column differs from the attribute name.
|
851
|
+
ADDRESS_ATTR_COL_HASH = { :zip_code => :zipcode }
|
852
|
+
|
853
|
+
# @param [CaTissue::Address] address the address to create
|
854
|
+
# @return [CaTissue::Address] the created address
|
855
|
+
# @raise [CaRuby::DatabaseError] if the address could not be created
|
856
|
+
# @see #create_object
|
857
|
+
def create_address(address)
|
858
|
+
vh = address.value_hash
|
859
|
+
cols = vh.keys.map { |pa| ADDRESS_ATTR_COL_HASH[pa] or pa }
|
860
|
+
sql = "insert into catissue_address ( #{cols.join(', ')} )\nvalues ( #{Array.new(vh.size).fill('?').join(', ')} )"
|
861
|
+
logger.debug { "Bypass the broken caTissue API address create and hit the database directly." }
|
862
|
+
next_addr_id = executor.query(MAX_ADDR_ID_SQL).first.first
|
863
|
+
executor.transact(sql, *vh.values)
|
864
|
+
while fetched = CaTissue::Address.new(:identifier => next_addr_id).find do
|
865
|
+
if vh.all? { |pa, v| fetched.send(pa) == v } then
|
866
|
+
address.identifier = fetched.identifier
|
867
|
+
address.take_snapshot
|
868
|
+
logger.debug { "Created #{address.qp}." }
|
869
|
+
return address
|
870
|
+
end
|
871
|
+
next_addr_id += 1
|
872
|
+
end
|
873
|
+
Jinx.fail(CaRuby::DatabaseError, "No address found which matches the created #{address}")
|
874
|
+
end
|
875
|
+
|
876
|
+
# Overrides +CaRuby::Database.create_from_template+ as follows:
|
521
877
|
# * Surrogate {Annotation::Proxy} is "created" by setting the identifier to its hook owner.
|
522
878
|
# The create operation then creates referenced uncreated dependents.
|
523
879
|
#
|
@@ -526,7 +882,7 @@ module CaTissue
|
|
526
882
|
if Annotation::Proxy === obj then
|
527
883
|
hook = obj.hook
|
528
884
|
if hook.identifier.nil? then
|
529
|
-
|
885
|
+
Jinx.fail(CaRuby::DatabaseError, "Annotation proxy #{obj.qp} hook owner #{hook.qp} does not have an identifier")
|
530
886
|
end
|
531
887
|
obj.identifier = hook.identifier
|
532
888
|
obj.take_snapshot
|
@@ -543,51 +899,54 @@ module CaTissue
|
|
543
899
|
# Creates the given specimen by working around the following bug:
|
544
900
|
#
|
545
901
|
# @quirk caTissue Bug #160: Missing Is Available? validation.
|
546
|
-
#
|
547
|
-
#
|
548
|
-
#
|
549
|
-
#
|
550
|
-
#
|
551
|
-
#
|
552
|
-
#
|
553
|
-
#
|
554
|
-
#
|
555
|
-
#
|
556
|
-
# work-around as follows:
|
557
|
-
#
|
558
|
-
#
|
559
|
-
#
|
560
|
-
#
|
902
|
+
# Cannot create a Specimen with any of the following conditions:
|
903
|
+
# * zero available_quantity
|
904
|
+
# * is_available flag set to false
|
905
|
+
# * activity_status is +Closed+
|
906
|
+
#
|
907
|
+
# The work-around is to set the flags to true and +Active+, resp., set the quantities
|
908
|
+
# to a non-zero value, create the Specimen and then update the created Specimen with
|
909
|
+
# the original values.
|
910
|
+
#
|
911
|
+
# If the specimen has a disposal event, then this work-around interacts with the
|
912
|
+
# {#save_changed_dependents} work-around as follows:
|
913
|
+
# * Delete that event from the Specimen.
|
914
|
+
# * Create the Specimen as described above.
|
915
|
+
# * Update the Specimen as described above, but do not set the activity_status to +Closed+.
|
916
|
+
# * Create the pending disposal event.
|
561
917
|
#
|
562
918
|
# @param [CaTissue::Specimen] specimen the specimen to create
|
563
919
|
def create_unavailable_specimen(specimen)
|
564
920
|
logger.debug { "Resetting #{specimen} quantities and available flag temporarily to work around caTissue Bug #160..." }
|
565
921
|
specimen.is_available = true
|
922
|
+
# Capture the intended initial quantity and status.
|
566
923
|
oiqty = specimen.initial_quantity
|
567
|
-
oaqty = specimen.available_quantity
|
568
924
|
ostatus = specimen.activity_status
|
925
|
+
# Reset the quantities and status to values which caTissue will accept.
|
569
926
|
specimen.initial_quantity = 1.0
|
570
927
|
specimen.available_quantity = 1.0
|
571
928
|
specimen.activity_status = 'Active'
|
572
929
|
# Cannot reset a disposed Specimen quantity, so postpone disposal until
|
573
930
|
# quantities are reset.
|
574
931
|
dsp = specimen.specimen_events.detect { |sep| CaTissue::DisposalEventParameters === sep }
|
575
|
-
if dsp then
|
576
|
-
specimen.specimen_events.delete(dsp)
|
577
|
-
end
|
932
|
+
if dsp then specimen.specimen_events.delete(dsp) end
|
578
933
|
|
579
|
-
#
|
580
|
-
|
934
|
+
# Delegate to the standard create.
|
935
|
+
self.class.superclass.instance_method(:create_object).bind(self).call(specimen)
|
581
936
|
|
582
|
-
logger.debug { "
|
937
|
+
logger.debug { "Complete the caTissue Bug #160 work-around by reupdating the created #{specimen} with the initial quantity set back to the original value..." }
|
938
|
+
# Restore the available flag and initial quantity.
|
583
939
|
specimen.is_available = false
|
584
940
|
specimen.initial_quantity = oiqty
|
585
|
-
#
|
941
|
+
# The available quantity is always zero, since the available flag is set to false.
|
586
942
|
specimen.available_quantity = 0.0
|
587
943
|
# Leave status Active if there is a disposal event, since quantities cannot be reset
|
588
944
|
# on a closed Specimen and creating the disposal event below will close the Specimen.
|
589
945
|
specimen.activity_status = ostatus unless dsp
|
590
|
-
update(specimen)
|
946
|
+
# Update directly without a cyclic operation check, since update(specimen) of a
|
947
|
+
# derived specimen delegates to the parent, which in turn might be the outer
|
948
|
+
# save context.
|
949
|
+
update_from_template(specimen)
|
591
950
|
|
592
951
|
# Finally, create the disposal event if one is pending.
|
593
952
|
if dsp then
|
@@ -598,8 +957,23 @@ module CaTissue
|
|
598
957
|
logger.debug { "#{specimen} caTissue Bug #160 work-around completed." }
|
599
958
|
specimen
|
600
959
|
end
|
960
|
+
|
961
|
+
# Overrides +CaRuby::Database::Persistifier.detoxify+ to work around the
|
962
|
+
# caTissue bugs described in {CaTissue::Specimen.remove_phantom_external_identifier}
|
963
|
+
# and {CaTissue::Participant.remove_phantom_medical_identifier}.
|
964
|
+
def detoxify(toxic)
|
965
|
+
if toxic.collection? then
|
966
|
+
case toxic.first
|
967
|
+
when CaTissue::ExternalIdentifier then
|
968
|
+
CaTissue::Specimen.remove_phantom_external_identifier(toxic)
|
969
|
+
when CaTissue::ParticipantMedicalIdentifier then
|
970
|
+
CaTissue::Participant.remove_phantom_medical_identifier(toxic)
|
971
|
+
end
|
972
|
+
end
|
973
|
+
super
|
974
|
+
end
|
601
975
|
|
602
|
-
# Overrides
|
976
|
+
# Overrides +CaRuby::Database::Reader.fetch_object} to circumvent {Annotation+ fetch, since an annotation
|
603
977
|
# does not have a key.
|
604
978
|
def fetch_object(obj)
|
605
979
|
super or fetch_alternative(obj)
|
@@ -635,24 +1009,10 @@ module CaTissue
|
|
635
1009
|
# Annotations are created following the owner create.
|
636
1010
|
#
|
637
1011
|
# @param obj (see #create_object)
|
638
|
-
# @param [
|
1012
|
+
# @param [CaRuby::Property] prop the candidate attribute metadata
|
639
1013
|
# @return [Boolean] whether the attribute should not be included in the create template
|
640
|
-
def exclude_pending_create_attribute?(obj,
|
641
|
-
|
642
|
-
end
|
643
|
-
|
644
|
-
# Override {CaRuby::Database#query_safe} to work around the following +caTissue+ bugs:
|
645
|
-
# * @quirk caTissue Specimen auto-generates blank ExternalIdentifier.
|
646
|
-
# cf. https://cabig-kc.nci.nih.gov/Biospecimen/forums/viewtopic.php?f=19&t=436&sid=ef98f502fc0ab242781b7759a0eaff36
|
647
|
-
# * @quirk caTissue Specimen auto-generates blank PMI.
|
648
|
-
def query_safe(obj_or_hql, *path)
|
649
|
-
if path.last == :external_identifiers then
|
650
|
-
CaTissue::Specimen.remove_empty_external_identifier(super)
|
651
|
-
elsif path.last == :participant_medical_identifiers then
|
652
|
-
CaTissue::Specimen.remove_empty_medical_identifier(super)
|
653
|
-
else
|
654
|
-
super
|
655
|
-
end
|
1014
|
+
def exclude_pending_create_attribute?(obj, prop)
|
1015
|
+
prop.type < Annotation or super
|
656
1016
|
end
|
657
1017
|
|
658
1018
|
# @quirk caTissue Bug #147: SpecimenRequirement query ignores CPE.
|
@@ -687,8 +1047,8 @@ module CaTissue
|
|
687
1047
|
# @return [Boolean] whether the given attribute is a reference from an {Annotatable} to a #{Annotation::Proxy}
|
688
1048
|
def hook_proxy_attribute?(obj, attribute)
|
689
1049
|
return false if attribute.nil?
|
690
|
-
|
691
|
-
|
1050
|
+
prop = obj.class.property(attribute)
|
1051
|
+
prop.declarer < Annotatable and prop.type < Annotation::Proxy
|
692
1052
|
end
|
693
1053
|
|
694
1054
|
# Queries on the given object attribute using the {Annotation::IntegrationService}.
|
@@ -722,7 +1082,7 @@ module CaTissue
|
|
722
1082
|
annotator.integrator.find(proxy)
|
723
1083
|
end
|
724
1084
|
|
725
|
-
# @quirk caCORE Override
|
1085
|
+
# @quirk caCORE Override +CaRuby::Database::Reader.invertible_query?+ to enable the Bug #147 work
|
726
1086
|
# around in {#query_object}. Invertible queries are performed to work around Bug #79. However, this
|
727
1087
|
# work-around induces Bug #147, so we disable the Bug #79 work-around here for the special case of
|
728
1088
|
# a CPE in order to enable the Bug #147 work-around. And so it goes....
|
@@ -788,4 +1148,4 @@ module CaTissue
|
|
788
1148
|
attribute ? query(cpe, :specimen_requirements, attribute) : query(cpe, :specimen_requirements)
|
789
1149
|
end
|
790
1150
|
end
|
791
|
-
end
|
1151
|
+
end
|