jinx-migrate 2.1.1 → 2.1.2
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/Gemfile +1 -1
- data/History.md +4 -0
- data/lib/jinx/csv/csvio.rb +1 -1
- data/lib/jinx/migration/filter.rb +1 -1
- data/lib/jinx/migration/migrator.rb +35 -97
- data/lib/jinx/migration/version.rb +1 -1
- metadata +4 -6
- data/Gemfile.lock +0 -38
data/Gemfile
CHANGED
data/History.md
CHANGED
@@ -1,6 +1,10 @@
|
|
1
1
|
This history lists major release themes. See the GitHub commits (https://github.com/jinx/migrate)
|
2
2
|
for change details.
|
3
3
|
|
4
|
+
2.1.2 / 2012-06-12
|
5
|
+
------------------
|
6
|
+
* Support caSmall web service.
|
7
|
+
|
4
8
|
2.1.1 / 2012-04-13
|
5
9
|
------------------
|
6
10
|
* Initial public release spun off from caruby/core.
|
data/lib/jinx/csv/csvio.rb
CHANGED
@@ -110,7 +110,7 @@ module Jinx
|
|
110
110
|
when Enumerable then
|
111
111
|
''.parse_csv(:headers => hdr_opt, :header_converters => :symbol, :return_headers => true)
|
112
112
|
else
|
113
|
-
|
113
|
+
raise ArgumentError.new("CSV headers option value not supported: #{hdr_opt}")
|
114
114
|
end
|
115
115
|
# The field value accessors consist of the header row headers converted to a symbol.
|
116
116
|
@accessors = hdr_row.headers
|
@@ -139,7 +139,7 @@ module Jinx
|
|
139
139
|
reopt = if opt then
|
140
140
|
case opt
|
141
141
|
when 'i' then Regexp::IGNORECASE
|
142
|
-
else
|
142
|
+
else raise MigrationError.new("Migration value filter regular expression #{k} qualifier not supported: expected 'i', found '#{opt}'")
|
143
143
|
end
|
144
144
|
end
|
145
145
|
# the Regexp object
|
@@ -7,7 +7,6 @@ require 'jinx/helpers/lazy_hash'
|
|
7
7
|
require 'jinx/helpers/log'
|
8
8
|
require 'jinx/helpers/inflector'
|
9
9
|
require 'jinx/helpers/pretty_print'
|
10
|
-
require 'jinx/helpers/stopwatch'
|
11
10
|
require 'jinx/helpers/transitive_closure'
|
12
11
|
require 'jinx/migration/migratable'
|
13
12
|
require 'jinx/migration/reader'
|
@@ -20,15 +19,13 @@ module Jinx
|
|
20
19
|
class Migrator
|
21
20
|
include Enumerable
|
22
21
|
|
23
|
-
# Creates a new Migrator
|
24
|
-
# implements the +open+ method, which returns a {Migration::Reader}
|
22
|
+
# Creates a new Migrator from the given options.
|
25
23
|
#
|
26
24
|
# @param [{Symbol => Object}] opts the migration options
|
27
|
-
# @option opts [
|
25
|
+
# @option opts [Class] :target the required target domain class
|
28
26
|
# @option opts [<String>, String] :mapping the required input field => caTissue attribute mapping file(s)
|
29
27
|
# @option opts [String, Migration::Reader] :input the required input file name or an adapter which
|
30
28
|
# implements the {Migration::Reader} methods
|
31
|
-
# @option opts [Jinx::Database] :database the optional destination +Jinx::Database+
|
32
29
|
# @option opts [<String>, String] :defaults the optional caTissue attribute => value default mapping file(s)
|
33
30
|
# @option opts [<String>, String] :filters the optional caTissue attribute input value => caTissue value filter file(s)
|
34
31
|
# @option opts [<String>, String] :shims the optional shim file(s) to load
|
@@ -48,30 +45,13 @@ module Jinx
|
|
48
45
|
parse_options(opts)
|
49
46
|
build
|
50
47
|
end
|
51
|
-
|
52
|
-
# Imports this migrator's file into the database with the given connect options.
|
53
|
-
# This method creates or updates the domain objects mapped from the import source.
|
54
|
-
# If a block is given to this method, then the block is called on each stored
|
55
|
-
# migration target object.
|
56
|
-
#
|
57
|
-
# If the +:create+ option is set, then an input record for a target object which already
|
58
|
-
# exists in the database is noted in a debug log message and ignored rather than updated.
|
59
|
-
#
|
60
|
-
# @yield (see #migrate)
|
61
|
-
# @yieldparam (see #migrate)
|
62
|
-
# @return (see #migrate)
|
63
|
-
def migrate_to_database(&block)
|
64
|
-
# migrate with save
|
65
|
-
tm = Stopwatch.measure { execute_save(&block) }.elapsed
|
66
|
-
logger.debug { format_migration_time_log_message(tm) }
|
67
|
-
end
|
68
48
|
|
69
49
|
# Imports this migrator's CSV file and calls the given block on each migrated target
|
70
50
|
# domain object. If no block is given, then this method returns an array of the
|
71
51
|
# migrated target objects.
|
72
52
|
#
|
73
|
-
# @yield [target, row]
|
74
|
-
# @yieldparam [
|
53
|
+
# @yield [target, row] operates on the migration target
|
54
|
+
# @yieldparam [Resource] target the migrated target domain object
|
75
55
|
# @yieldparam [{Symbol => Object}] row the migration source record
|
76
56
|
def migrate(&block)
|
77
57
|
unless block_given? then
|
@@ -150,46 +130,10 @@ module Jinx
|
|
150
130
|
end
|
151
131
|
end
|
152
132
|
|
153
|
-
# {#migrate} with a {#save} block on the migration target. Each migrated object
|
154
|
-
# is created, if necessary, after the target save.
|
155
|
-
def execute_save
|
156
|
-
if @database.nil? then
|
157
|
-
Jinx.fail(MigrationError, "Migrator cannot save records since the database option was not specified.")
|
158
|
-
end
|
159
|
-
@database.open do |db|
|
160
|
-
migrate do |tgt, rec|
|
161
|
-
save(tgt, db)
|
162
|
-
# Ensure that each migrated object is created if necessary.
|
163
|
-
@migrated.each do |obj|
|
164
|
-
next if obj.identifier
|
165
|
-
logger.debug { "The migrator is saving the migrated #{obj}..." }
|
166
|
-
save(obj, db)
|
167
|
-
logger.debug { "The migrator saved the migrated #{obj}." }
|
168
|
-
end
|
169
|
-
yield(tgt, rec) if block_given?
|
170
|
-
db.clear
|
171
|
-
end
|
172
|
-
end
|
173
|
-
end
|
174
|
-
|
175
|
-
# @return a log message String for the given migration time in seconds
|
176
|
-
def format_migration_time_log_message(time)
|
177
|
-
# the database execution time
|
178
|
-
dt = @database.execution_time
|
179
|
-
if time > 120 then
|
180
|
-
time /= 60
|
181
|
-
dt /= 60
|
182
|
-
unit = "minutes"
|
183
|
-
else
|
184
|
-
unit = "seconds"
|
185
|
-
end
|
186
|
-
"Migration took #{'%.2f' % time} #{unit}, of which #{'%.2f' % dt} were database operations."
|
187
|
-
end
|
188
|
-
|
189
133
|
def parse_options(opts)
|
190
134
|
@fld_map_files = opts[:mapping]
|
191
135
|
if @fld_map_files.nil? then
|
192
|
-
|
136
|
+
raise MigrationError.new("Migrator missing required field mapping file parameter")
|
193
137
|
end
|
194
138
|
@def_files = opts[:defaults]
|
195
139
|
@flt_files = opts[:filters]
|
@@ -200,12 +144,11 @@ module Jinx
|
|
200
144
|
@from = opts[:from] ||= 1
|
201
145
|
@input = opts[:input]
|
202
146
|
if @input.nil? then
|
203
|
-
|
147
|
+
raise MigrationError.new("Migrator missing required source file parameter")
|
204
148
|
end
|
205
|
-
@database = opts[:database]
|
206
149
|
@target_class = opts[:target]
|
207
150
|
if @target_class.nil? then
|
208
|
-
|
151
|
+
raise MigrationError.new("Migrator missing required target class parameter")
|
209
152
|
end
|
210
153
|
@bad_file = opts[:bad]
|
211
154
|
@extract = opts[:extract]
|
@@ -225,7 +168,7 @@ module Jinx
|
|
225
168
|
|
226
169
|
def build
|
227
170
|
# the current source class => instance map
|
228
|
-
|
171
|
+
raise MigrationError.new("No file to migrate") if @input.nil?
|
229
172
|
|
230
173
|
# If the input is a file name, then make a CSV loader which only converts input fields
|
231
174
|
# corresponding to non-String attributes.
|
@@ -258,7 +201,7 @@ module Jinx
|
|
258
201
|
# An abstract class cannot be instantiated.
|
259
202
|
@creatable_classes.each do |klass|
|
260
203
|
if klass.abstract? then
|
261
|
-
|
204
|
+
raise MigrationError.new("Migrator cannot create the abstract class #{klass}; specify a subclass instead in the mapping file.")
|
262
205
|
end
|
263
206
|
end
|
264
207
|
|
@@ -544,7 +487,7 @@ module Jinx
|
|
544
487
|
@rejects.flush
|
545
488
|
logger.debug("Invalid record #{rec_no} was written to the rejects file #{@bad_file}.")
|
546
489
|
else
|
547
|
-
|
490
|
+
raise MigrationError.new("Migration not performed on record #{rec_no}")
|
548
491
|
end
|
549
492
|
# Bump the record count.
|
550
493
|
@rec_cnt += 1
|
@@ -593,7 +536,7 @@ module Jinx
|
|
593
536
|
# create an instance for each creatable class
|
594
537
|
created = Set.new
|
595
538
|
# the migrated objects
|
596
|
-
migrated = @creatable_classes.map { |klass|
|
539
|
+
migrated = @creatable_classes.map { |klass| create_instance(klass, row, created) }
|
597
540
|
# migrate each object from the input row
|
598
541
|
migrated.each do |obj|
|
599
542
|
# First uniquify the object if necessary.
|
@@ -691,7 +634,7 @@ module Jinx
|
|
691
634
|
# @param [{Symbol => Object}] row the input row
|
692
635
|
# @param [<Resource>] created the migrated instances for this row
|
693
636
|
# @return [Resource] the new instance
|
694
|
-
def
|
637
|
+
def create_instance(klass, row, created)
|
695
638
|
# the new object
|
696
639
|
logger.debug { "The migrator is building #{klass.qp}..." }
|
697
640
|
created << obj = klass.new
|
@@ -758,7 +701,7 @@ module Jinx
|
|
758
701
|
# @return the new object
|
759
702
|
def create_reference(obj, property, row, created)
|
760
703
|
if property.type.abstract? then
|
761
|
-
|
704
|
+
raise MigrationError.new("Cannot create #{obj.qp} #{property} with abstract type #{property.type}")
|
762
705
|
end
|
763
706
|
ref = property.type.new
|
764
707
|
ref.migrate(row, Array::EMPTY_ARRAY)
|
@@ -785,8 +728,8 @@ module Jinx
|
|
785
728
|
# set the attribute
|
786
729
|
begin
|
787
730
|
obj.send(property.writer, value)
|
788
|
-
rescue Exception
|
789
|
-
|
731
|
+
rescue Exception
|
732
|
+
raise MigrationError.new("Could not set #{obj.qp} #{property} to #{value.qp} - " + $!)
|
790
733
|
end
|
791
734
|
logger.debug { "Migrated #{obj.qp} #{property} to #{value}." }
|
792
735
|
end
|
@@ -810,20 +753,6 @@ module Jinx
|
|
810
753
|
flts = @attr_flt_hash[obj.class] || return
|
811
754
|
flts[attribute]
|
812
755
|
end
|
813
|
-
|
814
|
-
# @param [Resource] obj the domain object to save in the database
|
815
|
-
# @return [Resource, nil] obj if the save is successful, nil otherwise
|
816
|
-
def save(obj, database)
|
817
|
-
if @create then
|
818
|
-
logger.debug { "Migrator creating #{obj}..." }
|
819
|
-
database.create(obj)
|
820
|
-
logger.debug { "Migrator created #{obj}." }
|
821
|
-
else
|
822
|
-
logger.debug { "Migrator saving #{obj}..." }
|
823
|
-
database.save(obj)
|
824
|
-
logger.debug { "Migrator saved #{obj}." }
|
825
|
-
end
|
826
|
-
end
|
827
756
|
|
828
757
|
def current_record
|
829
758
|
@rec_cnt + 1
|
@@ -878,7 +807,7 @@ module Jinx
|
|
878
807
|
begin
|
879
808
|
config = YAML.load_file(file)
|
880
809
|
rescue
|
881
|
-
|
810
|
+
raise MigrationError.new("Could not read field map file #{file}: " + $!)
|
882
811
|
end
|
883
812
|
populate_field_map(config, hash)
|
884
813
|
end
|
@@ -892,7 +821,7 @@ module Jinx
|
|
892
821
|
# the header accessor method for the field
|
893
822
|
header = @reader.accessor(field)
|
894
823
|
if header.nil? then
|
895
|
-
|
824
|
+
raise MigrationError.new("Field defined in migration configuration not found in input file #{@input} headers: #{field}")
|
896
825
|
end
|
897
826
|
# associate each attribute path in the property value with the header
|
898
827
|
attr_list.split(/,\s*/).each do |path_s|
|
@@ -921,7 +850,7 @@ module Jinx
|
|
921
850
|
begin
|
922
851
|
config = YAML::load_file(file)
|
923
852
|
rescue
|
924
|
-
|
853
|
+
raise MigrationError.new("Could not read defaults file #{file}: " + $!)
|
925
854
|
end
|
926
855
|
# collect the class => path => value entries
|
927
856
|
config.each do |path_s, value|
|
@@ -952,13 +881,15 @@ module Jinx
|
|
952
881
|
begin
|
953
882
|
config = YAML::load_file(file)
|
954
883
|
rescue
|
955
|
-
|
884
|
+
raise MigrationError.new("Could not read filter file #{file}: " + $!)
|
956
885
|
end
|
957
886
|
config.each do |path_s, flt|
|
958
887
|
next if flt.nil_or_empty?
|
959
888
|
klass, path = create_attribute_path(path_s)
|
960
|
-
|
961
|
-
|
889
|
+
if path.empty? then
|
890
|
+
raise MigrationError.new("Migration filter configuration path does not include a property: #{path_s}")
|
891
|
+
elsif path.size > 1 then
|
892
|
+
raise MigrationError.new("Migration filter configuration path with more than one property is not supported: #{path_s}")
|
962
893
|
end
|
963
894
|
pa = klass.standard_attribute(path.first.to_sym)
|
964
895
|
flt_hash = hash[klass] ||= {}
|
@@ -973,10 +904,10 @@ module Jinx
|
|
973
904
|
names = path_s.split('.')
|
974
905
|
# If the path starts with a capitalized class name, then resolve the class.
|
975
906
|
# Otherwise, the target class is the start of the path.
|
976
|
-
klass = names.first =~ /^[A-Z]/ ?
|
907
|
+
klass = names.first =~ /^[A-Z]/ ? class_for_name(names.shift) : @target_class
|
977
908
|
# There must be at least one attribute.
|
978
909
|
if names.empty? then
|
979
|
-
|
910
|
+
raise MigrationError.new("Property entry in migration configuration is not in <class>.<attribute> format: #{path_s}")
|
980
911
|
end
|
981
912
|
|
982
913
|
# Build the attribute path.
|
@@ -985,11 +916,11 @@ module Jinx
|
|
985
916
|
pa = name.to_sym
|
986
917
|
prop = begin
|
987
918
|
parent.property(pa)
|
988
|
-
rescue NameError
|
989
|
-
|
919
|
+
rescue NameError
|
920
|
+
raise MigrationError.new("Migration field mapping attribute #{parent}.#{pa} not found - " + $!)
|
990
921
|
end
|
991
922
|
if prop.collection? then
|
992
|
-
|
923
|
+
raise MigrationError.new("Migration field mapping attribute #{parent}.#{prop} is a collection, which is not supported")
|
993
924
|
end
|
994
925
|
path << prop
|
995
926
|
prop.type
|
@@ -1008,6 +939,13 @@ module Jinx
|
|
1008
939
|
def context_module
|
1009
940
|
@target_class.domain_module
|
1010
941
|
end
|
942
|
+
|
943
|
+
# @param [String] the class name to resolve in the context of this migrator
|
944
|
+
# @return [Class] the corresponding class
|
945
|
+
# @raise [NameError] if the name cannot be resolved
|
946
|
+
def class_for_name(name)
|
947
|
+
context_module.module_for_name(name)
|
948
|
+
end
|
1011
949
|
|
1012
950
|
# @return a new class => [paths] hash from the migration fields configuration map
|
1013
951
|
def create_class_paths_hash(fld_map, def_map)
|
metadata
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
name: jinx-migrate
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: 2.1.
|
5
|
+
version: 2.1.2
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- OHSU
|
@@ -10,8 +10,7 @@ autorequire:
|
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
12
|
|
13
|
-
date: 2012-
|
14
|
-
default_executable:
|
13
|
+
date: 2012-06-12 00:00:00 Z
|
15
14
|
dependencies:
|
16
15
|
- !ruby/object:Gem::Dependency
|
17
16
|
name: rack
|
@@ -81,7 +80,6 @@ files:
|
|
81
80
|
- .rspec
|
82
81
|
- .yardopts
|
83
82
|
- Gemfile
|
84
|
-
- Gemfile.lock
|
85
83
|
- History.md
|
86
84
|
- LEGAL
|
87
85
|
- LICENSE
|
@@ -141,7 +139,6 @@ files:
|
|
141
139
|
- test/fixtures/csv/data/empty.csv
|
142
140
|
- test/fixtures/csv/data/variety.csv
|
143
141
|
- test/lib/csv/csvio_test.rb
|
144
|
-
has_rdoc: yard
|
145
142
|
homepage: http://github.com/jinx/migrate
|
146
143
|
licenses:
|
147
144
|
- MIT
|
@@ -165,7 +162,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
165
162
|
requirements: []
|
166
163
|
|
167
164
|
rubyforge_project: jinx
|
168
|
-
rubygems_version: 1.
|
165
|
+
rubygems_version: 1.8.15
|
169
166
|
signing_key:
|
170
167
|
specification_version: 3
|
171
168
|
summary: Jinx JSON plug-in.
|
@@ -204,3 +201,4 @@ test_files:
|
|
204
201
|
- spec/unique/parents.csv
|
205
202
|
- spec/unique/shims.rb
|
206
203
|
- spec/unique/unique_spec.rb
|
204
|
+
has_rdoc: yard
|
data/Gemfile.lock
DELETED
@@ -1,38 +0,0 @@
|
|
1
|
-
PATH
|
2
|
-
remote: .
|
3
|
-
specs:
|
4
|
-
jinx-migrate (2.1.1)
|
5
|
-
bundler
|
6
|
-
fastercsv
|
7
|
-
rack
|
8
|
-
|
9
|
-
PATH
|
10
|
-
remote: /Users/loneyf/workspace/jinx/core
|
11
|
-
specs:
|
12
|
-
jinx (2.1.1)
|
13
|
-
bundler
|
14
|
-
|
15
|
-
GEM
|
16
|
-
remote: http://rubygems.org/
|
17
|
-
specs:
|
18
|
-
diff-lcs (1.1.3)
|
19
|
-
fastercsv (1.5.4)
|
20
|
-
rack (1.4.1)
|
21
|
-
rake (0.9.2.2)
|
22
|
-
rspec (2.9.0)
|
23
|
-
rspec-core (~> 2.9.0)
|
24
|
-
rspec-expectations (~> 2.9.0)
|
25
|
-
rspec-mocks (~> 2.9.0)
|
26
|
-
rspec-core (2.9.0)
|
27
|
-
rspec-expectations (2.9.1)
|
28
|
-
diff-lcs (~> 1.1.3)
|
29
|
-
rspec-mocks (2.9.0)
|
30
|
-
|
31
|
-
PLATFORMS
|
32
|
-
java
|
33
|
-
|
34
|
-
DEPENDENCIES
|
35
|
-
jinx!
|
36
|
-
jinx-migrate!
|
37
|
-
rake
|
38
|
-
rspec (>= 2.6)
|