caruby-tissue 1.2.2 → 1.2.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (153) hide show
  1. data/History.txt +4 -0
  2. data/bin/crtdump +11 -3
  3. data/bin/{seed → crtseed} +0 -0
  4. data/conf/annotation/pathology_scg/AdditionalFinding.hbm.xml +19 -0
  5. data/conf/annotation/pathology_scg/BasePathologyAnnotation.hbm.xml +260 -0
  6. data/conf/annotation/pathology_scg/BreastMargin.hbm.xml +21 -0
  7. data/conf/annotation/pathology_scg/BreastMarginInvolved.hbm.xml +15 -0
  8. data/conf/annotation/pathology_scg/BreastMarginUninvolved.hbm.xml +14 -0
  9. data/conf/annotation/pathology_scg/CNSMargin.hbm.xml +19 -0
  10. data/conf/annotation/pathology_scg/CNSMarginLocation.hbm.xml +14 -0
  11. data/conf/annotation/pathology_scg/CarcinomaInSituStatus.hbm.xml +14 -0
  12. data/conf/annotation/pathology_scg/ColorectalLocalExcisionMarginUninvolved.hbm.xml +15 -0
  13. data/conf/annotation/pathology_scg/ColorectalResectedMarginUninvolved.hbm.xml +22 -0
  14. data/conf/annotation/pathology_scg/Cytogenetics.hbm.xml +15 -0
  15. data/conf/annotation/pathology_scg/DeepMelanomaMargin.hbm.xml +16 -0
  16. data/conf/annotation/pathology_scg/Details.hbm.xml +14 -0
  17. data/conf/annotation/pathology_scg/DirectExtensionOfTumor.hbm.xml +14 -0
  18. data/conf/annotation/pathology_scg/DistalMargin.hbm.xml +15 -0
  19. data/conf/annotation/pathology_scg/DistanceFromAnalVerge.hbm.xml +23 -0
  20. data/conf/annotation/pathology_scg/DistantMetastasis.hbm.xml +19 -0
  21. data/conf/annotation/pathology_scg/ExcionalBiopsyMarginUninvolved.hbm.xml +15 -0
  22. data/conf/annotation/pathology_scg/ExcisionalBiopsyColorectalDeepMargin.hbm.xml +15 -0
  23. data/conf/annotation/pathology_scg/ExcisionalBiopsyColorectalLateralOrMucosalMargin.hbm.xml +15 -0
  24. data/conf/annotation/pathology_scg/ExtraprostaticExtension.hbm.xml +20 -0
  25. data/conf/annotation/pathology_scg/ExtraprostaticExtensionTissueSites.hbm.xml +14 -0
  26. data/conf/annotation/pathology_scg/GleasonScore.hbm.xml +16 -0
  27. data/conf/annotation/pathology_scg/HistologicGrade.hbm.xml +16 -0
  28. data/conf/annotation/pathology_scg/HistologicType.hbm.xml +19 -0
  29. data/conf/annotation/pathology_scg/HistologicVariantType.hbm.xml +14 -0
  30. data/conf/annotation/pathology_scg/ImmunoPhenotyping.hbm.xml +16 -0
  31. data/conf/annotation/pathology_scg/Invasion.hbm.xml +16 -0
  32. data/conf/annotation/pathology_scg/KidneyMarginLocation.hbm.xml +15 -0
  33. data/conf/annotation/pathology_scg/KidneyNephrectomyMargin.hbm.xml +19 -0
  34. data/conf/annotation/pathology_scg/LateralMelanomaMargin.hbm.xml +16 -0
  35. data/conf/annotation/pathology_scg/LocalExcisionColorectalDeepMargin.hbm.xml +15 -0
  36. data/conf/annotation/pathology_scg/LocalExcisionColorectalLateralMargin.hbm.xml +20 -0
  37. data/conf/annotation/pathology_scg/LungResectionMargin.hbm.xml +17 -0
  38. data/conf/annotation/pathology_scg/LungResectionMarginsUninvolved.hbm.xml +14 -0
  39. data/conf/annotation/pathology_scg/MacroscopicExtentOfTumor.hbm.xml +14 -0
  40. data/conf/annotation/pathology_scg/MesentricMargin.hbm.xml +15 -0
  41. data/conf/annotation/pathology_scg/MetastasisTissueSite.hbm.xml +15 -0
  42. data/conf/annotation/pathology_scg/Microcalcification.hbm.xml +14 -0
  43. data/conf/annotation/pathology_scg/NottinghamHistologicScore.hbm.xml +17 -0
  44. data/conf/annotation/pathology_scg/OtherResectedOrgans.hbm.xml +15 -0
  45. data/conf/annotation/pathology_scg/PancreasMargin.hbm.xml +20 -0
  46. data/conf/annotation/pathology_scg/PancreasMarginInvolvedByInvasiveCarcinoma.hbm.xml +15 -0
  47. data/conf/annotation/pathology_scg/PancreasMarginUninvolvedByInvasiveCarcinoma.hbm.xml +20 -0
  48. data/conf/annotation/pathology_scg/PathologicalStaging.hbm.xml +16 -0
  49. data/conf/annotation/pathology_scg/PolypConfiguration.hbm.xml +15 -0
  50. data/conf/annotation/pathology_scg/PrimaryTumorStage.hbm.xml +14 -0
  51. data/conf/annotation/pathology_scg/ProstateMarginLocation.hbm.xml +15 -0
  52. data/conf/annotation/pathology_scg/ProximalMargin.hbm.xml +15 -0
  53. data/conf/annotation/pathology_scg/RadialMargin.hbm.xml +15 -0
  54. data/conf/annotation/pathology_scg/RadicalProstatectomyMargin.hbm.xml +20 -0
  55. data/conf/annotation/pathology_scg/RegionalLymphNode.hbm.xml +19 -0
  56. data/conf/annotation/pathology_scg/SatelliteNodule.hbm.xml +14 -0
  57. data/conf/annotation/pathology_scg/SpecimenCollectionGroup.hbm.xml +87 -0
  58. data/conf/annotation/pathology_scg/SpecimenIntegrity.hbm.xml +15 -0
  59. data/conf/annotation/pathology_scg/SpecimenSize.hbm.xml +17 -0
  60. data/conf/annotation/pathology_scg/TissueSide.hbm.xml +14 -0
  61. data/conf/annotation/pathology_scg/TumorSize.hbm.xml +29 -0
  62. data/conf/annotation/pathology_scg/TumorTissueSite.hbm.xml +20 -0
  63. data/conf/annotation/pathology_scg/UninvolvedMelanomaMargin.hbm.xml +15 -0
  64. data/conf/annotation/pathology_specimen/AdditionalFinding.hbm.xml +19 -0
  65. data/conf/annotation/pathology_specimen/AdditionalPathologicFinding.hbm.xml +18 -0
  66. data/conf/annotation/pathology_specimen/Details.hbm.xml +15 -0
  67. data/conf/annotation/pathology_specimen/GleasonScore.hbm.xml +16 -0
  68. data/conf/annotation/pathology_specimen/HistologicGrade.hbm.xml +16 -0
  69. data/conf/annotation/pathology_specimen/HistologicType.hbm.xml +19 -0
  70. data/conf/annotation/pathology_specimen/HistologicVariantType.hbm.xml +14 -0
  71. data/conf/annotation/pathology_specimen/Invasion.hbm.xml +16 -0
  72. data/conf/annotation/pathology_specimen/NottinghamHistologicScore.hbm.xml +17 -0
  73. data/conf/annotation/pathology_specimen/Specimen.hbm.xml +52 -0
  74. data/conf/annotation/pathology_specimen/SpecimenBaseSolidTissuePathologyAnnotation.hbm.xml +73 -0
  75. data/examples/galena/lib/galena/cli/seed.rb +0 -21
  76. data/examples/galena/lib/galena/migration/frozen_shims.rb +6 -5
  77. data/examples/galena/lib/galena/seed/defaults.rb +0 -5
  78. data/{lib → examples/galena/lib}/galena.rb +0 -0
  79. data/lib/catissue/annotation/annotatable.rb +37 -0
  80. data/lib/catissue/annotation/annotatable_class.rb +255 -0
  81. data/lib/catissue/annotation/annotation.rb +49 -0
  82. data/lib/catissue/annotation/annotation_class.rb +277 -0
  83. data/lib/catissue/annotation/annotation_module.rb +77 -0
  84. data/lib/catissue/annotation/hibernate_mapping.rb +46 -0
  85. data/lib/catissue/annotation/proxy.rb +28 -0
  86. data/lib/catissue/annotation/proxy_class.rb +68 -0
  87. data/lib/catissue/cli/migrate.rb +2 -2
  88. data/lib/catissue/cli/smoke.rb +6 -4
  89. data/lib/catissue/database/annotation/annotation_service.rb +75 -61
  90. data/lib/catissue/database/annotation/annotator.rb +17 -76
  91. data/lib/catissue/database/annotation/entity_facade.rb +265 -0
  92. data/lib/catissue/database/annotation/id_generator.rb +62 -0
  93. data/lib/catissue/database/annotation/integration_service.rb +105 -59
  94. data/lib/catissue/database/annotation/reference_writer.rb +150 -0
  95. data/lib/catissue/database/controlled_values.rb +12 -12
  96. data/lib/catissue/database.rb +148 -58
  97. data/lib/catissue/domain/abstract_specimen.rb +40 -14
  98. data/lib/catissue/domain/abstract_specimen_collection_group.rb +1 -3
  99. data/lib/catissue/domain/collection_protocol.rb +13 -5
  100. data/lib/catissue/domain/collection_protocol_event.rb +1 -14
  101. data/lib/catissue/domain/consent_tier_response.rb +2 -0
  102. data/lib/catissue/domain/consent_tier_status.rb +5 -3
  103. data/lib/catissue/domain/container.rb +14 -10
  104. data/lib/catissue/domain/container_position.rb +8 -0
  105. data/lib/catissue/domain/container_type.rb +13 -6
  106. data/lib/catissue/domain/participant.rb +15 -10
  107. data/lib/catissue/domain/site.rb +9 -3
  108. data/lib/catissue/domain/specimen.rb +79 -40
  109. data/lib/catissue/domain/specimen_array.rb +11 -1
  110. data/lib/catissue/domain/specimen_collection_group.rb +79 -41
  111. data/lib/catissue/domain/specimen_event_parameters.rb +5 -8
  112. data/lib/catissue/domain/specimen_position.rb +0 -2
  113. data/lib/catissue/domain/specimen_requirement.rb +1 -1
  114. data/lib/catissue/domain/storage_container.rb +109 -48
  115. data/lib/catissue/domain/storage_type.rb +1 -1
  116. data/lib/catissue/migration/migrator.rb +6 -14
  117. data/lib/catissue/resource.rb +18 -8
  118. data/lib/catissue/util/position.rb +11 -1
  119. data/lib/catissue/util/storable.rb +18 -11
  120. data/lib/catissue/util/storage_type_holder.rb +44 -6
  121. data/lib/catissue/version.rb +1 -1
  122. metadata +86 -35
  123. data/bin/migrate.rb +0 -42
  124. data/bin/seed.rb +0 -43
  125. data/examples/galena/doc/CaTissue/Participant.html +0 -241
  126. data/examples/galena/doc/CaTissue/SpecimenCollectionGroup.html +0 -190
  127. data/examples/galena/doc/CaTissue/StorageContainer.html +0 -179
  128. data/examples/galena/doc/CaTissue/TissueSpecimen.html +0 -320
  129. data/examples/galena/doc/CaTissue.html +0 -93
  130. data/examples/galena/doc/Galena/Seed/Defaults.html +0 -650
  131. data/examples/galena/doc/Galena/Seed.html +0 -203
  132. data/examples/galena/doc/Galena.html +0 -172
  133. data/examples/galena/doc/_index.html +0 -181
  134. data/examples/galena/doc/class_list.html +0 -36
  135. data/examples/galena/doc/css/common.css +0 -1
  136. data/examples/galena/doc/css/full_list.css +0 -53
  137. data/examples/galena/doc/css/style.css +0 -307
  138. data/examples/galena/doc/file.README.html +0 -153
  139. data/examples/galena/doc/file_list.html +0 -38
  140. data/examples/galena/doc/frames.html +0 -13
  141. data/examples/galena/doc/index.html +0 -153
  142. data/examples/galena/doc/js/app.js +0 -202
  143. data/examples/galena/doc/js/full_list.js +0 -149
  144. data/examples/galena/doc/js/jquery.js +0 -154
  145. data/examples/galena/doc/method_list.html +0 -163
  146. data/examples/galena/doc/top-level-namespace.html +0 -112
  147. data/lib/README.html +0 -33
  148. data/lib/catissue/database/annotation/annotatable_service.rb +0 -25
  149. data/lib/catissue/database/annotation/entity_manager.rb +0 -10
  150. data/lib/galena/cli/seed.rb +0 -43
  151. data/lib/galena/migration/filter_shims.rb +0 -43
  152. data/lib/galena/migration/frozen_shims.rb +0 -53
  153. data/lib/galena/seed/defaults.rb +0 -109
