diff options
Diffstat (limited to 'activerecord')
-rw-r--r-- | activerecord/CHANGELOG.md | 15 | ||||
-rw-r--r-- | activerecord/lib/active_record/migration.rb | 44 | ||||
-rw-r--r-- | activerecord/test/cases/migration/logger_test.rb | 2 | ||||
-rw-r--r-- | activerecord/test/cases/migration_test.rb | 32 |
4 files changed, 85 insertions, 8 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 97616ffc58..a0ca36788d 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,5 +1,20 @@ ## Rails 4.0.0 (unreleased) ## +* Make it possible to execute migrations without a transaction even + if the database adapter supports DDL transactions. + Fixes #9483. + + Example: + + class ChangeEnum < ActiveRecord::Migration + self.disable_ddl_transaction! + def up + execute "ALTER TYPE model_size ADD VALUE 'new_value'" + end + end + + *Yves Senn* + * Assigning "0.0" to a nullable numeric column does not make it dirty. Fix #9034. diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index 823595a128..62e8881c4c 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -330,6 +330,23 @@ module ActiveRecord # # For a list of commands that are reversible, please see # <tt>ActiveRecord::Migration::CommandRecorder</tt>. + # + # == Transactional Migrations + # + # If the database adapter supports DDL transactions, all migrations will + # automatically be wrapped in a transaction. There are queries that you + # can't execute inside a transaction though, and for these situations + # you can turn the automatic transactions off. + # + # class ChangeEnum < ActiveRecord::Migration + # self.disable_ddl_transaction! + # def up + # execute "ALTER TYPE model_size ADD VALUE 'new_value'" + # end + # end + # + # Remember that you can still open your own transactions, even if you + # are in a Migration with <tt>self.disable_ddl_transaction!</tt>. class Migration autoload :CommandRecorder, 'active_record/migration/command_recorder' @@ -351,6 +368,7 @@ module ActiveRecord class << self attr_accessor :delegate # :nodoc: + attr_accessor :disable_ddl_transaction # :nodoc: end def self.check_pending! @@ -365,8 +383,16 @@ module ActiveRecord new.migrate direction end - cattr_accessor :verbose + # Disable DDL transactions for this migration. + def self.disable_ddl_transaction! + @disable_ddl_transaction = true + end + + def disable_ddl_transaction # :nodoc: + self.class.disable_ddl_transaction + end + cattr_accessor :verbose attr_accessor :name, :version def initialize(name = self.class.name, version = nil) @@ -375,8 +401,8 @@ module ActiveRecord @connection = nil end + self.verbose = true # instantiate the delegate object after initialize is defined - self.verbose = true self.delegate = new # Reverses the migration commands for the given block and @@ -663,7 +689,7 @@ module ActiveRecord File.basename(filename) end - delegate :migrate, :announce, :write, :to => :migration + delegate :migrate, :announce, :write, :disable_ddl_transaction, to: :migration private @@ -856,12 +882,12 @@ module ActiveRecord Base.logger.info "Migrating to #{migration.name} (#{migration.version})" if Base.logger begin - ddl_transaction do + ddl_transaction(migration) do migration.migrate(@direction) record_version_state_after_migrating(migration.version) end rescue => e - canceled_msg = Base.connection.supports_ddl_transactions? ? "this and " : "" + canceled_msg = use_transaction?(migration) ? "this and " : "" raise StandardError, "An error has occurred, #{canceled_msg}all later migrations canceled:\n\n#{e}", e.backtrace end end @@ -935,12 +961,16 @@ module ActiveRecord end # Wrap the migration in a transaction only if supported by the adapter. - def ddl_transaction - if Base.connection.supports_ddl_transactions? + def ddl_transaction(migration) + if use_transaction?(migration) Base.transaction { yield } else yield end end + + def use_transaction?(migration) + !migration.disable_ddl_transaction && Base.connection.supports_ddl_transactions? + end end end diff --git a/activerecord/test/cases/migration/logger_test.rb b/activerecord/test/cases/migration/logger_test.rb index ee0c20747e..97efb94b66 100644 --- a/activerecord/test/cases/migration/logger_test.rb +++ b/activerecord/test/cases/migration/logger_test.rb @@ -7,6 +7,7 @@ module ActiveRecord self.use_transactional_fixtures = false Migration = Struct.new(:name, :version) do + def disable_ddl_transaction; false end def migrate direction # do nothing end @@ -34,4 +35,3 @@ module ActiveRecord end end end - diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index 831b2ee2b4..960d28fcf5 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -254,7 +254,39 @@ class MigrationTest < ActiveRecord::TestCase assert_equal "An error has occurred, this and all later migrations canceled:\n\nSomething broke", e.message Person.reset_column_information + assert_not Person.column_methods_hash.include?(:last_name), + "On error, the Migrator should revert schema changes but it did not." + end + + def test_migration_without_transaction + unless ActiveRecord::Base.connection.supports_ddl_transactions? + skip "not supported on #{ActiveRecord::Base.connection.class}" + end + assert_not Person.column_methods_hash.include?(:last_name) + + migration = Class.new(ActiveRecord::Migration) { + self.disable_ddl_transaction! + + def version; 101 end + def migrate(x) + add_column "people", "last_name", :string + raise 'Something broke' + end + }.new + + migrator = ActiveRecord::Migrator.new(:up, [migration], 101) + e = assert_raise(StandardError) { migrator.migrate } + assert_equal "An error has occurred, all later migrations canceled:\n\nSomething broke", e.message + + Person.reset_column_information + assert Person.column_methods_hash.include?(:last_name), + "without ddl transactions, the Migrator should not rollback on error but it did." + ensure + Person.reset_column_information + if Person.column_methods_hash.include?(:last_name) + Person.connection.remove_column('people', 'last_name') + end end def test_schema_migrations_table_name |