migratrix 0.0.9 → 0.8.1
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/migratrix.rb +62 -6
- data/lib/migratrix/exceptions.rb +4 -1
- data/lib/migratrix/{extractors → extractions}/active_record.rb +14 -10
- data/lib/migratrix/{extractors/extractor.rb → extractions/extraction.rb} +21 -20
- data/lib/migratrix/loads/load.rb +43 -0
- data/lib/migratrix/loads/yaml.rb +15 -0
- data/lib/migratrix/migration.rb +115 -27
- data/lib/migratrix/migratrix.rb +43 -84
- data/lib/migratrix/registry.rb +20 -0
- data/lib/migratrix/transforms/map.rb +57 -0
- data/lib/migratrix/transforms/transform.rb +268 -0
- data/lib/migratrix/valid_options.rb +22 -0
- data/lib/patches/object_ext.rb +0 -4
- data/spec/fixtures/migrations/marbles_migration.rb +6 -4
- data/spec/lib/migratrix/{loggable_spec.rb → _loggable_spec.rb} +0 -0
- data/spec/lib/migratrix/extractions/active_record_spec.rb +146 -0
- data/spec/lib/migratrix/extractions/extraction_spec.rb +71 -0
- data/spec/lib/migratrix/loads/load_spec.rb +59 -0
- data/spec/lib/migratrix/loads/yaml_spec.rb +39 -0
- data/spec/lib/migratrix/migration_spec.rb +195 -27
- data/spec/lib/migratrix/migratrix_spec.rb +57 -85
- data/spec/lib/migratrix/registry_spec.rb +28 -0
- data/spec/lib/migratrix/transforms/map_spec.rb +55 -0
- data/spec/lib/migratrix/transforms/transform_spec.rb +134 -0
- data/spec/lib/migratrix_spec.rb +98 -0
- data/spec/lib/patches/object_ext_spec.rb +0 -7
- data/spec/spec_helper.rb +18 -13
- metadata +21 -10
- data/spec/lib/migratrix/extractors/active_record_spec.rb +0 -43
- data/spec/lib/migratrix/extractors/extractor_spec.rb +0 -63
- data/spec/lib/migratrix_module_spec.rb +0 -63
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
module Migratrix
|
3
|
+
module ValidOptions
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
module ClassMethods
|
7
|
+
def set_valid_options(*options)
|
8
|
+
@local_valid_options = options.map(&:to_s).sort.map(&:to_sym)
|
9
|
+
end
|
10
|
+
|
11
|
+
def valid_options
|
12
|
+
options = local_valid_options.dup
|
13
|
+
options += self.ancestors.map {|klass| klass.local_valid_options rescue nil }.compact.flatten
|
14
|
+
options.map(&:to_s).sort.uniq.map(&:to_sym)
|
15
|
+
end
|
16
|
+
|
17
|
+
def local_valid_options
|
18
|
+
@local_valid_options ||= []
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/patches/object_ext.rb
CHANGED
@@ -1,17 +1,19 @@
|
|
1
1
|
module Migratrix
|
2
2
|
# Fake migration fixture for "Marbles"
|
3
3
|
class MarblesMigration < Migration
|
4
|
+
# :nocov: # because we play some games with file loading/unloading, SimpleCov often misses lines in this file
|
4
5
|
def initialize(options={})
|
5
6
|
super
|
6
|
-
|
7
|
+
@migrated = false
|
7
8
|
end
|
8
9
|
|
9
10
|
def migrate
|
10
|
-
|
11
|
+
@migrated = true
|
11
12
|
end
|
12
13
|
|
13
|
-
def
|
14
|
-
|
14
|
+
def migrated?
|
15
|
+
@migrated
|
15
16
|
end
|
17
|
+
# :nocov:
|
16
18
|
end
|
17
19
|
end
|
File without changes
|
@@ -0,0 +1,146 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'active_record'
|
3
|
+
|
4
|
+
class TestModel < ::ActiveRecord::Base
|
5
|
+
end
|
6
|
+
|
7
|
+
class TestActiveRecordExtraction < Migratrix::Extractions::ActiveRecord
|
8
|
+
end
|
9
|
+
|
10
|
+
describe Migratrix::Extractions::ActiveRecord do
|
11
|
+
let(:extraction) { TestActiveRecordExtraction.new :test }
|
12
|
+
describe "sanity check cat" do
|
13
|
+
it "is sanity checked" do
|
14
|
+
Migratrix::Extractions::Extraction.should_not be_nil
|
15
|
+
TestActiveRecordExtraction.should_not be_nil
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe ".new" do
|
20
|
+
it "raises TypeError unless source is Active" do
|
21
|
+
lambda { TestActiveRecordExtraction.new :test, :source => Object }.should raise_error(TypeError)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe "#source=" do
|
26
|
+
it "raises TypeError unless source is Active" do
|
27
|
+
lambda { extraction.source = Object }.should raise_error(TypeError)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe ".local_valid_options" do
|
32
|
+
it "returns the valid set of option keys" do
|
33
|
+
Migratrix::Extractions::ActiveRecord.local_valid_options.should == [:fetchall]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe ".valid_options" do
|
38
|
+
it "returns the valid set of option keys" do
|
39
|
+
Migratrix::Extractions::ActiveRecord.valid_options.should == [:fetchall] + Migratrix::Extractions::Extraction.valid_options
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe "#obtain_source" do
|
44
|
+
it "raises ExtractionSourceUndefined unless source is defined" do
|
45
|
+
lambda { extraction.extract }.should raise_error(Migratrix::ExtractionSourceUndefined)
|
46
|
+
end
|
47
|
+
|
48
|
+
it "returns the legacy ActiveRecord class" do
|
49
|
+
extraction.source = TestModel
|
50
|
+
extraction.obtain_source(TestModel).should == TestModel
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe "handler functions" do
|
55
|
+
let(:source) { TestModel }
|
56
|
+
before do
|
57
|
+
extraction.source = source
|
58
|
+
end
|
59
|
+
|
60
|
+
[:where, :order, :limit, :offset].each do |handler|
|
61
|
+
describe "#handle_#{handler}" do
|
62
|
+
it "calls #{handler} on the source ActiveRelation" do
|
63
|
+
source.should_receive(handler).with(1).and_return(nil)
|
64
|
+
extraction.send("handle_#{handler}", source, 1)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
describe "#to_query" do
|
71
|
+
let(:source) { TestModel }
|
72
|
+
let(:relation) { source.where(1) }
|
73
|
+
let(:lolquery) { 'SELECT "HAY GUYZ IM A QUARY LOL"' }
|
74
|
+
before do
|
75
|
+
extraction.source = source
|
76
|
+
end
|
77
|
+
|
78
|
+
describe "when source is ActiveRecord" do
|
79
|
+
it "converts it to ActiveRelation with where(1)" do
|
80
|
+
extraction.should_receive(:handle_where).with(source, 1).and_return(relation)
|
81
|
+
relation.should_receive(:to_sql).and_return(lolquery)
|
82
|
+
extraction.to_query(source).should == lolquery
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
describe "When source has already been converted to ActiveRelation" do
|
87
|
+
it "delegates to to_sql on source" do
|
88
|
+
relation.should_receive(:to_sql).and_return(lolquery)
|
89
|
+
extraction.to_query(relation).should == lolquery
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
describe "#execute_extract" do
|
95
|
+
let(:source) { TestModel }
|
96
|
+
let(:relation) { source.where(1) }
|
97
|
+
let(:lolquery) { 'SELECT "HAY GUYZ IM A QUARY LOL"' }
|
98
|
+
|
99
|
+
before do
|
100
|
+
TestModel.stub!(:establish_connection).and_return true
|
101
|
+
end
|
102
|
+
|
103
|
+
describe "with 'fetchall' option" do
|
104
|
+
let(:extraction) { TestActiveRecordExtraction.new :test, "fetchall" => true }
|
105
|
+
|
106
|
+
describe "and source is an ActiveRelation" do
|
107
|
+
it "calls all on the relation" do
|
108
|
+
relation.should_receive(:all).and_return([])
|
109
|
+
extraction.execute_extract(relation, extraction.options).should == []
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
describe "and source is still ActiveRecord" do
|
114
|
+
it "converts it to ActiveRelation with where(1)" do
|
115
|
+
source.should_receive(:all).and_return([])
|
116
|
+
extraction.execute_extract(source, extraction.options).should == []
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
describe "without 'fetchall' option" do
|
122
|
+
before do
|
123
|
+
# hrm, okay, this is tricky. The should == is setting off a
|
124
|
+
# tripwire in ActiveRecord that makes it try to connect to
|
125
|
+
# the database. This is AR internals dependent, but if the
|
126
|
+
# relation has a cached to_sql, it will return it without
|
127
|
+
# trying to connect.
|
128
|
+
relation.should_receive(:to_sql).at_least(1).times.and_return(lolquery)
|
129
|
+
end
|
130
|
+
|
131
|
+
describe "and source is an ActiveRelation" do
|
132
|
+
it "returns source unmodified" do
|
133
|
+
extraction.execute_extract(relation).should == relation
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
describe "and source is still ActiveRecord" do
|
138
|
+
it "converts it to ActiveRelation with where(1)" do
|
139
|
+
extraction.should_receive(:handle_where).with(source, 1).and_return(relation)
|
140
|
+
extraction.execute_extract(source).should == relation
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class TestExtraction < Migratrix::Extractions::Extraction
|
4
|
+
end
|
5
|
+
|
6
|
+
describe Migratrix::Extractions::Extraction do
|
7
|
+
describe "sanity check cat" do
|
8
|
+
it "is sanity checked" do
|
9
|
+
Migratrix::Extractions::Extraction.should_not be_nil
|
10
|
+
TestExtraction.should_not be_nil
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe ".local_valid_options" do
|
15
|
+
it "returns the valid set of option keys" do
|
16
|
+
Migratrix::Extractions::Extraction.local_valid_options.should == [:limit, :offset, :order, :where]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "unimplemented methods:" do
|
21
|
+
let(:base_extraction) { Migratrix::Extractions::Extraction.new :test }
|
22
|
+
[:obtain_source, :handle_where, :handle_limit, :handle_offset, :handle_order, :to_query, :execute_extract].each do |method|
|
23
|
+
describe "#{method}" do
|
24
|
+
it "raises NotImplementedError" do
|
25
|
+
lambda { base_extraction.send(method, nil) }.should raise_error(NotImplementedError)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe "#extract (default strategy)" do
|
32
|
+
describe "with no options" do
|
33
|
+
let(:extraction) { TestExtraction.new :test }
|
34
|
+
it "should call handle_source and execute_extract only" do
|
35
|
+
extraction.should_receive(:obtain_source).with(nil, {}).and_return(13)
|
36
|
+
extraction.should_receive(:execute_extract).with(13, {}).and_return(64)
|
37
|
+
extraction.extract.should == 64
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe "with all options" do
|
42
|
+
let(:options) { { :where => 1, :order => 2, :limit => 3, :offset => 4 } }
|
43
|
+
let(:extraction) { TestExtraction.new :test, options }
|
44
|
+
it "should call entire handler chain" do
|
45
|
+
extraction.should_receive(:obtain_source).with(nil, options).and_return("A")
|
46
|
+
extraction.should_receive(:handle_where).with("A", 1).and_return("B")
|
47
|
+
extraction.should_receive(:handle_order).with("B", 2).and_return("C")
|
48
|
+
extraction.should_receive(:handle_limit).with("C", 3).and_return("D")
|
49
|
+
extraction.should_receive(:handle_offset).with("D", 4).and_return("E")
|
50
|
+
extraction.should_receive(:execute_extract).with("E", options).and_return("BONG")
|
51
|
+
extraction.extract.should == "BONG"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe "with overridden options" do
|
56
|
+
let(:options) { { :where => 1, :order => 2, :limit => 3, :offset => 4 } }
|
57
|
+
let(:extraction) { TestExtraction.new :test, options }
|
58
|
+
let(:overrides) { {:where => 5, :order => 6, :limit => 7, :offset => 8 } }
|
59
|
+
it "should call entire handler chain" do
|
60
|
+
extraction.should_receive(:obtain_source).with(nil, overrides).and_return("A")
|
61
|
+
extraction.should_receive(:handle_where).with("A", 5).and_return("B")
|
62
|
+
extraction.should_receive(:handle_order).with("B", 6).and_return("C")
|
63
|
+
extraction.should_receive(:handle_limit).with("C", 7).and_return("D")
|
64
|
+
extraction.should_receive(:handle_offset).with("D", 8).and_return("E")
|
65
|
+
extraction.should_receive(:execute_extract).with("E", overrides).and_return("BONG")
|
66
|
+
extraction.extract(overrides).should == "BONG"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class TestLoad < Migratrix::Loads::Load
|
4
|
+
end
|
5
|
+
|
6
|
+
describe Migratrix::Loads::Load do
|
7
|
+
describe "sanity check cat" do
|
8
|
+
it "is sanity checked" do
|
9
|
+
Migratrix::Loads::Load.should_not be_nil
|
10
|
+
TestLoad.should_not be_nil
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
let(:loggable) { TestLoad.new(:loggable) }
|
15
|
+
it_should_behave_like "loggable"
|
16
|
+
|
17
|
+
# describe ".local_valid_options" do
|
18
|
+
# it "returns the valid set of option keys" do
|
19
|
+
# Migratrix::Extractors::Extractor.local_valid_options.should == [:limit, :offset, :order, :where]
|
20
|
+
# end
|
21
|
+
# end
|
22
|
+
|
23
|
+
describe '#load' do
|
24
|
+
describe "default strategy" do
|
25
|
+
it "saves every transformed object" do
|
26
|
+
data = mock("transformed_object", :save => true)
|
27
|
+
load = TestLoad.new(:test_load)
|
28
|
+
data.should_receive(:save).exactly(3).times
|
29
|
+
load.load([data,data,data])
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "#transform" do
|
35
|
+
it "returns transform name when set" do
|
36
|
+
load = Migratrix::Loads::Load.new(:pants_load, { transform: :pants_transform })
|
37
|
+
load.transform.should == :pants_transform
|
38
|
+
end
|
39
|
+
|
40
|
+
it "#returns load name when no transform name is set" do
|
41
|
+
load = Migratrix::Loads::Load.new(:pants_load)
|
42
|
+
load.transform.should == :pants_load
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# describe "unimplemented methods:" do
|
47
|
+
# [ [:before_load, []],
|
48
|
+
# [:after_load, []] ].each do |method, args|
|
49
|
+
# describe "#{method}(#{args.map(&:inspect)*','})" do
|
50
|
+
# let(:object_with_not_implemented_methods) { Migratrix::Loads::Load.new(:brain_damaged_transform) }
|
51
|
+
# it "raises NotImplementedError" do
|
52
|
+
# lambda { object_with_not_implemented_methods.send(method, *args) }.should raise_error(NotImplementedError)
|
53
|
+
# end
|
54
|
+
# end
|
55
|
+
# end
|
56
|
+
# end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class TestYamlLoad < Migratrix::Loads::Yaml
|
4
|
+
end
|
5
|
+
|
6
|
+
describe Migratrix::Loads::Yaml do
|
7
|
+
describe "sanity check cat" do
|
8
|
+
it "is sanity checked" do
|
9
|
+
Migratrix::Loads::Load.should_not be_nil
|
10
|
+
TestYamlLoad.should_not be_nil
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
let(:loggable) { TestYamlLoad.new(:loggable) }
|
15
|
+
it_should_behave_like "loggable"
|
16
|
+
|
17
|
+
describe ".local_valid_options" do
|
18
|
+
it "returns the valid set of option keys" do
|
19
|
+
Migratrix::Loads::Yaml.local_valid_options.should == [:filename]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe "#load" do
|
24
|
+
let(:input) { [1, 2, 3]}
|
25
|
+
let(:output) { "---\n- 1\n- 2\n- 3\n"}
|
26
|
+
let(:buffer) { StringIO.new }
|
27
|
+
let(:load) { TestYamlLoad.new(:test, filename: '/tmp/test.yml')}
|
28
|
+
|
29
|
+
before do
|
30
|
+
File.should_receive(:open).with('/tmp/test.yml', 'w').and_yield(buffer)
|
31
|
+
end
|
32
|
+
|
33
|
+
it "writes transformed_items to file as YAML" do
|
34
|
+
load.load(input)
|
35
|
+
buffer.string.should == output
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
@@ -2,25 +2,24 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
# This migration is embedded in migration_spec.rb to allow testing of
|
4
4
|
# the class methods that specialize subclasses.
|
5
|
-
class
|
5
|
+
class TestMigration < Migratrix::Migration
|
6
6
|
end
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
let(:loggable) { Migratrix::TestMigration.new }
|
11
|
-
it_should_behave_like "loggable"
|
8
|
+
class ChildMigration1 < TestMigration
|
9
|
+
end
|
12
10
|
|
13
|
-
|
14
|
-
|
15
|
-
conditions = ["id=? AND approved=?", 42, true]
|
16
|
-
migration = Migratrix::TestMigration.new({ "where" => conditions })
|
11
|
+
class ChildMigration2 < TestMigration
|
12
|
+
end
|
17
13
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
14
|
+
class GrandchildMigration1 < ChildMigration1
|
15
|
+
end
|
16
|
+
|
17
|
+
describe Migratrix::Migration do
|
18
|
+
let(:migration) { TestMigration.new :cheese => 42 }
|
19
|
+
let(:mock_extraction) { mock("Migratrix::Extractions::ActiveRecord", :name => :pets, :extract => 43, :valid_options => ["fetchall", "limit", "offset", "order", "where"])}
|
20
|
+
|
21
|
+
let(:loggable) { TestMigration.new }
|
22
|
+
it_should_behave_like "loggable"
|
24
23
|
|
25
24
|
describe "#migrate" do
|
26
25
|
it "delegates to extract, transform, and load" do
|
@@ -31,29 +30,198 @@ describe Migratrix::Migration do
|
|
31
30
|
end
|
32
31
|
end
|
33
32
|
|
34
|
-
describe "with
|
35
|
-
let(:
|
33
|
+
describe "with mocked components" do
|
34
|
+
let(:map) { { :id => :id, :name => :name }}
|
35
|
+
let(:extraction) {
|
36
|
+
mock("Migratrix::Extractions::ActiveRecord", {
|
37
|
+
:extract => {:animals => [42,13,43,14]},
|
38
|
+
:valid_options => [:fetchall, :limit, :offset, :order, :where]
|
39
|
+
}
|
40
|
+
)
|
41
|
+
}
|
42
|
+
let(:transform1) {
|
43
|
+
mock("Migratrix::Transforms::Map", {
|
44
|
+
:name => :tame,
|
45
|
+
:transform => [{:id => 42, :name => "Mister Bobo"}, {:id => 43, :name => "Mrs. Bobo"}],
|
46
|
+
:valid_options => [:map],
|
47
|
+
:extraction => :animals
|
48
|
+
})
|
49
|
+
}
|
50
|
+
let(:transform2) {
|
51
|
+
mock("Migratrix::Transforms::Map", {
|
52
|
+
:name => :animals,
|
53
|
+
:extraction => :animals,
|
54
|
+
:transform => [{:id => 13, :name => "Sparky"}, {:id => 14, :name => "Fido"}],
|
55
|
+
:valid_options => [:map]
|
56
|
+
}
|
57
|
+
)
|
58
|
+
}
|
59
|
+
let(:load1) {
|
60
|
+
mock("Migratrix::Loads::Yaml", {
|
61
|
+
:name => :cute,
|
62
|
+
:filename => "/tmp/monkeys.yml",
|
63
|
+
:valid_options => [:filename],
|
64
|
+
:transform => :tame
|
65
|
+
})
|
66
|
+
}
|
67
|
+
let(:load2) {
|
68
|
+
mock("Migratrix::Loads::Yaml", {
|
69
|
+
:name => :adorable,
|
70
|
+
:filename => "/tmp/puppies.yml",
|
71
|
+
:valid_options => [:filename],
|
72
|
+
:transform => :animals
|
73
|
+
})
|
74
|
+
}
|
75
|
+
|
36
76
|
before do
|
37
|
-
|
38
|
-
|
77
|
+
# Clear out any named components
|
78
|
+
TestMigration.extractions.clear
|
79
|
+
TestMigration.transforms.clear
|
80
|
+
TestMigration.loads.clear
|
81
|
+
|
82
|
+
# Intercept the delegated component creations
|
83
|
+
Migratrix::Extractions::ActiveRecord.should_receive(:new).with(:animals, {:source => Object }).and_return(extraction)
|
84
|
+
|
85
|
+
Migratrix::Transforms::Map.should_receive(:new).with(:monkeys, :transform => map, :extraction => :animals).and_return(transform1)
|
86
|
+
Migratrix::Transforms::Map.should_receive(:new).with(:puppies, :transform => map, :extraction => :animals).and_return(transform2)
|
87
|
+
|
88
|
+
Migratrix::Loads::Yaml.should_receive(:new).with(:monkeys, :filename => '/tmp/monkeys.yml').and_return(load1)
|
89
|
+
Migratrix::Loads::Yaml.should_receive(:new).with(:puppies, :filename => '/tmp/puppies.yml').and_return(load2)
|
90
|
+
|
91
|
+
|
92
|
+
TestMigration.set_extraction :animals, :active_record, :source => Object
|
93
|
+
TestMigration.set_transform :monkeys, :map, :transform => map, :extraction => :animals
|
94
|
+
TestMigration.set_transform :puppies, :map, :transform => map, :extraction => :animals
|
95
|
+
TestMigration.set_load :monkeys, :yaml, :filename => '/tmp/monkeys.yml'
|
96
|
+
TestMigration.set_load :puppies, :yaml, :filename => '/tmp/puppies.yml'
|
97
|
+
end
|
98
|
+
|
99
|
+
describe ".set_extraction" do
|
100
|
+
it "sets the class instance variable for extraction" do
|
101
|
+
TestMigration.extractions[:animals].should == extraction
|
102
|
+
end
|
103
|
+
|
104
|
+
it "also sets convenience instance method for extraction" do
|
105
|
+
TestMigration.new.extractions[:animals].should == extraction
|
106
|
+
end
|
39
107
|
end
|
40
108
|
|
41
|
-
describe ".
|
42
|
-
it "sets the class
|
43
|
-
|
109
|
+
describe ".set_transform" do
|
110
|
+
it "sets the class instance variable for transforms" do
|
111
|
+
TestMigration.transforms.should == { :monkeys => transform1, :puppies => transform2 }
|
44
112
|
end
|
45
113
|
|
46
|
-
it "also sets convenience instance method for
|
47
|
-
|
114
|
+
it "also sets convenience instance method for transform" do
|
115
|
+
TestMigration.new.transforms.should == { :monkeys => transform1, :puppies => transform2 }
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
describe ".set_load" do
|
120
|
+
it "sets the class instance variable for loads" do
|
121
|
+
TestMigration.loads.should == { :monkeys => load1, :puppies => load2 }
|
122
|
+
end
|
123
|
+
|
124
|
+
it "also sets convenience instance method for load" do
|
125
|
+
TestMigration.new.loads.should == { :monkeys => load1, :puppies => load2 }
|
48
126
|
end
|
49
127
|
end
|
50
128
|
|
51
129
|
describe "#extract" do
|
52
|
-
it "delegates to
|
53
|
-
|
130
|
+
it "delegates to extraction" do
|
131
|
+
extraction.should_receive(:extract).and_return([42,13,43,14])
|
132
|
+
migration.extract.should == {:animals => [42,13,43,14]}
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
describe "#transform" do
|
137
|
+
it "should pass named extracted_items to each transform" do
|
138
|
+
transform1.should_receive(:transform).with([42,13,43,14])
|
139
|
+
transform2.should_receive(:transform).with([42,13,43,14])
|
140
|
+
|
141
|
+
migration = TestMigration.new
|
142
|
+
migration.transform(extraction.extract)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
describe "#load" do
|
147
|
+
it "should pass named transformed_items to each load" do
|
148
|
+
load1.should_receive(:load).with([{:id => 42, :name => "Mister Bobo"}, {:id => 43, :name => "Mrs. Bobo"}])
|
149
|
+
load2.should_receive(:load).with([{:id => 13, :name => "Sparky"}, {:id => 14, :name => "Fido"}])
|
150
|
+
|
151
|
+
migration = TestMigration.new
|
152
|
+
extracteds = extraction.extract
|
153
|
+
transforms = migration.transform(extracteds)
|
154
|
+
migration.load(transforms)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
describe ".valid_options" do
|
159
|
+
it "returns valid options from itself and components" do
|
160
|
+
TestMigration.valid_options.should == [:console, :fetchall, :limit, :map, :offset, :order, :where]
|
54
161
|
end
|
55
162
|
end
|
56
163
|
end
|
57
|
-
end
|
58
164
|
|
165
|
+
describe "extending" do
|
166
|
+
before do
|
167
|
+
[TestMigration, ChildMigration1, ChildMigration2, GrandchildMigration1].each do |klass|
|
168
|
+
[:extractions, :transforms, :loads].each do |kollection|
|
169
|
+
klass.send(kollection).send(:clear)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
TestMigration.set_extraction :cheese, :extraction, { first_option: 'id>100' }
|
173
|
+
TestMigration.set_transform :cheese, :transform, { first_option: 'id>100' }
|
174
|
+
TestMigration.set_load :cheese, :load, { first_option: 'id>100' }
|
175
|
+
|
176
|
+
end
|
177
|
+
|
178
|
+
[:extraction, :transform, :load ].each do |component|
|
179
|
+
describe "#{component}" do
|
180
|
+
it "extends the #{component} to child class" do
|
181
|
+
ChildMigration1.send("extend_#{component}", :cheese, { second_option: 2 })
|
182
|
+
ChildMigration1.new.send("#{component}s")[:cheese].options.should == { second_option: 2, first_option: 'id>100'}
|
183
|
+
end
|
184
|
+
|
185
|
+
it "extends the #{component} to the grandchild class" do
|
186
|
+
ChildMigration1.send("extend_#{component}", :cheese, { second_option: 2 })
|
187
|
+
GrandchildMigration1.send("extend_#{component}", :cheese, { surprise_option: 50 })
|
188
|
+
GrandchildMigration1.new.send("#{component}s")[:cheese].options.should == { second_option: 2, first_option: 'id>100', surprise_option: 50 }
|
189
|
+
end
|
190
|
+
|
191
|
+
it "extends the #{component} to the grandchild class even if the child class does not extend" do
|
192
|
+
GrandchildMigration1.send("extend_#{component}", :cheese, { surprise_option: 50 })
|
193
|
+
GrandchildMigration1.new.send("#{component}s")[:cheese].options.should == { first_option: 'id>100', surprise_option: 50 }
|
194
|
+
end
|
195
|
+
|
196
|
+
it "overrides parent options" do
|
197
|
+
ChildMigration1.send("extend_#{component}", :cheese, { second_option: 2, first_option: 'id>50' })
|
198
|
+
ChildMigration1.new.send("#{component}s")[:cheese].options.should == { second_option: 2, first_option: 'id>50'}
|
199
|
+
end
|
200
|
+
|
201
|
+
it "does not affect sibling class options" do
|
202
|
+
ChildMigration1.send("extend_#{component}", :cheese, { second_option: 2, first_option: 'id>50' })
|
203
|
+
ChildMigration2.send("extend_#{component}", :cheese, { zany_option: Hash, first_option: 'id>75' })
|
204
|
+
ChildMigration1.new.send("#{component}s")[:cheese].options.should == { second_option: 2, first_option: 'id>50'}
|
205
|
+
ChildMigration2.new.send("#{component}s")[:cheese].options.should == { zany_option: Hash, first_option: 'id>75'}
|
206
|
+
end
|
207
|
+
|
208
|
+
it "does not affect parent class options" do
|
209
|
+
ChildMigration1.send("extend_#{component}", :cheese, { second_option: 2, first_option: 'id>50' })
|
210
|
+
ChildMigration1.new.send("#{component}s")[:cheese].options.should == { second_option: 2, first_option: 'id>50'}
|
211
|
+
TestMigration.new.send("#{component}s")[:cheese].options.should == { first_option: 'id>100'}
|
212
|
+
end
|
213
|
+
|
214
|
+
it "raises #{component.capitalize}NotDefined if no parent has that #{component}" do
|
215
|
+
exception = "Migratrix::#{component.capitalize}NotDefined".constantize
|
216
|
+
lambda { ChildMigration1.send("extend_#{component}", :blargle, { second_option: 2, first_option: 'id>50' }) }.should raise_error(exception)
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
|
222
|
+
# TODO: lambdas cannot be deep-copied, and form closures at the
|
223
|
+
# time of creation. Is there a way to detect if a lambda has a
|
224
|
+
# closure?
|
225
|
+
end
|
226
|
+
end
|
59
227
|
|