Skip to content

Commit

Permalink
Add Migration#reversible for reversible data operations [#8267]
Browse files Browse the repository at this point in the history
  • Loading branch information
marcandre committed Dec 21, 2012
1 parent 65e154f commit 99770e4
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 1 deletion.
41 changes: 41 additions & 0 deletions activerecord/lib/active_record/migration.rb
Expand Up @@ -439,6 +439,47 @@ def reverting?
@connection.respond_to?(:reverting) && @connection.reverting @connection.respond_to?(:reverting) && @connection.reverting
end end


class ReversibleBlockHelper < Struct.new(:reverting)
def up
yield unless reverting
end

def down
yield if reverting
end
end

# Used to specify an operation that can be run in one direction or another.
# Call the methods +up+ and +down+ of the yielded object to run a block
# only in one given direction.
# The whole block will be called in the right order within the migration.
#
# In the following example, the looping on users will always be done
# when the three columns 'first_name', 'last_name' and 'full_name' exist,
# even when migrating down:
#
# class SplitNameMigration < ActiveRecord::Migration
# def change
# add_column :users, :first_name, :string
# add_column :users, :last_name, :string
#
# reversible do |dir|
# User.reset_column_information
# User.all.each do |u|
# dir.up { u.first_name, u.last_name = u.full_name.split(' ') }
# dir.down { u.full_name = "#{u.first_name} #{u.last_name}" }
# u.save
# end
# end
#
# revert { add_column :users, :full_name, :string }
# end
# end
def reversible
helper = ReversibleBlockHelper.new(reverting?)
transaction{ yield helper }
end

# Runs the given migration classes. # Runs the given migration classes.
# Last argument can specify options: # Last argument can specify options:
# - :direction (default is :up) # - :direction (default is :up)
Expand Down
6 changes: 5 additions & 1 deletion activerecord/lib/active_record/migration/command_recorder.rb
Expand Up @@ -72,7 +72,7 @@ def respond_to?(*args) # :nodoc:


[:create_table, :create_join_table, :change_table, :rename_table, :add_column, :remove_column, [:create_table, :create_join_table, :change_table, :rename_table, :add_column, :remove_column,
:rename_index, :rename_column, :add_index, :remove_index, :add_timestamps, :remove_timestamps, :rename_index, :rename_column, :add_index, :remove_index, :add_timestamps, :remove_timestamps,
:change_column, :change_column_default, :add_reference, :remove_reference, :change_column, :change_column_default, :add_reference, :remove_reference, :transaction,
].each do |method| ].each do |method|
class_eval <<-EOV, __FILE__, __LINE__ + 1 class_eval <<-EOV, __FILE__, __LINE__ + 1
def #{method}(*args, &block) # def create_table(*args, &block) def #{method}(*args, &block) # def create_table(*args, &block)
Expand All @@ -85,6 +85,10 @@ def #{method}(*args, &block) # def create_table(*args, &block)


private private


def invert_transaction(args, &block)
[:transaction, args, block]
end

def invert_create_table(args) def invert_create_table(args)
[:drop_table, [args.first]] [:drop_table, [args.first]]
end end
Expand Down
39 changes: 39 additions & 0 deletions activerecord/test/cases/invertible_migration_test.rb
Expand Up @@ -28,6 +28,26 @@ def change
end end
end end


class InvertibleByPartsMigration < SilentMigration
attr_writer :test
def change
create_table("new_horses") do |t|
t.column :breed, :string
end
reversible do |dir|
@test.yield :both
dir.up { @test.yield :up }
dir.down { @test.yield :down }
end
revert do
create_table("horses") do |t|
t.column :content, :text
t.column :remind_at, :datetime
end
end
end
end

class NonInvertibleMigration < SilentMigration class NonInvertibleMigration < SilentMigration
def change def change
create_table("horses") do |t| create_table("horses") do |t|
Expand Down Expand Up @@ -107,6 +127,25 @@ def test_migrate_revert
assert !migration.connection.table_exists?("horses") assert !migration.connection.table_exists?("horses")
end end


def test_migrate_revert_by_part
InvertibleMigration.new.migrate :up
received = []
migration = InvertibleByPartsMigration.new
migration.test = ->(dir){
assert migration.connection.table_exists?("horses")
assert migration.connection.table_exists?("new_horses")
received << dir
}
migration.migrate :up
assert_equal [:both, :up], received
assert !migration.connection.table_exists?("horses")
assert migration.connection.table_exists?("new_horses")
migration.migrate :down
assert_equal [:both, :up, :both, :down], received
assert migration.connection.table_exists?("horses")
assert !migration.connection.table_exists?("new_horses")
end

def test_migrate_revert_whole_migration def test_migrate_revert_whole_migration
migration = InvertibleMigration.new migration = InvertibleMigration.new
[LegacyMigration, InvertibleMigration].each do |klass| [LegacyMigration, InvertibleMigration].each do |klass|
Expand Down

0 comments on commit 99770e4

Please sign in to comment.