migratrix 0.8.3 → 0.8.5

Sign up to get free protection for your applications and to get access to all the features.
data/lib/migratrix.rb CHANGED
@@ -10,6 +10,7 @@ module Migratrix
10
10
  require EXT + 'object_ext'
11
11
  require EXT + 'andand'
12
12
  require APP + 'exceptions'
13
+ require APP + 'active_record_migration_helpers'
13
14
 
14
15
  require APP + 'callbacks'
15
16
  require APP + 'loggable'
@@ -20,12 +21,15 @@ module Migratrix
20
21
  require APP + 'migratrix'
21
22
 
22
23
  require APP + 'extractions/extraction'
24
+ require APP + 'extractions/no_op'
23
25
  require APP + 'extractions/active_record'
24
26
 
25
27
  require APP + 'transforms/transform'
28
+ require APP + 'transforms/no_op'
26
29
  require APP + 'transforms/map'
27
30
 
28
31
  require APP + 'loads/load'
32
+ require APP + 'loads/no_op'
29
33
  require APP + 'loads/yaml'
30
34
  # require APP + 'loads/csv'
31
35
  # require APP + 'loads/active_record'
@@ -73,11 +77,15 @@ module Migratrix
73
77
  # Register in-gem Components
74
78
  register_extraction :extraction, Extractions::Extraction
75
79
  register_extraction :active_record, Extractions::ActiveRecord
80
+ register_extraction :no_op, Extractions::NoOp
76
81
 
77
82
  register_transform :transform, Transforms::Transform
78
83
  register_transform :map, Transforms::Map
84
+ register_transform :no_op, Transforms::NoOp
79
85
 
80
86
  register_load :load, Loads::Load
87
+ register_load :no_op, Loads::NoOp
81
88
  register_load :yaml, Loads::Yaml
89
+
82
90
  end
83
91
 
@@ -1,57 +1,64 @@
1
+ require 'active_support/concern'
2
+
1
3
  module Migratrix
2
4
  module ActiveRecordMigrationHelpers
5
+ extend ActiveSupport::Concern
3
6
 
4
- # Executes a query on this class' connection, and logs the query
5
- # or an optional message to Migratrix.log.
6
- def execute(query, msg=nil)
7
- Migratrix.log(msg || query) unless msg == false
8
- connection.execute query
9
- end
7
+ module ClassMethods
8
+ # Executes a query on this class' connection, and logs the query
9
+ # or an optional message to Migratrix.logger.
10
+ def execute(query, msg=nil)
11
+ Migratrix.logger.info(msg || query) unless msg == false
12
+ connection.execute query
13
+ end
10
14
 
11
- # MySQL ONLY: truncates a table using TRUNCATE, which drops the
12
- # data very quickly and resets any autoindexing primary key to 1.
13
- def mysql_truncate(table)
14
- execute("TRUNCATE #{table}")
15
- end
15
+ # MySQL ONLY: truncates a table using TRUNCATE, which drops the
16
+ # data very quickly and resets any autoindexing primary key to 1.
17
+ def mysql_truncate(table=nil)
18
+ table ||= table_name
19
+ execute("TRUNCATE #{table}")
20
+ end
16
21
 
