caruby-tissue 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. data/History.txt +4 -0
  2. data/LEGAL +5 -0
  3. data/LICENSE +22 -0
  4. data/README.md +44 -0
  5. data/bin/crtdump +31 -0
  6. data/bin/crtexample +18 -0
  7. data/bin/crtextract +47 -0
  8. data/bin/crtmigrate +17 -0
  9. data/bin/crtsmoke +27 -0
  10. data/examples/galena/README.md +53 -0
  11. data/examples/galena/bin/migrate.rb +42 -0
  12. data/examples/galena/bin/seed.rb +43 -0
  13. data/examples/galena/conf/extract/simple_fields.yaml +4 -0
  14. data/examples/galena/conf/migration/filter_fields.yaml +7 -0
  15. data/examples/galena/conf/migration/filter_migration.yaml +9 -0
  16. data/examples/galena/conf/migration/frozen_fields.yaml +11 -0
  17. data/examples/galena/conf/migration/frozen_migration.yaml +9 -0
  18. data/examples/galena/conf/migration/general_fields.yaml +42 -0
  19. data/examples/galena/conf/migration/general_migration.yaml +9 -0
  20. data/examples/galena/conf/migration/simple_fields.yaml +30 -0
  21. data/examples/galena/conf/migration/simple_migration.yaml +7 -0
  22. data/examples/galena/conf/migration/small_fields.yaml +24 -0
  23. data/examples/galena/conf/migration/small_migration.yaml +9 -0
  24. data/examples/galena/data/filter.csv +1 -0
  25. data/examples/galena/data/frozen.csv +1 -0
  26. data/examples/galena/data/general.csv +1 -0
  27. data/examples/galena/data/minimal.csv +1 -0
  28. data/examples/galena/data/simple.csv +1 -0
  29. data/examples/galena/data/small.csv +1 -0
  30. data/examples/galena/doc/CaTissue.html +93 -0
  31. data/examples/galena/doc/CaTissue/CollectionProtocolRegistration.html +181 -0
  32. data/examples/galena/doc/CaTissue/Participant.html +241 -0
  33. data/examples/galena/doc/CaTissue/SpecimenCollectionGroup.html +190 -0
  34. data/examples/galena/doc/CaTissue/StorageContainer.html +179 -0
  35. data/examples/galena/doc/CaTissue/TissueSpecimen.html +320 -0
  36. data/examples/galena/doc/Galena.html +290 -0
  37. data/examples/galena/doc/Galena/Seed.html +203 -0
  38. data/examples/galena/doc/Galena/Seed/Defaults.html +646 -0
  39. data/examples/galena/doc/_index.html +188 -0
  40. data/examples/galena/doc/class_list.html +36 -0
  41. data/examples/galena/doc/css/common.css +1 -0
  42. data/examples/galena/doc/css/full_list.css +53 -0
  43. data/examples/galena/doc/css/style.css +307 -0
  44. data/examples/galena/doc/file.README.html +108 -0
  45. data/examples/galena/doc/file_list.html +38 -0
  46. data/examples/galena/doc/frames.html +13 -0
  47. data/examples/galena/doc/index.html +108 -0
  48. data/examples/galena/doc/js/app.js +202 -0
  49. data/examples/galena/doc/js/full_list.js +149 -0
  50. data/examples/galena/doc/js/jquery.js +154 -0
  51. data/examples/galena/doc/method_list.html +179 -0
  52. data/examples/galena/doc/top-level-namespace.html +112 -0
  53. data/examples/galena/lib/README.html +33 -0
  54. data/examples/galena/lib/galena.rb +8 -0
  55. data/examples/galena/lib/galena/cli/seed.rb +43 -0
  56. data/examples/galena/lib/galena/migration/filter_shims.rb +43 -0
  57. data/examples/galena/lib/galena/migration/frozen_shims.rb +54 -0
  58. data/examples/galena/lib/galena/seed/defaults.rb +97 -0
  59. data/lib/catissue.rb +26 -0
  60. data/lib/catissue/cli/command.rb +51 -0
  61. data/lib/catissue/cli/example.rb +31 -0
  62. data/lib/catissue/cli/migrate.rb +60 -0
  63. data/lib/catissue/cli/smoke.rb +45 -0
  64. data/lib/catissue/database.rb +451 -0
  65. data/lib/catissue/database/annotation/annotatable_service.rb +25 -0
  66. data/lib/catissue/database/annotation/annotation_service.rb +79 -0
  67. data/lib/catissue/database/annotation/annotator.rb +84 -0
  68. data/lib/catissue/database/annotation/entity_manager.rb +10 -0
  69. data/lib/catissue/database/annotation/integration_service.rb +87 -0
  70. data/lib/catissue/database/controlled_value_finder.rb +43 -0
  71. data/lib/catissue/database/controlled_values.rb +162 -0
  72. data/lib/catissue/domain/abstract_domain_object.rb +8 -0
  73. data/lib/catissue/domain/abstract_position.rb +22 -0
  74. data/lib/catissue/domain/abstract_specimen.rb +288 -0
  75. data/lib/catissue/domain/abstract_specimen_collection_group.rb +25 -0
  76. data/lib/catissue/domain/address.rb +13 -0
  77. data/lib/catissue/domain/cancer_research_group.rb +11 -0
  78. data/lib/catissue/domain/capacity.rb +34 -0
  79. data/lib/catissue/domain/check_in_check_out_event_parameter.rb +19 -0
  80. data/lib/catissue/domain/collection_event_parameters.rb +13 -0
  81. data/lib/catissue/domain/collection_protocol.rb +177 -0
  82. data/lib/catissue/domain/collection_protocol_event.rb +108 -0
  83. data/lib/catissue/domain/collection_protocol_registration.rb +108 -0
  84. data/lib/catissue/domain/consent_tier_response.rb +13 -0
  85. data/lib/catissue/domain/consent_tier_status.rb +29 -0
  86. data/lib/catissue/domain/container.rb +234 -0
  87. data/lib/catissue/domain/container_position.rb +21 -0
  88. data/lib/catissue/domain/container_type.rb +131 -0
  89. data/lib/catissue/domain/department.rb +13 -0
  90. data/lib/catissue/domain/disposal_event_parameters.rb +13 -0
  91. data/lib/catissue/domain/embedded_event_parameters.rb +10 -0
  92. data/lib/catissue/domain/external_identifier.rb +22 -0
  93. data/lib/catissue/domain/frozen_event_parameters.rb +10 -0
  94. data/lib/catissue/domain/institution.rb +13 -0
  95. data/lib/catissue/domain/new_specimen_array_order_item.rb +35 -0
  96. data/lib/catissue/domain/order_details.rb +25 -0
  97. data/lib/catissue/domain/participant.rb +138 -0
  98. data/lib/catissue/domain/participant_medical_identifier.rb +38 -0
  99. data/lib/catissue/domain/password.rb +11 -0
  100. data/lib/catissue/domain/race.rb +11 -0
  101. data/lib/catissue/domain/received_event_parameters.rb +25 -0
  102. data/lib/catissue/domain/scg_event_parameters.rb +11 -0
  103. data/lib/catissue/domain/site.rb +30 -0
  104. data/lib/catissue/domain/specimen.rb +456 -0
  105. data/lib/catissue/domain/specimen_array.rb +47 -0
  106. data/lib/catissue/domain/specimen_array_content.rb +19 -0
  107. data/lib/catissue/domain/specimen_array_type.rb +20 -0
  108. data/lib/catissue/domain/specimen_characteristics.rb +20 -0
  109. data/lib/catissue/domain/specimen_collection_group.rb +412 -0
  110. data/lib/catissue/domain/specimen_event_parameters.rb +111 -0
  111. data/lib/catissue/domain/specimen_position.rb +38 -0
  112. data/lib/catissue/domain/specimen_protocol.rb +34 -0
  113. data/lib/catissue/domain/specimen_requirement.rb +143 -0
  114. data/lib/catissue/domain/storage_container.rb +204 -0
  115. data/lib/catissue/domain/storage_type.rb +82 -0
  116. data/lib/catissue/domain/transfer_event_parameters.rb +53 -0
  117. data/lib/catissue/domain/user.rb +100 -0
  118. data/lib/catissue/extract/command.rb +31 -0
  119. data/lib/catissue/extract/delta.rb +62 -0
  120. data/lib/catissue/extract/extractor.rb +99 -0
  121. data/lib/catissue/migration/migrator.rb +101 -0
  122. data/lib/catissue/migration/shims.rb +108 -0
  123. data/lib/catissue/migration/uniquify.rb +111 -0
  124. data/lib/catissue/resource.rb +84 -0
  125. data/lib/catissue/util/controlled_value.rb +29 -0
  126. data/lib/catissue/util/location.rb +116 -0
  127. data/lib/catissue/util/log.rb +30 -0
  128. data/lib/catissue/util/person.rb +31 -0
  129. data/lib/catissue/util/position.rb +54 -0
  130. data/lib/catissue/util/storable.rb +34 -0
  131. data/lib/catissue/util/storage_type_holder.rb +30 -0
  132. data/lib/catissue/version.rb +7 -0
  133. metadata +212 -0
