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,177 @@
1
+ require 'date'
2
+
3
+ module CaTissue
4
+ # import the Java class
5
+ java_import('edu.wustl.catissuecore.domain.CollectionProtocol')
6
+
7
+ # The CollectionProtocol domain class.
8
+ class CollectionProtocol
9
+ include Resource
10
+
11
+ # caTissue alert - Bug #64: Some domain collection properties not initialized.
12
+ # Initialize consent_tiers if necessary.
13
+ #
14
+ # @return [Java::JavaUtil::Set] the tiers
15
+ def consent_tiers
16
+ getConsentTierCollection or (self.consent_tiers = Java::JavaUtil::LinkedHashSet.new)
17
+ end
18
+
19
+ add_attribute_aliases(:events => :collection_protocol_events, :registrations => :collection_protocol_registrations)
20
+
21
+ set_secondary_key_attributes(:short_title)
22
+
23
+ add_attribute_defaults(:consents_waived => false, :aliquot_in_same_container => false)
24
+
25
+ add_mandatory_attributes(:aliquot_in_same_container, :collection_protocol_events, :consents_waived, :enrollment, :start_date, :title)
26
+
27
+ add_dependent_attribute(:collection_protocol_events)
28
+
29
+ add_dependent_attribute(:consent_tiers)
30
+
31
+ # caTissue alert - Augment the standard metadata storable reference attributes to work around caTissue Bug #150:
32
+ # Create CollectionProtocol in API ignores startDate.
33
+ qualify_attribute(:start_date, :update_only)
34
+
35
+ # caTissue alert - Augment the standard metadata storable reference attributes to work around caTissue Bug #150:
36
+ # Create CollectionProtocol in API ignores startDate.
37
+ set_attribute_type(:coordinators, CaTissue::User)
38
+
39
+ # caTissue alert - Augment the standard metadata storable reference attributes to work around caTissue Bug #150:
40
+ # Create CollectionProtocol in API ignores startDate.
41
+ qualify_attribute(:coordinators, :fetched)
42
+
43
+ def initialize(params=nil)
44
+ super
45
+ respond_to?(:consent_tiers)
46
+ # work around caTissue Bug #64 - consent tiers is nil rather than an empty set
47
+ self.consent_tiers ||= Java::JavaUtil::LinkedHashSet.new
48
+ end
49
+
50
+ # Returns all participants registered in this protocol.
51
+ def participants
52
+ registrations.nil? ? [] : registrations.map { |reg| reg.participant }
53
+ end
54
+
55
+ # Overrides the Java CollectionProtocol hashCode to make the hash insensitive to identifier assignment.
56
+ #
57
+ # @see #==
58
+ def hash
59
+ # caTissue alert - bad caTissue API hashCode leads to ugly cascading errors when using a CP in a Set
60
+ (object_id * 31) + 17
61
+ end
62
+
63
+ # Returns whether other is {#equal?} to CollectionProtocol.
64
+ #
65
+ # This method is a work-around for caTissue Bug #70: CollectionProtocol and non-CollectionProtocol are equal in caTissue 1.1.
66
+ def ==(other)
67
+ object_id == other.object_id
68
+ end
69
+
70
+ alias :eql? :==
71
+
72
+ # Returns a new CollectionProtocolRegistration for the specified participant in this CollectionProtocol with
73
+ # optional +protocol_participant_identifier+ ppi.
74
+ def register(participant, ppi=nil)
75
+ CollectionProtocolRegistration.new(:participant => participant, :protocol => self, :protocol_participant_identifier => ppi)
76
+ end
77
+
78
+ # Returns the CollectionProtocolRegistration for the specified participant in this CollectionProtocol,
79
+ def registration(participant)
80
+ registrations.detect { |registration| registration.participant == participant }
81
+ end
82
+
83
+ # Returns the event in this protocol with the earliest study calendar event point.
84
+ def first_event
85
+ events.sort_by { |event| event.event_point or CollectionProtocolEvent::DEFAULT_EVENT_POINT }.first
86
+ end
87
+
88
+ # Returns the specimens collected from the given participant for this CollectionProtocol,
89
+ # or all specimens in this protocol if participant is nil.
90
+ def specimens(participant=nil)
91
+ if participant.nil? then return registrations.map { |reg| reg.specimens }.flatten end
92
+ reg = registration(participant)
93
+ reg.nil? ? Array::EMPTY_ARRAY : reg.specimens
94
+ end
95
+
96
+ # Adds specimens to this protocol. Arguments:
97
+ # * participant - the Participant from whom the specimen is collected
98
+ # * biospecimens - the collected top-level underived specimens
99
+ # * params - additional SCG parameters as described in {SpecimenCollectionGroup#merge} method
100
+ # If params does not include a :collectionProtocolEvent parameter, then the SCG is assigned
101
+ # to the first collection event in this protocol.
102
+ # If params does not include a :specimen_collection_site parameter, then the SCG is assigned
103
+ # to the participant's collection site as determined by {Participant#collection_site}, if that
104
+ # can be uniquely determined.
105
+ #
106
+ # This add_specimens method adds the following association to params before calling the
107
+ # SpecimenCollectionGroup constructor:
108
+ # * :registration => a new CollectionProtocolRegistration for this protocol and the specified participant
109
+ # If there is no :name parameter, then this method builds a new unique SCG name as this
110
+ # CollectionProtocol's name followed by a unique suffix.
111
+ #
112
+ # Returns a new SpecimenCollectionGroup for the given participant containing the specimens.
113
+ #
114
+ # Raises ArgumentError if the SpecimenCollectionGroup does not include all required attributes.
115
+ def add_specimens(*specimens_and_params)
116
+ params = specimens_and_params.pop
117
+ spcs = specimens_and_params
118
+ # validate arguments
119
+ unless params then
120
+ raise ArgumentError.new("Collection parameters are missing when adding specimens to protocol #{self}")
121
+ end
122
+ # there must be a participant
123
+ pnt = params.delete(:participant)
124
+ unless pnt then
125
+ raise ArgumentError.new("Participant missing from collection parameters: #{params.qp}")
126
+ end
127
+ # there must be a receiver
128
+ unless params[:receiver] then
129
+ raise ArgumentError.new("Receiver missing from collection parameters: #{params.qp}")
130
+ end
131
+ # the required registration
132
+ params[:registration] ||= registration(pnt) || make_cpr(pnt)
133
+ # the new SCG
134
+ scg = SpecimenCollectionGroup.new(params)
135
+ # set each Specimen SCG
136
+ spcs.each { |spc| spc.specimen_collection_group = scg }
137
+ scg
138
+ end
139
+
140
+ private
141
+
142
+ # Sets the defaults as follows:
143
+ # * The start date is set to now.
144
+ # * The title is set to the short title.
145
+ # * If there is no CP coordinator and there is exactly one site with a coordinator, then the
146
+ # default CP coordinator is the site coordinator.
147
+ # * If there is no CP site and there is exactly one coordinator site, then the default CP site
148
+ # is the coordinator site.
149
+ def add_defaults_local
150
+ super
151
+ self.title ||= short_title
152
+ self.short_title ||= title
153
+ self.start_date ||= Java::JavaUtil::Date.new
154
+ if coordinators.empty? and sites.size == 1 then
155
+ coord = sites.first.coordinator
156
+ coordinators << coord if coord
157
+ elsif sites.empty? and coordinators.size == 1 then
158
+ site = coordinators.first.sites.first
159
+ sites << site if site
160
+ end
161
+ make_default_collection_event unless events.detect { |evt| CollectionProtocolEvent === evt }
162
+ end
163
+
164
+ def make_default_collection_event
165
+ # make this protocol's CPE
166
+ cpe = CollectionProtocolEvent.new(:protocol => self)
167
+ # make a tissue requirement
168
+ CaTissue::TissueSpecimenRequirement.new(
169
+ :collection_event => cpe,
170
+ :specimen_characteristics => CaTissue::SpecimenCharacteristics.new)
171
+ end
172
+
173
+ def make_cpr(participant)
174
+ CollectionProtocolRegistration.new(:participant => participant, :protocol => self)
175
+ end
176
+ end
177
+ end
@@ -0,0 +1,108 @@
1
+ require 'caruby/util/collection'
2
+
3
+ module CaTissue
4
+ # import the Java class
5
+ java_import('edu.wustl.catissuecore.domain.CollectionProtocolEvent')
6
+
7
+ # The CollectionProtocolRegistration domain class.
8
+ class CollectionProtocolEvent
9
+ include Resource
10
+
11
+ # caTissue alert - Bug #64: Some domain collection properties not initialized.
12
+ # Initialize specimen_collection_groups if necessary.
13
+ #
14
+ # @return [Java::JavaUtil::Set] the SCGs
15
+ def specimen_collection_groups
16
+ getSpecimenCollectionGroupCollection or (self.specimen_collection_groups = Java::JavaUtil::LinkedHashSet.new)
17
+ end
18
+
19
+ add_attribute_aliases(:label => :collection_point_label,
20
+ :protocol => :collection_protocol,
21
+ :requirements => :specimen_requirements,
22
+ :event_point => :study_calendar_event_point)
23
+
24
+ # CPE secondary key is the CP and collection point.
25
+ set_secondary_key_attributes(:collection_protocol, :collection_point_label)
26
+
27
+ # CPE alternate key is the CP and event point.
28
+ set_alternate_key_attributes(:collection_protocol, :study_calendar_event_point)
29
+
30
+ # Default event point is day one.
31
+ add_attribute_defaults(:study_calendar_event_point => 1.0)
32
+
33
+ add_mandatory_attributes(:collection_protocol, :clinical_diagnosis, :specimen_requirements)
34
+
35
+ # caTissue alert - specimen_requirements is a cascaded dependent, but it is not fetched.
36
+ # It is marked as auto-generated, since it must be refetched and matched on CPE create.
37
+ # CollectionProtocol create cascades through each dependent CPE to each SpecimenRequirement.
38
+ # TODO - confirm that the :autogenerated flag is necessary.
39
+ add_dependent_attribute(:specimen_requirements, :autogenerated, :unfetched)
40
+
41
+ # The event point used for saving this CollectionProtocolEvent if none other is set.
42
+ DEFAULT_EVENT_POINT = 1.0
43
+
44
+ def initialize(params=nil)
45
+ super
46
+ respond_to?(:specimen_collection_groups)
47
+ # work around caTissue Bug #64
48
+ self.specimen_collection_groups ||= Java::JavaUtil::LinkedHashSet.new
49
+ end
50
+
51
+ # Overrides {CaRuby::ResourceAttributes#mandatory_attributes} to correct the following caTissue bug:
52
+ #
53
+ # caTissue alert - specimen_collection_site is incorrectly attached in the caTissue class model
54
+ # to AbstractSpecimenCollectionGroup rather than SpecimenCollectionGroup. Since CollectionProtocolEvent
55
+ # is a subclass of AbstractSpecimenCollectionGroup and mandatory attributes are inherited,
56
+ # specimen_collection_site must be listed as optional here to counteract the effect of marking it
57
+ # as mandatory for a SCG (cf. caTissue Bug #116).
58
+ def mandatory_attributes
59
+ @mdtry_attrs_filter ||= @mandatory_attributes.filter { |attr| attr != :specimen_collection_site }
60
+ end
61
+
62
+ # Overrides the Java CollectionProtocolEvent hashCode to make the hash insensitive to identifier assignment.
63
+ def hash
64
+ # caTissue alert - caTissue determines the hashCode from the identifier. Consequently, a CollectionProtocolEvent
65
+ # added to a HashSet without an identifier can no longer find the CPE when it is assigned an identifier.
66
+ # This bug results in obscure delayed cascade errors. Work-around is to override the hash method in the
67
+ # Ruby CPE wrapper class.
68
+ (object_id * 31) + 17
69
+ end
70
+
71
+ # Returns whether other is a CollectionProtocolEvent with the same identifier as this CollectionProtocolEvent,
72
+ # or the same object_id if this CollectionProtocolEvent's identifier is nil.
73
+ #
74
+ # This method is a work-around for caTissue bug: CollectionProtocolEvent and non-CollectionProtocolEvent are equal in caTissue 1.1.
75
+ def ==(other)
76
+ object_id == other.object_id
77
+ end
78
+
79
+ alias :eql? :==
80
+
81
+ # Removes associations to this registration
82
+ def delete
83
+ protocol.events.delete(self) if protocol
84
+ end
85
+
86
+ # Overrides {CaRuby::Resource#references} in the case of the _specimen_requirements_ attribute to select
87
+ # only top-level SpecimenRequirements not derived from another SpecimenRequirement.
88
+ def direct_dependents(attribute)
89
+ if attribute == :specimen_requirements then
90
+ super.reject { |spc| spc.parent }
91
+ else
92
+ super
93
+ end
94
+ end
95
+
96
+ private
97
+
98
+ # Sets the default label to the protcol name followed by the event point.
99
+ def add_defaults_local
100
+ super
101
+ self.label ||= default_label
102
+ end
103
+
104
+ def default_label
105
+ "#{protocol.short_title.sub(' ', '_')}_#{event_point}" if protocol and protocol.short_title and event_point
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,108 @@
1
+ require 'caruby/util/collection'
2
+
3
+ module CaTissue
4
+ # import the Java class
5
+ java_import('edu.wustl.catissuecore.domain.CollectionProtocolRegistration')
6
+
7
+ # The CollectionProtocolRegistration domain class.
8
+ class CollectionProtocolRegistration
9
+ include Resource
10
+
11
+ # caTissue alert - Bug #64: Some domain collection properties not initialized.
12
+ # Initialize consent_tier_responses if necessary.
13
+ #
14
+ # @return [Java::JavaUtil::Set] the responses
15
+ def consent_tier_responses
16
+ getConsentTierResponseCollection or (self.consent_tier_responses = Java::JavaUtil::LinkedHashSet.new)
17
+ end
18
+
19
+ # Returns whether the consent available flag is equal to the String 'true'. This method converts
20
+ # the caTissue String to a Boolean.
21
+ def consent_available
22
+ getIsConsentAvailable == 'true'
23
+ end
24
+
25
+ # Sets the consent available flag to the specified value. A Boolean value is converted to a String.
26
+ def consent_available=(value)
27
+ value = value.to_s if value
28
+ setIsConsentAvailable(value)
29
+ end
30
+
31
+ # Sets the consent available flag to the specified value. An Integer value is converted to a String.
32
+ def protocol_participant_identifier=(value)
33
+ value = value.to_s if value
34
+ setProtocolParticipantIdentifier(value)
35
+ end
36
+
37
+ add_attribute_aliases("consented?".to_sym => :is_consent_available, :protocol => :collection_protocol,
38
+ :participant_identifier => :protocol_participant_identifier, :consent_responses => :consent_tier_responses)
39
+
40
+ set_secondary_key_attributes(:collection_protocol, :participant)
41
+
42
+ set_alternate_key_attributes(:collection_protocol, :protocol_participant_identifier)
43
+
44
+ add_attribute_defaults(:activity_status => 'Active')
45
+
46
+ add_mandatory_attributes(:registration_date)
47
+
48
+ # consent_tier_responses is a cascaded dependent but is not fetched
49
+ add_dependent_attribute(:consent_tier_responses, :unfetched)
50
+
51
+ # The CPR-CP association is bi-directional.
52
+ set_attribute_inverse(:collection_protocol, :collection_protocol_registrations)
53
+
54
+ # The CPR-Participant association is bi-directional.
55
+ set_attribute_inverse(:participant, :collection_protocol_registrations)
56
+
57
+ add_dependent_attribute(:specimen_collection_groups, :logical, :autogenerated)
58
+
59
+ # CPR PPI is part of a key if it exists, but is optional.
60
+ qualify_attribute(:protocol_participant_identifier, :optional)
61
+
62
+ # caTissue alert - Augment the standard metadata storable reference attributes to work around caTissue Bug #150:
63
+ # Create CollectionProtocol in API ignores startDate.
64
+ qualify_attribute(:registration_date, :update_only)
65
+
66
+ # caTissue alert - Augment the standard metadata storable reference attributes to work around caTissue Bug #63
67
+ # that requires a SpecimenCollectionGroup with an identifier which references a CollectionProtocolRegistration
68
+ # with an identifier to nevertheless hold extraneous CollectionProtocolRegistration content, including the CPR
69
+ # collection protocol. The referenced CP should not itself cascade to its dependents. This is enforced by a
70
+ # Catissue::Database work-around.
71
+ #
72
+ # caTissue alert - CPR fetches the associated CP. This is unnecessary for the predominant caRuby use case,
73
+ # where the CP is known when the CPR is fetched. Don't mark the CP as fetched in the CPR metadata, since that
74
+ # precipitates an unnecessary CP copy/match/merge into a fetched CPR.
75
+ qualify_attribute(:collection_protocol)
76
+
77
+ def initialize(params=nil)
78
+ super
79
+ # following line works around an obscure problem whereby CPR init could not call consent_tier_responses below;
80
+ # TODO - verify that it is still necessary
81
+ respond_to?(:consent_tier_responses)
82
+ # work around caTissue Bug #64
83
+ self.consent_tier_responses ||= Java::JavaUtil::LinkedHashSet.new
84
+ end
85
+
86
+ # Removes associations to this registration
87
+ def delete
88
+ participant.collection_registrations.delete(self) if participant
89
+ protocol.registrations.delete(self) if protocol
90
+ end
91
+
92
+ # @return all specimens collected for this CollectionProtocolRegistration
93
+ def specimens
94
+ Flattener.new(specimen_collection_groups.map { |group| group.specimens })
95
+ end
96
+
97
+ private
98
+
99
+ # Adds defaults as follows:
100
+ # * The default registration date is the current time.
101
+ # * The default PPI is a unique number.
102
+ def add_defaults_local
103
+ super
104
+ self.registration_date ||= Java.now
105
+ self.protocol_participant_identifier ||= Uniquifier.qualifier.to_s
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,13 @@
1
+ module CaTissue
2
+ # import the Java class
3
+ java_import('edu.wustl.catissuecore.domain.ConsentTierResponse')
4
+
5
+ class ConsentTierResponse
6
+ include Resource
7
+
8
+ add_mandatory_attributes(:consent_tier, :response)
9
+
10
+ add_attribute_defaults(:response => 'Not Specified')
11
+
12
+ end
13
+ end
@@ -0,0 +1,29 @@
1
+ module CaTissue
2
+ # import the Java class
3
+ java_import('edu.wustl.catissuecore.domain.ConsentTierStatus')
4
+
5
+ class ConsentTierStatus
6
+ include Resource
7
+
8
+ add_mandatory_attributes(:consent_tier, :status)
9
+
10
+ add_attribute_defaults(:status => 'Not Specified')
11
+
12
+ # Returns whether this ConsentTierStatus is minimally consistent with the other ConsentTierStatus.
13
+ # This method returns whether the referenced ConsentTier has the same identifer or statement text
14
+ # as the other referenced ConsentTier.
15
+ def minimal_match?(other)
16
+ super and statement_match?(other)
17
+ end
18
+
19
+ private
20
+
21
+ # Returns true if this ConsentTierStatus ConsentTier is nil, the other ConsentTierStatus ConsentTier is nil,
22
+ # both ConsentTier identifiers are equal, or both ConsentTier statements are equal.
23
+ def statement_match?(other)
24
+ ct = resume_lazy_loader { consent_tier }
25
+ oct = other.resume_lazy_loader { other.consent_tier }
26
+ ct and oct and (ct.identifier == oct.identifier or ct.statement == oct.statement)
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,234 @@
1
+ require 'caruby/util/coordinate'
2
+ require 'catissue/util/storable'
3
+ require 'catissue/util/location'
4
+
5
+ module CaTissue
6
+ # import the Java class
7
+ java_import('edu.wustl.catissuecore.domain.Container')
8
+
9
+ # The +caTissue+ +Container+ domain class wrapper.
10
+ # Each Container subclass is required to implement the {#container_type} method.
11
+ class Container
12
+ include Storable, Resource
13
+
14
+ add_attribute_aliases(:position => :located_at_position, :subcontainer_positions => :occupied_positions)
15
+
16
+ set_secondary_key_attributes(:name)
17
+
18
+ add_attribute_defaults(:activity_status => 'Active', :full => false)
19
+
20
+ add_dependent_attribute(:capacity, :logical, :autogenerated)
21
+
22
+ # located_at_position is cascaded but not fetched.
23
+ add_dependent_attribute(:located_at_position, :unfetched)
24
+
25
+ # Like SCG, Container name is ignored and assigned by caTissue. Therefore, this
26
+ # attribute is marked auto-generated.
27
+ qualify_attribute(:name, :autogenerated)
28
+
29
+ # Returns the ContainerType which constrains a Container in its roles as a Storable
30
+ # occupant rather than a Storable holder. {#storable_type} aliases the _container_type_
31
+ # defined by every Container subclass.
32
+ def storable_type
33
+ # can't alias because container_type is defined by subclasses
34
+ container_type
35
+ end
36
+
37
+ # @return this Container's ContainerType
38
+ def container_type
39
+ if self.class < Container then raise NotImplementedError.new("Container subclass does not implement the container_type method") end
40
+ end
41
+
42
+ def copy(*attributes)
43
+ ctr = super
44
+ ctr.container_type = self.container_type if attributes.empty?
45
+ ctr
46
+ end
47
+
48
+ # Returns the ContainerPosition class which this Container can occupy in its role as
49
+ # a Storable.
50
+ def position_class
51
+ CaTissue::ContainerPosition
52
+ end
53
+
54
+ # Lazy-initializes this Container's capacity to a copy of the {#storable_type} capacity.
55
+ def capacity
56
+ # caTissue alert - this override is necessary because the +caTissue+ API does
57
+ # not make the reasonable assumption that the default Container capacity is the
58
+ # ContainerType capacity.
59
+ getCapacity or copy_container_type_capacity
60
+ end
61
+
62
+ def bounds
63
+ capacity.bounds if capacity
64
+ end
65
+
66
+ # @return the number of rows in this Container
67
+ def rows
68
+ capacity.rows
69
+ end
70
+
71
+ # @return the number of columns in this Container
72
+ def columns
73
+ capacity.columns
74
+ end
75
+
76
+ # @return this Container's parent container
77
+ def parent
78
+ position and position.parent
79
+ end
80
+
81
+ # @return the occupants in this Container's positions.
82
+ # @see
83
+ def occupants
84
+ all_occupied_positions.wrap { |pos| pos.occupant }
85
+ end
86
+
87
+ alias :contents :occupants
88
+
89
+ # Returns whether this Container holds the given item or this Container holds
90
+ # a subcontainer which holds the item.
91
+ def include?(item)
92
+ occupants.detect { |occ| occ == item or occ.include?(item) }
93
+ end
94
+
95
+ # @return the Specimen occupants
96
+ def specimens
97
+ occupants.filter { |occ| Specimen === occ }
98
+ end
99
+
100
+ # @return the Container occupants
101
+ def subcontainers
102
+ occupants.filter { |occ| Container === occ }
103
+ end
104
+
105
+ # @return the Containers in this StorageContainer hierarchy
106
+ def subcontainers_in_hierarchy
107
+ @ctr_enum ||= SUBCTR_VISITOR.to_enum(self)
108
+ end
109
+
110
+ # @return whether this container or a subcontainer in the hierarchy holds the given object
111
+ def holds?(storable)
112
+ contents.include?(storable) or subcontainers.any? { |ctr| ctr.holds?(storable) }
113
+ end
114
+
115
+ # @return true if this Container or a subcontainer in the hierarchy can hold the given storable
116
+ #
117
+ # @see #can_hold_child?
118
+ def can_hold?(storable)
119
+ can_hold_child?(storable) or subcontainers.detect { |ctr| ctr.can_hold?(storable) }
120
+ end
121
+
122
+ # @return true if this Container is not full and the {#container_type} can hold the storable as a child
123
+ def can_hold_child?(storable)
124
+ not full? and container_type.can_hold_child?(storable)
125
+ end
126
+
127
+ # @return whether this Container and every subcontainer in the hierarchy are full
128
+ def completely_full?
129
+ full? and subcontainers.all? { |ctr| ctr.completely_full? }
130
+ end
131
+
132
+ # Returns -1, 0, or 1 if self is contained in, contains or the same as the other
133
+ # Container, resp.
134
+ def <=>(other)
135
+ raise TypeError.new("Can't compare #{qp} to #{other}") unless StorageContainer === self
136
+ return 0 if equal?(other) or (name and name == other.name)
137
+ return 1 if subcontainers.detect { |child| child >= other if StorageContainer === child }
138
+ -1 if other > self
139
+ end
140
+
141
+ # @return the occupant at the given zero-based row and column, or nil if none
142
+ def [](column, row)
143
+ all_occupied_positions.detect_value do |pos|
144
+ return if row < pos.row
145
+ next unless row == pos.row
146
+ pos.occupant if pos.column == column
147
+ end
148
+ end
149
+
150
+ # Moves the given Storable from its current Position, if any, to this Container at the optional
151
+ # coordinate. The default coordinate is the first available slot within this Container.
152
+ # The storable Storable position is updated to reflect the new location. Returns self.
153
+ #
154
+ # Raises IndexError if this Container is full.
155
+ # Raises IndexError if the row and column are given but exceed the Container bounds.
156
+ def add(storable, coordinate=nil, attribute=nil)
157
+ validate_type(storable)
158
+ loc = create_location(coordinate)
159
+ pos = storable.position || storable.position_class.new
160
+ pos.location = loc
161
+ pos.occupant = storable
162
+ pos.holder = self
163
+ logger.debug { "Added #{storable.qp} to #{qp} at #{loc.coordinate}." }
164
+ update_full_flag
165
+ self
166
+ end
167
+
168
+ alias :<< :add
169
+
170
+ protected
171
+
172
+ # Returns the the content collection to which the storable is added. This default returns
173
+ # occupied_positions if storable is a Container, nil otherwise. Subclasses can override.
174
+ #
175
+ # @param [Storable] the item to store
176
+ # @return [<Position>] the occupied positions
177
+ def content_collection_for(storable)
178
+ subcontainer_positions if Container === storable
179
+ end
180
+
181
+ private
182
+
183
+ # Subcontainer visitor.
184
+ SUBCTR_VISITOR = CaRuby::ReferenceVisitor.new { [:subcontainers] }
185
+
186
+ # @param [Storable] the item to store
187
+ # @raise [TypeError] if this container cannot hold the storable
188
+ def validate_type(storable)
189
+ unless container_type.can_hold_child?(storable) then
190
+ raise TypeError.new("Container #{self} cannot hold an item of the #{storable} type")
191
+ end
192
+ end
193
+
194
+ # @param [Coordinate] coordinate the optional location to create
195
+ # @return [Location] the created location
196
+ def create_location(coordinate=nil)
197
+ if coordinate then
198
+ Location.new(:in => self, :at => coordinate)
199
+ else
200
+ first_available_location or raise IndexError.new("Container #{qp} does not have an available location")
201
+ end
202
+ end
203
+
204
+ # @return [Location] the next available Location in this container, or nil if no unoccupied
205
+ # location is available
206
+ def first_available_location
207
+ return if full?
208
+ # look for the first unoccupied location
209
+ occupied = all_occupied_positions.map { |pos| pos.location }.sort
210
+ # find a gap, if one exists, otherwise return the next location
211
+ # after the last occupied location
212
+ current = Location.new(:in => self, :at => Coordinate.new(0, 0))
213
+ occupied.each do |loc|
214
+ break if current < loc
215
+ current.succ!
216
+ end
217
+ current
218
+ end
219
+
220
+ # Copies this Container's ContainerType capacity, if it exists, to the Container capacity.
221
+ #
222
+ # @return [Capacity, nil] the initialized capacity, if any
223
+ def copy_container_type_capacity
224
+ return unless container_type and container_type.capacity
225
+ self.capacity = container_type.capacity.copy(:rows, :columns)
226
+ update_full_flag
227
+ capacity
228
+ end
229
+
230
+ def update_full_flag
231
+ self.full = all_occupied_positions.size == rows * columns
232
+ end
233
+ end
234
+ end