17
- # PostGreSQL ONLY: truncates a table by deleting all its rows and
18
- # restarting its id sequence at 1.
19
- #
20
- # Note: TRUNCATE was added to PostGreSQL in version 8.3, which at
21
- # the time of this writing is still poorly adopted. This code
22
- # works on earlier versions, is MVCC-safe, and will trigger
23
- # cascading deletes.
24
- #
25
- # It does NOT, however, actually look up the table's sequence
26
- # definition. It assumes the sequence for a is named a_id_seq and
27
- # that it should be reset to 1. (A tiny dash of extra cleverness
28
- # is all that would be needed to read start_value from the
29
- # sequence, but for now this is a pure-SQL, stateless call.)
30
- def psql_truncate(table)
31
- execute("DELETE FROM #{table}; ALTER SEQUENCE #{table}_id_seq RESTART WITH 1")
32
- end
22
+ # PostGreSQL ONLY: truncates a table by deleting all its rows and
23
+ # restarting its id sequence at 1.
24
+ #
25
+ # Note: TRUNCATE was added to PostGreSQL in version 8.3, which at
26
+ # the time of this writing is still poorly adopted. This code
27
+ # works on earlier versions, is MVCC-safe, and will trigger
28
+ # cascading deletes.
29
+ #
30
+ # It does NOT, however, actually look up the table's sequence
31
+ # definition. It assumes the sequence for a is named a_id_seq and
32
+ # that it should be reset to 1. (A tiny dash of extra cleverness
33
+ # is all that would be needed to read start_value from the
34
+ # sequence, but for now this is a pure-SQL, stateless call.)
35
+ def psql_truncate(table=nil)
36
+ table ||= table_name
37
+ execute("DELETE FROM #{table}; ALTER SEQUENCE #{table}_id_seq RESTART WITH 1")
38
+ end
33
39
 
34
- # MySQL ONLY: Disables indexes on a table and locks it for
35
- # writing, optionally read-locks another list of tables, then
36
- # yields to the given block before unlocking. This prevents MySQL
37
- # from indexing the migrated data until the block is complete.
38
- # This produces a significant speedup on InnoDB tables with
39
- # multiple indexes. (The plural of anecdote is not data, but on
40
- # one heavily-indexed table, migrating 10 million records took 38
41
- # hours with indexes enabled and under 2 hours with them
42
- # disabled.)
43
- def with_mysql_indexes_disabled_on(table, *read_locked_tables, &block)
44
- log "Locking table '#{table}' and disabling indexes..."
45
- lock_cmd = "LOCK TABLES `#{table}` WRITE"
46
- if read_locked_tables.andand.size > 0
47
- lock_cmd += ', ' + (read_locked_tables.map {|t| "#{t} READ"} * ", ")
40
+ # MySQL ONLY: Disables indexes on a table and locks it for
41
+ # writing, optionally read-locks another list of tables, then
42
+ # yields to the given block before unlocking. This prevents MySQL
43
+ # from indexing the migrated data until the block is complete.
44
+ # This produces a significant speedup on InnoDB tables with
45
+ # multiple indexes. (The plural of anecdote is not data, but on
46
+ # one heavily-indexed table, migrating 10 million records took 38
47
+ # hours with indexes enabled and under 2 hours with them
48
+ # disabled.)
49
+ def with_mysql_indexes_disabled_on(table, *read_locked_tables, &block)
50
+ info "Locking table '#{table}' and disabling indexes..."
51
+ lock_cmd = "LOCK TABLES `#{table}` WRITE"
52
+ if read_locked_tables.andand.size > 0
53
+ lock_cmd += ', ' + (read_locked_tables.map {|t| "#{t} READ"} * ", ")
54
+ end
55
+ execute lock_cmd
56
+ execute("/*!40000 ALTER TABLE `#{table}` DISABLE KEYS */")
57
+ yield
58
+ info "Unlocking table '#{table}' and re-enabling indexes..."
59
+ execute("/*!40000 ALTER TABLE `#{table}` ENABLE KEYS */")
60
+ execute("UNLOCK TABLES")
48
61
  end
49
- execute lock_cmd
50
- execute("/*!40000 ALTER TABLE `#{table}` DISABLE KEYS */")
51
- yield
52
- log "Unlocking table '#{table}' and re-enabling indexes..."
53
- execute("/*!40000 ALTER TABLE `#{table}` ENABLE KEYS */")
54
- execute("UNLOCK TABLES")
55
62
  end
56
63
  end
57
64
  end
@@ -2,7 +2,7 @@ module Migratrix
2
2
  module Extractions
3
3
  # Extraction that expects to be pointed at an ActiveRecord class.
4
4
  class ActiveRecord < Extraction
