caruby-tissue 1.4.2 → 1.5.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (115) hide show
  1. data/History.md +53 -0
  2. data/bin/crtdump +1 -6
  3. data/bin/crtexample +1 -6
  4. data/bin/crtmigrate +1 -6
  5. data/bin/crtsmoke +1 -6
  6. data/conf/migration/filter_fields.yaml +1 -1
  7. data/conf/migration/general_fields.yaml +1 -1
  8. data/conf/migration/simple_fields.yaml +1 -1
  9. data/conf/migration/small_fields.yaml +1 -1
  10. data/examples/galena/README.md +34 -25
  11. data/examples/galena/bin/seed +2 -7
  12. data/examples/galena/conf/migration/annotation_fields.yaml +1 -1
  13. data/examples/galena/conf/migration/filter_fields.yaml +1 -1
  14. data/examples/galena/conf/migration/frozen_fields.yaml +2 -2
  15. data/examples/galena/conf/migration/general_fields.yaml +1 -1
  16. data/examples/galena/conf/migration/registration_fields.yaml +1 -0
  17. data/examples/galena/conf/migration/simple_fields.yaml +1 -1
  18. data/examples/galena/data/frozen.csv +1 -1
  19. data/examples/galena/data/general.csv +1 -1
  20. data/examples/galena/data/registration.csv +1 -1
  21. data/examples/galena/lib/galena/tissue/migration/frozen_shims.rb +8 -4
  22. data/examples/galena/lib/galena/tissue/seed/defaults.rb +16 -9
  23. data/examples/pcbin/README.md +8 -8
  24. data/examples/pcbin/conf/biopsy_fields.yaml +1 -1
  25. data/examples/pcbin/conf/patient_fields.yaml +1 -1
  26. data/examples/pcbin/conf/surgery_fields.yaml +1 -1
  27. data/examples/pcbin/conf/t_stage_fields.yaml +1 -1
  28. data/examples/pcbin/conf/therapy_fields.yaml +1 -1
  29. data/lib/catissue.rb +1 -1
  30. data/lib/catissue/annotation/annotatable.rb +12 -22
  31. data/lib/catissue/annotation/annotatable_class.rb +87 -114
  32. data/lib/catissue/annotation/annotation.rb +9 -44
  33. data/lib/catissue/annotation/annotation_class.rb +238 -145
  34. data/lib/catissue/annotation/annotation_module.rb +47 -59
  35. data/lib/catissue/annotation/de_integration.rb +12 -2
  36. data/lib/catissue/annotation/proxy.rb +11 -17
  37. data/lib/catissue/annotation/proxy_1_1.rb +47 -0
  38. data/lib/catissue/annotation/proxy_class.rb +55 -37
  39. data/lib/catissue/annotation/record_entry_proxy.rb +20 -0
  40. data/lib/catissue/database.rb +164 -112
  41. data/lib/catissue/database/annotation/annotation_service.rb +5 -5
  42. data/lib/catissue/database/annotation/annotator.rb +2 -2
  43. data/lib/catissue/database/annotation/entity_facade.rb +24 -21
  44. data/lib/catissue/database/annotation/id_generator.rb +26 -26
  45. data/lib/catissue/database/annotation/integration_service.rb +8 -8
  46. data/lib/catissue/database/annotation/record_entry_integrator.rb +24 -6
  47. data/lib/catissue/database/annotation/reference_writer.rb +33 -6
  48. data/lib/catissue/domain.rb +26 -0
  49. data/lib/catissue/domain/abstract_position.rb +1 -1
  50. data/lib/catissue/domain/abstract_specimen.rb +16 -16
  51. data/lib/catissue/domain/check_in_check_out_event_parameter.rb +1 -1
  52. data/lib/catissue/domain/collection_event_parameters.rb +1 -1
  53. data/lib/catissue/domain/collection_protocol.rb +9 -13
  54. data/lib/catissue/domain/collection_protocol_event.rb +4 -4
  55. data/lib/catissue/domain/collection_protocol_registration.rb +4 -4
  56. data/lib/catissue/domain/container.rb +12 -21
  57. data/lib/catissue/domain/container_type.rb +65 -64
  58. data/lib/catissue/domain/disposal_event_parameters.rb +1 -1
  59. data/lib/catissue/domain/hash_code.rb +3 -6
  60. data/lib/catissue/domain/new_specimen_array_order_item.rb +4 -4
  61. data/lib/catissue/domain/order_details.rb +3 -3
  62. data/lib/catissue/domain/participant.rb +28 -20
  63. data/lib/catissue/domain/participant/clinical/chemotherapy.rb +21 -0
  64. data/lib/catissue/domain/participant/clinical/duration.rb +11 -0
  65. data/lib/catissue/domain/participant/clinical/radiation_therapy.rb +21 -0
  66. data/lib/catissue/domain/participant_medical_identifier.rb +2 -2
  67. data/lib/catissue/domain/site.rb +6 -6
  68. data/lib/catissue/domain/specimen.rb +41 -34
  69. data/lib/catissue/domain/specimen/pathology/prostate_specimen_gleason_score.rb +18 -0
  70. data/lib/catissue/domain/specimen/pathology/prostate_specimen_pathology_annotation.rb +13 -8
  71. data/lib/catissue/domain/specimen/pathology/specimen_additional_finding.rb +21 -0
  72. data/lib/catissue/domain/specimen/pathology/specimen_base_solid_tissue_pathology_annotation.rb +30 -7
  73. data/lib/catissue/domain/specimen/pathology/specimen_details.rb +18 -0
  74. data/lib/catissue/domain/specimen/pathology/specimen_histologic_grade.rb +18 -0
  75. data/lib/catissue/domain/specimen/pathology/specimen_histologic_type.rb +22 -6
  76. data/lib/catissue/domain/specimen/pathology/specimen_histologic_variant_type.rb +18 -0
  77. data/lib/catissue/domain/specimen/pathology/specimen_invasion.rb +18 -0
  78. data/lib/catissue/domain/specimen_array.rb +8 -8
  79. data/lib/catissue/domain/specimen_array_content.rb +4 -4
  80. data/lib/catissue/domain/specimen_collection_group.rb +43 -41
  81. data/lib/catissue/domain/specimen_collection_group/pathology/base_pathology_annotation.rb +16 -0
  82. data/lib/catissue/domain/specimen_collection_group/pathology/base_solid_tissue_pathology_annotation.rb +16 -0
  83. data/lib/catissue/domain/specimen_position.rb +3 -3
  84. data/lib/catissue/domain/specimen_protocol.rb +7 -7
  85. data/lib/catissue/domain/specimen_requirement.rb +9 -9
  86. data/lib/catissue/domain/storage_container.rb +13 -13
  87. data/lib/catissue/domain/storage_type.rb +12 -7
  88. data/lib/catissue/domain/transfer_event_parameters.rb +2 -2
  89. data/lib/catissue/domain/user.rb +15 -15
  90. data/lib/catissue/extract/extractor.rb +1 -1
  91. data/lib/catissue/resource.rb +8 -36
  92. data/lib/catissue/util/location.rb +0 -13
  93. data/lib/catissue/util/person.rb +2 -1
  94. data/lib/catissue/util/position.rb +1 -11
  95. data/lib/catissue/util/uniquify.rb +1 -1
  96. data/lib/catissue/version.rb +1 -1
  97. data/lib/catissue/wustl/logger.rb +17 -16
  98. data/test/fixtures/lib/catissue/defaults_test_fixture.rb +6 -4
  99. data/test/lib/catissue/domain/collection_protocol_test.rb +0 -1
  100. data/test/lib/catissue/domain/participant_test.rb +74 -19
  101. data/test/lib/catissue/domain/specimen_collection_group_test.rb +14 -10
  102. data/test/lib/catissue/domain/specimen_test.rb +36 -60
  103. data/test/lib/catissue/domain/storage_type_test.rb +5 -5
  104. data/test/lib/catissue/domain/transfer_event_parameters_test.rb +14 -12
  105. data/test/lib/catissue/migration/test_case.rb +0 -1
  106. data/test/lib/catissue/test_case.rb +12 -12
  107. data/test/lib/examples/galena/tissue/domain/examples_test.rb +1 -1
  108. data/test/lib/examples/galena/tissue/migration/annotation_test.rb +9 -7
  109. data/test/lib/examples/galena/tissue/migration/frozen_test.rb +1 -1
  110. data/test/lib/examples/galena/tissue/migration/general_test.rb +1 -1
  111. data/test/lib/examples/galena/tissue/migration/registration_test.rb +3 -9
  112. data/test/lib/examples/galena/tissue/migration/seedify.rb +3 -3
  113. data/test/lib/examples/pcbin/migration_test.rb +37 -16
  114. metadata +24 -10
  115. data/History.txt +0 -50