@@ -98,11 +98,6 @@ module Galena
98
98
  @freezer_type << rack_type
99
99
  rack_type << box_type
100
100
  @box_type << 'Tissue'
101
-
102
- # a sample freezer and box
103
- frz = CaTissue::StorageContainer.new(:name => 'GTB Freezer 1', :storage_type=>@freezer_type, :site=>@tissue_bank)
104
- @box = CaTissue::StorageContainer.new(:name => 'GTB Box 1', :storage_type=>@box_type)
105
- frz << @box
106
101
  end
107
102
  end
108
103
  end
File without changes
@@ -0,0 +1,37 @@
1
+ require 'catissue/annotation/annotatable_class'
2
+
3
+ module CaTissue
4
+ # {CaTissue::Resource} annotation hook mix-in.
5
+ module Annotatable
6
+ def method_missing(mth, *args)
7
+ name = mth.to_s
8
+ # remove trailing assignment '=' character if present
9
+ attr = name =~ /=$/ ? name.chop.to_sym : mth
10
+ # If an annotation can be generated on demand, then resend the method.
11
+ # Otherwise, delegate to super for the standard error.
12
+ self.class.annotation_attribute?(attr) ? send(mth, *args) : super
13
+ end
14
+
15
+ # Creates a {Annotation::Proxy} whose hook reference is set to this annotatable object.
16
+ #
17
+ # @param [Class] the proxy class
18
+ # @return [Resource] the new proxy
19
+ def create_proxy(klass)
20
+ # make the proxy instance
21
+ pxy = klass.new
22
+ # set the proxy hook reference to this hook instance
23
+ pxy.hook = self
24
+ logger.debug { "Created #{qp} #{klass.domain_module.qp} annotation proxy #{pxy}." }
25
+ pxy
26
+ end
27
+
28
+ # @param [Symbol] attribute the hook -> annotation attribute
29
+ # @return [Annotation] the hook -> proxy attribute
30
+ # @raise (see AnnotatableClass#annotation_proxy_attribute)
31
+ def annotation_proxy(attribute)
32
+ pxy_attr = self.class.annotation_proxy_attribute(attribute)
33
+ # the hook -> proxy attribute value
34
+ send(pxy_attr)
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,255 @@
1
+ require 'forwardable'
2
+ require 'caruby/util/inflector'
3
+ require 'catissue/annotation/annotation_module'
4
+
5
+ module CaTissue
6
+ # Mix-in for extending a caTissue domain class with annotations.
7
+ module AnnotatableClass
8
+
9
+ # @return [Integer, nil] the the hook class designator that is used by caTissue to persist primary
10
+ # annotation objects, or nil if this is not a primary annotation class
11
+ attr_reader :entity_id
12
+
13
+ def self.extended(klass)
14
+ super
15
+ # the annotation name => spec hash
16
+ klass.class_eval { extend Forwardable; @ann_spec_hash = {} }
17
+ end
18
+
19
+ # @return [Integer, nil] this class's entity id, if it exists, otherwise the superclass effective entity id
20
+ # if the superclass is an annotation class
21
+ def effective_entity_id
22
+ @entity_id or parent_entity_id
23
+ end
24
+
25
+ # If there is an existing annotation whose proxy accessor is the
26
+ # given symbol, then return true. Otherwise, attempt to import
27
+ # an annotation and return whether the import was successful.
28
+ #
29
+ # @param [Symbol] symbol the potential accessor attribute
30
+ # @return [Boolean] whether there is a corresponding annotation
31
+ def annotation_attribute?(symbol)
32
+ # load annotations if necessary
33
+ ensure_annotations_loaded
34
+ # check for the annotation attribute
35
+ annotation_defined?(symbol)
36
+ end
37
+
38
+ # Refines the {CaRuby::ResourceAttributes#toxic_attributes} to exclude annotation attributes.
39
+ #
40
+ # @return [<Symbol>] the non-annotation unfetched attributes
41
+ def toxic_attributes
42
+ @anntbl_toxic_attrs ||= unfetched_attributes.compose { |attr_md| not attr_md.type < Annotation }
43
+ end
44
+
45
+ def annotation_proxy_attribute(ann_attr)
46
+ annotatable_class_hierarchy.detect_value { |klass| klass.local_annotation_proxy_attribute(ann_attr) }
47
+ end
48
+
49
+ # Makes a new attribute in this hook class for the given annotation proxy domain attribute.
50
+ # The hook annotation reference attribute delegates to the proxy. This method is intended for
51
+ # the exclusive use of {Annotation::ProxyClass}.
52
+ #
53
+ # @param [AnnotationModule] mod the annotation module
54
+ # @param [Symbol] attribute the proxy => annotation reference
55
+ def create_annotation_attribute(mod, attribute)
56
+ pxy = mod.proxy
57
+ pxy_attr = @ann_mod_pxy_hash[mod]
58
+ ann_attr_md = pxy.attribute_metadata(attribute)
59
+ # the type referenced by the annotation proxy
60
+ klass = ann_attr_md.type
61
+ # create annotation accessors which delegate to the proxy
62
+ def_delegators(pxy_attr, *ann_attr_md.accessors)
63
+ logger.debug { "Created #{qp}.#{attribute} which delegates to the annotation proxy #{pxy_attr}." }
64
+ # add the attribute
65
+ add_annotation_attribute(attribute, klass)
66
+ end
67
+
68
+ # Loads the annotations, if necessary, and tries to get the constant again.
69
+ #
70
+ # @param [Symbol] symbol the missing constant
71
+ # @return [AnnotationModule] the imported annotation, if successful
72
+ # @raise [NameError] if the annotation could not be imported
73
+ def const_missing(symbol)
74
+ if annotations_loaded? then
75
+ super
76
+ else
77
+ ensure_annotations_loaded
78
+ const_get(symbol)
79
+ end
80
+ end
81
+
82
+ protected
83
+
84
+ # @return [<AnnotationModule>] the annotation modules in the class hierarchy
85
+ def annotation_modules
86
+ @ann_mods ||= load_annotations
87
+ end
88
+
89
+ # @param [Symbol] ann_attr the annotation attribute
90
+ # @return [Symbol] the annotation proxy attribute
91
+ # @raise [TypeError] if the given attribute is not an annotation attribute
92
+ def local_annotation_proxy_attribute(ann_attr)
93
+ unless annotation_attribute?(ann_attr) then
94
+ raise TypeError.new("#{qp} #{ann_attr} is not an annotation attribute")
95
+ end
96
+ # the annotation class
97
+ klass = attribute_metadata(ann_attr).type
98
+ mod = klass.domain_module
99
+ @ann_mod_pxy_hash[mod]
100
+ end
101
+
102
+ private
103
+
104
+ # Loads the annotations defined for this class if necessary.
105
+ def ensure_annotations_loaded
106
+ # referencing the annotations loads them
107
+ annotation_modules
108
+ end
109
+
110
+ # Loads the annotation modules in the class hierarchy.
111
+ #
112
+ # @return [<AnnotationModule>] an Enumerable on the loaded annotation modules
113
+ def load_annotations
114
+ @local_ann_mods = load_local_annotations
115
+ superclass < Annotatable ? @local_ann_mods.union(superclass.annotation_modules) : @local_ann_mods
116
+ end
117
+
118
+ def parent_entity_id
119
+ superclass.entity_id if superclass < Annotatable
120
+ end
121
+
122
+ def annotatable_class_hierarchy
123
+ class_hierarchy.filter { |klass| klass < Annotatable }
124
+ end
125
+
126
+ # Sets the annotation service name for the given hook proxy atttribute.
127
+ # The default service name is the camelized attribute.
128
+ #
129
+ # @param [Symbol] attribute the hook proxy atttribute
130
+ # @param [{Symbol => Object}] opts the annotation options
131
+ # @option opts [String] :package the package name (default the decapitalized camelized name)
132
+ # @option opts [String] :service the service name (default the decapitalized underscore name)
133
+ def add_annotation(name, opts={})
134
+ # the module spec defaults
135
+ opts[:package] ||= name.camelize(:lower)
136
+ opts[:service] ||= name.underscore
137
+ # add the annotation entry
138
+ @ann_spec_hash[name.to_sym] = opts
139
+ logger.info("Added #{qp} annotation named #{name} with package #{opts[:package]} and service #{opts[:service]}.")
140
+ end
141
+
142
+ # @return [Boolean] whether this annotatable class's annotations are loaded
143
+ def annotations_loaded?
144
+ not @ann_mods.nil?
145
+ end
146
+
147
+ # Loads this class's annotations.
148
+ #
149
+ # @return [<AnnotationModule>] the loaded annotation modules
150
+ def load_local_annotations
151
+ # the annotation module => proxy hash
152
+ @ann_mod_pxy_hash = {}
153
+ # an annotated class has a hook entity id
154
+ unless @ann_spec_hash.empty? then
155
+ @entity_id = Annotation::EntityFacade.instance.hook_entity_id(self)
156
+ end
157
+ # build the annotations
158
+ @ann_spec_hash.map { |name, opts| import_annotation(name, opts) }
159
+ end
160
+
161
+ # @param [Symbol] attribute the annotation accessor
162
+ # @return [Module] the annotation module which implements the attribute
163
+ def annotation_attribute_module(attribute)
164
+ annotation_modules.detect { |mod| mod.proxy.attribute_defined?(attribute) }
165
+ end
166
+
167
+ # Builds a new annotation module for the given attribute.
168
+ #
169
+ # @param [<Symbol>] attribute the proxy attribute to import
170
+ # @return [Module] the annotation module
171
+ # @raise [AnnotationError] if there is no annotation proxy class
172
+ def import_annotation(name, opts)
173
+ logger.debug { "Importing #{qp} annotation #{name}..." }
174
+ # make the annotation module scoped by this Annotatable class
175
+ class_eval("module #{name}; end")
176
+ mod = const_get(name)
177
+ # append the AnnotationModule methods
178
+ AnnotationModule.extend_module(mod, self, opts)
179
+ # make the proxy attribute
180
+ create_proxy_attribute(mod)
181
+ # make the annotation dependent attributes
182
+ create_annotation_attributes(mod)
183
+ # add proxy references
184
+ mod.ensure_proxy_attributes_are_defined
185
+ mod.add_annotation_dependents
186
+ logger.debug { "Imported #{qp} annotation #{name}." }
187
+ mod
188
+ end
189
+
190
+ # Returns whether this class has an annotation whose proxy accessor is the
191
+ # given symbol.
192
+ #
193
+ # @param (see #annotation?)
194
+ # @return (see #annotation?)
195
+ # @see #annotation?
196
+ def annotation_defined?(symbol)
197
+ attribute_defined?(symbol) and attribute_metadata(symbol).type < Annotation
198
+ end
199
+
200
+ # Makes an attribute whose name is the demodulized underscored given module name.
201
+ # The attribute reader creates an {Annotation::Proxy} instance of the method
202
+ # receiver {Annotatable} instance on demand.
203
+ #
204
+ # @param [AnnotationModule] mod the subject annotation module
205
+ # @return [Symbol] the proxy attribute
206
+ def create_proxy_attribute(mod)
207
+ # the proxy class
208
+ pxy = mod.proxy
209
+ # the proxy attribute
210
+ attr = mod.name.demodulize.underscore.to_sym
211
+ # define the proxy attribute
212
+ attr_create_on_demand_accessor(attr) { |obj| obj.create_proxy(pxy) }
213
+ # add it as a standard (but unpersisted) attribute
214
+ add_attribute(attr, pxy, :unsaved)
215
+ # create the proxy => hook inverse
216
+ pxy.set_hook(self, attr)
217
+ logger.debug { "Added #{qp} #{mod.qp} annotation proxy attribute #{attr}." }
218
+ # the annotation module => proxy attribute association
219
+ @ann_mod_pxy_hash[mod] = attr
220
+ attr
221
+ end
222
+
223
+ # Makes a new attribute in this hook class for each of the given annotation module's
224
+ # proxy domain attributes. The hook annotation reference attribute delegates to the
225
+ # proxy.
226
+ #
227
+ # @param [AnnotationModule] mod the subject annotation module
228
+ # @param [Symbol] proxy_attribute the hook => proxy reference
229
+ def create_annotation_attributes(mod)
230
+ # create annotation attributes which delegate to the proxy
231
+ mod.proxy.annotation_attributes.each do |attr|
232
+ create_annotation_attribute(mod, attr)
233
+ end
234
+ end
235
+
236
+ # Adds the given annotation attribute as a dependent collection attribute with meta-data.
237
+ #
238
+ # @param [Symbol] attribute the annotation accessor
239
+ # @param [Class] type the attribute domain type
240
+ def add_annotation_attribute(attribute, type)
241
+ logger.debug { "Adding #{qp} #{type.qp} annotation attribute #{attribute}..." }
242
+ # Mark the attribute as a collection.
243
+ add_attribute(attribute, type, :collection)
244
+
245
+ # the camel-case attribute is a potential alias
246
+ jattr = attribute.to_s.camelize(:lower).to_sym
247
+ unless attribute == jattr then
248
+ add_attribute_aliases(jattr => attribute)
249
+ end
250
+
251
+ # the annotation is a dependent
252
+ add_dependent_attribute(attribute, :logical)
253
+ end
254
+ end
255
+ end
@@ -0,0 +1,49 @@
1
+ require 'caruby/resource'
2
+ require 'caruby/domain/id_alias'
3
+
4
+ module CaTissue
5
+ # Annotation acceess error class.
6
+ class AnnotationError < StandardError; end
7
+
8
+ # Annotation class mix-in.
9
+ module Annotation
10
+ include CaRuby::Resource, CaRuby::IdAlias
11
+
12
+ # Returns the CaTissue::Database which stores this object.
13
+ def database
14
+ CaTissue::Database.instance
15
+ end
16
+
17
+ # Updates the annotation proxy to reflect the hook, if necessary.
18
+ #
19
+ # @see Proxy#ensure_identifier_reflects_hook
20
+ def ensure_proxy_reflects_hook
21
+ pxy = proxy || return
22
+ pxy.ensure_identifier_reflects_hook
23
+ end
24
+
25
+ # @return [Annotatable] the hook object which owns this annotation, or nil if this annotation
26
+ # is not directly owned by a hook entity
27
+ def hook
28
+ pxy_attr = self.class.proxy_attribute
29
+ send(pxy_attr) if pxy_attr
30
+ end
31
+
32
+ # If there is no conventional owner, then try the hook.
33
+ #
34
+ # @return the {CaRuby::Resource#owner} or the {#hook}
35
+ def owner
36
+ super or hook
37
+ end
38
+
39
+ # @return [Proxy] the proxy which references this annotation
40
+ def proxy
41
+ # the annotation owner hook instance
42
+ ownr = hook || return
43
+ # the hook -> annotation reference attribute
44
+ attr = self.class.hook_proxy_attribute
45
+ # the owner proxy for the attribute
46
+ ownr.annotation_proxy(attr)
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,277 @@
1
+ require 'xmlsimple'
2
+ require 'catissue/annotation/annotation'
3
+ require 'catissue/database/annotation/reference_writer'
4
+
5
+ module CaTissue
6
+ module AnnotationClass
7
+
8
+ # @return [Symbol, nil] the annotation => proxy attribute, or nil if this is not a primary annotation
9
+ attr_reader :proxy_attribute
10
+
11
+ # @return [Integer, nil] the annotation class designator that is used by caTissue to persist primary
12
+ # annotation objects, or nil if this is not a primary annotation
13
+ attr_reader :entity_id
14
+
15
+ # @return [Intger, nil] the container id, or nil if this is not a primary annotation
16
+ attr_reader :container_id
17
+
18
+ # Infers the given annotation class dependent attributes as described in
19
+ # {#infer_dependents}.
20
+ #
21
+ # @param [Class] klass the annotation class
22
+ def self.extended(klass)
23
+ super
24
+ klass.extend_as_annotation
25
+ end
26
+
27
+ # Adds metadata to this annotation class.
28
+ def extend_as_annotation
29
+ efcd = Annotation::EntityFacade.instance
30
+ # The entity id, or nil if this is not a primary entity.
31
+ @entity_id = efcd.primary_entity_id(self, false)
32
+ # A primary entity has a container id.
33
+ if @entity_id then @container_id = efcd.container_id(@entity_id) end
34
+ # infer non-dependent attribute inverses
35
+ detect_inverses
36
+ end
37
+
38
+ # Creates the proxy attribute if this is a primary_entity annotation class which does not
39
+ # have a caTissue proxy property.
40
+ #
41
+ # @param [Class] proxy the {AnnotationProxy} class
42
+ def ensure_primary_has_proxy(proxy)
43
+ if primary? then @proxy_attribute ||= create_proxy_attribute(proxy) end
44
+ end
45
+
46
+ # Saves the annotations referenced by the given annotation.
47
+ #
48
+ # @param [Annotation] annotation the subject annotation
49
+ def save_dependent_attributes(annotation)
50
+ dependent_attributes.each { |attr| save_dependent_attribute(annotation, attr) }
51
+ end
52
+
53
+ # Detects or creates the proxy attribute that references the given proxy class.
54
+ # if this is a primary_entity annotation class which does not
55
+ # have a caTissue proxy property.
56
+ #
57
+ # @param [Class] proxy the {AnnotationProxy} class
58
+ def proxy=(proxy)
59
+ # msut be primary
60
+ unless primary? then raise AnnotationError.new("Can't set proxy for non-primary annotation class #{qp}") end
61
+ # make the proxy attribute
62
+ @proxy_attribute = obtain_proxy_attribute(proxy)
63
+ # set the hook
64
+ self.hook = proxy.hook
65
+ # primary superclass gets a proxy as well
66
+ if superclass < Annotation and superclass.primary? then superclass.proxy = proxy end
67
+ end
68
+
69
+ # @param [Class] klass the hook class for this primary annotation
70
+ def hook=(klass)
71
+ # only a primary can have a hook
72
+ unless primary? then
73
+ raise AnnotationError.new("#{domain_module.qp} annotation #{qp} #{domain_module.proxy.qp} proxy owner accessor attribute not found.")
74
+ end
75
+ # redirect the hook writer method to write to the proxy instead
76
+ convert_hook_to_proxy(klass)
77
+ end
78
+
79
+ # @return [Symbol, nil] the hook proxy class attribute which references this annotation, or nil if this
80
+ # class is not a primary annotation class
81
+ def hook_proxy_attribute
82
+ # The hook => primary attribute symbol is the same as the proxy => primary attribute symbol.
83
+ attribute_metadata(@proxy_attribute).inverse if @proxy_attribute
84
+ end
85
+
86
+ # @return [Symbol, nil] the primary owner annotation, if it exists
87
+ def primary_owner_attributes
88
+ @pr_owr_attrs ||= domain_attributes.compose do |attr_md|
89
+ attr_md.type < Annotation and attr_md.type.method_defined?(:hook)
90
+ end
91
+ end
92
+
93
+ # @return [Boolean] whether this annotation class references a hook proxy
94
+ def primary?
95
+ not @entity_id.nil?
96
+ end
97
+
98
+ # @return [Array] an empty array, since no annotation reference is lazy-loaded by caTissue.
99
+ def toxic_attributes
100
+ Array::EMPTY_ARRAY
101
+ end
102
+
103
+ protected
104
+
105
+ # @return [Symbol => ReferenceWriter] this class's attribute => writer hash
106
+ def attribute_writer_hash
107
+ @attr_writer_hash ||= map_writers
108
+ end
109
+
110
+ # Marks each of this annotation class's non-owner domain attributes as a dependent.
111
+ def add_dependent_attributes
112
+ domain_attributes.each_pair do |attr, attr_md|
113
+ next if attr == @proxy_attribute or attr_md.dependent? or attr_md.owner? or not attr_md.declarer == self
114
+ logger.debug { "Adding annotation #{qp} #{attr} attribute as a dependent..." }
115
+ add_dependent_attribute(attr)
116
+ end
117
+ end
118
+
119
+ # Recurses the dependency hierarchy to this annotation class's dependents in a
120
+ # breadth-first manner.
121
+ #
122
+ # @param [<CaRuby::AttributeMetadata>] the visited attributes
123
+ def add_dependent_attribute_closure(path=[])
124
+ return if path.include?(self)
125
+ attrs = dependent_attributes(false)
126
+ return if attrs.empty?
127
+ logger.debug { "Adding #{qp} dependents..." }
128
+ # add breadth-first dependencies
129
+ dependent_attributes(false).each_pair do |attr, attr_md|
130
+ attr_md.type.add_dependent_attributes
131
+ end
132
+ # recurse to dependents
133
+ dependent_attributes(false).each_metadata do |attr_md|
134
+ klass = attr_md.type
135
+ path.push(klass)
136
+ klass.add_dependent_attribute_closure(path)
137
+ path.pop
138
+ end
139
+ logger.debug { "Added #{qp} dependents #{dependent_attributes(false).qp}." }
140
+ end
141
+
142
+ private
143
+
144
+ # Infers this annotation class inverses attributes. A domain attribute is
145
+ # recognized as an inverse according to the {ResourceInverse#detect_inverse_attribute}
146
+ # criterion. Annotation dependencies are not specified in a configuration and
147
+ # follow the naming convention described in {ResourceInverse#detect_inverse_attribute}.
148
+ def detect_inverses
149
+ domain_attributes.each do |attr|
150
+ attr_md = attribute_metadata(attr)
151
+ next if attr_md.inverse
152
+ inverse = attr_md.type.detect_inverse_attribute(self)
153
+ if inverse then set_attribute_inverse(attr, inverse) end
154
+ end
155
+ end
156
+
157
+ # Saves the annotations referenced by the given annotation attribute.
158
+ #
159
+ # @param annotation (see #save_annotation)
160
+ # @param [Symbol] attribute the attribute to save
161
+ def save_dependent_attribute(annotation, attribute)
162
+ annotation.send(attribute).enumerate do |ref|
163
+ logger.debug { "Saving #{annotation} #{attribute} dependent #{ref.qp}..." }
164
+ writer(attribute).save(ref)
165
+ end
166
+ end
167
+
168
+ # @param [Symbol] attribute the annotation attribute
169
+ # @return [AnnotationWriter] the attribute writer for instances of this class
170
+ def writer(attribute)
171
+ attribute_writer_hash[attribute]
172
+ end
173
+
174
+ # @return [{Symbol => Annotation::ReferenceWriter}] the annotation attribute => writer hash
175
+ def map_writers
176
+ hash = {}
177
+ cascaded_attributes.each_pair do |attr, attr_md|
178
+ # skip attributes defined in a superclass
179
+ next unless attr_md.declarer == self
180
+ hash[attr] = Annotation::ReferenceWriter.new(@entity_id, attr_md)
181
+ end
182
+ # If the superclass is also an annotation, then form the union of its writers with the local writers.
183
+ superclass < Annotation && superclass.primary? ? hash + superclass.attribute_writer_hash : hash
184
+ end
185
+
186
+ def obtain_proxy_attribute(proxy)
187
+ # parent proxy is reserved for RadiationTherapy use case described in ParticipantTest.
188
+ # TODO - either support this use case of delete the parent proxy call
189
+ detect_proxy_attribute(proxy) or create_proxy_attribute(proxy) or parent_proxy_attribute
190
+ end
191
+
192
+ def parent_proxy_attribute
193
+ superclass.proxy_attribute if superclass < Annotation
194
+ end
195
+
196
+ # @return [Symbol] the annotation -> proxy attribute
197
+ def detect_proxy_attribute(proxy)
198
+ attr_md = detect_proxy_attribute_metadata(proxy)
199
+ if attr_md then
200
+ attr_md.type = proxy.hook
201
+ logger.debug { "Reset #{qp} #{attr_md} attribute type from the proxy class #{proxy} to the hook class #{proxy.hook}" }
202
+ end
203
+ attr_md
204
+ end
205
+
206
+ # @return [Symbol] the annotation -> proxy attribute
207
+ def detect_proxy_attribute_metadata(proxy)
208
+ domain_attributes.each_metadata { |attr_md| return attr_md if attr_md.type == proxy }
209
+ nil
210
+ end
211
+
212
+ # @param [Class] proxy the domain module {Annotation::ProxyClass}
213
+ # @return [Symbol] the new annotation -> proxy attribute
214
+ def create_proxy_attribute(proxy)
215
+ # the proxy attribute symbol
216
+ attr = proxy.name.demodulize.underscore.to_sym
217
+ logger.debug { "Creating primary annotation #{qp} proxy attribute #{attr}..." }
218
+ # make the attribute
219
+ attr_accessor(attr)
220
+ # Add the attribute. Setting the saved flag ensures that the save template passed to
221
+ # the annotation service includes a reference to the hook object. This in turn allows
222
+ # the annotation service to call the integration service to associate the annotation
223
+ # to the hook object.
224
+ add_attribute(attr, proxy.hook, :saved)
225
+ # make the inverse proxy -> annotation dependent attribute
226
+ proxy.create_annotation_attribute(self, attr)
227
+ attr
228
+ end
229
+
230
+ # Wraps the proxy owner attribute with a proxy <-> hook converter as follows:
231
+ # * the reader method is redefined to convert a proxy to its hook
232
+ # * the writer method is redefined to convert a hook argument to its proxy
233
+ #
234
+ # @param [Class] klass the hook class
235
+ def convert_hook_to_proxy(klass)
236
+ # the proxy reference attribute
237
+ attr_md = attribute_metadata(@proxy_attribute)
238
+ # wrap the proxy reader with a proxy => hook converter
239
+ convert_proxy_reader_result_to_hook(attr_md.reader)
240
+ # the proxy => hook attribute metadata
241
+ pxy_hook_attr_md = domain_module.proxy.attribute_metadata(:hook)
242
+ # the hook => proxy attribute
243
+ hook_pxy_attr = pxy_hook_attr_md.inverse
244
+ # wrap the proxy writer with a hook -> proxy converter
245
+ convert_proxy_writer_hook_argument_to_proxy(attr_md.writer, klass, hook_pxy_attr)
246
+ end
247
+
248
+ # Wraps the proxy reader method to convert a proxy to its hook.
249
+ #
250
+ # @param [Symbol] reader the proxy reader method
251
+ def convert_proxy_reader_result_to_hook(reader)
252
+ redefine_method(reader) do |old_mth|
253
+ lambda do
254
+ pxy = send(old_mth)
255
+ pxy.hook if pxy
256
+ end
257
+ end
258
+ logger.debug { "Redefined the #{qp} #{reader} reader method to convert a proxy parameter to its hook object." }
259
+ end
260
+
261
+ # Wraps the proxy writer method to convert a hook argument to its proxy.
262
+ #
263
+ # @param [Symbol] writer the proxy writer method
264
+ # @param klass (see #convert_hook_to_proxy)
265
+ # @param [Symbol] inverse the hook => proxy attribute
266
+ def convert_proxy_writer_hook_argument_to_proxy(writer, klass, inverse)
267
+ redefine_method(writer) do |old_mth|
268
+ lambda do |value|
269
+ # Convert the parameter from a hook to a proxy, if necessary.
270
+ pxy = klass === value ? value.send(inverse) : value
271
+ send(old_mth, pxy)
272
+ end
273
+ end
274
+ logger.debug { "Redefined the #{klass.qp} #{inverse} proxy writer #{writer} to convert a hook #{klass.qp} parameter to the hook proxy." }
275
+ end
276
+ end
277
+ end