5
- set_valid_options :fetchall
5
+ set_valid_options :fetchall, :includes, :joins
6
6
 
7
7
  def source=(new_source)
8
8
  raise TypeError.new(":source is of type must be an ActiveRecord model class (must inherit from ActiveRecord::Base)") unless is_ar?(new_source)
@@ -15,10 +15,25 @@ module Migratrix
15
15
 
16
16
  def obtain_source(source, options={})
17
17
  raise ExtractionSourceUndefined unless source
18
- raise TypeError.new(":source is of type must be an ActiveRecord model class (must inherit from ActiveRecord::Base)") unless is_ar?(source)
19
18
  source
20
19
  end
21
20
 
21
+ def process_source(source, options={})
22
+ options = @options.merge(options)
23
+ source = super source, options
24
+ source = handle_joins(source, options[:joins]) if options[:joins]
25
+ source = handle_includes(source, options[:includes]) if options[:includes]
26
+ source
27
+ end
28
+
29
+ def handle_joins(source, clause)
30
+ source.joins(clause)
31
+ end
32
+
33
+ def handle_includes(source, clause)
34
+ source.includes(clause)
35
+ end
36
+
22
37
  def handle_where(source, clause)
23
38
  source.where(clause)
24
39
  end
@@ -36,8 +51,33 @@ module Migratrix
36
51
  end
37
52
 
38
53
  # Constructs the query
39
- def to_query(source)
40
- if source.is_a?(::ActiveRecord::Relation)
54
+ #
55
+ # TODO: A bit of a POLS violation here. Let's say you define a
56
+ # migration class that has an extraction, then instantiate that
57
+ # migration with m = MyMigration.new(where: 'id=42'). You might
58
+ # expect to be abe to call m.extractions[:default].to_sql and
59
+ # get the query, but the reality is that the extraction hasn't
60
+ # actually seen the migration's options yet. They are passed in
61
+ # to extract, but not to to_sql. Not sure how to resolve this
62
+ # cleanly. Some options: 1. have Components know who their
63
+ # Migration is and ask it for its options (blegh); 2. pass in
64
+ # options here to to_sql (also blegh, because now end-users have
65
+ # to know they need to pass in the migration's options to
66
+ # to_sql); 3. have a proxy method on Migration that essentially
67
+ # works just like extract, but returns the query instead of the
68
+ # results. I dislike this mechanism the least but it's still
69
+ # only applicable to certain subclasses of Extraction so I
70
+ # hesitate to clutter Migration's API; 4. change
71
+ # Migration#extractions (et al) so it settles its options with
72
+ # the component before returning it. This seems like it would
73
+ # appear the cleanest to the end user but also seems like it
74
+ # would be excessively magical and a weird source of bugs--eg if
75
+ # you try to access migration.loads[:cheese] and it crashes
76
+ # because of an invalid option in loads[:wine].
77
+ def to_sql(source=nil)
78
+ source ||= @source
79
+ source = process_source(obtain_source(source))
80
+ if source.respond_to? :to_sql
41
81
  source.to_sql
42
82
  else
43
83
  handle_where(source, 1).to_sql
@@ -46,7 +86,7 @@ module Migratrix
46
86
 
47
87
  def execute_extract(src, options={})
48
88
  return src.all if options['fetchall']
49
- ret = if src.is_a?(::ActiveRecord::Relation)
89
+ ret = if src.respond_to? :to_sql
50
90
  src
51
91
  else
52
92
  handle_where(src, 1)
@@ -15,6 +15,8 @@ module Migratrix
15
15
  end
16
16
 
17
17
  def extract(options={})
18
+ options = options.deep_copy
19
+ options[:where] = Array(options[:where]) + Array(@options[:where])
18
20
  options = @options.merge(options).symbolize_keys
19
21
 
20
22
  # TODO: Raise error if self.abstract? DANGER/NOTE that this is
@@ -22,15 +24,23 @@ module Migratrix
22
24
  # extracted to a strategy object.
