diff options
Diffstat (limited to 'activerecord/lib')
17 files changed, 255 insertions, 144 deletions
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 0b33ee881b..cecb2dbd0b 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1019,7 +1019,7 @@ module ActiveRecord # can affect what it does. # # Note that <tt>:dependent</tt> option is ignored for +has_one+ <tt>:through</tt> associations. - # + # # === Delete or destroy? # # +has_many+ and +has_and_belongs_to_many+ associations have the methods <tt>destroy</tt>, @@ -1529,8 +1529,6 @@ module ActiveRecord # NOTE: <tt>required</tt> is set to <tt>true</tt> by default and is deprecated. If # you don't want to have association presence validated, use <tt>optional: true</tt>. # - # - # # Option examples: # belongs_to :firm, foreign_key: "client_of" # belongs_to :person, primary_key: "name", foreign_key: "person_name" diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb index a2777fcd0a..0acc815d51 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -142,6 +142,41 @@ module ActiveRecord end end + module ColumnMethods + # Appends a primary key definition to the table definition. + # Can be called multiple times, but this is probably not a good idea. + def primary_key(name, type = :primary_key, **options) + column(name, type, options.merge(primary_key: true)) + end + + # Appends a column or columns of a specified type. + # + # t.string(:goat) + # t.string(:goat, :sheep) + # + # See TableDefinition#column + [ + :bigint, + :binary, + :boolean, + :date, + :datetime, + :decimal, + :float, + :integer, + :string, + :text, + :time, + :timestamp, + ].each do |column_type| + module_eval <<-CODE, __FILE__, __LINE__ + 1 + def #{column_type}(*args, **options) + args.each { |name| column(name, :#{column_type}, options) } + end + CODE + end + end + # Represents the schema of an SQL table in an abstract way. This class # provides methods for manipulating the schema representation. # @@ -163,6 +198,8 @@ module ActiveRecord # The table definitions # The Columns are stored as a ColumnDefinition in the +columns+ attribute. class TableDefinition + include ColumnMethods + # An array of ColumnDefinition objects, representing the column changes # that have been defined. attr_accessor :indexes @@ -181,12 +218,6 @@ module ActiveRecord def columns; @columns_hash.values; end - # Appends a primary key definition to the table definition. - # Can be called multiple times, but this is probably not a good idea. - def primary_key(name, type = :primary_key, options = {}) - column(name, type, options.merge(:primary_key => true)) - end - # Returns a ColumnDefinition for the column with name +name+. def [](name) @columns_hash[name.to_s] @@ -343,14 +374,6 @@ module ActiveRecord @columns_hash.delete name.to_s end - [:string, :text, :integer, :bigint, :float, :decimal, :datetime, :timestamp, :time, :date, :binary, :boolean].each do |column_type| - define_method column_type do |*args| - options = args.extract_options! - column_names = args - column_names.each { |name| column(name, column_type, options) } - end - end - # Adds index options to the indexes hash, keyed by column name # This is primarily used to track indexes that need to be created after the table # @@ -462,6 +485,7 @@ module ActiveRecord # Available transformations are: # # change_table :table do |t| + # t.primary_key # t.column # t.index # t.rename_index @@ -474,6 +498,7 @@ module ActiveRecord # t.string # t.text # t.integer + # t.bigint # t.float # t.decimal # t.datetime @@ -490,6 +515,8 @@ module ActiveRecord # end # class Table + include ColumnMethods + attr_reader :name def initialize(table_name, base) @@ -640,21 +667,6 @@ module ActiveRecord end alias :remove_belongs_to :remove_references - # Adds a column or columns of a specified type. - # - # t.string(:goat) - # t.string(:goat, :sheep) - # - # See SchemaStatements#add_column - [:string, :text, :integer, :float, :decimal, :datetime, :timestamp, :time, :date, :binary, :boolean].each do |column_type| - define_method column_type do |*args| - options = args.extract_options! - args.each do |column_name| - @base.add_column(name, column_name, column_type, options) - end - end - end - def foreign_key(*args) # :nodoc: @base.add_foreign_key(name, *args) end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb index 0438c95bd7..d42f9a894b 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -770,7 +770,7 @@ module ActiveRecord # # Check a foreign key exists # foreign_key_exists?(:accounts, :branches) # - # # Check a foreign key on specified column exists + # # Check a foreign key on a specified column exists # foreign_key_exists?(:accounts, column: :owner_id) # # # Check a foreign key with a custom name exists diff --git a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb index 11440e30d4..3a1e4a4a88 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb @@ -77,6 +77,10 @@ module ActiveRecord @state.set_state(:committed) end + def before_commit_records + records.uniq.each(&:before_committed!) + end + def commit_records ite = records.uniq while record = ite.shift @@ -159,13 +163,15 @@ module ActiveRecord def commit_transaction inner_transaction = @stack.pop - inner_transaction.commit if current_transaction.joinable? + inner_transaction.commit inner_transaction.records.each do |r| r.add_to_transaction end else + inner_transaction.before_commit_records + inner_transaction.commit inner_transaction.commit_records end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index c307189980..ae42e8ef8d 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -478,7 +478,6 @@ module ActiveRecord message = "#{e.class.name}: #{e.message.force_encoding sql.encoding}: #{sql}" end - @logger.error message if @logger exception = translate_exception(e, message) exception.set_backtrace e.backtrace exception diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb index 8db4bcd7e3..c084431588 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -6,13 +6,31 @@ module ActiveRecord class AbstractMysqlAdapter < AbstractAdapter include Savepoints - class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition - def primary_key(name, type = :primary_key, options = {}) - options[:auto_increment] ||= type == :bigint + module ColumnMethods + def primary_key(name, type = :primary_key, **options) + options[:auto_increment] = true if type == :bigint super end end + class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition + include ColumnMethods + + def new_column_definition(name, type, options) # :nodoc: + column = super + case column.type + when :primary_key + column.type = :integer + column.auto_increment = true + end + column + end + end + + class Table < ActiveRecord::ConnectionAdapters::Table + include ColumnMethods + end + class SchemaCreation < AbstractAdapter::SchemaCreation def visit_AddColumn(o) add_column_position!(super, column_options(o)) @@ -57,6 +75,10 @@ module ActiveRecord end end + def update_table_definition(table_name, base) # :nodoc: + Table.new(table_name, base) + end + def schema_creation SchemaCreation.new self end @@ -76,6 +98,7 @@ module ActiveRecord def prepare_column_options(column) spec = super spec.delete(:precision) if /time/ === column.sql_type && column.precision == 0 + spec.delete(:limit) if :boolean === column.type spec end @@ -212,6 +235,16 @@ module ActiveRecord end end + MAX_INDEX_LENGTH_FOR_CHARSETS_OF_4BYTES_MAXLEN = 191 + CHARSETS_OF_4BYTES_MAXLEN = ['utf8mb4', 'utf16', 'utf16le', 'utf32'] + def initialize_schema_migrations_table + if CHARSETS_OF_4BYTES_MAXLEN.include?(charset) + ActiveRecord::SchemaMigration.create_table(MAX_INDEX_LENGTH_FOR_CHARSETS_OF_4BYTES_MAXLEN) + else + ActiveRecord::SchemaMigration.create_table + end + end + # Returns true, since this connection adapter supports migrations. def supports_migrations? true diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb index fac6f81540..21631be25c 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -37,15 +37,6 @@ module ActiveRecord configure_connection end - MAX_INDEX_LENGTH_FOR_UTF8MB4 = 191 - def initialize_schema_migrations_table - if charset == 'utf8mb4' - ActiveRecord::SchemaMigration.create_table(MAX_INDEX_LENGTH_FOR_UTF8MB4) - else - ActiveRecord::SchemaMigration.create_table - end - end - def supports_explain? true end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/referential_integrity.rb b/activerecord/lib/active_record/connection_adapters/postgresql/referential_integrity.rb index 52b307c432..c1835380f9 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/referential_integrity.rb @@ -8,20 +8,39 @@ module ActiveRecord def disable_referential_integrity # :nodoc: if supports_disable_referential_integrity? + original_exception = nil + begin - execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";")) - rescue - execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER USER" }.join(";")) + transaction(requires_new: true) do + execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";")) + end + rescue => e + original_exception = e end - end - yield - ensure - if supports_disable_referential_integrity? + + begin + yield + rescue ActiveRecord::InvalidForeignKey => e + warn <<-WARNING +WARNING: Rails was not able to disable referential integrity. + +This is most likely caused due to missing permissions. +Rails needs superuser privileges to disable referential integrity. + + cause: #{original_exception.try(:message)} + + WARNING + raise e + end + begin - execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER ALL" }.join(";")) + transaction(require_new: true) do + execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER ALL" }.join(";")) + end rescue - execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER USER" }.join(";")) end + else + yield end end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb index b9078d4c86..022dbdfa27 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb @@ -2,90 +2,129 @@ module ActiveRecord module ConnectionAdapters module PostgreSQL module ColumnMethods - def xml(*args) - options = args.extract_options! - column(args[0], :xml, options) + # Defines the primary key field. + # Use of the native PostgreSQL UUID type is supported, and can be used + # by defining your tables as such: + # + # create_table :stuffs, id: :uuid do |t| + # t.string :content + # t.timestamps + # end + # + # By default, this will use the +uuid_generate_v4()+ function from the + # +uuid-ossp+ extension, which MUST be enabled on your database. To enable + # the +uuid-ossp+ extension, you can use the +enable_extension+ method in your + # migrations. To use a UUID primary key without +uuid-ossp+ enabled, you can + # set the +:default+ option to +nil+: + # + # create_table :stuffs, id: false do |t| + # t.primary_key :id, :uuid, default: nil + # t.uuid :foo_id + # t.timestamps + # end + # + # You may also pass a different UUID generation function from +uuid-ossp+ + # or another library. + # + # Note that setting the UUID primary key default value to +nil+ will + # require you to assure that you always provide a UUID value before saving + # a record (as primary keys cannot be +nil+). This might be done via the + # +SecureRandom.uuid+ method and a +before_save+ callback, for instance. + def primary_key(name, type = :primary_key, **options) + options[:default] = options.fetch(:default, 'uuid_generate_v4()') if type == :uuid + super + end + + def bigserial(*args, **options) + args.each { |name| column(name, :bigserial, options) } + end + + def bit(*args, **options) + args.each { |name| column(name, :bit, options) } end - def tsvector(*args) - options = args.extract_options! - column(args[0], :tsvector, options) + def bit_varying(*args, **options) + args.each { |name| column(name, :bit_varying, options) } end - def int4range(name, options = {}) - column(name, :int4range, options) + def cidr(*args, **options) + args.each { |name| column(name, :cidr, options) } end - def int8range(name, options = {}) - column(name, :int8range, options) + def citext(*args, **options) + args.each { |name| column(name, :citext, options) } end - def tsrange(name, options = {}) - column(name, :tsrange, options) + def daterange(*args, **options) + args.each { |name| column(name, :daterange, options) } end - def tstzrange(name, options = {}) - column(name, :tstzrange, options) + def hstore(*args, **options) + args.each { |name| column(name, :hstore, options) } end - def numrange(name, options = {}) - column(name, :numrange, options) + def inet(*args, **options) + args.each { |name| column(name, :inet, options) } end - def daterange(name, options = {}) - column(name, :daterange, options) + def int4range(*args, **options) + args.each { |name| column(name, :int4range, options) } end - def hstore(name, options = {}) - column(name, :hstore, options) + def int8range(*args, **options) + args.each { |name| column(name, :int8range, options) } end - def ltree(name, options = {}) - column(name, :ltree, options) + def json(*args, **options) + args.each { |name| column(name, :json, options) } end - def inet(name, options = {}) - column(name, :inet, options) + def jsonb(*args, **options) + args.each { |name| column(name, :jsonb, options) } end - def cidr(name, options = {}) - column(name, :cidr, options) + def ltree(*args, **options) + args.each { |name| column(name, :ltree, options) } end - def macaddr(name, options = {}) - column(name, :macaddr, options) + def macaddr(*args, **options) + args.each { |name| column(name, :macaddr, options) } end - def uuid(name, options = {}) - column(name, :uuid, options) + def money(*args, **options) + args.each { |name| column(name, :money, options) } end - def json(name, options = {}) - column(name, :json, options) + def numrange(*args, **options) + args.each { |name| column(name, :numrange, options) } end - def jsonb(name, options = {}) - column(name, :jsonb, options) + def point(*args, **options) + args.each { |name| column(name, :point, options) } end - def citext(name, options = {}) - column(name, :citext, options) + def serial(*args, **options) + args.each { |name| column(name, :serial, options) } end - def point(name, options = {}) - column(name, :point, options) + def tsrange(*args, **options) + args.each { |name| column(name, :tsrange, options) } end - def bit(name, options = {}) - column(name, :bit, options) + def tstzrange(*args, **options) + args.each { |name| column(name, :tstzrange, options) } end - def bit_varying(name, options = {}) - column(name, :bit_varying, options) + def tsvector(*args, **options) + args.each { |name| column(name, :tsvector, options) } end - def money(name, options = {}) - column(name, :money, options) + def uuid(*args, **options) + args.each { |name| column(name, :uuid, options) } + end + + def xml(*args, **options) + args.each { |name| column(name, :xml, options) } end end @@ -96,39 +135,6 @@ module ActiveRecord class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition include ColumnMethods - # Defines the primary key field. - # Use of the native PostgreSQL UUID type is supported, and can be used - # by defining your tables as such: - # - # create_table :stuffs, id: :uuid do |t| - # t.string :content - # t.timestamps - # end - # - # By default, this will use the +uuid_generate_v4()+ function from the - # +uuid-ossp+ extension, which MUST be enabled on your database. To enable - # the +uuid-ossp+ extension, you can use the +enable_extension+ method in your - # migrations. To use a UUID primary key without +uuid-ossp+ enabled, you can - # set the +:default+ option to +nil+: - # - # create_table :stuffs, id: false do |t| - # t.primary_key :id, :uuid, default: nil - # t.uuid :foo_id - # t.timestamps - # end - # - # You may also pass a different UUID generation function from +uuid-ossp+ - # or another library. - # - # Note that setting the UUID primary key default value to +nil+ will - # require you to assure that you always provide a UUID value before saving - # a record (as primary keys cannot be +nil+). This might be done via the - # +SecureRandom.uuid+ method and a +before_save+ callback, for instance. - def primary_key(name, type = :primary_key, options = {}) - options[:default] = options.fetch(:default, 'uuid_generate_v4()') if type == :uuid - super - end - def new_column_definition(name, type, options) # :nodoc: column = super column.array = options[:array] diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb index 400b586c95..7e184dd510 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -121,6 +121,7 @@ module ActiveRecord @config = config @visitor = Arel::Visitors::SQLite.new self + @quoted_column_names = {} if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true }) @prepared_statements = true @@ -240,7 +241,7 @@ module ActiveRecord end def quote_column_name(name) #:nodoc: - %Q("#{name.to_s.gsub('"', '""')}") + @quoted_column_names[name] ||= %Q("#{name.to_s.gsub('"', '""')}") end #-- diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index de48681746..d41872d767 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -488,7 +488,7 @@ module ActiveRecord end def has_transactional_callbacks? # :nodoc: - !_rollback_callbacks.empty? || !_commit_callbacks.empty? + !_rollback_callbacks.empty? || !_commit_callbacks.empty? || !_before_commit_callbacks.empty? end # Updates the attributes on this particular ActiveRecord object so that diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index 739be524da..75b0e1e08d 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -708,6 +708,10 @@ module ActiveRecord def lhs_key @association.through_reflection.foreign_key end + + def join_table + @association.through_reflection.table_name + end end private diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb index a58d8355aa..a09437b4b0 100644 --- a/activerecord/lib/active_record/locking/optimistic.rb +++ b/activerecord/lib/active_record/locking/optimistic.rb @@ -93,7 +93,7 @@ module ActiveRecord self.class.primary_key => id, lock_col => previous_lock_value, ).update_all( - attribute_names.map do |name| + attributes_for_update(attribute_names).map do |name| [name, _read_attribute(name)] end.to_h ) diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index 4a8e6bdd23..a83b90a95f 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -395,7 +395,7 @@ module ActiveRecord def load_schema_if_pending! if ActiveRecord::Migrator.needs_migration? || !ActiveRecord::Migrator.any_migrations? - # Roundrip to Rake to allow plugins to hook into database initialization. + # Roundtrip to Rake to allow plugins to hook into database initialization. FileUtils.cd Rails.root do current_config = Base.connection_config Base.clear_all_connections! diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb index f1bdbc845c..b6de90e89d 100644 --- a/activerecord/lib/active_record/railtie.rb +++ b/activerecord/lib/active_record/railtie.rb @@ -58,6 +58,9 @@ module ActiveRecord require "active_record/railties/console_sandbox" if app.sandbox? require "active_record/base" console = ActiveSupport::Logger.new(STDERR) + console.formatter = Rails.logger.formatter + console.level = Rails.logger.level + Rails.logger.extend ActiveSupport::Logger.broadcast console end diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 7514401072..69ce5cdc2a 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -999,11 +999,15 @@ module ActiveRecord end def arel_columns(columns) - columns.map do |field| - if (Symbol === field || String === field) && columns_hash.key?(field.to_s) - arel_table[field] - else - field + if from_clause.value + columns + else + columns.map do |field| + if (Symbol === field || String === field) && columns_hash.key?(field.to_s) + arel_table[field] + else + field + end end end end diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb index dd405c7796..598defce50 100644 --- a/activerecord/lib/active_record/transactions.rb +++ b/activerecord/lib/active_record/transactions.rb @@ -7,6 +7,10 @@ module ActiveRecord included do define_callbacks :commit, :rollback, + :before_commit, + :before_commit_without_transaction_enrollment, + :commit_without_transaction_enrollment, + :rollback_without_transaction_enrollment, scope: [:kind, :name] end @@ -206,6 +210,11 @@ module ActiveRecord connection.transaction(options, &block) end + def before_commit(*args, &block) # :nodoc: + set_options_for_callbacks!(args) + set_callback(:before_commit, :before, *args, &block) + end + # This callback is called after a record has been created, updated, or destroyed. # # You can specify that the callback should only be fired by a certain action with @@ -233,6 +242,21 @@ module ActiveRecord set_callback(:rollback, :after, *args, &block) end + def before_commit_without_transaction_enrollment(*args, &block) # :nodoc: + set_options_for_callbacks!(args) + set_callback(:before_commit_without_transaction_enrollment, :before, *args, &block) + end + + def after_commit_without_transaction_enrollment(*args, &block) # :nodoc: + set_options_for_callbacks!(args) + set_callback(:commit_without_transaction_enrollment, :after, *args, &block) + end + + def after_rollback_without_transaction_enrollment(*args, &block) # :nodoc: + set_options_for_callbacks!(args) + set_callback(:rollback_without_transaction_enrollment, :after, *args, &block) + end + def raise_in_transactional_callbacks ActiveSupport::Deprecation.warn('ActiveRecord::Base.raise_in_transactional_callbacks is deprecated and will be removed without replacement.') true @@ -296,12 +320,20 @@ module ActiveRecord clear_transaction_record_state end + def before_committed! # :nodoc: + _run_before_commit_without_transaction_enrollment_callbacks + _run_before_commit_callbacks + end + # Call the +after_commit+ callbacks. # # Ensure that it is not called if the object was never persisted (failed create), # but call it after the commit of a destroyed object. def committed!(should_run_callbacks: true) #:nodoc: - _run_commit_callbacks if should_run_callbacks && destroyed? || persisted? + if should_run_callbacks && destroyed? || persisted? + _run_commit_without_transaction_enrollment_callbacks + _run_commit_callbacks + end ensure force_clear_transaction_record_state end @@ -309,7 +341,10 @@ module ActiveRecord # Call the +after_rollback+ callbacks. The +force_restore_state+ argument indicates if the record # state should be rolled back to the beginning or just to the last savepoint. def rolledback!(force_restore_state: false, should_run_callbacks: true) #:nodoc: - _run_rollback_callbacks if should_run_callbacks + if should_run_callbacks + _run_rollback_without_transaction_enrollment_callbacks + _run_rollback_callbacks + end ensure restore_transaction_record_state(force_restore_state) clear_transaction_record_state |