@@ -0,0 +1,21 @@
1
+ module CaTissue
2
+ # import the Java class
3
+ java_import('edu.wustl.catissuecore.domain.ContainerPosition')
4
+
5
+ class ContainerPosition
6
+ include Resource
7
+
8
+ add_mandatory_attributes(:parent_container)
9
+
10
+ add_attribute_aliases(:parent => :parent_container, :holder => :parent_container, :occupant => :occupied_container)
11
+
12
+ # Each ContainerPosition has a container and there is only one position per container.
13
+ set_secondary_key_attributes(:occupied_container)
14
+
15
+ set_attribute_inverse(:parent_container, :occupied_positions)
16
+
17
+ set_attribute_inverse(:occupied_container, :located_at_position)
18
+
19
+ qualify_attribute(:parent_container, :fetched)
20
+ end
21
+ end
@@ -0,0 +1,131 @@
1
+ require 'caruby/util/options'
2
+
3
+ module CaTissue
4
+ # import the Java class
5
+ java_import('edu.wustl.catissuecore.domain.ContainerType')
6
+
7
+ # The caTissue ContainerType domain class wrapper.
8
+ # Each {ContainerType} subclass is required to implement the container_class method.
9
+ #
10
+ # caTissue alert - the ContainerType and Container class hierarchy is a confusing
11
+ # olio of entangled relationships. Conceptually, specimens are contained in boxes,
12
+ # vials and specimen arrays of various types. When specimens are frozen, these
13
+ # containers are placed on a rack in a freezer.
14
+ #
15
+ # This conceptual model is implemented in caTissue as follows:
16
+ # * The specimen collection container type, e.g. +Citrate Vacutainer+, is captured
17
+ # as a {CaTissue::CollectionEventParameters#container} String. There is no separate
18
+ # collection container instance or container type instance.
19
+ # * A tissue specimen storage box is captured as a {CaTissue::StorageContainer}
20
+ # instance constrained to a {CaTissue::StorageType} instance. Boxes with different
21
+ # types are instances of the same {CaTissue::StorageContainer} class but belong
22
+ # to different {CaTissue::StorageType} instances.
23
+ # * {CaTissue::SpecimenArray} is a {CaTissue::Container} but not a {CaTissue::StorageContainer}.
24
+ # The specimen array class is {CaTissue::SpecimenArray}, but its type is a
25
+ # {CaTissue::SpecimenArrayType} instance, which is a {CaTissue::ContainerType} but not
26
+ # a {CaTissue::StorageType}.
27
+ # * A rack is a {CaTissue::StorageContainer} instance whose type is a {CaTissue::StorageType}
28
+ # instance which can hold the box {CaTissue::StorageType}.
29
+ # * A freezer is a {CaTissue::StorageContainer} instance whose type is a {CaTissue::StorageType}
30
+ # instance which can hold the rack {CaTissue::StorageType}.
31
+ # * Each {CaTissue::StorageContainer} belongs to a given {CaTissue::Site}. A child
32
+ # {CaTissue::StorageContainer} site defaults to its parent container site.
33
+ # Site consistency is not enforced by caTissue, i.e. it is possible to create
34
+ # a rack whose site differs from that of its parent freezer and child boxes.
35
+ # * {CaTissue::SpecimenArray} is not associated to a site.
36
+ # * The container children are partitioned into three methods for the three different
37
+ # types and pseudo-types of contained items: {CaTissue::StorageContainer},
38
+ # {CaTissue::SpecimenArray} and {CaTissue::Specimen#class}.
39
+ # * {CaTissue::SpecimenArray} holds {CaTissue::SpecimenArrayContent} positions, which
40
+ # are the functional equivalent of {CaTissue::SpecimenPosition} adapted for specimen
41
+ # arrays, although {CaTissue::SpecimenArrayContent} is not a {CaTissue::SpecimenPosition}
42
+ # or even an {CaTissue::AbstractPosition}. {CaTissue::SpecimenPosition} is functionally
43
+ # a specimen position in a box, whereas {CaTissue::SpecimenArrayContent} is functionally
44
+ # a specimen position in a specimen array.
45
+ #
46
+ # The ContainerType/Container mish-mash is partially alleviated in caRuby as follows:
47
+ # * {CaTissue::StorageType} and {CaTissue::StorageContainer} include the
48
+ # {CaTissue::StorageTypeHolder} module, which unifies treatment of contained
49
+ # types.
50
+ # * Similarly, {CaTissue::AbstractPosition} and {CaTissue::SpecimenArrayContent} include
51
+ # the {CaTissue::Position} module, which unifies treatment of positions.
52
+ # * Contained child types are consolidated into {CaTissue::StorageTypeHolder#child_types}
53
+ # * Similarly, {CaTissue::StorageContainer} child items are consolidated into
54
+ # {CaTissue::StorageContainer#child_types}
55
+ # * The various container and position classes are augmented with helper methods to
56
+ # add, move and find specimens and subcontainers. These methods hide the mind-numbing
57
+ # eccentricity of caTissue specimen storage interaction.
58
+ class ContainerType
59
+ include Resource
60
+
61
+ add_attribute_aliases(:column_label => :oneDimensionLabel, :row_label => :twoDimensionLabel)
62
+
63
+ add_attribute_defaults(:activity_status => 'Active')
64
+
65
+ set_secondary_key_attributes(:name)
66
+
67
+ add_mandatory_attributes(:activity_status, :capacity, :one_dimension_label, :two_dimension_label)
68
+
69
+ # caTissue alert - although capacity is not marked cascaded in Hibernate, it is created when the
70
+ # ContainerType is created.
71
+ add_dependent_attribute(:capacity)
72
+
73
+ # Override default {CaRuby::Resource#merge_attributes} to support the Capacity :rows and :columns
74
+ # pseudo-attributes.
75
+ #
76
+ # @param (see CaRuby::Resource#merge_attributes)
77
+ def merge_attributes(other, attributes=nil)
78
+ if Hash === other then
79
+ # partition the other hash into the Capacity attributes and ContainerType attributes
80
+ cp_hash, ct_hash = other.partition { |key, value| key == :rows or key == :columns }
81
+ self.capacity ||= Capacity.new(cp_hash).add_defaults unless cp_hash.empty?
82
+ super(ct_hash, attributes)
83
+ else
84
+ super(other, attributes)
85
+ end
86
+ end
87
+
88
+ # @param [CaTissue::Site] site the site where the candidate containers are located
89
+ # @param opts (see CaRuby::Writer#find)
90
+ # @option (see CaRuby::Writer#find)
91
+ # @return an available container of this ContainerType which is not
92
+ # {CaTissue::Container#completely_full?}.
93
+ def find_available(site, opts=nil)
94
+ find_containers(:site => site).detect { |ctr| not ctr.completely_full? } or
95
+ (create_container(:site => site).create if Options.get(:create, opts))
96
+ end
97
+
98
+ # Fetches containers of this ContainerType from the database.
99
+ #
100
+ # @param [<Symbol => Object>] params the optional search attribute => value hash
101
+ # @return the containers of this type which satisfy the search parameters
102
+ def find_containers(params=nil)
103
+ tmpl = create_container(params)
104
+ logger.debug { "Finding #{name} containers..." }
105
+ tmpl.query
106
+ end
107
+
108
+ # Returns a new Container instance of this ContainerType with an optional attribute => value hash.
109
+ # The container_type of the new Container is this ContainerType.
110
+ #
111
+ # @param [{Symbol => Object}] vh the attribute => value hash
112
+ # @return [Container] the new container
113
+ def create_container(vh=nil)
114
+ vh ||= {}
115
+ vh[:container_type] = self
116
+ container_class.new(vh)
117
+ end
118
+
119
+ private
120
+
121
+ # Adds an empty capacity and default dimension labels, if necessary.
122
+ # The default {#one_dimension_label} is 'Column' if there is a non-zero dimension capacity, 'Unused' otherwise.
123
+ # The default {#two_dimension_label} is 'Row' if there is a non-zero dimension capacity, 'Unused' otherwise.
124
+ def add_defaults_local
125
+ super
126
+ self.capacity ||= Capacity.new.add_defaults
127
+ self.row_label ||= capacity.rows && capacity.rows > 0 ? 'Row' : 'Unused'
128
+ self.column_label ||= capacity.columns && capacity.columns > 0 ? 'Column' : 'Unused'
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,13 @@
1
+
2
+
3
+ module CaTissue
4
+ # import the Java class
5
+ java_import('edu.wustl.catissuecore.domain.Department')
6
+
7
+ # The Department domain class.
8
+ class Department
9
+ include Resource
10
+
11
+ set_secondary_key_attributes(:name)
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ module CaTissue
2
+ # import the Java class
3
+ java_import('edu.wustl.catissuecore.domain.DisposalEventParameters')
4
+
5
+ class DisposalEventParameters
6
+ include Resource
7
+
8
+ add_attribute_defaults(:activity_status => 'Closed')
9
+
10
+ # caTissue alert - DisposalEventParameters activity status is transient.
11
+ qualify_attribute(:activity_status, :unfetched)
12
+ end
13
+ end
@@ -0,0 +1,10 @@
1
+ module CaTissue
2
+ # import the Java class
3
+ java_import('edu.wustl.catissuecore.domain.EmbeddedEventParameters')
4
+
5
+ class EmbeddedEventParameters
6
+ include Resource
7
+
8
+ add_attribute_aliases(:medium => :embedding_medium)
9
+ end
10
+ end
@@ -0,0 +1,22 @@
1
+
2
+
3
+ module CaTissue
4
+ # import the Java class
5
+ java_import('edu.wustl.catissuecore.domain.ExternalIdentifier')
6
+
7
+ # The ExternalIdentifier domain class.
8
+ class ExternalIdentifier
9
+ include Resource
10
+
11
+ # Sets this ExternalIdentifier value to the given value.
12
+ # A Numeric value is converted to a String.
13
+ def value=(value)
14
+ value = value.to_s if value
15
+ setValue(value)
16
+ end
17
+
18
+ add_mandatory_attributes(:value)
19
+
20
+ set_secondary_key_attributes(:specimen, :name)
21
+ end
22
+ end
@@ -0,0 +1,10 @@
1
+ module CaTissue
2
+ # import the Java class
3
+ java_import('edu.wustl.catissuecore.domain.FrozenEventParameters')
4
+
5
+ class FrozenEventParameters
6
+ include Resource
7
+
8
+ add_attribute_aliases(:freeze_method => :frozen_event_parameters_method)
9
+ end
10
+ end
@@ -0,0 +1,13 @@
1
+
2
+
3
+ module CaTissue
4
+ # import the Java class
5
+ java_import('edu.wustl.catissuecore.domain.Institution')
6
+
7
+ # The Institution domain class.
8
+ class Institution
9
+ include Resource
10
+
11
+ set_secondary_key_attributes(:name)
12
+ end
13
+ end
@@ -0,0 +1,35 @@
1
+ require 'catissue/resource'
2
+
3
+ module CaTissue
4
+ # import the Java class
5
+ java_import('edu.wustl.catissuecore.domain.NewSpecimenArrayOrderItem')
6
+
7
+ # The NewSpecimenArrayOrderItem domain class.
8
+ class NewSpecimenArrayOrderItem
9
+ include Resource
10
+
11
+ # caTissue alert - Bug #64: Some domain collection properties not initialized.
12
+ # Initialize order_items if necessary.
13
+ #
14
+ # @return [Java::JavaUtil::Set] the items
15
+ def order_items
16
+ getOrderItemCollection or (self.order_items = Java::JavaUtil::LinkedHashSet.new)
17
+ end
18
+
19
+ # caTissue alert - Bug #64: Some domain collection properties not initialized.
20
+ # Initialize distributions if necessary.
21
+ def distributions
22
+ getDistributionCollection or (self.distributions = Java::JavaUtil::LinkedHashSet.new)
23
+ end
24
+
25
+ def initialize(params=nil)
26
+ super
27
+ # jRuby bug? - Java methods not acceesible until respond_to? called; TODO - reconfirm this
28
+ respond_to?(:order_items)
29
+ respond_to?(:distributions)
30
+ # work around caTissue Bug #64
31
+ self.order_items ||= Java::JavaUtil::LinkedHashSet.new
32
+ self.distributions ||= Java::JavaUtil::LinkedHashSet.new
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,25 @@
1
+
2
+
3
+ module CaTissue
4
+ java_import('edu.wustl.catissuecore.domain.OrderDetails')
5
+
6
+ # The OrderDetails domain class.
7
+ class OrderDetails
8
+ include Resource
9
+
10
+ # caTissue alert - Bug #64: Some domain collection properties not initialized.
11
+ # Initialize order_items if necessary.
12
+ #
13
+ # @return [Java::JavaUtil::Set] the items
14
+ def order_items
15
+ getOrderItemCollection or (self.order_items = Java::JavaUtil::LinkedHashSet.new)
16
+ end
17
+
18
+ def initialize(params=nil)
19
+ super
20
+ respond_to?(:order_items)
21
+ # caTissue alert - work around caTissue Bug #64
22
+ self.order_items ||= Java::JavaUtil::LinkedHashSet.new
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,138 @@
1
+ require 'caruby/util/validation'
2
+ require 'catissue/resource'
3
+ require 'catissue/util/person'
4
+
5
+ module CaTissue
6
+ # import the Java class
7
+ java_import('edu.wustl.catissuecore.domain.Participant')
8
+
9
+ # The Participant domain class.
10
+ class Participant
11
+ include Resource, Person
12
+
13
+ # The convenience Person name aggregate is not a Java property but is added as a transient attribute
14
+ # which is reflected in the saved Java property name subfields.
15
+ add_attribute(:name)
16
+
17
+ # caTissue alert - clinical study is unsupported by caTissue.
18
+ remove_attribute(:clinical_study_registrations)
19
+
20
+ add_attribute_aliases(:collection_registrations => :collection_protocol_registrations,
21
+ :registrations => :collection_protocol_registrations,
22
+ :medical_identifiers => :participant_medical_identifiers)
23
+
24
+ set_secondary_key_attributes(:social_security_number)
25
+
26
+ # Clarification on defaults:
27
+ # * 'Unknown': value is unknown by anybody
28
+ # * 'Unspecified': value is known by somebody, but the data was not communicated to the bank
29
+ # cf. https://cabig-kc.nci.nih.gov/Biospecimen/forums/viewtopic.php?f=16&t=672&p=2343&e=2343
30
+ add_attribute_defaults(:activity_status => 'Active', :ethnicity => 'Unknown', :gender => 'Unspecified',
31
+ :sex_genotype => 'Unknown', :vital_status => 'Unknown')
32
+
33
+ # caTissue alert - Bug #154: Participant gender is specified by caTissue as optional, but if it is not set then
34
+ # it appears as Female in the GUI even though it is null in the database.
35
+ add_mandatory_attributes(:activity_status, :gender)
36
+
37
+ add_dependent_attribute(:collection_protocol_registrations, :logical)
38
+
39
+ add_dependent_attribute(:races)
40
+
41
+ # PMI is not cascaded, but insert is done in the bizlogic.
42
+ add_dependent_attribute(:participant_medical_identifiers)
43
+
44
+ # SSN is a key, if present, but is not required.
45
+ qualify_attribute(:social_security_number, :optional)
46
+
47
+ qualify_attribute(:collection_protocol_registrations, :fetched)
48
+
49
+ qualify_attribute(:participant_medical_identifiers, :fetched)
50
+
51
+ def merge_attribute_value(attribute, oldval, newval)
52
+ # caTissue alert - remove the autogenerated blank PMI.
53
+ # TODO - file bug
54
+ # @see CaTissue::Database#query_safe
55
+ if attribute == :participant_medical_identifiers and newval then
56
+ CaTissue::Participant.remove_empty_medical_identifier(newval)
57
+ end
58
+ super
59
+ end
60
+
61
+ # Returns the SSN if it exists, otherwise the first ParticipantMedicalIdentifier, if any, otherwise nil.
62
+ def key
63
+ super or medical_identifiers.first
64
+ end
65
+
66
+ # Returns a new ParticipantMedicalIdentifier which adds this Participant to the given site
67
+ # with Medical Record Number mrn.
68
+ def add_mrn(site, mrn)
69
+ CaTissue::ParticipantMedicalIdentifier.new(:participant => self, :site => site, :medical_record_number => mrn)
70
+ end
71
+
72
+ # Returns this Participant's medical record numbers. Each medical record number is Site-specific.
73
+ # @see the medical_identifiers attribute for MRN-site associations
74
+ def medical_record_numbers
75
+ medical_identifiers.map { |pmi| pmi.medicalRecordNumber }
76
+ end
77
+
78
+ # Returns all specimens collected from this Participant.
79
+ def specimens
80
+ Flattener.new(registrations.specimens.map { |cpr| cpr.specimens })
81
+ end
82
+
83
+ # Returns this Participant's CollectionProtocolRegistration protocols.
84
+ def collection_protocols
85
+ collection_registrations.map { |reg| reg.protocol }.uniq
86
+ end
87
+
88
+ # Returns the MRN for this participant. If this Participant does not have exactly one
89
+ # MRN, then this method returns nil. This method is a convenience for the common situation
90
+ # where a participant is enrolled at one site.
91
+ def medical_record_number
92
+ return medical_identifiers.first.medical_record_number if medical_identifiers.size == 1
93
+ end
94
+
95
+ # Returns the collection site for which this participant has a MRN. If there is not exactly one
96
+ # such site, then this method returns nil. This method is a convenience for the common situation
97
+ # where a participant is enrolled at one site.
98
+ def collection_site
99
+ return unless medical_identifiers.size == 1
100
+ site = medical_identifiers.first.site
101
+ return if site.nil?
102
+ site.site_type == Site::SiteType::COLLECTION ? site : nil
103
+ end
104
+
105
+ protected
106
+
107
+ def self.remove_empty_medical_identifier(mids)
108
+ bogus = mids.detect { |mid| mid.medical_record_number.nil? }
109
+ if bogus then
110
+ logger.debug { "Work around caTissue bug by removing empty fetched #{bogus.participant.qp} #{bogus.qp}..." }
111
+ # dissociate the participant
112
+ bogus.participant = nil
113
+ # remove the bogus medical identifier
114
+ mids.delete(bogus)
115
+ end
116
+ mids
117
+ end
118
+
119
+ private
120
+
121
+ # Returns the first Medical Record Number qualified by the MRN site, if this exists.
122
+ def alternate_key
123
+ return if medical_identifiers.empty?
124
+ pmi = medical_identifiers.first
125
+ pmi.key if pmi
126
+ end
127
+
128
+ # Adds a default Unknown Race, if necessary. Although Race is not strictly mandatory in the caTissue
129
+ # API, setting this default emulates the caTissue UI Add Participant page.
130
+ def add_defaults_local
131
+ super
132
+ # Make a new default Race which references this Participant, if necessary. Setting the Race
133
+ # participant to self automatically adds the Race to this Participant's races collection.
134
+ # The Race name defaults to Unknown.
135
+ if races.empty? then CaTissue::Race.new(:participant => self).add_defaults end
136
+ end
137
+ end
138
+ end