23
25
 
24
26
  src = obtain_source(self.source, options)
25
- src = handle_where(src, options[:where]) if options[:where]
26
- src = handle_order(src, options[:order]) if options[:order]
27
- src = handle_limit(src, options[:limit]) if options[:limit]
28
- src = handle_offset(src, options[:offset]) if options[:offset]
27
+ src = process_source(src, options)
29
28
  execute_extract(src, options)
30
29
  end
31
30
 
31
+ def process_source(source, options)
32
+ if options[:where]
33
+ options[:where].each do |where|
34
+ source = handle_where(source, where)
35
+ end
36
+ end
37
+ source = handle_order(source, options[:order]) if options[:order]
38
+ source = handle_limit(source, options[:limit]) if options[:limit]
39
+ source = handle_offset(source, options[:offset]) if options[:offset]
40
+ source
41
+ end
32
42
 
33
- # = extraction filter methods
43
+ # = extraction filter methods
34
44
  #
35
45
  # The handle_* methods receive a source and return a source and
36
46
  # must be chainable. For example, source might come in as an
@@ -55,28 +65,22 @@ module Migratrix
55
65
  end
56
66
 
57
67
  # Apply where clause to source, return new source.
58
- def handle_where(source)
68
+ def handle_where(source, where)
59
69
  raise NotImplementedError
60
70
  end
61
71
 
62
72
  # Apply limit clause to source, return new source.
63
- def handle_limit(source)
73
+ def handle_limit(source, limit)
64
74
  raise NotImplementedError
65
75
  end
66
76
 
67
77
  # Apply offset clause to source, return new source.
68
- def handle_offset(source)
78
+ def handle_offset(source, offset)
69
79
  raise NotImplementedError
70
80
  end
71
81
 
72
82
  # Apply order clause to source, return new source.
73
- def handle_order(source)
74
- raise NotImplementedError
75
- end
76
-
77
- # Constructs the query, if applicable. May not exist or make
78
- # sense for non-SQL and/or non-ActiveRecord extractions.
79
- def to_query(source)
83
+ def handle_order(source, order)
80
84
  raise NotImplementedError
81
85
  end
82
86
 
@@ -0,0 +1,11 @@
1
+ module Migratrix
2
+ module Extractions
3
+ # An Extraction object that does nothing. Useful for plugging into
4
+ # a Migration when you need to debug other parts of the migration
5
+ class NoOp < Extraction
6
+ def extract(options={})
7
+ []
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ module Migratrix
2
+ module Loads
3
+ # A Load object that does nothing. Useful for plugging into a
4
+ # Migration when you need to debug other parts of the migration
5
+ class NoOp < Load
6
+ def load(transformed_items, options={})
7
+ []
8
+ end
9
+ end
10
+ end
11
+ end
@@ -63,7 +63,8 @@ module Migratrix
63
63
  extractions[nickname] = Migratrix.extraction(nickname, registered_name, options)
64
64
  end
65
65
 
66
- def self.extend_extraction(nickname, options={})
66
+ def self.extend_extraction(nickname, options=nil)
67
+ nickname, options = :default, nickname if options.nil?
67
68
  migration = ancestors.detect {|k| k.respond_to?(:extractions) && k.extractions[nickname]}
68
69
  raise ExtractionNotDefined.new("Could not extend extraction '%s'; no parent Migration defines it" % nickname) unless migration
69
70
  extraction = migration.extractions[nickname]
@@ -104,11 +105,12 @@ module Migratrix
104
105
  transforms[nickname] = Migratrix.transform(nickname, registered_name, options)
105
106
  end
106
107
 
