diff options
author | Aaron Patterson <aaron.patterson@gmail.com> | 2012-12-21 13:22:52 -0800 |
---|---|---|
committer | Aaron Patterson <aaron.patterson@gmail.com> | 2012-12-21 13:22:52 -0800 |
commit | 68e91da765565f0c473463b0b47814592dea5de3 (patch) | |
tree | 7b97a7d3ac2788c3acc2da90245208e844621710 /activerecord/lib/active_record/migration.rb | |
parent | 59ea907a30438a3aa458ef18f0ccb6ceadb8322d (diff) | |
parent | a81845f26864d076970e706863c766aead432672 (diff) | |
download | rails-68e91da765565f0c473463b0b47814592dea5de3.tar.gz rails-68e91da765565f0c473463b0b47814592dea5de3.tar.bz2 rails-68e91da765565f0c473463b0b47814592dea5de3.zip |
Merge pull request #8267 from marcandre/reversible_drop_table_etc
Reversible commands
Diffstat (limited to 'activerecord/lib/active_record/migration.rb')
-rw-r--r-- | activerecord/lib/active_record/migration.rb | 162 |
1 files changed, 132 insertions, 30 deletions
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index ef2107ad24..806b367c6b 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -373,22 +373,129 @@ module ActiveRecord @name = name @version = version @connection = nil - @reverting = false end # instantiate the delegate object after initialize is defined self.verbose = true self.delegate = new - def revert - @reverting = true - yield - ensure - @reverting = false + # Reverses the migration commands for the given block and + # the given migrations. + # + # The following migration will remove the table 'horses' + # and create the table 'apples' on the way up, and the reverse + # on the way down. + # + # class FixTLMigration < ActiveRecord::Migration + # def change + # revert do + # create_table(:horses) do |t| + # t.text :content + # t.datetime :remind_at + # end + # end + # create_table(:apples) do |t| + # t.string :variety + # end + # end + # end + # + # Or equivalently, if +TenderloveMigration+ is defined as in the + # documentation for Migration: + # + # require_relative '2012121212_tenderlove_migration' + # + # class FixupTLMigration < ActiveRecord::Migration + # def change + # revert TenderloveMigration + # + # create_table(:apples) do |t| + # t.string :variety + # end + # end + # end + # + # This command can be nested. + def revert(*migration_classes) + run(*migration_classes.reverse, revert: true) unless migration_classes.empty? + if block_given? + if @connection.respond_to? :revert + @connection.revert { yield } + else + recorder = CommandRecorder.new(@connection) + @connection = recorder + suppress_messages do + @connection.revert { yield } + end + @connection = recorder.delegate + recorder.commands.each do |cmd, args, block| + send(cmd, *args, &block) + end + end + end end def reverting? - @reverting + @connection.respond_to?(:reverting) && @connection.reverting + 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. + # Last argument can specify options: + # - :direction (default is :up) + # - :revert (default is false) + def run(*migration_classes) + opts = migration_classes.extract_options! + dir = opts[:direction] || :up + dir = (dir == :down ? :up : :down) if opts[:revert] + if reverting? + # If in revert and going :up, say, we want to execute :down without reverting, so + revert { run(*migration_classes, direction: dir, revert: true) } + else + migration_classes.each do |migration_class| + migration_class.new.exec_migration(@connection, dir) + end + end end def up @@ -414,29 +521,9 @@ module ActiveRecord time = nil ActiveRecord::Base.connection_pool.with_connection do |conn| - @connection = conn - if respond_to?(:change) - if direction == :down - recorder = CommandRecorder.new(@connection) - suppress_messages do - @connection = recorder - change - end - @connection = conn - time = Benchmark.measure { - self.revert { - recorder.inverse.each do |cmd, args| - send(cmd, *args) - end - } - } - else - time = Benchmark.measure { change } - end - else - time = Benchmark.measure { send(direction) } + time = Benchmark.measure do + exec_migration(conn, direction) end - @connection = nil end case direction @@ -445,6 +532,21 @@ module ActiveRecord end end + def exec_migration(conn, direction) + @connection = conn + if respond_to?(:change) + if direction == :down + revert { change } + else + change + end + else + send(direction) + end + ensure + @connection = nil + end + def write(text="") puts(text) if verbose end @@ -483,7 +585,7 @@ module ActiveRecord arg_list = arguments.map{ |a| a.inspect } * ', ' say_with_time "#{method}(#{arg_list})" do - unless reverting? + unless @connection.respond_to? :revert unless arguments.empty? || method == :execute arguments[0] = Migrator.proper_table_name(arguments.first) arguments[1] = Migrator.proper_table_name(arguments.second) if method == :rename_table |