@@ -1,4 +1,4 @@
1
- require 'caruby/domain/resource_module'
1
+ require 'caruby/domain'
2
2
  require 'catissue/annotation/annotation'
3
3
  require 'catissue/annotation/annotation_class'
4
4
  require 'catissue/annotation/proxy'
@@ -7,15 +7,15 @@ require 'catissue/annotation/de_integration'
7
7
 
8
8
  module CaTissue
9
9
  module AnnotationModule
10
- include CaRuby::ResourceModule
10
+ include CaRuby::Domain
11
11
 
12
- # @return [AnnotationClass] the annotation proxy class
13
- attr_accessor :proxy
12
+ # @return [ProxyClass] the annotation proxy class
13
+ attr_reader :proxy
14
14
 
15
15
  # @return [Class] the hook-annotation association class, or nil for 1.1.x caTissue
16
16
  attr_reader :record_entry_class
17
17
 
18
- # @return [Symbol] the {#record_entry_class} hook writer method, or nil for 1.1.x caTissue
18
+ # @return [Symbol] the {#de_integration_proxy_class} hook writer method, or nil for 1.1.x caTissue
19
19
  attr_reader :record_entry_hook_writer
20
20
 
21
21
  # @param [AnnotationModule] mod the annotation module to build
@@ -29,43 +29,33 @@ module CaTissue
29
29
  mod.initialize_annotation(hook, opts)
30
30
  end
31
31
 
32
+ # Builds the annotation module.
33
+ # This method intended to be called only by {AnnotationModule.extend_module}.
34
+ #
35
+ # @param (see AnnotationModule.extend_module)
32
36
  def initialize_annotation(hook, opts)
33
37
  logger.debug { "Building #{hook.qp} annotation #{qp}..." }
34
- @java_package = opts[:package]
38
+ pkg = opts[:package]
35
39
  @svc_nm = opts[:service]
36
- create_mixin(hook)
37
- # Proxy initialization has to set proxy mid-initialization.
38
- # That is why @proxy is writable. Although setting @proxy
39
- # is redundant here, do so since that is the better approach
40
- # and will be necessary if and when proxy init is cleaned up.
41
- rec_entry = opts[:record_entry]
42
- if rec_entry then
43
- if Annotation::DEIntegration.const_defined?(rec_entry) then
44
- @record_entry_class = Annotation::DEIntegration.const_get(rec_entry)
45
- @record_entry_hook_writer = "#{hook.name.demodulize.underscore}=".to_sym
46
- else
47
- logger.warn("Ignored missing annotation #{name} record entry class #{rec_entry}.")
48
- rec_entry = nil
49
- end
40
+ # Enable the resource metadata aspect.
41
+ md_proc = Proc.new { |klass| AnnotationClass.extend_class(klass, self) }
42
+ CaRuby::Domain::Importer.extend_module(self, :mixin => Annotation, :metadata => md_proc, :package => pkg)
43
+ dei = hook.de_integration_proxy_class
44
+ if dei then
45
+ import_record_entry_class(dei, hook)
46
+ pxy_nm = dei.name.demodulize
50
47
  end
51
- @proxy = import_proxy(hook, rec_entry)
52
- logger.debug { "Building #{name} #{hook.qp} annotation proxy #{@proxy.class.name}..." }
53
- @proxy.extend(Annotation::ProxyClass)
48
+ @proxy = import_proxy(hook, pxy_nm)
49
+ load_annotation_class_definitions(hook)
54
50
  logger.debug { "Built #{hook.qp} annotation #{qp}." }
55
51
  end
56
52
 
57
- # Ensures that each primary annotation in this module has a proxy reference attribute.
58
- # The primary annotation creates a proxy attribute if necessary.
59
- def ensure_proxy_attributes_are_defined
60
- logger.debug { "Ensuring that #{qp} primary annotations reference the proxy #{@proxy.qp}..." }
61
- @rsc_classes.each { |klass| klass.ensure_primary_has_proxy(@proxy) }
62
- end
63
-
64
- # Builds an annotation dependency hierarchy starting at the proxy.
65
- def add_annotation_dependents
66
- @proxy.add_annotation_dependents
53
+ # @return (ProxyClass#hook)
54
+ def hook
55
+ @proxy.hook
67
56
  end
68
57
 
58
+ # @return [CaRuby::PersistenceService] this module's application service
69
59
  def persistence_service
70
60
  @ann_svc ||= Database.instance.annotator.create_annotation_service(self, @svc_nm)
71
61
  end
@@ -73,41 +63,39 @@ module CaTissue
73
63
  private
