migratrix 0.0.9 → 0.8.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (31) hide show
  1. data/lib/migratrix.rb +62 -6
  2. data/lib/migratrix/exceptions.rb +4 -1
  3. data/lib/migratrix/{extractors → extractions}/active_record.rb +14 -10
  4. data/lib/migratrix/{extractors/extractor.rb → extractions/extraction.rb} +21 -20
  5. data/lib/migratrix/loads/load.rb +43 -0
  6. data/lib/migratrix/loads/yaml.rb +15 -0
  7. data/lib/migratrix/migration.rb +115 -27
  8. data/lib/migratrix/migratrix.rb +43 -84
  9. data/lib/migratrix/registry.rb +20 -0
  10. data/lib/migratrix/transforms/map.rb +57 -0
  11. data/lib/migratrix/transforms/transform.rb +268 -0
  12. data/lib/migratrix/valid_options.rb +22 -0
  13. data/lib/patches/object_ext.rb +0 -4
  14. data/spec/fixtures/migrations/marbles_migration.rb +6 -4
  15. data/spec/lib/migratrix/{loggable_spec.rb → _loggable_spec.rb} +0 -0
  16. data/spec/lib/migratrix/extractions/active_record_spec.rb +146 -0
  17. data/spec/lib/migratrix/extractions/extraction_spec.rb +71 -0
  18. data/spec/lib/migratrix/loads/load_spec.rb +59 -0
  19. data/spec/lib/migratrix/loads/yaml_spec.rb +39 -0
  20. data/spec/lib/migratrix/migration_spec.rb +195 -27
  21. data/spec/lib/migratrix/migratrix_spec.rb +57 -85
  22. data/spec/lib/migratrix/registry_spec.rb +28 -0
  23. data/spec/lib/migratrix/transforms/map_spec.rb +55 -0
  24. data/spec/lib/migratrix/transforms/transform_spec.rb +134 -0
  25. data/spec/lib/migratrix_spec.rb +98 -0
  26. data/spec/lib/patches/object_ext_spec.rb +0 -7
  27. data/spec/spec_helper.rb +18 -13
  28. metadata +21 -10
  29. data/spec/lib/migratrix/extractors/active_record_spec.rb +0 -43
  30. data/spec/lib/migratrix/extractors/extractor_spec.rb +0 -63
  31. 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
@@ -1,8 +1,4 @@
1
1
  class Object
2
- def in?(array)
3
- array.include? self
4
- end
5
-
6
2
  def deep_copy
7
3
  Marshal::load(Marshal::dump(self))
8
4
  end
@@ -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
- @@migrated = false
7
+ @migrated = false
7
8
  end
8
9
 
9
10
  def migrate
10
- @@migrated = true
11
+ @migrated = true
11
12
  end
12
13
 
13
- def self.migrated?
14
- @@migrated
14
+ def migrated?
15
+ @migrated
15
16
  end
17
+ # :nocov:
16
18
  end
17
19
  end
@@ -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 Migratrix::TestMigration < Migratrix::Migration
5
+ class TestMigration < Migratrix::Migration
6
6
  end
7
7
 
8
- describe Migratrix::Migration do
9
- let(:migration) { Migratrix::TestMigration.new }
10
- let(:loggable) { Migratrix::TestMigration.new }
11
- it_should_behave_like "loggable"
8
+ class ChildMigration1 < TestMigration
9
+ end
12
10
 
13
- describe ".new" do
14
- it "does not modify given options hash" do
15
- conditions = ["id=? AND approved=?", 42, true]
16
- migration = Migratrix::TestMigration.new({ "where" => conditions })
11
+ class ChildMigration2 < TestMigration
12
+ end
17
13
 
18
- migration.options["where"][0] += " AND pants=?"
19
- migration.options["where"] << false
20
- migration.options["where"].should == ["id=? AND approved=? AND pants=?", 42, true, false]
21
- conditions.should == ["id=? AND approved=?", 42, true]
22
- end
23
- end
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 mock active_record extractor" do
35
- let(:mock_extractor) { mock("Extractor", :extract => 43)}
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
- Migratrix::Extractors::ActiveRecord.should_receive(:new).with({ :source => Object}).and_return(mock_extractor)
38
- Migratrix::TestMigration.class_eval "set_extractor :active_record, :source => Object"
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 ".set_extractor" do
42
- it "sets the class accessor for extractor" do
43
- Migratrix::TestMigration.extractor.should == mock_extractor
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 extractor" do
47
- Migratrix::TestMigration.new.extractor.should == mock_extractor
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 extractor" do
53
- migration.extract.should == 43
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