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.
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