74
64
 
75
65
  # The location of the domain class definitions.
76
- DOMAIN_DIR = File.join(File.dirname(__FILE__), '..', 'domain')
66
+ DOMAIN_DIR = File.join(File.dirname(File.dirname(__FILE__)), 'domain')
77
67
 
78
- def create_mixin(hook)
79
- module_eval("module Resource; include Annotation; end")
80
- @mixin = const_get('Resource')
81
- class << self
82
- def class_added(klass)
83
- klass.extend(AnnotationClass)
84
- logger.debug { "#{klass} marked as an annotation class." }
85
- if @proxy then klass.ensure_primary_has_proxy(@proxy) end
86
- end
87
- end
88
- ann_subdir = name.demodulize.underscore
89
- @ann_def_dir = File.join(DOMAIN_DIR, hook.name.demodulize.underscore, ann_subdir)
90
- load_dir(@ann_def_dir)
68
+ def load_annotation_class_definitions(hook)
69
+ dir = File.join(DOMAIN_DIR, hook.name.demodulize.underscore, name.demodulize.underscore)
70
+ load_dir(dir)
71
+ end
72
+
73
+ # Sets the record entry instance variables for the given class name, if it exists
74
+ # as a {Annotation::DEIntegration} proxy class. caTissue v1.1.x does not have
75
+ # a record entry class.
76
+ #
77
+ # @param [String] the record entry class name specified in the
78
+ # {CaTissue::AnntatableClass#add_annotation_attribute} +:record_entry+ option
79
+ def import_record_entry_class(klass, hook)
80
+ @record_entry_class = const_get(klass.name.demodulize.to_sym)
81
+ @record_entry_hook_writer = "#{hook.name.demodulize.underscore}=".to_sym
91
82
  end
92
83
 
93
84
  # @param hook (see #initialize_annotation)
94
- # @param [String] name the demodulized name of the proxy class, or nil for caTissue 1.1.x
85
+ # @param [String] name the demodulized name of the proxy class
86
+ # (default is the demodulized hook class name)
95
87
  def import_proxy(hook, name=nil)
96
- logger.debug { "Importing #{qp} #{hook.qp} proxy#{' ' + name if name}..." }
97
88
  name ||= hook.name.demodulize
89
+ logger.debug { "Importing #{qp} #{hook.qp} annotation proxy..." }
98
90
  begin
99
- const_get(name.to_sym)
100
- rescue CaRuby::JavaIncludeError
101
- raise
91
+ klass = const_get(name.to_sym)
92
+ rescue CaRuby::JavaImportError
102
93
  raise AnnotationError.new("#{hook.qp} annotation #{qp} does not have a hook proxy class - #{$!}")
103
94
  end
104
- end
105
-
106
- # Infers the given annotation class's inverses attributes.
107
- #
108
- # @param (see ResourceModule#imported)
109
- def imported(klass)
110
- klass.infer_inverses
95
+ klass.extend(Annotation::ProxyClass)
96
+ klass.hook = hook
97
+ logger.debug { "Built #{name} #{hook.qp} annotation proxy #{klass}." }
98
+ klass
111
99
  end
112
100
  end
113
101
  end
@@ -1,3 +1,5 @@
1
+ require 'caruby/domain'
2
+
1
3
  module CaTissue
2
4
  module Annotation
3
5
  # DEIntegration encapsulates the +edu.wustl.catissuecore.domain.deintegration+ package in caTissue 1.2 and higher.
@@ -6,13 +8,21 @@ module CaTissue
6
8
  # @return [Class] yet another undocumented special-purpose association record entry class
7
9
  # which associates the given hook proxy class symbol to an annotation
8
10
  def self.const_missing(symbol)
11
+ name = [PKG, symbol].join('.')
12
+ logger.debug { "Importing DE integration proxy Java class #{name}..." }
9
13
  begin
10
- java_import [PKG, symbol].join('.')
11
- rescue Exception
14
+ java_import name
15
+ rescue NameError
12
16
  super
13
17
  end
14
18
  end
15
19
 
20
+ # @param [String] name the annotated hook class name
21
+ # @return [Class, nil] the hook proxy class, or nil if none defined
22
+ def self.proxy(name)
23
+ const_get(name.to_sym) rescue nil
24
+ end
25
+
16
26
  private
17
27
 
18
28
  # The auxiliary record entry class Java package name.
@@ -2,26 +2,20 @@ module CaTissue
2
2
  module Annotation
3
3
  # {CaTissue::Resource} annotation hook proxy mix-in.
4
4
  module Proxy
5
- # The hook proxy identifier is the hook identifier.
6
- # This method delegates to the hook.
7
- #
8
- # @return [Integer] the hook identifier
9
- def identifier
10
- hook.identifier if hook
5
+ # @return [Annotatable] the annotated domain object
6
+ def hook
7
+ send(self.class.owner_attribute_metadata.reader)
11
8
  end
12
9
 
13
- # The hook proxy identifier cannot be set directly. Assignment is a no-op.
14
- #
15
- # @param [Integer] value the (ignored) identifier value
16
- def identifier=(value); end
10
+ # @param [Annotatable] obj the domain object to annotate
11
+ def hook=(obj)
12
+ send(self.class.owner_attribute_metadata.writer, obj)
13
+ end
17
14
 
18
- # Sets the +id+ Java property to the hook identifier.
19
- # This method must be called before saving an annotation that references this proxy.
20
- def ensure_identifier_reflects_hook
21
- if getId.nil? then
22
- setId(hook.identifier)
23
- logger.debug { "Set annotation proxy #{self} identifier to that of the hook entity #{hook.qp}." }
24
- end
15
+ # Ensures that this proxy's hook exists in the database.
16
+ def ensure_hook_exists
17
+ if hook.nil? then raise AnnotationError.new("Annotation proxy #{self} is missing the hook domain object") end
18
+ hook.ensure_exists
25
19
  end
26
20
  end
27
21
  end