107
- def self.extend_transform(transform_name, options={})
108
- migration = ancestors.detect {|k| k.respond_to?(:transforms) && k.transforms[transform_name]}
109
- raise TransformNotDefined.new("Could not extend extractar '%s'; no parent Migration defines it" % transform_name) unless migration
110
- transform = migration.transforms[transform_name]
111
- transforms[transform_name] = transform.class.new(transform_name, transform.options.merge(options))
108
+ def self.extend_transform(nickname, options=nil)
109
+ nickname, options = :default, nickname if options.nil?
110
+ migration = ancestors.detect {|k| k.respond_to?(:transforms) && k.transforms[nickname]}
111
+ raise TransformNotDefined.new("Could not extend transform '%s'; no parent Migration defines it" % nickname) unless migration
112
+ transform = migration.transforms[nickname]
113
+ transforms[nickname] = transform.class.new(nickname, transform.options.merge(options))
112
114
  end
113
115
 
114
116
  def self.transforms
@@ -149,11 +151,12 @@ module Migratrix
149
151
  # loads[name] = Migratrix.load(name, type, options)
150
152
  # end
151
153
 
152
- def self.extend_load(load_name, options={})
153
- migration = ancestors.detect {|k| k.respond_to?(:loads) && k.loads[load_name]}
154
- raise LoadNotDefined.new("Could not extend extractar '%s'; no parent Migration defines it" % load_name) unless migration
155
- load = migration.loads[load_name]
156
- loads[load_name] = load.class.new(load_name, load.options.merge(options))
154
+ def self.extend_load(nickname, options=nil)
155
+ nickname, options = :default, nickname if options.nil?
156
+ migration = ancestors.detect {|k| k.respond_to?(:loads) && k.loads[nickname]}
157
+ raise LoadNotDefined.new("Could not extend load '%s'; no parent Migration defines it" % nickname) unless migration
158
+ load = migration.loads[nickname]
159
+ loads[nickname] = load.class.new(nickname, load.options.merge(options))
157
160
  end
158
161
 
159
162
  def self.loads
@@ -0,0 +1,11 @@
1
+ module Migratrix
2
+ module Transforms
3
+ # A Transform object that does nothing. Useful for plugging into a
4
+ # Migration when you need to debug other parts of the migration
5
+ class NoOp < Transform
6
+ def transform(extracted_items, options={})
7
+ []
8
+ end
9
+ end
10
+ end
11
+ end
@@ -226,14 +226,18 @@ module Migratrix
226
226
 
227
227
  def extract_attribute(object, attribute_or_extract)
228
228
  raise NotImplementedError unless options[:extract_attribute]
229
- option = options[:extract_attribute]
230
- case option
231
- when Proc
232
- option.call(object, attribute_or_extract)
233
- when Symbol
234
- object.send(option, attribute_or_extract)
229
+ if attribute_or_extract.is_a? Proc
230
+ attribute_or_extract.call(object)
235
231
  else
236
- raise TypeError
232
+ option = options[:extract_attribute]
233
+ case option
234
+ when Proc
235
+ option.call(object, attribute_or_extract)
236
+ when Symbol
237
+ object.send(option, attribute_or_extract)
238
+ else
239
+ raise TypeError
240
+ end
237
241
  end
238
242
  end
239
243
 
@@ -1,6 +1,22 @@
1
1
  class Object
2
+ # Make a deep copy of an object as safely as possible. This was
3
+ # originally a simple Marshal::load(Marshal::dump(self)) but it
4
+ # turns out in Migratrix that we frequently need to copy IO streams
5
+ # and lambdas, neither of which can be marshaled. Then it was a
6
+ # simple dup, but some singleton types like Fixnum cannot be dup'ed.
7
+ # So now we have this monstrosity.
2
8
  def deep_copy
3
- Marshal::load(Marshal::dump(self))
9
+ if is_a?(Array)
10
+ map(&:deep_copy)
11
+ elsif is_a?(Hash)
12
+ Hash[to_a.map {|k,v| [k, v.deep_copy]}]
13
+ else
14
+ begin
15
+ Marshal::load(Marshal::dump(self))
16
+ rescue TypeError
17
+ dup
18
+ end
19
+ end
4
20
  end
5
21
  end
6
22
 
