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.
- data/History.txt +4 -0
- data/LEGAL +5 -0
- data/LICENSE +22 -0
- data/README.md +44 -0
- data/bin/crtdump +31 -0
- data/bin/crtexample +18 -0
- data/bin/crtextract +47 -0
- data/bin/crtmigrate +17 -0
- data/bin/crtsmoke +27 -0
- data/examples/galena/README.md +53 -0
- data/examples/galena/bin/migrate.rb +42 -0
- data/examples/galena/bin/seed.rb +43 -0
- data/examples/galena/conf/extract/simple_fields.yaml +4 -0
- data/examples/galena/conf/migration/filter_fields.yaml +7 -0
- data/examples/galena/conf/migration/filter_migration.yaml +9 -0
- data/examples/galena/conf/migration/frozen_fields.yaml +11 -0
- data/examples/galena/conf/migration/frozen_migration.yaml +9 -0
- data/examples/galena/conf/migration/general_fields.yaml +42 -0
- data/examples/galena/conf/migration/general_migration.yaml +9 -0
- data/examples/galena/conf/migration/simple_fields.yaml +30 -0
- data/examples/galena/conf/migration/simple_migration.yaml +7 -0
- data/examples/galena/conf/migration/small_fields.yaml +24 -0
- data/examples/galena/conf/migration/small_migration.yaml +9 -0
- data/examples/galena/data/filter.csv +1 -0
- data/examples/galena/data/frozen.csv +1 -0
- data/examples/galena/data/general.csv +1 -0
- data/examples/galena/data/minimal.csv +1 -0
- data/examples/galena/data/simple.csv +1 -0
- data/examples/galena/data/small.csv +1 -0
- data/examples/galena/doc/CaTissue.html +93 -0
- data/examples/galena/doc/CaTissue/CollectionProtocolRegistration.html +181 -0
- data/examples/galena/doc/CaTissue/Participant.html +241 -0
- data/examples/galena/doc/CaTissue/SpecimenCollectionGroup.html +190 -0
- data/examples/galena/doc/CaTissue/StorageContainer.html +179 -0
- data/examples/galena/doc/CaTissue/TissueSpecimen.html +320 -0
- data/examples/galena/doc/Galena.html +290 -0
- data/examples/galena/doc/Galena/Seed.html +203 -0
- data/examples/galena/doc/Galena/Seed/Defaults.html +646 -0
- data/examples/galena/doc/_index.html +188 -0
- data/examples/galena/doc/class_list.html +36 -0
- data/examples/galena/doc/css/common.css +1 -0
- data/examples/galena/doc/css/full_list.css +53 -0
- data/examples/galena/doc/css/style.css +307 -0
- data/examples/galena/doc/file.README.html +108 -0
- data/examples/galena/doc/file_list.html +38 -0
- data/examples/galena/doc/frames.html +13 -0
- data/examples/galena/doc/index.html +108 -0
- data/examples/galena/doc/js/app.js +202 -0
- data/examples/galena/doc/js/full_list.js +149 -0
- data/examples/galena/doc/js/jquery.js +154 -0
- data/examples/galena/doc/method_list.html +179 -0
- data/examples/galena/doc/top-level-namespace.html +112 -0
- data/examples/galena/lib/README.html +33 -0
- data/examples/galena/lib/galena.rb +8 -0
- data/examples/galena/lib/galena/cli/seed.rb +43 -0
- data/examples/galena/lib/galena/migration/filter_shims.rb +43 -0
- data/examples/galena/lib/galena/migration/frozen_shims.rb +54 -0
- data/examples/galena/lib/galena/seed/defaults.rb +97 -0
- data/lib/catissue.rb +26 -0
- data/lib/catissue/cli/command.rb +51 -0
- data/lib/catissue/cli/example.rb +31 -0
- data/lib/catissue/cli/migrate.rb +60 -0
- data/lib/catissue/cli/smoke.rb +45 -0
- data/lib/catissue/database.rb +451 -0
- data/lib/catissue/database/annotation/annotatable_service.rb +25 -0
- data/lib/catissue/database/annotation/annotation_service.rb +79 -0
- data/lib/catissue/database/annotation/annotator.rb +84 -0
- data/lib/catissue/database/annotation/entity_manager.rb +10 -0
- data/lib/catissue/database/annotation/integration_service.rb +87 -0
- data/lib/catissue/database/controlled_value_finder.rb +43 -0
- data/lib/catissue/database/controlled_values.rb +162 -0
- data/lib/catissue/domain/abstract_domain_object.rb +8 -0
- data/lib/catissue/domain/abstract_position.rb +22 -0
- data/lib/catissue/domain/abstract_specimen.rb +288 -0
- data/lib/catissue/domain/abstract_specimen_collection_group.rb +25 -0
- data/lib/catissue/domain/address.rb +13 -0
- data/lib/catissue/domain/cancer_research_group.rb +11 -0
- data/lib/catissue/domain/capacity.rb +34 -0
- data/lib/catissue/domain/check_in_check_out_event_parameter.rb +19 -0
- data/lib/catissue/domain/collection_event_parameters.rb +13 -0
- data/lib/catissue/domain/collection_protocol.rb +177 -0
- data/lib/catissue/domain/collection_protocol_event.rb +108 -0
- data/lib/catissue/domain/collection_protocol_registration.rb +108 -0
- data/lib/catissue/domain/consent_tier_response.rb +13 -0
- data/lib/catissue/domain/consent_tier_status.rb +29 -0
- data/lib/catissue/domain/container.rb +234 -0
- data/lib/catissue/domain/container_position.rb +21 -0
- data/lib/catissue/domain/container_type.rb +131 -0
- data/lib/catissue/domain/department.rb +13 -0
- data/lib/catissue/domain/disposal_event_parameters.rb +13 -0
- data/lib/catissue/domain/embedded_event_parameters.rb +10 -0
- data/lib/catissue/domain/external_identifier.rb +22 -0
- data/lib/catissue/domain/frozen_event_parameters.rb +10 -0
- data/lib/catissue/domain/institution.rb +13 -0
- data/lib/catissue/domain/new_specimen_array_order_item.rb +35 -0
- data/lib/catissue/domain/order_details.rb +25 -0
- data/lib/catissue/domain/participant.rb +138 -0
- data/lib/catissue/domain/participant_medical_identifier.rb +38 -0
- data/lib/catissue/domain/password.rb +11 -0
- data/lib/catissue/domain/race.rb +11 -0
- data/lib/catissue/domain/received_event_parameters.rb +25 -0
- data/lib/catissue/domain/scg_event_parameters.rb +11 -0
- data/lib/catissue/domain/site.rb +30 -0
- data/lib/catissue/domain/specimen.rb +456 -0
- data/lib/catissue/domain/specimen_array.rb +47 -0
- data/lib/catissue/domain/specimen_array_content.rb +19 -0
- data/lib/catissue/domain/specimen_array_type.rb +20 -0
- data/lib/catissue/domain/specimen_characteristics.rb +20 -0
- data/lib/catissue/domain/specimen_collection_group.rb +412 -0
- data/lib/catissue/domain/specimen_event_parameters.rb +111 -0
- data/lib/catissue/domain/specimen_position.rb +38 -0
- data/lib/catissue/domain/specimen_protocol.rb +34 -0
- data/lib/catissue/domain/specimen_requirement.rb +143 -0
- data/lib/catissue/domain/storage_container.rb +204 -0
- data/lib/catissue/domain/storage_type.rb +82 -0
- data/lib/catissue/domain/transfer_event_parameters.rb +53 -0
- data/lib/catissue/domain/user.rb +100 -0
- data/lib/catissue/extract/command.rb +31 -0
- data/lib/catissue/extract/delta.rb +62 -0
- data/lib/catissue/extract/extractor.rb +99 -0
- data/lib/catissue/migration/migrator.rb +101 -0
- data/lib/catissue/migration/shims.rb +108 -0
- data/lib/catissue/migration/uniquify.rb +111 -0
- data/lib/catissue/resource.rb +84 -0
- data/lib/catissue/util/controlled_value.rb +29 -0
- data/lib/catissue/util/location.rb +116 -0
- data/lib/catissue/util/log.rb +30 -0
- data/lib/catissue/util/person.rb +31 -0
- data/lib/catissue/util/position.rb +54 -0
- data/lib/catissue/util/storable.rb +34 -0
- data/lib/catissue/util/storage_type_holder.rb +30 -0
- data/lib/catissue/version.rb +7 -0
- metadata +212 -0
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'caruby/util/collection'
|
2
|
+
require 'caruby/util/partial_order'
|
3
|
+
require 'catissue/util/storage_type_holder'
|
4
|
+
|
5
|
+
module CaTissue
|
6
|
+
# import the Java class
|
7
|
+
java_import('edu.wustl.catissuecore.domain.StorageType')
|
8
|
+
|
9
|
+
# The StorageType domain class.
|
10
|
+
class StorageType
|
11
|
+
include StorageTypeHolder, PartialOrder, Resource
|
12
|
+
|
13
|
+
add_attribute_aliases(:default_temperature => :default_temprature_in_centigrade)
|
14
|
+
|
15
|
+
qualify_attribute(:holds_storage_types, :fetched)
|
16
|
+
|
17
|
+
qualify_attribute(:holds_specimen_array_types, :fetched)
|
18
|
+
|
19
|
+
set_attribute_type(:holds_specimen_array_types, CaTissue::SpecimenArrayType)
|
20
|
+
|
21
|
+
set_attribute_type(:holds_specimen_classes, String)
|
22
|
+
|
23
|
+
set_attribute_type(:holds_storage_types, CaTissue::StorageType)
|
24
|
+
|
25
|
+
# @return StorageContainer
|
26
|
+
def container_class
|
27
|
+
CaTissue::StorageContainer
|
28
|
+
end
|
29
|
+
|
30
|
+
alias :add_child :add_child_type
|
31
|
+
|
32
|
+
alias :add :add_child_type
|
33
|
+
|
34
|
+
alias :<< :add_child_type
|
35
|
+
|
36
|
+
# @return whether this StorageType can hold a child of the given Storable storable type
|
37
|
+
def can_hold_child?(storable)
|
38
|
+
child_types.include?(storable.storable_type)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Returns a StorageType array from this StorageType to a descendant StorageType which can
|
42
|
+
# hold the given storable, or nil if no such path exists.
|
43
|
+
def path_to(storable)
|
44
|
+
return [self] if can_hold_child?(storable)
|
45
|
+
path = holds_storage_types.detect_value { |child| child.path_to(storable) }
|
46
|
+
return path.unshift(self) if path
|
47
|
+
end
|
48
|
+
|
49
|
+
# @return the closure of ContainerTypes held by this ContainerType, including self
|
50
|
+
def closure
|
51
|
+
cts = [self]
|
52
|
+
child_types.each { |ct| cts.concat(ct.closure) if StorageType === ct }
|
53
|
+
cts
|
54
|
+
end
|
55
|
+
|
56
|
+
# @return whether this StorageType has a non-nil name equal to the other name or is {#equal?} to this StorageType
|
57
|
+
#
|
58
|
+
# This method is a work-around for caTissue Bug #70: StorageType and non-StorageType are equal.
|
59
|
+
def ==(other)
|
60
|
+
equal?(other) or (StorageType === other and name and name == other.name)
|
61
|
+
end
|
62
|
+
|
63
|
+
alias :eql? :==
|
64
|
+
|
65
|
+
# Returns -1, 0, or 1 if self is contained in, contains or the same as the other
|
66
|
+
# StorageType, resp.
|
67
|
+
def <=>(other)
|
68
|
+
raise TypeError.new("Can't compare #{qp} to #{other}") unless StorageType === self
|
69
|
+
return 0 if eql?(other)
|
70
|
+
return 1 if holds_storage_types.detect { |child| child >= other }
|
71
|
+
-1 if other > self
|
72
|
+
end
|
73
|
+
|
74
|
+
# Overrides the Java StorageType hashCode to make the hash insensitive to identifier assignment.
|
75
|
+
#
|
76
|
+
# @see #==
|
77
|
+
def hash
|
78
|
+
# caTissue alert - bad caTissue API hashCode leads to ugly cascading errors when using a CP in a Set
|
79
|
+
(object_id * 31) + 17
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'caruby/util/validation'
|
2
|
+
require 'catissue/util/location'
|
3
|
+
|
4
|
+
module CaTissue
|
5
|
+
# import the Java class
|
6
|
+
java_import('edu.wustl.catissuecore.domain.TransferEventParameters')
|
7
|
+
|
8
|
+
# The +caTissue+ TransferEventParameters class is augmented with zero-based
|
9
|
+
# +from_row+, +from_column+, +to_row+ and +to_column+ methods wrapping the corresponding one-based dimension attributes.
|
10
|
+
class TransferEventParameters
|
11
|
+
include Resource
|
12
|
+
|
13
|
+
add_attribute_aliases(:from_container => :from_storage_container, :to_container => :to_storage_container)
|
14
|
+
|
15
|
+
# column and row are the zero-offset counterparts of position_dimension_one and position_dimension_two, resp.
|
16
|
+
offset_attribute(:from_column => :from_position_dimension_one, :from_row => :from_position_dimension_two,
|
17
|
+
:to_column => :to_position_dimension_one, :to_row => :to_position_dimension_two)
|
18
|
+
|
19
|
+
add_mandatory_attributes(:to_container, :to_position_dimension_one, :to_position_dimension_two)
|
20
|
+
|
21
|
+
# Returns the from Location.
|
22
|
+
def from
|
23
|
+
Location.new(:in => from_container, :at => [from_column, from_row]) if from_container
|
24
|
+
end
|
25
|
+
|
26
|
+
# Sets the from Location.
|
27
|
+
def from=(location)
|
28
|
+
if location then
|
29
|
+
self.from_container = location.container
|
30
|
+
self.from_row = location.row
|
31
|
+
self.from_column = location.column
|
32
|
+
end
|
33
|
+
location
|
34
|
+
end
|
35
|
+
|
36
|
+
add_attribute(:from)
|
37
|
+
|
38
|
+
# Returns the to Location.
|
39
|
+
def to
|
40
|
+
Location.new(:in => to_container, :at => [to_column, to_row]) if to_container
|
41
|
+
end
|
42
|
+
|
43
|
+
# Sets the to Location.
|
44
|
+
def to=(location)
|
45
|
+
if location.nil? then raise ArgumentError.new("Specimen cannot be moved to an empty location") end
|
46
|
+
self.to_container = location.container
|
47
|
+
self.to_row = location.row
|
48
|
+
self.to_column = location.column
|
49
|
+
end
|
50
|
+
|
51
|
+
add_attribute(:to)
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'caruby/util/validation'
|
2
|
+
require 'catissue/resource'
|
3
|
+
require 'catissue/util/person'
|
4
|
+
|
5
|
+
module CaTissue
|
6
|
+
# import the Java class
|
7
|
+
java_import('edu.wustl.catissuecore.domain.User')
|
8
|
+
|
9
|
+
# The User domain class.
|
10
|
+
class User
|
11
|
+
include Person, Resource
|
12
|
+
|
13
|
+
# caTissue alert - work-around for caTissue Bug #66 - Client missing CSException class et al.
|
14
|
+
# caTissue User class initializes roleId to "", which triggers a client exception on subsequent
|
15
|
+
# getRoleId call. Use a private variable instead and bypass getRoleId.
|
16
|
+
def role_id
|
17
|
+
# TODO - uncomment following and get rid of @role_id i.v. when bug is fixed.
|
18
|
+
#value = send(old_method)
|
19
|
+
#return if value == ''
|
20
|
+
#value.to_i
|
21
|
+
@role_id
|
22
|
+
end
|
23
|
+
|
24
|
+
# Sets the role id to the given value, which can be either a String or an Integer.
|
25
|
+
# An empty or zero value is converted to nil.
|
26
|
+
#
|
27
|
+
# caTissue alert - caTissue API roleId is a String although the intended value domain is the
|
28
|
+
# integer csm_role.identifier.
|
29
|
+
def role_id=(value)
|
30
|
+
# value as an integer (nil is zero)
|
31
|
+
value_i = value.to_i
|
32
|
+
# set the Bug #66 work-around i.v.
|
33
|
+
@role_id = value_i.zero? ? nil : value_i
|
34
|
+
# value as a String (if non-nil)
|
35
|
+
value_s = @role_id.to_s if @role_id
|
36
|
+
# call Java with a String
|
37
|
+
setRoleId(value_s)
|
38
|
+
end
|
39
|
+
|
40
|
+
# make the convenience Person name a first-class attribute
|
41
|
+
add_attribute(:name)
|
42
|
+
|
43
|
+
# caTissue alert - clinical study is unsupported by caTissue.
|
44
|
+
remove_attribute(:clinical_studies)
|
45
|
+
|
46
|
+
# clarify that collection_protocols is a coordinator -> protocol association.
|
47
|
+
# make assigned protocol and site attribute names consistent.
|
48
|
+
add_attribute_aliases(:coordinated_protocols => :collection_protocols, :protocols => :assigned_protocols, :assigned_sites => :sites)
|
49
|
+
|
50
|
+
# login_name is a database unique key.
|
51
|
+
set_secondary_key_attributes(:login_name)
|
52
|
+
|
53
|
+
# email_address is expected to be unique, and is enforced by the caTissue business logic.
|
54
|
+
set_alternate_key_attributes(:email_address)
|
55
|
+
|
56
|
+
# Set defaults as follows:
|
57
|
+
# * page_of is the value set when creating a User in the GUI
|
58
|
+
# * role id is 7 = Scientist (public)
|
59
|
+
# * initial password is 'changeMe1'
|
60
|
+
add_attribute_defaults(:activity_status => 'Active', :page_of => 'pageOfUserAdmin', :role_id => 7, :new_password => 'changeMe1')
|
61
|
+
|
62
|
+
# caTissue alert - obscure GUI artifact User page_of attribute is insinuated into the data
|
63
|
+
# layer as a required attribute. Work-around is to simulate the GUI with a default value.
|
64
|
+
add_mandatory_attributes(:activity_status, :address, :cancer_research_group, :department,
|
65
|
+
:email_address, :first_name, :institution, :last_name, :page_of, :role_id)
|
66
|
+
|
67
|
+
add_dependent_attribute(:address)
|
68
|
+
|
69
|
+
add_dependent_attribute(:passwords)
|
70
|
+
|
71
|
+
set_attribute_inverse(:protocols, :assigned_protocol_users)
|
72
|
+
|
73
|
+
set_attribute_inverse(:sites, :assigned_site_users)
|
74
|
+
|
75
|
+
qualify_attribute(:cancer_research_group, :fetched)
|
76
|
+
|
77
|
+
qualify_attribute(:department, :fetched)
|
78
|
+
|
79
|
+
qualify_attribute(:institution, :fetched)
|
80
|
+
|
81
|
+
qualify_attribute(:protocols, :saved, :fetched)
|
82
|
+
|
83
|
+
qualify_attribute(:sites, :saved)
|
84
|
+
|
85
|
+
qualify_attribute(:page_of, :unfetched)
|
86
|
+
|
87
|
+
qualify_attribute(:new_password, :unfetched)
|
88
|
+
|
89
|
+
qualify_attribute(:role_id, :unfetched)
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
# By default, the emailAddress is the same as the loginName.
|
94
|
+
def add_defaults_local
|
95
|
+
super
|
96
|
+
self.login_name ||= email_address
|
97
|
+
self.email_address ||= login_name
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'catissue/cli/command'
|
2
|
+
require 'catissue/extract/delta'
|
3
|
+
require 'catissue/extract/extractor'
|
4
|
+
|
5
|
+
module CaTissue
|
6
|
+
# ExtractCommand extracts target CaTissue domain class objects whose modification date is
|
7
|
+
# within a time interval to a CSV file based on a CSV mapping file.
|
8
|
+
class ExtractCommand < CaTissue::Command
|
9
|
+
# Creates a new ExtractCommand.
|
10
|
+
# The delta range is given by the required :since option and optional :before option.
|
11
|
+
# The default before value is the current DateTime. These are used to build a Delta
|
12
|
+
# which is passed to {CaRuby::Command#initialize} as the :ids option.
|
13
|
+
# The :log option specifies a log file.
|
14
|
+
# Other supported options are described in {Extractor#initialize}.
|
15
|
+
def initialize
|
16
|
+
# prep the options
|
17
|
+
since = opts.delete(:since)
|
18
|
+
raise ArgumentError.new("Missing required beginning of date selection range option") unless since
|
19
|
+
before = opts.delete(:before) || DateTime.now
|
20
|
+
# the data acquirer
|
21
|
+
opts[:ids] = Delta.new(@target, since, before)
|
22
|
+
# make the command
|
23
|
+
super(opts)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Starts an Extractor with the command-line options.
|
27
|
+
def run
|
28
|
+
super { |opts| Extractor.new(opts) }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'date'
|
2
|
+
require 'caruby/csv/csvio'
|
3
|
+
require 'caruby/util/log'
|
4
|
+
require 'caruby/util/collection'
|
5
|
+
require 'caruby/util/pretty_print'
|
6
|
+
require 'caruby/database/sql_executor'
|
7
|
+
|
8
|
+
module CaTissue
|
9
|
+
# Delta determines caTissue objects which changed within a time interval.
|
10
|
+
class Delta
|
11
|
+
include Enumerable
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
SQL_FILE = File.join(File.dirname(__FILE__), '..', '..', '..', 'sql', 'delta.sql')
|
16
|
+
|
17
|
+
public
|
18
|
+
|
19
|
+
# Creates a new Delta for objects of the given target type which changed
|
20
|
+
# at or after the since Date and earlier but not at the before Date.
|
21
|
+
# The default before Date is now.
|
22
|
+
def initialize(target, since, before=nil)
|
23
|
+
# convert the required target to a CaTissue class if necessary
|
24
|
+
@matcher = create_table_regex(target)
|
25
|
+
@since = since
|
26
|
+
@before = before || DateTime.now
|
27
|
+
end
|
28
|
+
|
29
|
+
# Calls the given block on each caTissue identifier satisfying the delta condition.
|
30
|
+
# This method submits the delta SQL and filters the result on the target class.
|
31
|
+
# This method always submits the query; the caller is responsible for preserving
|
32
|
+
# the result if necessary using {#to_a}.
|
33
|
+
def each(&block)
|
34
|
+
execute_query(&block)
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
# Returns the result of running the delta SQL on the target CaTissue domain class.
|
40
|
+
def execute_query
|
41
|
+
sql = File.open(SQL_FILE) { |file| file.read }
|
42
|
+
logger.debug { "Executing identifier change set selection range #{@since} - #{@before}, SQL:\n#{sql}" }
|
43
|
+
CaRuby::SQLExecutor.new(CaTissue.access_properties).execute do |dbh|
|
44
|
+
dbh.select_all(sql, @since, @before) do |row|
|
45
|
+
table, identifier = row
|
46
|
+
yield identifier.to_i if table =~ @matcher
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Returns the table match REs for the given target class.
|
52
|
+
def create_table_regex(target)
|
53
|
+
# The class => table RE hash. Make this hash rather than defining a constant in order to enable
|
54
|
+
# logging before touching a domain class.
|
55
|
+
@cls_tbl_hash ||= {
|
56
|
+
CaTissue::Specimen => /catissue_[[:alpha:]]+_specimen/i,
|
57
|
+
CaTissue::SpecimenCollectionGroup => /catissue_specimen_coll_group/i
|
58
|
+
}
|
59
|
+
@cls_tbl_hash.detect_value { |klass, table| table if target <= klass }
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require 'caruby/util/properties'
|
2
|
+
require 'caruby/csv/csv_mapper'
|
3
|
+
require 'caruby/csv/csvio'
|
4
|
+
|
5
|
+
module CaTissue
|
6
|
+
# Extracts caTissue objects.
|
7
|
+
class Extractor
|
8
|
+
include Enumerable
|
9
|
+
|
10
|
+
# The default name of this migrator.
|
11
|
+
DEF_NAME = 'caTissue Extractor'
|
12
|
+
|
13
|
+
# The extract output file
|
14
|
+
attr_reader :output
|
15
|
+
|
16
|
+
# Creates a new Extractor with the given options.
|
17
|
+
#
|
18
|
+
# @option options [String] :file the extract configuration file name
|
19
|
+
# @option options [String] :name name of this Migrator (default is +caTissue Migrator+)
|
20
|
+
# @option options [String] :output optional output CSV file name
|
21
|
+
# @option options [String] :target required target domain class or class name
|
22
|
+
# @option options [String] :ids the database identifiers of the target objects to extract
|
23
|
+
# @option options [String] :log log file (default +log/extract.log+)
|
24
|
+
def initialize(options={})
|
25
|
+
conf_file = options.delete(:file)
|
26
|
+
if conf_file then
|
27
|
+
CaRuby::Properties.new(conf_file, :array => [:shims]).each { |key, value| options[key.to_sym] ||= value }
|
28
|
+
end
|
29
|
+
# tailor the options
|
30
|
+
name = options[:name] || DEF_NAME
|
31
|
+
super(name)
|
32
|
+
@ids = options[:ids]
|
33
|
+
raise ArgumentError.new("Missing required ids option") unless @ids
|
34
|
+
# convert the required target to a CaTissue class if necessary
|
35
|
+
@target = target_class_from_option(options[:target])
|
36
|
+
@target ||= CaTissue::Specimen
|
37
|
+
# the CSV output file
|
38
|
+
@output = options[:output]
|
39
|
+
raise ArgumentError.new("Missing required extract output file option") unless @output
|
40
|
+
# the field mapping configuration
|
41
|
+
fld_conf = options[:mapping]
|
42
|
+
mapper = CaRuby::CsvMapper.new(fld_conf, @target, @output, :mode => "w")
|
43
|
+
@csvio = mapper.csvio
|
44
|
+
@fld_path_hash = {}
|
45
|
+
mapper.paths.each do |path|
|
46
|
+
fld = mapper.header(path)
|
47
|
+
# the path node is either an attribute symbol or attribute metadata;
|
48
|
+
# if metadata, then use the reader method.
|
49
|
+
@fld_path_hash[fld] = path.map { |attr_or_md| CaRuby::AttributeMetadata === attr_or_md ? attr_or_md.reader : attr_or_md }
|
50
|
+
end
|
51
|
+
logger.debug { "Extract field => path map: #{@fld_path_hash.transform { |path| path.join('.') }.pp_s}" }
|
52
|
+
end
|
53
|
+
|
54
|
+
# Exports the selected target records from the database to the output file.
|
55
|
+
def run
|
56
|
+
begin
|
57
|
+
extract { |obj| write(obj) }
|
58
|
+
ensure
|
59
|
+
@csvio.close
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Executes this extractor CSV file and calls the block given to this method on each target domain object.
|
64
|
+
def extract
|
65
|
+
logger.debug { "Found #{@ids.size} extract targets." }
|
66
|
+
CaTissue::Database.instance.open do
|
67
|
+
@ids.each do |identifier|
|
68
|
+
obj = @target.new(:identifier => identifier)
|
69
|
+
logger.debug { "Finding extract target #{obj}..." }
|
70
|
+
if obj.find then
|
71
|
+
logger.debug { "Extractor fetched #{obj}." }
|
72
|
+
yield obj
|
73
|
+
else
|
74
|
+
logger.debug { "Extract target #{obj} not found." }
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
alias :each :extract
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
def write(obj)
|
85
|
+
# collect the field values in order by resolving the path on obj
|
86
|
+
rec = @csvio.headers.map do |fld|
|
87
|
+
obj.path_value(@fld_path_hash[fld])
|
88
|
+
end
|
89
|
+
@csvio << rec
|
90
|
+
logger.debug { "Extractor wrote #{obj} to CSV output file." }
|
91
|
+
end
|
92
|
+
|
93
|
+
def target_class_from_option(option)
|
94
|
+
return if option.nil?
|
95
|
+
return option if Class === option
|
96
|
+
CaTissue.const_get(option)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'caruby/util/properties'
|
2
|
+
require 'caruby/migration/migrator'
|
3
|
+
require 'catissue/resource'
|
4
|
+
require 'catissue/database/controlled_values'
|
5
|
+
require 'catissue/database/controlled_value_finder'
|
6
|
+
|
7
|
+
module CaTissue
|
8
|
+
# Even though Migratable is included in CaRuby::Resource, the Migratable methods
|
9
|
+
# are not appended to a CaTissue Resource class since the class already includes
|
10
|
+
# CaRuby::Resource. In Ruby, A include B followed by B include C does not imply
|
11
|
+
# that A includes C. Therefore, notify CaTissue that its mixin has changed and each
|
12
|
+
# loaded class must reinclude the mixin.
|
13
|
+
CaTissue.mixin_changed
|
14
|
+
|
15
|
+
# Migrates a CSV extract to caTissue. See the {#initialize} documentation for usage options.
|
16
|
+
#
|
17
|
+
# See the Galena Cancer Center Tissue Bank Migration Example for further information
|
18
|
+
# about how the options tailor migration, esp. the use of the field mappings and shims.
|
19
|
+
class Migrator < CaRuby::Migrator
|
20
|
+
# The default name of this migrator.
|
21
|
+
NAME = 'caTissue Migrator'
|
22
|
+
|
23
|
+
DEF_CONF_FILE = File.join('conf', 'migration.yaml')
|
24
|
+
|
25
|
+
# The built-in caTissue migration shims.
|
26
|
+
SHIM_FILE = File.join(File.dirname(__FILE__), 'shims.rb')
|
27
|
+
|
28
|
+
# Creates a new Migrator with the given options.
|
29
|
+
#
|
30
|
+
# This migrator must include sufficient information to build a well-formed migration target object.
|
31
|
+
# For example, if the target object is a new SpecimenCollectionGroup, then the migration must also be
|
32
|
+
# able to build that SCG's CollectionProtocolRegistration. The CPR in turn must either exist in the
|
33
|
+
# database or the migration must build a Participant and a CollectionProtocol.
|
34
|
+
#
|
35
|
+
# @option (see CaRuby::Migrator#initialize)
|
36
|
+
def initialize(opts={})
|
37
|
+
# if there is a configuration file, then add config options into the parameter options
|
38
|
+
conf_file = opts.delete(:file)
|
39
|
+
if conf_file then
|
40
|
+
conf = CaRuby::Properties.new(conf_file, :array => [:shims])
|
41
|
+
# add config options but don't override the parameter options
|
42
|
+
opts.merge!(conf, :deep) { |key, oldval, newval| oldval }
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
# TODO - move opt parsing to CaTissue::CLI::Migrate and call that from test cases
|
47
|
+
# Migrate then calls this Migrator with parsed options
|
48
|
+
|
49
|
+
|
50
|
+
# open the log file before building structure
|
51
|
+
log_file = opts[:log]
|
52
|
+
CaRuby::Log.instance.open(log_file, :debug => opts[:debug]) if log_file
|
53
|
+
|
54
|
+
# tailor the options
|
55
|
+
opts[:name] ||= NAME
|
56
|
+
opts[:database] ||= CaTissue::Database.instance
|
57
|
+
# prepend this migrator's shims
|
58
|
+
shims = opts[:shims] ||= []
|
59
|
+
shims.unshift(SHIM_FILE)
|
60
|
+
|
61
|
+
# call the CaRuby::Migrator initializer with the augmented options
|
62
|
+
super
|
63
|
+
|
64
|
+
# the options specific to this CaTissue::Migrator subclass
|
65
|
+
tissue_sites = opts[:tissue_sites]
|
66
|
+
if tissue_sites then
|
67
|
+
CaTissue::SpecimenCharacteristics.tissue_site_cv_finder = ControlledValueFinder.new(:tissue_site, tissue_sites)
|
68
|
+
logger.info("Migrator enabled controlled value lookup.")
|
69
|
+
end
|
70
|
+
diagnoses = opts[:diagnoses]
|
71
|
+
if diagnoses then
|
72
|
+
CaTissue::SpecimenCollectionGroup.diagnosis_cv_finder = ControlledValueFinder.new(:clinical_diagnosis, diagnoses)
|
73
|
+
logger.info("Migrator enabled controlled value lookup.")
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
# Clears the migration protocol CPR and SCG references.
|
80
|
+
# This action frees up memory for the next iteration, thereby ensuring that migration is an
|
81
|
+
# O(1) rather than O(n) operation.
|
82
|
+
def clear(target)
|
83
|
+
pcl = target_protocol(target) || return
|
84
|
+
logger.debug { "Clearing #{pcl.qp} CPR and SCG references..." }
|
85
|
+
pcl.suspend_lazy_loader do
|
86
|
+
pcl.registrations.clear
|
87
|
+
pcl.events.each { |event| event.suspend_lazy_loader { event.specimen_collection_groups.clear } }
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def target_protocol(target)
|
92
|
+
case target
|
93
|
+
when CaTissue::SpecimenCollectionGroup then
|
94
|
+
cpe = target.collection_protocol_event
|
95
|
+
cpe.collection_protocol if cpe
|
96
|
+
when CaTissue::Specimen then
|
97
|
+
target_protocol(target.specimen_collection_group)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|