@@ -0,0 +1,47 @@
1
+ require 'catissue/annotation/proxy'
2
+
3
+ module CaTissue
4
+ module Annotation
5
+ # caTissue 1.1.x {CaTissue::Resource} annotation hook proxy mix-in.
6
+ module Proxy_1_1
7
+ include Proxy
8
+
9
+ # The hook proxy identifier is the hook identifier.
10
+ # This method delegates to the hook.
11
+ #
12
+ # @return [Integer] the hook identifier
13
+ def identifier
14
+ hook.identifier if hook
15
+ end
16
+
17
+ # The proxy identifier cannot be set directly. Assignment is a no-op.
18
+ #
19
+ # @param [Integer] value the (ignored) identifier value
20
+ def identifier=(value); end
21
+
22
+ # @param [Annotatable] obj the domain object to annotate
23
+ def hook=(obj)
24
+ super
25
+ ensure_identifier_reflects_hook
26
+ end
27
+
28
+ # Ensures that this proxy's hook exists in the database. This proxy's identifier is set to
29
+ # the hook identifier.
30
+ def ensure_hook_exists
31
+ super
32
+ ensure_identifier_reflects_hook
33
+ end
34
+
35
+ private
36
+
37
+ # Sets the +id+ Java property to the hook identifier.
38
+ # This method must be called before saving a caTissue 1.1.x annotation that references this proxy.
39
+ def ensure_identifier_reflects_hook
40
+ if getId.nil? and hook and hook.identifier then
41
+ setId(hook.identifier)
42
+ logger.debug { "Set annotation proxy #{self} identifier to that of the hook entity #{hook.qp}." }
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -1,57 +1,61 @@
1
1
  require 'set'
2
+ require 'caruby/util/collection'
3
+ require 'caruby/util/partial_order'
2
4
  require 'catissue/annotation/annotation'
3
- require 'catissue/annotation/proxy'
5
+ require 'catissue/annotation/proxy_1_1'
6
+ require 'catissue/annotation/record_entry_proxy'
4
7
 
5
8
  module CaTissue
6
9
  module Annotation
7
10
  # Annotation hook proxy class mix-in.
8
11
  module ProxyClass
9
- # @return [AnnotatableClass] the hook class for this proxy
10
- attr_reader :hook
11
-
12
12
  # @param [Class] klass the proxy class
13
13
  def self.extended(klass)
14
14
  super
15
- klass.class_eval { include Proxy }
15
+ # distinguish the 1.1 from the 1.2 proxy class
16
+ mixin = klass.name =~ /RecordEntry$/ ? RecordEntryProxy : Proxy_1_1
17
+ klass.class_eval { include mixin }
16
18
  end
17
19
 
18
- def annotation_attributes
19
- @ann_attrs ||= infer_annotation_attributes
20
+ # @return [AnnotatableClass] the hook class for this proxy
21
+ def hook
22
+ owner_type
20
23
  end
21
24
 
22
- # Sets this proxy's hook to the given class.
23
- # Creates the proxy => hook attribute with the given hook => proxy inverse.
25
+ # Sets this proxy's hook to the given class and creates the
26
+ # proxy => hook attribute with the given hook => proxy inverse.
24
27
  #
25
- # @param [Class] klass the hook class
26
- # @param [Symbol] inverse the hook class hook => proxy attribute
27
- def set_hook(klass, inverse)
28
- @hook = klass
28
+ # @param [AnnotatableClass] klass the annotated domain object class
29
+ def hook=(klass)
29
30
  # Make a new hook reference attribute.
30
- attr_accessor(:hook)
31
+ attr = klass.name.demodulize.underscore
32
+ attr_accessor(attr)
31
33
  # The attribute type is the given hook class.
32
- add_attribute(:hook, klass)
33
- # Setting one end of the hook <-> proxy association sets the other end.
34
- set_attribute_inverse(:hook, inverse)
35
- logger.debug { "Added #{klass.qp} annotation proxy => hook attribute with inverse #{klass.qp}.#{inverse}." }
34
+ add_attribute(attr, klass)
35
+ logger.debug { "Added #{klass.qp} annotation proxy => hook attribute #{attr}." }
36
36
  end
37
37
 
38
38
  # Adds each proxy => annotation reference as a dependent attribute.
39
- def add_annotation_dependents
40
- # first add the direct dependents
41
- annotation_attributes.each_metadata do |attr_md|
42
- attr_md.type.add_dependent_attributes
43
- end
44
- # now add the recursive indirect dependents
45
- annotation_attributes.each_metadata do |attr_md|
46
- attr_md.type.add_dependent_attribute_closure
39
+ # Recursively adds dependents of all referenced annotations.
40
+ def build_annotation_dependency_hierarchy
41
+ logger.debug { "Building annotation dependency hierarchy..." }
42
+ non_proxy_annotation_classes.each do |klass|
43
+ klass.annotation_hierarchy.each do |anc|
44
+ if anc.primary? and anc.proxy_attribute.nil? then
45
+ anc.define_proxy_attribute(self)
46
+ end
47
+ end
48
+ logger.info(klass.pp_s)
47
49
  end
50
+ set_inverses
51
+ add_dependent_attributes
52
+ add_dependent_attribute_closure
48
53
  end
49
54
 
50
55
  # Creates a reference attribute from this proxy to the given primary {Annotation} class.
51
56
  #
52
57
  # @param [Class] klass the annotation class
53
- # @param [Symbol] inverse the annotation => proxy attribute
54
- def create_annotation_attribute(klass, inverse)
58
+ def create_annotation_attribute(klass)
55
59
  # the new attribute symbol
56
60
  attr = klass.name.demodulize.underscore.pluralize.to_sym
57
61
  logger.debug { "Creating annotation proxy #{qp} attribute #{attr} to hold primary annotation #{klass.qp} instances..." }
@@ -59,20 +63,34 @@ module CaTissue
59
63
  attr_create_on_demand_accessor(attr) { Set.new }
60
64
  # add the annotation collection attribute
61
65
  add_attribute(attr, klass, :collection)
62
- # make the hook attribute which delegates to this proxy
63
- @hook.create_annotation_attribute(domain_module, attr)
64
- # set the attribute inverse
65
- set_attribute_inverse(attr, inverse)
66
+ # The annotation is dependent.
67
+ add_dependent_attribute(attr, :logical)
66
68
  attr
67
69
  end
68
70
 
69
71
  private
72
+
73
+ # Sets each annotation reference attribute inverse to the direct, unwrapped proxy
74
+ # reference named by the annotation module. E.g. the caTissue +Participant+
75
+ # +clinical+ proxy +ParticipantRecordEntry+ -> +NewDiagnosisAnnotation+ attribute
76
+ # inverse is set to the +NewDiagnosisAnnotation+ -> +ParticipantRecordEntry+
77
+ # +clinical+ reference attribute.
78
+ def set_inverses
79
+ # The inverse is the direct, unwrapped proxy reference named by the annotation module.
80
+ inv = annotation_module.name.demodulize.underscore.to_sym
81
+ # The attributes in class hierarchy general-to-specific order
82
+ attr_mds = annotation_attributes.enum_metadata.partial_sort_by { |attr_md| attr_md.type }.reverse
83
+ logger.debug { "Setting #{self} inverses for annotation attributes #{attr_mds.to_series}." }
84
+ attr_mds.each do |attr_md|
85
+ attr_md.type.define_proxy_attribute(self)
86
+ set_attribute_inverse(attr_md.to_sym, inv)
87
+ end
88
+ end
70
89
 