@@ -1,5 +1,3 @@
1
- require 'no_op_components'
2
-
3
1
  class TestCallbackMigration < Migratrix::Migration
4
2
  set_extraction :test, :no_op
5
3
  set_transform :test, :no_op
@@ -127,9 +125,6 @@ end
127
125
  describe "callbacks" do
128
126
  describe "sanity check cat" do
129
127
  it "is sanity checked" do
130
- NoOpExtraction.should_not be_nil
131
- NoOpTransform.should_not be_nil
132
- NoOpLoad.should_not be_nil
133
128
  TestCallbackMigration.should_not be_nil
134
129
  MethodCallbackMigration.should_not be_nil
135
130
  end
@@ -30,13 +30,13 @@ describe Migratrix::Extractions::ActiveRecord do
30
30
 
31
31
  describe ".local_valid_options" do
32
32
  it "returns the valid set of option keys" do
33
- Migratrix::Extractions::ActiveRecord.local_valid_options.should == [:fetchall]
33
+ Migratrix::Extractions::ActiveRecord.local_valid_options.should == [:fetchall, :includes, :joins]
34
34
  end
35
35
  end
36
36
 
37
37
  describe ".valid_options" do
38
38
  it "returns the valid set of option keys" do
39
- Migratrix::Extractions::ActiveRecord.valid_options.should == [:fetchall] + Migratrix::Extractions::Extraction.valid_options
39
+ Migratrix::Extractions::ActiveRecord.valid_options.should == [:fetchall, :includes, :joins] + Migratrix::Extractions::Extraction.valid_options
40
40
  end
41
41
  end
42
42
 
@@ -57,7 +57,7 @@ describe Migratrix::Extractions::ActiveRecord do
57
57
  extraction.source = source
58
58
  end
59
59
 
60
- [:where, :order, :limit, :offset].each do |handler|
60
+ [:where, :order, :limit, :offset, :includes, :joins].each do |handler|
61
61
  describe "#handle_#{handler}" do
62
62
  it "calls #{handler} on the source ActiveRelation" do
63
63
  source.should_receive(handler).with(1).and_return(nil)
@@ -67,7 +67,7 @@ describe Migratrix::Extractions::ActiveRecord do
67
67
  end
68
68
  end
69
69
 
70
- describe "#to_query" do
70
+ describe "#to_sql" do
71
71
  let(:source) { TestModel }
72
72
  let(:relation) { source.where(1) }
73
73
  let(:lolquery) { 'SELECT "HAY GUYZ IM A QUARY LOL"' }
@@ -75,18 +75,27 @@ describe Migratrix::Extractions::ActiveRecord do
75
75
  extraction.source = source
76
76
  end
77
77
 
78
- describe "when source is ActiveRecord" do
78
+ describe "when source does not have to_sql (e.g. is ActiveRecord)" do
79
79
  it "converts it to ActiveRelation with where(1)" do
80
80
  extraction.should_receive(:handle_where).with(source, 1).and_return(relation)
81
81
  relation.should_receive(:to_sql).and_return(lolquery)
82
- extraction.to_query(source).should == lolquery
82
+ extraction.to_sql(source).should == lolquery
83
83
  end
84
84
  end
85
85
 
86
- describe "When source has already been converted to ActiveRelation" do
86
+ describe "When source responds to to_sql (e.g. is already an ActiveRelation)" do
87
87
  it "delegates to to_sql on source" do
88
+ relation.should_receive(:respond_to?).with(:to_sql).and_return(true)
88
89
  relation.should_receive(:to_sql).and_return(lolquery)
89
- extraction.to_query(relation).should == lolquery
90
+ extraction.to_sql(relation).should == lolquery
91
+ end
92
+ end
93
+
94
+ describe "with default source" do
95
+ it "uses @source" do
96
+ extraction = Migratrix::Extractions::ActiveRecord.new(:default, source: source)
97
+ source.should_receive(:to_sql).and_return(lolquery)
98
+ extraction.to_sql.should == lolquery
90
99
  end
