From 100f8b2dfc9d547b23804b648ad0dba748faa99b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Santiago=20Pastorino=20and=20Jos=C3=A9=20Ignacio=20Costa?= Date: Mon, 7 Jun 2010 22:21:28 -0300 Subject: update docs before_validation_on_create => before_validation(:on => :create) --- activerecord/lib/active_record/callbacks.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb index 498836aca4..44fee12001 100644 --- a/activerecord/lib/active_record/callbacks.rb +++ b/activerecord/lib/active_record/callbacks.rb @@ -31,7 +31,7 @@ module ActiveRecord # class CreditCard < ActiveRecord::Base # # Strip everything but digits, so the user can specify "555 234 34" or # # "5552-3434" or both will mean "55523434" - # def before_validation_on_create + # before_validation(:on => :create) do # self.number = number.gsub(/[^0-9]/, "") if attribute_present?("number") # end # end -- cgit v1.2.3 From 0e9b9d59859efa46a82b56e0715784fa52656650 Mon Sep 17 00:00:00 2001 From: Andrew Bloomgarden Date: Tue, 1 Jun 2010 22:47:34 -0700 Subject: Fix ActiveRecord::Base.compute_type swallowing NoMethodError. [#4751 state:resolved] Signed-off-by: David Heinemeier Hansson --- activerecord/lib/active_record/base.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index aa2826fb33..7cff6d9f1a 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -1219,7 +1219,9 @@ module ActiveRecord #:nodoc: begin constant = candidate.constantize return constant if candidate == constant.to_s - rescue NameError + rescue NameError => e + # We don't want to swallow NoMethodError < NameError errors + raise e unless e.instance_of?(NameError) rescue ArgumentError end end -- cgit v1.2.3 From 87cc3d556948f4cb644091c98969c03e319c864e Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Tue, 8 Jun 2010 16:57:46 -0400 Subject: Clarify Fixture#key_ and value_list --- activerecord/lib/active_record/fixtures.rb | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index 8099aaa7f7..82270c56b3 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -787,16 +787,14 @@ class Fixture #:nodoc: end def key_list - columns = @fixture.keys.collect{ |column_name| @connection.quote_column_name(column_name) } - columns.join(", ") + @fixture.keys.map { |column_name| @connection.quote_column_name(column_name) }.join(', ') end def value_list - list = @fixture.inject([]) do |fixtures, (key, value)| - col = model_class.columns_hash[key] if model_class.respond_to?(:ancestors) && model_class.ancestors.include?(ActiveRecord::Base) - fixtures << @connection.quote(value, col).gsub('[^\]\\n', "\n").gsub('[^\]\\r', "\r") - end - list * ', ' + cols = (model_class && model_class < ActiveRecord::Base) ? model_class.columns_hash : {} + @fixture.map do |key, value| + @connection.quote(value, cols[key]).gsub('[^\]\\n', "\n").gsub('[^\]\\r', "\r") + end.join(', ') end def find -- cgit v1.2.3 From b07073924002fd56ac5b63b24cb9318b3dee45c4 Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Tue, 8 Jun 2010 16:59:06 -0400 Subject: Revert "Temporarily revert "Update after_commit and after_rollback docs and tests to use new style API with an :on options instead of on_* suffix." and "Add after_commit and after_rollback callbacks to ActiveRecord that are called after transactions either commit or rollback on all records saved or destroyed in the transaction."" This reverts commit 1b2941cba1165b0721f57524645fe378bee2a950. [#2991] --- .../abstract/database_statements.rb | 56 ++++++++++ activerecord/lib/active_record/transactions.rb | 118 +++++++++++++++++++-- 2 files changed, 165 insertions(+), 9 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb index 0c87e052c4..b9fb452eee 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -122,6 +122,8 @@ module ActiveRecord requires_new = options[:requires_new] || !last_transaction_joinable transaction_open = false + @_current_transaction_records ||= [] + begin if block_given? if requires_new || open_transactions == 0 @@ -132,6 +134,7 @@ module ActiveRecord end increment_open_transactions transaction_open = true + @_current_transaction_records.push([]) end yield end @@ -141,8 +144,10 @@ module ActiveRecord decrement_open_transactions if open_transactions == 0 rollback_db_transaction + rollback_transaction_records(true) else rollback_to_savepoint + rollback_transaction_records(false) end end raise unless database_transaction_rollback.is_a?(ActiveRecord::Rollback) @@ -157,20 +162,35 @@ module ActiveRecord begin if open_transactions == 0 commit_db_transaction + commit_transaction_records else release_savepoint + save_point_records = @_current_transaction_records.pop + unless save_point_records.blank? + @_current_transaction_records.push([]) if @_current_transaction_records.empty? + @_current_transaction_records.last.concat(save_point_records) + end end rescue Exception => database_transaction_rollback if open_transactions == 0 rollback_db_transaction + rollback_transaction_records(true) else rollback_to_savepoint + rollback_transaction_records(false) end raise end end end + # Register a record with the current transaction so that its after_commit and after_rollback callbacks + # can be called. + def add_transaction_record(record) + last_batch = @_current_transaction_records.last + last_batch << record if last_batch + end + # Begins the transaction (and turns off auto-committing). def begin_db_transaction() end @@ -268,6 +288,42 @@ module ActiveRecord limit.to_i end end + + # Send a rollback message to all records after they have been rolled back. If rollback + # is false, only rollback records since the last save point. + def rollback_transaction_records(rollback) #:nodoc + if rollback + records = @_current_transaction_records.flatten + @_current_transaction_records.clear + else + records = @_current_transaction_records.pop + end + + unless records.blank? + records.uniq.each do |record| + begin + record.rolledback!(rollback) + rescue Exception => e + record.logger.error(e) if record.respond_to?(:logger) + end + end + end + end + + # Send a commit message to all records after they have been committed. + def commit_transaction_records #:nodoc + records = @_current_transaction_records.flatten + @_current_transaction_records.clear + unless records.blank? + records.uniq.each do |record| + begin + record.committed! + rescue Exception => e + record.logger.error(e) if record.respond_to?(:logger) + end + end + end + end end end end diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb index 3f2c1911e7..5a8e2ce880 100644 --- a/activerecord/lib/active_record/transactions.rb +++ b/activerecord/lib/active_record/transactions.rb @@ -8,6 +8,11 @@ module ActiveRecord class TransactionError < ActiveRecordError # :nodoc: end + included do + define_model_callbacks :commit, :commit_on_update, :commit_on_create, :commit_on_destroy, :only => :after + define_model_callbacks :rollback, :rollback_on_update, :rollback_on_create, :rollback_on_destroy + end + # Transactions are protective blocks where SQL statements are only permanent # if they can all succeed as one atomic action. The classic example is a # transfer between two accounts where you can only have a deposit if the @@ -158,6 +163,21 @@ module ActiveRecord # http://dev.mysql.com/doc/refman/5.0/en/savepoints.html # for more information about savepoints. # + # === Callbacks + # + # There are two types of callbacks associated with committing and rolling back transactions: + # +after_commit+ and +after_rollback+. + # + # +after_commit+ callbacks are called on every record saved or destroyed within a + # transaction immediately after the transaction is committed. +after_rollback+ callbacks + # are called on every record saved or destroyed within a transaction immediately after the + # transaction or savepoint is rolled back. + # + # These callbacks are useful for interacting with other systems since you will be guaranteed + # that the callback is only executed when the database is in a permanent state. For example, + # +after_commit+ is a good spot to put in a hook to clearing a cache since clearing it from + # within a transaction could trigger the cache to be regenerated before the database is updated. + # # === Caveats # # If you're on MySQL, then do not use DDL operations in nested transactions @@ -205,19 +225,50 @@ module ActiveRecord # Reset id and @new_record if the transaction rolls back. def rollback_active_record_state! - id_present = has_attribute?(self.class.primary_key) - previous_id = id - previous_new_record = new_record? + remember_transaction_record_state yield rescue Exception - @new_record = previous_new_record - if id_present - self.id = previous_id + restore_transaction_record_state + raise + ensure + clear_transaction_record_state + end + + # Call the after_commit callbacks + def committed! #:nodoc: + if transaction_record_state(:new_record) + _run_commit_on_create_callbacks + elsif transaction_record_state(:destroyed) + _run_commit_on_destroy_callbacks else - @attributes.delete(self.class.primary_key) - @attributes_cache.delete(self.class.primary_key) + _run_commit_on_update_callbacks + end + _run_commit_callbacks + ensure + clear_transaction_record_state + end + + # Call the after rollback callbacks. The 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) #:nodoc: + if transaction_record_state(:new_record) + _run_rollback_on_create_callbacks + elsif transaction_record_state(:destroyed) + _run_rollback_on_destroy_callbacks + else + _run_rollback_on_update_callbacks + end + _run_rollback_callbacks + ensure + restore_transaction_record_state(force_restore_state) + end + + # Add the record to the current transaction so that the :after_rollback and :after_commit callbacks + # can be called. + def add_to_transaction + if self.class.connection.add_transaction_record(self) + remember_transaction_record_state end - raise end # Executes +method+ within a transaction and captures its return value as a @@ -229,10 +280,59 @@ module ActiveRecord def with_transaction_returning_status status = nil self.class.transaction do + add_to_transaction status = yield raise ActiveRecord::Rollback unless status end status end + + protected + + # Save the new record state and id of a record so it can be restored later if a transaction fails. + def remember_transaction_record_state #:nodoc + @_start_transaction_state ||= {} + unless @_start_transaction_state.include?(:new_record) + @_start_transaction_state[:id] = id if has_attribute?(self.class.primary_key) + @_start_transaction_state[:new_record] = @new_record + end + unless @_start_transaction_state.include?(:destroyed) + @_start_transaction_state[:destroyed] = @new_record + end + @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) + 1 + end + + # Clear the new record state and id of a record. + def clear_transaction_record_state #:nodoc + if defined?(@_start_transaction_state) + @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) - 1 + remove_instance_variable(:@_start_transaction_state) if @_start_transaction_state[:level] < 1 + end + end + + # Restore the new record state and id of a record that was previously saved by a call to save_record_state. + def restore_transaction_record_state(force = false) #:nodoc + if defined?(@_start_transaction_state) + @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) - 1 + if @_start_transaction_state[:level] < 1 + restore_state = remove_instance_variable(:@_start_transaction_state) + if restore_state + @new_record = restore_state[:new_record] + @destroyed = restore_state[:destroyed] + if restore_state[:id] + self.id = restore_state[:id] + else + @attributes.delete(self.class.primary_key) + @attributes_cache.delete(self.class.primary_key) + end + end + end + end + end + + # Determine if a record was created or destroyed in a transaction. State should be one of :new_record or :destroyed. + def transaction_record_state(state) #:nodoc + @_start_transaction_state[state] if defined?(@_start_transaction_state) + end end end -- cgit v1.2.3 From 2500e6af6634c984f7d2b567403de1c4c544ff91 Mon Sep 17 00:00:00 2001 From: Brian Durand Date: Tue, 8 Jun 2010 14:41:42 -0500 Subject: Make logic for after_commit and after_rollback :on option work like it does for validation callbacks. [#2991 state:committed] Signed-off-by: Jeremy Kemper --- activerecord/lib/active_record/transactions.rb | 51 +++++++++++++++++--------- 1 file changed, 33 insertions(+), 18 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb index 5a8e2ce880..620758f5af 100644 --- a/activerecord/lib/active_record/transactions.rb +++ b/activerecord/lib/active_record/transactions.rb @@ -9,8 +9,7 @@ module ActiveRecord end included do - define_model_callbacks :commit, :commit_on_update, :commit_on_create, :commit_on_destroy, :only => :after - define_model_callbacks :rollback, :rollback_on_update, :rollback_on_create, :rollback_on_destroy + define_callbacks :commit, :rollback, :terminator => "result == false", :scope => [:kind, :name] end # Transactions are protective blocks where SQL statements are only permanent @@ -77,7 +76,7 @@ module ActiveRecord # # Both +save+ and +destroy+ come wrapped in a transaction that ensures # that whatever you do in validations or callbacks will happen under its - # protected cover. So you can use validations to check for values that + # protected cover. So you can use validations to check for values that # the transaction depends on or you can raise exceptions in the callbacks # to rollback, including after_* callbacks. # @@ -202,6 +201,24 @@ module ActiveRecord # See the ConnectionAdapters::DatabaseStatements#transaction API docs. connection.transaction(options, &block) end + + def after_commit(*args, &block) + options = args.last + if options.is_a?(Hash) && options[:on] + options[:if] = Array.wrap(options[:if]) + options[:if] << "transaction_include_action?(:#{options[:on]})" + end + set_callback(:commit, :after, *args, &block) + end + + def after_rollback(*args, &block) + options = args.last + if options.is_a?(Hash) && options[:on] + options[:if] = Array.wrap(options[:if]) + options[:if] << "transaction_include_action?(:#{options[:on]})" + end + set_callback(:rollback, :after, *args, &block) + end end # See ActiveRecord::Transactions::ClassMethods for detailed documentation. @@ -236,13 +253,6 @@ module ActiveRecord # Call the after_commit callbacks def committed! #:nodoc: - if transaction_record_state(:new_record) - _run_commit_on_create_callbacks - elsif transaction_record_state(:destroyed) - _run_commit_on_destroy_callbacks - else - _run_commit_on_update_callbacks - end _run_commit_callbacks ensure clear_transaction_record_state @@ -251,13 +261,6 @@ module ActiveRecord # Call the after rollback callbacks. The 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) #:nodoc: - if transaction_record_state(:new_record) - _run_rollback_on_create_callbacks - elsif transaction_record_state(:destroyed) - _run_rollback_on_destroy_callbacks - else - _run_rollback_on_update_callbacks - end _run_rollback_callbacks ensure restore_transaction_record_state(force_restore_state) @@ -297,7 +300,7 @@ module ActiveRecord @_start_transaction_state[:new_record] = @new_record end unless @_start_transaction_state.include?(:destroyed) - @_start_transaction_state[:destroyed] = @new_record + @_start_transaction_state[:destroyed] = @destroyed end @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) + 1 end @@ -334,5 +337,17 @@ module ActiveRecord def transaction_record_state(state) #:nodoc @_start_transaction_state[state] if defined?(@_start_transaction_state) end + + # Determine if a transaction included an action for :create, :update, or :destroy. Used in filtering callbacks. + def transaction_include_action?(action) #:nodoc + case action + when :create + transaction_record_state(:new_record) + when :destroy + destroyed? + when :update + !(transaction_record_state(:new_record) || destroyed?) + end + end end end -- cgit v1.2.3