71
- def infer_annotation_attributes
72
- # Infer the domain attributes first. Do so with a copy of the attribute metadata objects
73
- # since the domain type infereence can result in adding a new annotation attribute.
74
- attribute_metadata_hash.values.each { |attr_md| attr_md.domain? }
75
- domain_attributes.compose { |attr_md| attr_md.type < Annotation }
90
+ # @return <AnnotationClass> the non-proxy annotation classes
91
+ def non_proxy_annotation_classes
92
+ consts = annotation_module.constants.map { |s| annotation_module.const_get(s) }
93
+ consts.select { |c| Class === c and c < Annotation and not c < Proxy }
76
94
  end
77
95
  end
78
96
  end
@@ -0,0 +1,20 @@
1
+ require 'catissue/annotation/proxy'
2
+
3
+ module CaTissue
4
+ module Annotation
5
+ # caTissue 1.2 {CaTissue::Resource} annotation hook proxy mix-in.
6
+ module RecordEntryProxy
7
+ include Proxy
8
+
9
+ # @return [Annotatable] the annotated domain object
10
+ def hook
11
+ send(self.class.owner_attribute_metadata.reader)
12
+ end
13
+
14
+ # @param [Annotatable] obj the domain object to annotate
15
+ def hook=(obj)
16
+ send(self.class.owner_attribute_metadata.writer, obj)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -17,11 +17,10 @@ module CaTissue
17
17
  # return [CaRuby::SQLExecutor] a utility SQL executor
18
18
  attr_reader :executor, :access_properties
19
19
 
20
- # Creates a new Database with the {SERVICE_NAME} service and {CaTissue.access_properties}.
20
+ # Creates a new Database with the +catissuecore+ service and {CaTissue.access_properties}.
21
21
  def initialize
22
22
  @access_properties = CaTissue.access_properties
23
- @access_properties[:database] ||= DEF_DATABASE_NAME
24
- super(SERVICE_NAME, @access_properties)
23
+ super(SVC_NAME, @access_properties)
25
24
  @executor = CaRuby::SQLExecutor.new(@access_properties)
26
25
  end
27
26
 
@@ -37,16 +36,16 @@ module CaTissue
37
36
  # @param (see CaRuby::Database#persistence_service)
38
37
  # @return (see CaRuby::Database#persistence_service)
39
38
  def persistence_service(klass)
40
- klass < Annotation ? klass.domain_module.persistence_service : super
39
+ klass < Annotation ? klass.annotation_module.persistence_service : super
41
40
  end
42
41
 
43
42
  # Augments {CaRuby::Database#ensure_exists} to ensure that an {Annotation::Proxy} reference identifier
44
43
  # reflects the hook identifier.
45
44
  #
46
- # @param (see CaRuby::Database#ensure_exists)
47
- def ensure_exists(ref)
48
- if Annotation::Proxy === ref then
49
- ref.ensure_identifier_reflects_hook
45
+ # @param (see CaRuby::Database::Writer#ensure_exists)
46
+ def ensure_exists(obj)
47
+ if Annotation::Proxy === obj then
48
+ obj.ensure_hook_exists
50
49
  end
51
50
  super
52
51
  end
@@ -54,10 +53,7 @@ module CaTissue
54
53
  private
55
54
 
56
55
  # The application service name
57
- SERVICE_NAME = 'catissuecore'
58
-
59
- # The default database name
60
- DEF_DATABASE_NAME = 'catissue'
56
+ SVC_NAME = 'catissuecore'
61
57
 
62
58
  UPD_EID_SQL = 'update catissue_external_identifier set name = ?, value = ?, specimen_id = ? where identifier = ?'
63
59
 
@@ -103,13 +99,13 @@ module CaTissue
103
99
 
104
100
  # This method patches up fetched sources to correct the following anomaly:
105
101
  #
106
- # caCORE alert - fetched references are not reconciled within an existing query result, e.g.
107
- # given a query result with two Specimens s1 and s2, the parent reference is not fetched.
108
- # Subsequently fetching the parent is independent of the query result. Thus if s1 is the parent
109
- # of s2 in the database, the fetched s2 parent s3 is distinct from s1, even though
110
- # s1.identifier == s3.identifier. Thus, enforcing reference consistency requires a post-fetch step
111
- # that matches the fetched objects to the original query result on identifier and resets the
112
- # references.
102
+ # @quirk caCORE fetched references are not reconciled within an existing query result, e.g.
103
+ # given a query result with two Specimens s1 and s2, the parent reference is not fetched.
104
+ # Subsequently fetching the parent is independent of the query result. Thus if s1 is the parent
105
+ # of s2 in the database, the fetched s2 parent s3 is distinct from s1, even though
106
+ # s1.identifier == s3.identifier. Thus, enforcing reference consistency requires a post-fetch step
107
+ # that matches the fetched objects to the original query result on identifier and resets the
108
+ # references.
113
109
  #
114
110
  # Not yet enabled. TODO - plug this into fetch_object.
115
111
  #
@@ -125,10 +121,9 @@ module CaTissue
125
121
  end
126
122
  end
127
123
 
128
- # Work around the following caTissue bugs:
129
- # * caTissue alert - Bug #135: Update SCG SpecimenEventParameters raises AuditException.
124
+ # @quirk caTissue Bug #135: Update SCG SpecimenEventParameters raises AuditException.
130
125
  # Work around is to update the SCG instead.
131
- # * caTissue alert - CPR consent tier response update results in Access denied error.
126
+ # @quirk caTissue CPR consent tier response update results in Access denied error.
132
127
  # Work-around is to update the response using a direct SQL call.
133
128
  #
134
129
  # @param (see CaRuby::Database#update_object)
@@ -138,6 +133,8 @@ module CaTissue
138
133
  save_collectible_event_parameters(obj)
139
134
  elsif CaTissue::ConsentTierResponse === obj then
140
135
  update_consent_tier_response(obj)
136
+ elsif Annotation === obj then
137
+ raise CaRuby::DatabaseError.new("Annotation update is not supported on #{obj}")
141
138
  else
142
139
  case obj
143
140
  when CaTissue::Specimen then
@@ -154,8 +151,8 @@ module CaTissue
154
151
 
155
152
  # Updates the given dependent.
156
153
  #
157
- # caTissue alert - 1.2 user address update results in authorization error. Work-around is to
158
- # create a new address record.
154
+ # @quirk caTissue 1.2 user address update results in authorization error. Work-around is to
155
+ # create a new address record.
159
156
  #
160
157
  # @param (see CaRuby::Writer#update_changed_dependent)