91
100
  end
92
101
  end
@@ -19,10 +19,11 @@ describe Migratrix::Extractions::Extraction do
19
19
 
20
20
  describe "unimplemented methods:" do
21
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|
22
+ [:obtain_source, :handle_where, :handle_limit, :handle_offset, :handle_order, :execute_extract].each do |method|
23
23
  describe "#{method}" do
24
24
  it "raises NotImplementedError" do
25
- lambda { base_extraction.send(method, nil) }.should raise_error(NotImplementedError)
25
+ args = [nil, nil]
26
+ lambda { base_extraction.send(method, *args) }.should raise_error(NotImplementedError)
26
27
  end
27
28
  end
28
29
  end
@@ -31,17 +32,17 @@ describe Migratrix::Extractions::Extraction do
31
32
  describe "#extract (default strategy)" do
32
33
  describe "with no options" do
33
34
  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)
35
+ it "calls handle_source and execute_extract only" do
36
+ extraction.should_receive(:obtain_source).with(nil, {where: []}).and_return(13)
37
+ extraction.should_receive(:execute_extract).with(13, {where: []}).and_return(64)
37
38
  extraction.extract.should == 64
38
39
  end
39
40
  end
40
41
 
41
42
  describe "with all options" do
42
- let(:options) { { :where => 1, :order => 2, :limit => 3, :offset => 4 } }
43
+ let(:options) { { :where => [1], :order => 2, :limit => 3, :offset => 4 } }
43
44
  let(:extraction) { TestExtraction.new :test, options }
44
- it "should call entire handler chain" do
45
+ it "calls entire handler chain" do
45
46
  extraction.should_receive(:obtain_source).with(nil, options).and_return("A")
46
47
  extraction.should_receive(:handle_where).with("A", 1).and_return("B")
47
48
  extraction.should_receive(:handle_order).with("B", 2).and_return("C")
@@ -53,19 +54,27 @@ describe Migratrix::Extractions::Extraction do
53
54
  end
54
55
 
55
56
  describe "with overridden options" do
56
- let(:options) { { :where => 1, :order => 2, :limit => 3, :offset => 4 } }
57
+ let(:options) { { :where => [1], :order => 2, :limit => 3, :offset => 4 } }
57
58
  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")
59
+ let(:overrides) { {:where => [5], :order => 6, :limit => 7, :offset => 8 } }
60
+ let(:merged_options) { {:where => [5, 1], :order => 6, :limit => 7, :offset => 8 } }
61
+ it "calls entire handler chain, merging where clauses" do
62
+ extraction.should_receive(:obtain_source).with(nil, merged_options).and_return("A")
63
+ extraction.should_receive(:handle_where).exactly(2).times.and_return("B")
62
64
  extraction.should_receive(:handle_order).with("B", 6).and_return("C")
63
65
  extraction.should_receive(:handle_limit).with("C", 7).and_return("D")
64
66
  extraction.should_receive(:handle_offset).with("D", 8).and_return("E")
65
- extraction.should_receive(:execute_extract).with("E", overrides).and_return("BONG")
67
+ extraction.should_receive(:execute_extract).with("E", merged_options).and_return("BONG")
66
68
  extraction.extract(overrides).should == "BONG"
67
69
  end
68
70
  end
71
+
72
+ describe "with where clause" do
73
+ let(:options) { { :where => 1, :order => 2, :limit => 3, :offset => 4 } }
74
+ let(:extraction) { TestExtraction.new :test, options }
75
+ it "promotes where clause to array" do
76
+ end
77
+ end
69
78
  end
70
79
  end
71
80
 
@@ -1,5 +1,4 @@
1
1
  require 'spec_helper'
2
- require 'no_op_components'
3
2
  require 'test_migration'
4
3
  require 'inherited_migrations'
5
4
 
@@ -193,7 +192,7 @@ describe Migratrix::Migration do
193
192
  describe "#{component}" do
