jinx-migrate 2.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +14 -0
- data/.rspec +3 -0
- data/.yardopts +1 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +38 -0
- data/History.md +6 -0
- data/LEGAL +5 -0
- data/LICENSE +22 -0
- data/README.md +33 -0
- data/Rakefile +40 -0
- data/bin/csvjoin +24 -0
- data/examples/family/README.md +24 -0
- data/examples/family/conf/children/fields.yaml +2 -0
- data/examples/family/conf/parents/defaults.yaml +3 -0
- data/examples/family/conf/parents/fields.yaml +6 -0
- data/examples/family/conf/parents/values.yaml +4 -0
- data/examples/family/data/children.csv +1 -0
- data/examples/family/data/parents.csv +1 -0
- data/examples/family/lib/shims.rb +17 -0
- data/jinx-migrate.gemspec +26 -0
- data/lib/jinx/csv/csvio.rb +214 -0
- data/lib/jinx/csv/joiner.rb +196 -0
- data/lib/jinx/migration/filter.rb +167 -0
- data/lib/jinx/migration/migratable.rb +244 -0
- data/lib/jinx/migration/migrator.rb +1029 -0
- data/lib/jinx/migration/reader.rb +16 -0
- data/lib/jinx/migration/version.rb +5 -0
- data/spec/bad/bad_spec.rb +25 -0
- data/spec/bad/fields.yaml +1 -0
- data/spec/bad/parents.csv +1 -0
- data/spec/bad/shims.rb +16 -0
- data/spec/csv/join/join_helper.rb +35 -0
- data/spec/csv/join/join_spec.rb +100 -0
- data/spec/csv/join/jumbled_src.csv +7 -0
- data/spec/csv/join/jumbled_tgt.csv +7 -0
- data/spec/csv/join/source.csv +7 -0
- data/spec/csv/join/target.csv +7 -0
- data/spec/extract/extract.rb +13 -0
- data/spec/extract/extract_spec.rb +33 -0
- data/spec/extract/fields.yaml +1 -0
- data/spec/extract/parents.csv +1 -0
- data/spec/family/child_spec.rb +27 -0
- data/spec/family/family.rb +13 -0
- data/spec/family/parent_spec.rb +57 -0
- data/spec/filter/fields.yaml +1 -0
- data/spec/filter/filter_spec.rb +20 -0
- data/spec/filter/parents.csv +1 -0
- data/spec/filter/values.yaml +4 -0
- data/spec/primitive/children.csv +1 -0
- data/spec/primitive/fields.yaml +4 -0
- data/spec/primitive/primitive_spec.rb +24 -0
- data/spec/skip/fields.yaml +1 -0
- data/spec/skip/parents.csv +1 -0
- data/spec/skip/skip_spec.rb +17 -0
- data/spec/spec_helper.rb +17 -0
- data/spec/support/model.rb +7 -0
- data/spec/unique/fields.yaml +1 -0
- data/spec/unique/parent.rb +6 -0
- data/spec/unique/parents.csv +1 -0
- data/spec/unique/shims.rb +10 -0
- data/spec/unique/unique_spec.rb +20 -0
- data/test/fixtures/csv/data/empty.csv +1 -0
- data/test/fixtures/csv/data/variety.csv +1 -0
- data/test/lib/csv/csvio_test.rb +74 -0
- metadata +206 -0
@@ -0,0 +1,16 @@
|
|
1
|
+
module Jinx
|
2
|
+
module Migration
|
3
|
+
# A prototypical source reader which enumerates the input records.
|
4
|
+
module Reader
|
5
|
+
include Enumerable
|
6
|
+
|
7
|
+
# @param [String] name the migration mapping source field name, e.g. +First Name+
|
8
|
+
# @return [Symbol] the record value accessor symbol, e.g. +:first_name+
|
9
|
+
def accessor(name); end
|
10
|
+
|
11
|
+
# @yield [rec] migrate the source record
|
12
|
+
# @yieldparam [{Symbol => Object}] rec the source accessor => value record
|
13
|
+
def each; end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper'
|
2
|
+
|
3
|
+
module Model
|
4
|
+
RESULTS = File.dirname(__FILE__) + '/../../test/results'
|
5
|
+
|
6
|
+
describe 'Bad' do
|
7
|
+
# The rejects file.
|
8
|
+
bad = RESULTS + '/bad/rejects.csv'
|
9
|
+
|
10
|
+
# Migrate the input.
|
11
|
+
migrated = Jinx::Migrator.new(:debug => true, :target => Parent, :bad => bad,
|
12
|
+
:mapping => File.expand_path('fields.yaml', File.dirname(__FILE__)),
|
13
|
+
:shims => File.expand_path('shims.rb', File.dirname(__FILE__)),
|
14
|
+
:input => File.expand_path('parents.csv', File.dirname(__FILE__))
|
15
|
+
).to_a
|
16
|
+
|
17
|
+
# Validate the migration.
|
18
|
+
it "should migrate one record" do
|
19
|
+
migrated.size.should be 1
|
20
|
+
end
|
21
|
+
it "should capture two bad records" do
|
22
|
+
File.open(bad).to_a.size.should be 2
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
Name: Parent.name
|
@@ -0,0 +1 @@
|
|
1
|
+
Name
|
data/spec/bad/shims.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
module Model
|
2
|
+
shims Parent
|
3
|
+
|
4
|
+
class Parent
|
5
|
+
# Simulate an error.
|
6
|
+
def migrate_name(value, row)
|
7
|
+
raise StandardError.new("Simulated error") if value == 'Mark'
|
8
|
+
value
|
9
|
+
end
|
10
|
+
|
11
|
+
# Simulate invalidation.
|
12
|
+
def migration_valid?
|
13
|
+
name == 'Tom'
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'spec/spec_helper'
|
2
|
+
require 'fileutils'
|
3
|
+
require 'jinx/csv/csvio'
|
4
|
+
|
5
|
+
SOURCE = File.expand_path('source.csv', File.dirname(__FILE__))
|
6
|
+
|
7
|
+
TARGET = File.expand_path('target.csv', File.dirname(__FILE__))
|
8
|
+
|
9
|
+
RESULTS = File.dirname(__FILE__) + '/../../../test/results/join'
|
10
|
+
|
11
|
+
OUTPUT = File.expand_path('output.csv', RESULTS)
|
12
|
+
|
13
|
+
module Jinx
|
14
|
+
module JoinHelper
|
15
|
+
# Joins the given source fixture to the target fixture on the specified fields.
|
16
|
+
#
|
17
|
+
# @param [Symbol] source the source file fixture in the join spec directory
|
18
|
+
# @param [Symbol] target the target file fixture in the join spec directory
|
19
|
+
# @param [<String>] fields the source fields (default is all source fields)
|
20
|
+
# @return [<<String>>] the output records
|
21
|
+
def join(source, target, *fields, &block)
|
22
|
+
FileUtils.rm_rf OUTPUT
|
23
|
+
sf = File.expand_path("#{source}.csv", File.dirname(__FILE__))
|
24
|
+
tf = File.expand_path("#{target}.csv", File.dirname(__FILE__))
|
25
|
+
Jinx::CsvIO.join(sf, :to => tf, :for => fields, :as => OUTPUT, &block)
|
26
|
+
if File.exists?(OUTPUT) then
|
27
|
+
File.readlines(OUTPUT).map do |line|
|
28
|
+
line.chomp.split(',').map { |s| s unless s.blank? }
|
29
|
+
end
|
30
|
+
else
|
31
|
+
Array::EMPTY_ARRAY
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'spec/csv/join/join_helper'
|
2
|
+
|
3
|
+
shared_examples 'a join for all source fields' do
|
4
|
+
it 'joins each record' do
|
5
|
+
@output.size.should be 10
|
6
|
+
end
|
7
|
+
|
8
|
+
it 'writes the output header row' do
|
9
|
+
@output.first.should == ['A', 'B', 'U', 'X']
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'writes the matching source and target' do
|
13
|
+
@output[1].should == ['a1', 'b1', 'u', 'x']
|
14
|
+
@output[2].should == ['a1', 'b1', 'v', 'x']
|
15
|
+
@output[3].should == ['a1', 'b2', 'u', 'x']
|
16
|
+
@output[4].should == ['a1', 'b2', 'u', 'y']
|
17
|
+
@output[5].should == ['a2', 'b3', 'u', 'x']
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'writes the unmatched source' do
|
21
|
+
# Note that String split truncates the trailing blank array items,
|
22
|
+
# so the comparison is to ['a2', 'b4', 'u'] rather than ['a2', 'b4', 'u', nil].
|
23
|
+
@output[6].should == ['a2', 'b4', 'u']
|
24
|
+
@output[9].should == ['a4', 'b7', 'u']
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'writes the unmatched target' do
|
28
|
+
@output[7].should == ['a2', 'b5', nil, 'x']
|
29
|
+
@output[8].should == ['a3', nil, nil, 'x']
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe 'Join' do
|
34
|
+
include Jinx::JoinHelper
|
35
|
+
|
36
|
+
context 'Join for all source fields' do
|
37
|
+
before(:all) { @output = join(:source, :target) }
|
38
|
+
|
39
|
+
it_behaves_like 'a join for all source fields'
|
40
|
+
end
|
41
|
+
|
42
|
+
context 'Join with block' do
|
43
|
+
before(:all) do
|
44
|
+
@output = join(:source, :target) do |rec|
|
45
|
+
curr = rec[0..1]
|
46
|
+
if curr == @prev then
|
47
|
+
rec[1] = nil
|
48
|
+
else
|
49
|
+
@prev = curr
|
50
|
+
end
|
51
|
+
rec unless curr == ['a2', 'b3']
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'preserves the output header row' do
|
56
|
+
@output.first.should == ['A', 'B', 'U', 'X']
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'applies the block to the records before writing them to the ouput' do
|
60
|
+
@output[1].should == ['a1', 'b1', 'u', 'x']
|
61
|
+
@output[2].should == ['a1', nil, 'v', 'x']
|
62
|
+
@output[3].should == ['a1', 'b2', 'u', 'x']
|
63
|
+
@output[4].should == ['a1', nil, 'u', 'y']
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'omits the record if the block returns nil' do
|
67
|
+
@output[5].should == ['a2', 'b4', 'u']
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
context 'Join for jumbled source and target fields' do
|
72
|
+
before(:all) { @output = join(:jumbled_src, :jumbled_tgt) }
|
73
|
+
|
74
|
+
it_behaves_like 'a join for all source fields'
|
75
|
+
end
|
76
|
+
|
77
|
+
context 'Join for only the key source fields' do
|
78
|
+
before(:all) { @output = join(:source, :target, 'A', 'B') }
|
79
|
+
|
80
|
+
it 'joins each record' do
|
81
|
+
@output.size.should be 10
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'writes the output header row' do
|
85
|
+
@output.first.should == ['A', 'B', 'X']
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'writes the matching source and target records without the source-specific fields' do
|
89
|
+
@output[1].should == ['a1', 'b1', 'x']
|
90
|
+
@output[2].should == ['a1', 'b1', 'x']
|
91
|
+
@output[3].should == ['a1', 'b2', 'x']
|
92
|
+
@output[4].should == ['a1', 'b2', 'y']
|
93
|
+
@output[5].should == ['a2', 'b3', 'x']
|
94
|
+
@output[6].should == ['a2', 'b4']
|
95
|
+
@output[7].should == ['a2', 'b5', 'x']
|
96
|
+
@output[8].should == ['a3', nil, 'x']
|
97
|
+
@output[9].should == ['a4', 'b7']
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper'
|
2
|
+
|
3
|
+
module Model
|
4
|
+
describe 'Extract' do
|
5
|
+
EXTRACT = File.expand_path('ids.csv', Migration::Test::RESULTS + '/extract')
|
6
|
+
|
7
|
+
HEADERS = ['Name', 'Id']
|
8
|
+
|
9
|
+
before(:all) do
|
10
|
+
# Migrate the input.
|
11
|
+
@migrated = Jinx::Migrator.new(
|
12
|
+
:debug => true,
|
13
|
+
:target => Parent,
|
14
|
+
:mapping => File.expand_path('fields.yaml', File.dirname(__FILE__)),
|
15
|
+
:extract => EXTRACT,
|
16
|
+
:extract_headers => HEADERS,
|
17
|
+
:shims => File.expand_path('extract.rb', File.dirname(__FILE__)),
|
18
|
+
:input => File.expand_path('parents.csv', File.dirname(__FILE__))
|
19
|
+
).to_a
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should migrate the records" do
|
23
|
+
@migrated.size.should be 3
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should create the extract" do
|
27
|
+
xtr = File.readlines(EXTRACT).map { |line| line.chomp }
|
28
|
+
xtr.size.should be 4
|
29
|
+
xtr[0].should == HEADERS.join(',')
|
30
|
+
1.upto(3) { |i| xtr[i].chomp.split(',').should == [@migrated[i - 1].name, i.to_s] }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
Name: Parent.name
|
@@ -0,0 +1 @@
|
|
1
|
+
Name
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/family'
|
2
|
+
|
3
|
+
module Family
|
4
|
+
describe Child do
|
5
|
+
before(:all) do
|
6
|
+
# Migrate the input.
|
7
|
+
@migrated = Jinx::Migrator.new(
|
8
|
+
:debug => true,
|
9
|
+
:target => Child,
|
10
|
+
:mapping => CONFIGS + '/children/fields.yaml',
|
11
|
+
:shims => SHIMS,
|
12
|
+
:input => DATA + '/children.csv'
|
13
|
+
).to_a
|
14
|
+
end
|
15
|
+
|
16
|
+
# Validate the migration.
|
17
|
+
it "should migrate the records" do
|
18
|
+
@migrated.size.should be 3
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should migrate the parents" do
|
22
|
+
@migrated.each do |child|
|
23
|
+
child.parents.size.should be 1
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper'
|
2
|
+
|
3
|
+
# Load the jinx family example.
|
4
|
+
require Bundler.environment.specs.detect { |s| s.name == 'jinx' }.full_gem_path + '/examples/family/lib/family'
|
5
|
+
|
6
|
+
module Family
|
7
|
+
include Jinx::Migratable
|
8
|
+
|
9
|
+
ROOT = File.dirname(__FILE__) + '/../../examples/family'
|
10
|
+
DATA = ROOT + '/data'
|
11
|
+
CONFIGS = ROOT + '/conf'
|
12
|
+
SHIMS = ROOT + '/lib/shims.rb'
|
13
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/family'
|
2
|
+
|
3
|
+
module Family
|
4
|
+
# The specification for the family example.
|
5
|
+
describe Parent do
|
6
|
+
before(:all) do
|
7
|
+
# Migrate the input.
|
8
|
+
@migrated = Jinx::Migrator.new(
|
9
|
+
:debug => true,
|
10
|
+
:target => Parent,
|
11
|
+
:mapping => CONFIGS + '/parents/fields.yaml',
|
12
|
+
:defaults => CONFIGS + '/parents/defaults.yaml',
|
13
|
+
:filters => CONFIGS + '/parents/values.yaml',
|
14
|
+
:shims => SHIMS,
|
15
|
+
:input => DATA + '/parents.csv'
|
16
|
+
).to_a
|
17
|
+
end
|
18
|
+
|
19
|
+
# Validate the migration.
|
20
|
+
it "should migrate the records" do
|
21
|
+
@migrated.size.should be 2
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should create a household" do
|
25
|
+
@migrated.each do |parent|
|
26
|
+
parent.household.should_not be nil
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should migrate the addresses" do
|
31
|
+
@migrated.each do |parent|
|
32
|
+
parent.household.address.should_not be nil
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should abbreviate the street" do
|
37
|
+
addr = @migrated.first.household.address
|
38
|
+
addr.street1.should match /St/
|
39
|
+
addr.street1.should_not match /Street/
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should add the default state" do
|
43
|
+
@migrated.each do |parent|
|
44
|
+
parent.household.address.state.should == 'IL'
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should migrate the spouse" do
|
49
|
+
@migrated.first.spouse.should_not be nil
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should set the spouse household" do
|
53
|
+
hh = @migrated.first.household
|
54
|
+
@migrated.first.spouse.household.should be hh
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
Name: Parent.name
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper'
|
2
|
+
|
3
|
+
module Model
|
4
|
+
describe 'Filter' do
|
5
|
+
before(:all) do
|
6
|
+
@migrated = Jinx::Migrator.new(:debug => true, :target => Parent,
|
7
|
+
:mapping => File.expand_path('fields.yaml', File.dirname(__FILE__)),
|
8
|
+
:filters => File.expand_path('values.yaml', File.dirname(__FILE__)),
|
9
|
+
:input => File.expand_path('parents.csv', File.dirname(__FILE__))
|
10
|
+
).to_a
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should filter the name" do
|
14
|
+
@migrated.size.should be 3
|
15
|
+
@migrated[0].name.should == 'Joseph'
|
16
|
+
@migrated[1].name.should == 'Christine'
|
17
|
+
@migrated[2].name.should == 'Other'
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
Name
|
@@ -0,0 +1 @@
|
|
1
|
+
Name,Flag,Cardinal,Decimal
|