161
158
  def update_changed_dependent(owner, attribute, dependent, autogenerated)
@@ -197,44 +194,44 @@ module CaTissue
197
194
 
198
195
  # Augments {CaRuby::Database#save_with_template} to work around the following caTissue anomalies:
199
196
  #
200
- # caTissue alert - Bug #149: API update TissueSpecimen position validation incorrect.
201
- # The Specimen update argument must reference the old position, even though the position is not
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
- # caTissue alert - 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.
197
+ # @quirk caTissue Bug #149: API update TissueSpecimen position validation incorrect.
198
+ # The Specimen update argument must reference the old position, even though the position is not
199
+ # updatable, unless old status is Pending. The validation defect described in Bug #149 requires
200
+ # a work-around that is also used for a different reason described in the following paragraph.
201
+ #
202
+ # @quirk caTissue Update of a {CaTissue::Specimen} which references a position must include the former
203
+ # position in the caTissue service update argument. A Specimen position is altered as a side-effect
204
+ # by creating a proxy save {CaTissue::TransferEventParameters}. The changed position is not reflected
205
+ # in the Specimen position, which must be refetched to reflect the database state. This fetch is
206
+ # done automatically by {CaRuby::Database} as part of the save proxy mechanism. The Specimen update
207
+ # template must include a reference to the former position but not the changed position.
208
+ #
209
+ # However, the Specimen {CaRuby::Writer#update} argument will include the changed position, not the
210
+ # former position. The template built {CaRuby::Writer#update} for submission to the caTissue app
211
+ # does not include a position reference, since the position has a save proxy which handles position
212
+ # change as part of the {CaRuby::Writer} update dependent propagation.
213
+ #
214
+ # Thus, updating a Specimen which includes a position change is performed as follows:
215
+ # * reconstitute the former position from the Position snapshot taken as part of the
216
+ # {CaRuby::Persistable} change tracker.
217
+ # * add the former position to the template (which will now differ from the {CaRuby::Writer#update}
218
+ # argument).
219
+ # * submit the adjusted Specimen template to the caTissue app updateObject.
220
+ # * {CaRuby::Writer#update} will propagate the Specimen update to the changed position dependent,
221
+ # which in turn saves via the {CaTissue::TransferEventParameters} proxy.
222
+ # * The proxy save will in turn refetch the proxied Specimen position to obtain the identifier
223
+ # and merge this into the Specimen position.
224
+ # * The Specimen update template is used solely to satisfy the often arcane caTissue interaction
225
+ # requirements like this work-around, and is thrown away along with its aberrant state.
226
+ #
227
+ # This work-around is the only case of a save template modification to handle a caTissue special
228
+ # case. Note that the {CaTissue::SpecimenPosition} logic does not apply to a
229
+ # {CaTissue::ContainerPosition}, which can be updated directly.
230
+ #
231
+ # The additional complexity of this work-around is necessitated by the caTissue policy of update
232
+ # by indirect server-side side-effects that are not reflected back to the client. The caRuby
233
+ # policy of a declarative API that persists the save argument as given and reflects the
234
+ # changed database state requires this work-around.
238
235
  #
239
236
  # @param obj (see #store)
240
237
  # @param [Resource] template the obj template to submit to caCORE
@@ -294,18 +291,21 @@ module CaTissue
294
291
  logger.debug { "caTissue #{ctr} update work-around completed." }
295
292
  end
296
293
 
297
- # Overrides {CaRuby::Database::Writer#save_dependents} to handle the work-around described
298
- # in {#save_specimen_dependents}.
294
+ # Overrides {CaRuby::Database::Writer#save_changed_dependents} to handle the following anomalies:
295
+ # * create Specimen disposal event last, as described in {#save_changed_specimen_dependents}
299
296
  #
300
297
  # @param (see CaRuby::Writer#save_dependents)
301
- def save_dependents(obj)
302
- Specimen === obj ? save_specimen_dependents(obj) { super } : super
298
+ def save_changed_dependents(obj)
299
+ case obj
300
+ when Specimen then save_changed_specimen_dependents(obj) { super }
301
+ else super
302
+ end
303
303
  end
304
304
 
305
- # Overrides {CaRuby::Database::Writer#save_dependents} on a Specimen to correct the
305
+ # Overrides {CaRuby::Database::Writer#save_changed_dependents} on a Specimen to correct the
306
306
  # following problem:
307
307
  #
308
- # caTissue alert - DisposalEventParameters must be created after all other Specimen SEPs.
308
+ # @quirk caTissue DisposalEventParameters must be created after all other Specimen SEPs.
309
309
  #
310
310
  # The process for migrating a discarded Specimen is as follows:
311
311
  # * Create the Specimen with status Active.
@@ -321,9 +321,9 @@ module CaTissue
321
321
  # for the subtle interaction required between these two work-arounds.
322
322
  #
323
323
  # @param [CaTissue::Specimen] the specimen whose dependents are to be saved
324
- # @yield [dependent] calls the base {CaRuby::Writer#save_dependents}
324
+ # @yield [dependent] calls the base {CaRuby::Writer#save_changed_dependents}
325
325
  # @yieldparam [Resource] dependent the dependent to save
326
- def save_specimen_dependents(specimen)
326
+ def save_changed_specimen_dependents(specimen)
327
327
  dsp = specimen.specimen_events.detect { |ep| CaTissue::DisposalEventParameters === ep }
328
328
  if dsp then
329
329
  logger.debug { "Work around caTissue #{specimen.qp} event parameters save order dependency by deferring #{dsp.qp} save..." }
@@ -350,7 +350,7 @@ module CaTissue
350
350
  # @return (see CaRuby::Database#build_save_template)
351
351
  def build_save_template(obj, builder)
352
352
  Annotation === obj ? prepare_annotation_for_save(obj) : super
353
- end
353
+ end
354
354
 
355
355
  # Ensures that a primary annotation hook exists.
356
356
  #
@@ -358,8 +358,10 @@ module CaTissue
358
358
  # @return [Annotation] the annotation object
359
359
  # @raise [DatabaseError] if the annotation does not reference a hook entity
360
360
  def prepare_annotation_for_save(annotation)
361
- hook = annotation.owner
362
- if hook.nil? then raise CaRuby::DatabaseError.new("Cannot save annotation #{annotation} since it does not reference a hook entity") end
361
+ hook = annotation.hook
362
+ if hook.nil? then
363
+ raise CaRuby::DatabaseError.new("Cannot save annotation #{annotation} since it does not reference a hook entity")
364
+ end
363
365
  if hook.identifier.nil? then
364
366
  logger.debug { "Ensuring that the annotation #{annotation.qp} hook entity #{hook.qp} exists in the database..." }
