migratrix 0.0.9 → 0.8.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/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
|
|