jinx-migrate 2.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. data/.gitignore +14 -0
  2. data/.rspec +3 -0
  3. data/.yardopts +1 -0
  4. data/Gemfile +8 -0
  5. data/Gemfile.lock +38 -0
  6. data/History.md +6 -0
  7. data/LEGAL +5 -0
  8. data/LICENSE +22 -0
  9. data/README.md +33 -0
  10. data/Rakefile +40 -0
  11. data/bin/csvjoin +24 -0
  12. data/examples/family/README.md +24 -0
  13. data/examples/family/conf/children/fields.yaml +2 -0
  14. data/examples/family/conf/parents/defaults.yaml +3 -0
  15. data/examples/family/conf/parents/fields.yaml +6 -0
  16. data/examples/family/conf/parents/values.yaml +4 -0
  17. data/examples/family/data/children.csv +1 -0
  18. data/examples/family/data/parents.csv +1 -0
  19. data/examples/family/lib/shims.rb +17 -0
  20. data/jinx-migrate.gemspec +26 -0
  21. data/lib/jinx/csv/csvio.rb +214 -0
  22. data/lib/jinx/csv/joiner.rb +196 -0
  23. data/lib/jinx/migration/filter.rb +167 -0
  24. data/lib/jinx/migration/migratable.rb +244 -0
  25. data/lib/jinx/migration/migrator.rb +1029 -0
  26. data/lib/jinx/migration/reader.rb +16 -0
  27. data/lib/jinx/migration/version.rb +5 -0
  28. data/spec/bad/bad_spec.rb +25 -0
  29. data/spec/bad/fields.yaml +1 -0
  30. data/spec/bad/parents.csv +1 -0
  31. data/spec/bad/shims.rb +16 -0
  32. data/spec/csv/join/join_helper.rb +35 -0
  33. data/spec/csv/join/join_spec.rb +100 -0
  34. data/spec/csv/join/jumbled_src.csv +7 -0
  35. data/spec/csv/join/jumbled_tgt.csv +7 -0
  36. data/spec/csv/join/source.csv +7 -0
  37. data/spec/csv/join/target.csv +7 -0
  38. data/spec/extract/extract.rb +13 -0
  39. data/spec/extract/extract_spec.rb +33 -0
  40. data/spec/extract/fields.yaml +1 -0
  41. data/spec/extract/parents.csv +1 -0
  42. data/spec/family/child_spec.rb +27 -0
  43. data/spec/family/family.rb +13 -0
  44. data/spec/family/parent_spec.rb +57 -0
  45. data/spec/filter/fields.yaml +1 -0
  46. data/spec/filter/filter_spec.rb +20 -0
  47. data/spec/filter/parents.csv +1 -0
  48. data/spec/filter/values.yaml +4 -0
  49. data/spec/primitive/children.csv +1 -0
  50. data/spec/primitive/fields.yaml +4 -0
  51. data/spec/primitive/primitive_spec.rb +24 -0
  52. data/spec/skip/fields.yaml +1 -0
  53. data/spec/skip/parents.csv +1 -0
  54. data/spec/skip/skip_spec.rb +17 -0
  55. data/spec/spec_helper.rb +17 -0
  56. data/spec/support/model.rb +7 -0
  57. data/spec/unique/fields.yaml +1 -0
  58. data/spec/unique/parent.rb +6 -0
  59. data/spec/unique/parents.csv +1 -0
  60. data/spec/unique/shims.rb +10 -0
  61. data/spec/unique/unique_spec.rb +20 -0
  62. data/test/fixtures/csv/data/empty.csv +1 -0
  63. data/test/fixtures/csv/data/variety.csv +1 -0
  64. data/test/lib/csv/csvio_test.rb +74 -0
  65. 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,5 @@
1
+ module Jinx
2
+ module Migrate
3
+ VERSION = "2.1.1"
4
+ end
5
+ 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,7 @@
1
+ A,U,B
2
+ a1,u,b1
3
+ a1,v,b1
4
+ a1,u,b2
5
+ a2,u,b3
6
+ a2,u,b4
7
+ a4,u,b7
@@ -0,0 +1,7 @@
1
+ X,B,A
2
+ x,b1,a1
3
+ x,b2,a1
4
+ y,b2,a1
5
+ x,b3,a2
6
+ x,b5,a2
7
+ x,,a3
@@ -0,0 +1,7 @@
1
+ A,B,U
2
+ a1,b1,u
3
+ a1,b1,v
4
+ a1,b2,u
5
+ a2,b3,u
6
+ a2,b4,u
7
+ a4,b7,u
@@ -0,0 +1,7 @@
1
+ A,B,X
2
+ a1,b1,x
3
+ a1,b2,x
4
+ a1,b2,y
5
+ a2,b3,x
6
+ a2,b5,x
7
+ a3,,x
@@ -0,0 +1,13 @@
1
+ module Model
2
+ shims Parent
3
+
4
+ class Parent
5
+ @@id = 1
6
+
7
+ def extract(io)
8
+ io << [name, @@id]
9
+ @@id += 1
10
+ end
11
+ end
12
+ end
13
+
@@ -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,4 @@
1
+ Parent.name:
2
+ Joe : Joseph
3
+ /Chris/i : Christine
4
+ /.*/ : Other
@@ -0,0 +1 @@
1
+ Name,Flag,Cardinal,Decimal