365
367
  ensure_exists(hook)
@@ -367,15 +369,16 @@ module CaTissue
367
369
  annotation
368
370
  end
369
371
 
370
- # Overrides {CaRuby::Database::Writer#save_with_template} to work around the following
371
- # caTissue bugs:
372
- # * caTissue alert - Bug #63: a SpecimenCollectionGroup update requires the referenced CollectionProtocolRegistration
372
+ # Overrides {CaRuby::Database::Writer#save_with_template} to work around caTissue bugs.
373
+ # @quirk caTissue Bug #63: a SpecimenCollectionGroup update requires the referenced CollectionProtocolRegistration
373
374
  # with an identifier to hold extraneous CollectionProtocolRegistration content, including the CPR
374
375
  # collection protocol and PPI.
375
- # * caTissue alert - Bug: CollectionProtocolRegistration must cascade throughCP, but the CP events
376
+ # @quirk caTissue Bug: CollectionProtocolRegistration must cascade throughCP, but the CP events
376
377
  # cannot cascade to SpecimenRequirement without raising an Exception. Work-around is to clear the template CP events.
377
- # * caTissue alert - Create Specimen with nil label does not auto-generate the label.
378
+ # @quirk caTissue Create Specimen with nil label does not auto-generate the label.
378
379
  # Work-around is to set the label to a unique value.
380
+ #
381
+ # @raise DatabaseError if the object to save is an {Annotation::Proxy}, which is not supported
379
382
  def save_with_template(obj, template)
380
383
  # special cases to work around caTissue bugs
381
384
  if CaTissue::CollectionProtocolRegistration === obj and template.collection_protocol then
@@ -450,10 +453,12 @@ module CaTissue
450
453
  if cep.nil? then raise CaRuby::DatabaseError.new("Default collection event parameters were not added to #{obj}.") end
451
454
  cep.copy.merge_attributes(:user => cep.user, :specimen_collection_group => template)
452
455
  end
453
- elsif Annotation === obj and obj.class.secondary? then
456
+ elsif Annotation::Proxy === obj then
457
+ raise CaRuby::DatabaseError.new("Annotation proxy direct database save is not supported: #{obj}")
458
+ elsif Annotation === obj and obj.class.primary? then
454
459
  copy_annotation_proxy_owner_to_template(obj, template)
455
460
  end
456
-
461
+
457
462
  # delegate to standard save
458
463
  super
459
464
  end
@@ -474,18 +479,19 @@ module CaTissue
474
479
  template.send(wtr, pxy)
475
480
  end
476
481
 
477
- # Augment {CaRuby::Database::Writer#create_object} for the following work-arounds:
478
- # * caTissue alert - Bug #124: SCG SpecimenEventParameters save fails validation.
482
+ # Augment {CaRuby::Database::Writer#create_object} to work around caTissue bugs.
483
+ # @quirk caTissue Bug #124: SCG SpecimenEventParameters save fails validation.
479
484
  # Work-around is to create the SEP by updating the SCG.
480
- # * If obj is a CaTissue::Specimen with the is_available flag set to false, then work around the bug
485
+ # @quirk If obj is a CaTissue::Specimen with the is_available flag set to false, then work around the bug
481
486
  # described in {#create_unavailable_specimen}.
482
- # * caTissue alert - Bug #161: Specimen API disposal not reflected in result activity status.
487
+ # @quirk caTissue Bug #161: Specimen API disposal not reflected in result activity status.
483
488
  # DisposalEventParameters create sets the owner Specimen activity_status to +Closed+ as a side-effect.
484
489
  # Reflect this side-effect in the submitted DisposalEventParameters owner Specimen object.
490
+ # Pass through an {Annotation::Proxy} to the referenced annotations.
485
491
  #
486
492
  # @param [Resource] obj the dependent domain object to save
487
493
  def create_object(obj)
488
- if collectible_event_parameters?(obj) then
494
+ if collectible_event_parameters?(obj) then
489
495
  save_collectible_event_parameters(obj)
490
496
  elsif CaTissue::Specimen === obj then
491
497
  obj.add_defaults
@@ -507,10 +513,33 @@ module CaTissue
507
513
 
508
514
  obj
509
515
  end
510
-
516
+
517
+ # Overrides {CaRuby::Database#create_from_template} as follows:
518
+ # * Surrogate {Annotation::Proxy} is "created" by setting the identifier to its hook owner.
519
+ # The create operation then creates referenced uncreated dependents.
520
+ #
521
+ # @param (CaRuby::Database#create_from_template)
522
+ def create_from_template(obj)
523
+ if Annotation::Proxy === obj then
524
+ hook = obj.hook
525
+ if hook.identifier.nil? then
526
+ raise CaRuby::DatabaseError.new("Annotation proxy #{obj.qp} hook owner #{hook.qp} does not have an identifier")
527
+ end
528
+ obj.identifier = hook.identifier
529
+ obj.take_snapshot
530
+ logger.debug { "Marked annotation proxy #{obj} as created by setting the identifier to that of the hook owner #{hook}." }
531
+ logger.debug { "Creating annotation proxy #{obj} dependent primary annotations..." }
532
+ save_changed_dependents(obj)
533
+ persistify(obj)
534
+ obj
535
+ else
536
+ super
537
+ end
538
+ end
539
+
511
540
  # Creates the given specimen by working around the following bug:
512
541
  #
513
- # caTissue alert - Bug #160: Missing Is Available? validation.
542
+ # @quirk caTissue Bug #160: Missing Is Available? validation.
514
543
  # Cannot create a Specimen with any of the following conditions:
515
544
  # * zero available_quantity
516
545
  # * is_available flag set to false
@@ -567,6 +596,8 @@ module CaTissue
567
596
  specimen
568
597
  end
569
598
 
599
+ # Overrides {CaRuby::Database::Reader#fetch_object} to circumvent {Annotation} fetch, since an annotation
600
+ # does not have a key.
570
601
  def fetch_object(obj)
571
602
  super or fetch_alternative(obj)
572
603
  end
@@ -579,9 +610,9 @@ module CaTissue
579
610
  end
580
611
 
581
612
  # Override {CaRuby::Database#query_safe} to work around the following +caTissue+ bugs:
582
- # * caTissue alert - Specimen auto-generates blank ExternalIdentifier.
583
- # cf. https://cabig-kc.nci.nih.gov/Biospecimen/forums/viewtopic.php?f=19&t=436&sid=ef98f502fc0ab242781b7759a0eaff36
584
- # * caTissue alert - Specimen auto-generates blank PMI.
613
+ # * @quirk caTissue Specimen auto-generates blank ExternalIdentifier.
614
+ # cf. https://cabig-kc.nci.nih.gov/Biospecimen/forums/viewtopic.php?f=19&t=436&sid=ef98f502fc0ab242781b7759a0eaff36
615
+ # * @quirk caTissue Specimen auto-generates blank PMI.
585
616
  def query_safe(obj_or_hql, *path)