194
193
  it "extends the #{component} to child class" do
195
194
  ChildMigration1.send("extend_#{component}", :cheese, { second_option: 2 })
196
- ChildMigration1.new.send("#{component}s")[:cheese].options.should == { second_option: 2, first_option: 'id>100'}
195
+ ChildMigration1.new.send("#{component}s")[:cheese].options.should == { second_option: 2, first_option: 'id>100' }
197
196
  end
198
197
 
199
198
  it "extends the #{component} to the grandchild class" do
data/spec/spec_helper.rb CHANGED
@@ -24,13 +24,12 @@ module Rails
24
24
  end
25
25
  end
26
26
 
27
- Dir[SPEC + "support/**/*.rb"].each {|f| require f}
28
-
29
27
  require LIB + 'migratrix'
30
-
31
28
  $:.unshift SPEC + "fixtures/migrations/"
32
29
  $:.unshift SPEC + "fixtures/components/"
33
30
 
31
+ Dir[SPEC + "support/**/*.rb"].each {|f| require f}
32
+
34
33
 
35
34
  # Redirect singleton logger to logger of our choice, then release it
36
35
  # after the spec finishes or crashes.
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: migratrix
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.3
4
+ version: 0.8.5
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-10-26 00:00:00.000000000Z
12
+ date: 2011-10-31 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: trollop
16
- requirement: &2161676040 !ruby/object:Gem::Requirement
16
+ requirement: &2164754380 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,7 +21,7 @@ dependencies:
21
21
  version: '0'
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *2161676040
24
+ version_requirements: *2164754380
25
25
  description: Migratrix, a Rails legacy database migration tool supporting multiple
26
26
  strategies, including arbitrary n-ary migrations (1->n, n->1, n->m), arbitrary inputs
27
27
  and outputs (ActiveRecord, bare SQL, CSV) and migration logging
@@ -41,7 +41,9 @@ files:
41
41
  - lib/migratrix/exceptions.rb
42
42
  - lib/migratrix/extractions/active_record.rb
43
43
  - lib/migratrix/extractions/extraction.rb
44
+ - lib/migratrix/extractions/no_op.rb
44
45
  - lib/migratrix/loads/load.rb
46
+ - lib/migratrix/loads/no_op.rb
45
47
  - lib/migratrix/loads/yaml.rb
46
48
  - lib/migratrix/loggable.rb
47
49
  - lib/migratrix/migration.rb
@@ -49,12 +51,12 @@ files:
49
51
  - lib/migratrix/migratrix.rb
50
52
  - lib/migratrix/registry.rb
51
53
  - lib/migratrix/transforms/map.rb
54
+ - lib/migratrix/transforms/no_op.rb
52
55
  - lib/migratrix/transforms/transform.rb
53
56
  - lib/migratrix/valid_options.rb
54
57
  - lib/patches/andand.rb
55
58
  - lib/patches/object_ext.rb
56
59
  - lib/patches/string_ext.rb
57
- - spec/fixtures/components/no_op_components.rb
58
60
  - spec/fixtures/migrations/inherited_migrations.rb
59
61
  - spec/fixtures/migrations/test_migration.rb
60
62
  - spec/lib/migratrix/_loggable_spec.rb
@@ -1,22 +0,0 @@
1
- class NoOpExtraction < Migratrix::Extractions::Extraction
2
- def extract(options={})
3
- []
4
- end
5
- end
6
-
7
- class NoOpTransform < Migratrix::Transforms::Transform
8
- def transform(exts, options={})
9
- []
10
- end
11
- end
12
-
13
- class NoOpLoad < Migratrix::Loads::Load
14
- def load(trans, options={})
15
- []
16
- end
17
- end
18
-
19
- Migratrix::Migratrix.register_extraction :no_op, NoOpExtraction
20
- Migratrix::Migratrix.register_transform :no_op, NoOpTransform
21
- Migratrix::Migratrix.register_load :no_op, NoOpLoad
22
-