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.
- data/History.txt +4 -0
- data/bin/crtdump +11 -3
- data/bin/{seed → crtseed} +0 -0
- data/conf/annotation/pathology_scg/AdditionalFinding.hbm.xml +19 -0
- data/conf/annotation/pathology_scg/BasePathologyAnnotation.hbm.xml +260 -0
- data/conf/annotation/pathology_scg/BreastMargin.hbm.xml +21 -0
- data/conf/annotation/pathology_scg/BreastMarginInvolved.hbm.xml +15 -0
- data/conf/annotation/pathology_scg/BreastMarginUninvolved.hbm.xml +14 -0
- data/conf/annotation/pathology_scg/CNSMargin.hbm.xml +19 -0
- data/conf/annotation/pathology_scg/CNSMarginLocation.hbm.xml +14 -0
- data/conf/annotation/pathology_scg/CarcinomaInSituStatus.hbm.xml +14 -0
- data/conf/annotation/pathology_scg/ColorectalLocalExcisionMarginUninvolved.hbm.xml +15 -0
- data/conf/annotation/pathology_scg/ColorectalResectedMarginUninvolved.hbm.xml +22 -0
- data/conf/annotation/pathology_scg/Cytogenetics.hbm.xml +15 -0
- data/conf/annotation/pathology_scg/DeepMelanomaMargin.hbm.xml +16 -0
- data/conf/annotation/pathology_scg/Details.hbm.xml +14 -0
- data/conf/annotation/pathology_scg/DirectExtensionOfTumor.hbm.xml +14 -0
- data/conf/annotation/pathology_scg/DistalMargin.hbm.xml +15 -0
- data/conf/annotation/pathology_scg/DistanceFromAnalVerge.hbm.xml +23 -0
- data/conf/annotation/pathology_scg/DistantMetastasis.hbm.xml +19 -0
- data/conf/annotation/pathology_scg/ExcionalBiopsyMarginUninvolved.hbm.xml +15 -0
- data/conf/annotation/pathology_scg/ExcisionalBiopsyColorectalDeepMargin.hbm.xml +15 -0
- data/conf/annotation/pathology_scg/ExcisionalBiopsyColorectalLateralOrMucosalMargin.hbm.xml +15 -0
- data/conf/annotation/pathology_scg/ExtraprostaticExtension.hbm.xml +20 -0
- data/conf/annotation/pathology_scg/ExtraprostaticExtensionTissueSites.hbm.xml +14 -0
- data/conf/annotation/pathology_scg/GleasonScore.hbm.xml +16 -0
- data/conf/annotation/pathology_scg/HistologicGrade.hbm.xml +16 -0
- data/conf/annotation/pathology_scg/HistologicType.hbm.xml +19 -0
- data/conf/annotation/pathology_scg/HistologicVariantType.hbm.xml +14 -0
- data/conf/annotation/pathology_scg/ImmunoPhenotyping.hbm.xml +16 -0
- data/conf/annotation/pathology_scg/Invasion.hbm.xml +16 -0
- data/conf/annotation/pathology_scg/KidneyMarginLocation.hbm.xml +15 -0
- data/conf/annotation/pathology_scg/KidneyNephrectomyMargin.hbm.xml +19 -0
- data/conf/annotation/pathology_scg/LateralMelanomaMargin.hbm.xml +16 -0
- data/conf/annotation/pathology_scg/LocalExcisionColorectalDeepMargin.hbm.xml +15 -0
- data/conf/annotation/pathology_scg/LocalExcisionColorectalLateralMargin.hbm.xml +20 -0
- data/conf/annotation/pathology_scg/LungResectionMargin.hbm.xml +17 -0
- data/conf/annotation/pathology_scg/LungResectionMarginsUninvolved.hbm.xml +14 -0
- data/conf/annotation/pathology_scg/MacroscopicExtentOfTumor.hbm.xml +14 -0
- data/conf/annotation/pathology_scg/MesentricMargin.hbm.xml +15 -0
- data/conf/annotation/pathology_scg/MetastasisTissueSite.hbm.xml +15 -0
- data/conf/annotation/pathology_scg/Microcalcification.hbm.xml +14 -0
- data/conf/annotation/pathology_scg/NottinghamHistologicScore.hbm.xml +17 -0
- data/conf/annotation/pathology_scg/OtherResectedOrgans.hbm.xml +15 -0
- data/conf/annotation/pathology_scg/PancreasMargin.hbm.xml +20 -0
- data/conf/annotation/pathology_scg/PancreasMarginInvolvedByInvasiveCarcinoma.hbm.xml +15 -0
- data/conf/annotation/pathology_scg/PancreasMarginUninvolvedByInvasiveCarcinoma.hbm.xml +20 -0
- data/conf/annotation/pathology_scg/PathologicalStaging.hbm.xml +16 -0
- data/conf/annotation/pathology_scg/PolypConfiguration.hbm.xml +15 -0
- data/conf/annotation/pathology_scg/PrimaryTumorStage.hbm.xml +14 -0
- data/conf/annotation/pathology_scg/ProstateMarginLocation.hbm.xml +15 -0
- data/conf/annotation/pathology_scg/ProximalMargin.hbm.xml +15 -0
- data/conf/annotation/pathology_scg/RadialMargin.hbm.xml +15 -0
- data/conf/annotation/pathology_scg/RadicalProstatectomyMargin.hbm.xml +20 -0
- data/conf/annotation/pathology_scg/RegionalLymphNode.hbm.xml +19 -0
- data/conf/annotation/pathology_scg/SatelliteNodule.hbm.xml +14 -0
- data/conf/annotation/pathology_scg/SpecimenCollectionGroup.hbm.xml +87 -0
- data/conf/annotation/pathology_scg/SpecimenIntegrity.hbm.xml +15 -0
- data/conf/annotation/pathology_scg/SpecimenSize.hbm.xml +17 -0
- data/conf/annotation/pathology_scg/TissueSide.hbm.xml +14 -0
- data/conf/annotation/pathology_scg/TumorSize.hbm.xml +29 -0
- data/conf/annotation/pathology_scg/TumorTissueSite.hbm.xml +20 -0
- data/conf/annotation/pathology_scg/UninvolvedMelanomaMargin.hbm.xml +15 -0
- data/conf/annotation/pathology_specimen/AdditionalFinding.hbm.xml +19 -0
- data/conf/annotation/pathology_specimen/AdditionalPathologicFinding.hbm.xml +18 -0
- data/conf/annotation/pathology_specimen/Details.hbm.xml +15 -0
- data/conf/annotation/pathology_specimen/GleasonScore.hbm.xml +16 -0
- data/conf/annotation/pathology_specimen/HistologicGrade.hbm.xml +16 -0
- data/conf/annotation/pathology_specimen/HistologicType.hbm.xml +19 -0
- data/conf/annotation/pathology_specimen/HistologicVariantType.hbm.xml +14 -0
- data/conf/annotation/pathology_specimen/Invasion.hbm.xml +16 -0
- data/conf/annotation/pathology_specimen/NottinghamHistologicScore.hbm.xml +17 -0
- data/conf/annotation/pathology_specimen/Specimen.hbm.xml +52 -0
- data/conf/annotation/pathology_specimen/SpecimenBaseSolidTissuePathologyAnnotation.hbm.xml +73 -0
- data/examples/galena/lib/galena/cli/seed.rb +0 -21
- data/examples/galena/lib/galena/migration/frozen_shims.rb +6 -5
- data/examples/galena/lib/galena/seed/defaults.rb +0 -5
- data/{lib → examples/galena/lib}/galena.rb +0 -0
- data/lib/catissue/annotation/annotatable.rb +37 -0
- data/lib/catissue/annotation/annotatable_class.rb +255 -0
- data/lib/catissue/annotation/annotation.rb +49 -0
- data/lib/catissue/annotation/annotation_class.rb +277 -0
- data/lib/catissue/annotation/annotation_module.rb +77 -0
- data/lib/catissue/annotation/hibernate_mapping.rb +46 -0
- data/lib/catissue/annotation/proxy.rb +28 -0
- data/lib/catissue/annotation/proxy_class.rb +68 -0
- data/lib/catissue/cli/migrate.rb +2 -2
- data/lib/catissue/cli/smoke.rb +6 -4
- data/lib/catissue/database/annotation/annotation_service.rb +75 -61
- data/lib/catissue/database/annotation/annotator.rb +17 -76
- data/lib/catissue/database/annotation/entity_facade.rb +265 -0
- data/lib/catissue/database/annotation/id_generator.rb +62 -0
- data/lib/catissue/database/annotation/integration_service.rb +105 -59
- data/lib/catissue/database/annotation/reference_writer.rb +150 -0
- data/lib/catissue/database/controlled_values.rb +12 -12
- data/lib/catissue/database.rb +148 -58
- data/lib/catissue/domain/abstract_specimen.rb +40 -14
- data/lib/catissue/domain/abstract_specimen_collection_group.rb +1 -3
- data/lib/catissue/domain/collection_protocol.rb +13 -5
- data/lib/catissue/domain/collection_protocol_event.rb +1 -14
- data/lib/catissue/domain/consent_tier_response.rb +2 -0
- data/lib/catissue/domain/consent_tier_status.rb +5 -3
- data/lib/catissue/domain/container.rb +14 -10
- data/lib/catissue/domain/container_position.rb +8 -0
- data/lib/catissue/domain/container_type.rb +13 -6
- data/lib/catissue/domain/participant.rb +15 -10
- data/lib/catissue/domain/site.rb +9 -3
- data/lib/catissue/domain/specimen.rb +79 -40
- data/lib/catissue/domain/specimen_array.rb +11 -1
- data/lib/catissue/domain/specimen_collection_group.rb +79 -41
- data/lib/catissue/domain/specimen_event_parameters.rb +5 -8
- data/lib/catissue/domain/specimen_position.rb +0 -2
- data/lib/catissue/domain/specimen_requirement.rb +1 -1
- data/lib/catissue/domain/storage_container.rb +109 -48
- data/lib/catissue/domain/storage_type.rb +1 -1
- data/lib/catissue/migration/migrator.rb +6 -14
- data/lib/catissue/resource.rb +18 -8
- data/lib/catissue/util/position.rb +11 -1
- data/lib/catissue/util/storable.rb +18 -11
- data/lib/catissue/util/storage_type_holder.rb +44 -6
- data/lib/catissue/version.rb +1 -1
- metadata +86 -35
- data/bin/migrate.rb +0 -42
- data/bin/seed.rb +0 -43
- data/examples/galena/doc/CaTissue/Participant.html +0 -241
- data/examples/galena/doc/CaTissue/SpecimenCollectionGroup.html +0 -190
- data/examples/galena/doc/CaTissue/StorageContainer.html +0 -179
- data/examples/galena/doc/CaTissue/TissueSpecimen.html +0 -320
- data/examples/galena/doc/CaTissue.html +0 -93
- data/examples/galena/doc/Galena/Seed/Defaults.html +0 -650
- data/examples/galena/doc/Galena/Seed.html +0 -203
- data/examples/galena/doc/Galena.html +0 -172
- data/examples/galena/doc/_index.html +0 -181
- data/examples/galena/doc/class_list.html +0 -36
- data/examples/galena/doc/css/common.css +0 -1
- data/examples/galena/doc/css/full_list.css +0 -53
- data/examples/galena/doc/css/style.css +0 -307
- data/examples/galena/doc/file.README.html +0 -153
- data/examples/galena/doc/file_list.html +0 -38
- data/examples/galena/doc/frames.html +0 -13
- data/examples/galena/doc/index.html +0 -153
- data/examples/galena/doc/js/app.js +0 -202
- data/examples/galena/doc/js/full_list.js +0 -149
- data/examples/galena/doc/js/jquery.js +0 -154
- data/examples/galena/doc/method_list.html +0 -163
- data/examples/galena/doc/top-level-namespace.html +0 -112
- data/lib/README.html +0 -33
- data/lib/catissue/database/annotation/annotatable_service.rb +0 -25
- data/lib/catissue/database/annotation/entity_manager.rb +0 -10
- data/lib/galena/cli/seed.rb +0 -43
- data/lib/galena/migration/filter_shims.rb +0 -43
- data/lib/galena/migration/frozen_shims.rb +0 -53
- 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
|