586
617
  if path.last == :external_identifiers then
587
618
  CaTissue::Specimen.remove_empty_external_identifier(super)
@@ -592,19 +623,19 @@ module CaTissue
592
623
  end
593
624
  end
594
625
 
595
- # caTissue alert - Bug #147: SpecimenRequirement query ignores CPE.
596
- # Work around this bug by an inverted query on the referenced CPE.
626
+ # @quirk caTissue Bug #147: SpecimenRequirement query ignores CPE.
627
+ # Work around this bug by an inverted query on the referenced CPE.
597
628
  #
598
- # caTissue alert - Accessing an annotation hook DE proxy attribute uses a separate mechanism.
599
- # Redirect the query to the annotation integration service in that case.
629
+ # @quirk caTissue Accessing an annotation hook DE proxy attribute uses a separate mechanism.
630
+ # Redirect the query to the annotation integration service in that case.
600
631
  #
601
- # caTissue alert - Bug #169: ContainerPosition occupied container query returns parent
602
- # container instead. Substitute a hard-coded HQL search for this case.
632
+ # @quirk caTissue Bug #169: ContainerPosition occupied container query returns parent
633
+ # container instead. Substitute a hard-coded HQL search for this case.
603
634
  #
604
635
  # @see CaRuby::Database#query_object
605
636
  def query_object(obj, attribute=nil)
606
637
  if hook_proxy_attribute?(obj, attribute) then
607
- query_hook_proxy(obj, attribute)
638
+ query_hook_proxies(obj, attribute)
608
639
  elsif CaTissue::SpecimenRequirement === obj and not obj.identifier and obj.collection_protocol_event then
609
640
  query_requirement_using_cpe_inversion(obj, attribute)
610
641
  elsif CaTissue::ContainerPosition === obj and obj.identifier and attribute == :occupied_container then
@@ -621,34 +652,48 @@ module CaTissue
621
652
  end
622
653
 
623
654
  # @param (see #query_object)
624
- # @return [Boolean] whether the given attribute is a reference from an {Annotatable} to a #{Annotation}
655
+ # @return [Boolean] whether the given attribute is a reference from an {Annotatable} to a #{Annotation::Proxy}
625
656
  def hook_proxy_attribute?(obj, attribute)
626
657
  return false if attribute.nil?
627
658
  attr_md = obj.class.attribute_metadata(attribute)
628
- attr_md.declarer < Annotatable and attr_md.type < Annotation
659
+ attr_md.declarer < Annotatable and attr_md.type < Annotation::Proxy
629
660
  end
630
661
 
631
662
  # Queries on the given object attribute using the {Annotation::IntegationService}.
632
663
  #
633
- # @param (see #query_object)
664
+ # @param [Annotatable] hook the annotated domain object
665
+ # @param [Symbol] attribute the proxy attribute
634
666
  # @result (see #query_object)
635
- def query_hook_proxy(hook, attribute)
667
+ def query_hook_proxies(hook, attribute)
636
668
  unless hook.identifier then
637
669
  logger.debug { "Querying annotation hook #{hook.qp} proxy reference #{attribute} by collecting the matching #{hook.class.qp} proxy references..." }
638
- return query(hook).map { |ref| query_hook_proxy(ref, attribute) }.flatten
670
+ return query(hook).map { |ref| query_hook_proxies(ref, attribute) }.flatten
639
671
  end
640
- proxy = hook.annotation_proxy(attribute)
672
+ # the hook proxies
673
+ proxies = hook.send(attribute)
674
+ # catenate the query results for each proxy
675
+ proxies.each { |pxy| find_hook_proxy(pxy, hook) }
676
+ proxies
677
+ end
678
+
679
+ # Queries on the given proxy using the {Annotation::IntegationService}.
680
+ #
681
+ # @param [Annotation::Proxy] proxy the proxy object
682
+ # @param hook (see #query_hook_proxies)
683
+ # @param [Symbol] attribute the proxy attribute
684
+ # @result (see #query_object)
685
+ def find_hook_proxy(proxy, hook)
641
686
  # update the proxy identifier if necessary
642
687
  proxy.identifier ||= hook.identifier
643
688
  # delegate to the integration service to find the referenced hook annotation proxies
644
- logger.debug { "Delegating #{hook.qp} annotation #{attribute} query to proxy #{proxy} integration service query..." }
645
- annotator.integrator.query(proxy, attribute)
689
+ logger.debug { "Delegating #{hook.qp} proxy #{proxy} query to the integration service..." }
690
+ annotator.integrator.find(proxy)
646
691
  end
647
692
 
648
- # caCORE alert - Override {CaRuby::Database::Reader#invertible_query?} to enable the Bug #147 work
649
- # around in {#query_object}. Invertible queries are performed to work around Bug #79. However, this
650
- # work-around induces Bug #147, so we disable the Bug #79 work-around here for the special case of
651
- # a CPE in order to enable the Bug #147 work-around. And so it goes....
693
+ # @quirk caCORE Override {CaRuby::Database::Reader#invertible_query?} to enable the Bug #147 work
694
+ # around in {#query_object}. Invertible queries are performed to work around Bug #79. However, this
695
+ # work-around induces Bug #147, so we disable the Bug #79 work-around here for the special case of
696
+ # a CPE in order to enable the Bug #147 work-around. And so it goes....
652
697
  #
653
698
  # @see CaRuby::Database#invertible_query?
654
699
  def invertible_query?(obj, attribute)
@@ -673,9 +718,16 @@ module CaTissue
673
718
  pmi = pnt.medical_identifiers.first
674
719
  return if pmi.nil?
675
720
  logger.debug { "Using alternative Participant fetch strategy to find Participant by medical record number..." }
676
- return unless exists?(pmi)
677
- candidates = query(pmi.copy, :participant)
678
- candidates.first if candidates.size == 1
721
+ # If the PMI has an identifier (unlikely) then find the PMI participant.
722
+ if pmi.identifier then return query(pmi.copy, :participant).first end
723
+ # Add the default site. If no default site, then bail.
724
+ if pmi.site.nil? then
725
+ pmi.add_defaults
726
+ return unless pmi.site
727
+ end
728
+ return unless exists?(pmi.site)
729
+ # Find the PMI based on the site and MRN.
730
+ return query(pmi.copy(:site, :medical_record_number), :participant).first
679
731
  end
680
732
 
681
733
  # @param [CaTissue::Specimen] spc the specimen to fetch