aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib/active_record/transactions.rb
diff options
context:
space:
mode:
authorDavid Heinemeier Hansson <david@loudthinking.com>2004-11-24 01:04:44 +0000
committerDavid Heinemeier Hansson <david@loudthinking.com>2004-11-24 01:04:44 +0000
commitdb045dbbf60b53dbe013ef25554fd013baf88134 (patch)
tree257830e3c76458c8ff3d1329de83f32b23926028 /activerecord/lib/active_record/transactions.rb
downloadrails-db045dbbf60b53dbe013ef25554fd013baf88134.tar.gz
rails-db045dbbf60b53dbe013ef25554fd013baf88134.tar.bz2
rails-db045dbbf60b53dbe013ef25554fd013baf88134.zip
Initial
git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@4 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
Diffstat (limited to 'activerecord/lib/active_record/transactions.rb')
-rw-r--r--activerecord/lib/active_record/transactions.rb119
1 files changed, 119 insertions, 0 deletions
diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb
new file mode 100644
index 0000000000..d440e74346
--- /dev/null
+++ b/activerecord/lib/active_record/transactions.rb
@@ -0,0 +1,119 @@
+require 'active_record/vendor/simple.rb'
+require 'thread'
+
+module ActiveRecord
+ module Transactions # :nodoc:
+ TRANSACTION_MUTEX = Mutex.new
+
+ def self.append_features(base)
+ super
+ base.extend(ClassMethods)
+
+ base.class_eval do
+ alias_method :destroy_without_transactions, :destroy
+ alias_method :destroy, :destroy_with_transactions
+
+ alias_method :save_without_transactions, :save
+ alias_method :save, :save_with_transactions
+ end
+ 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 withdrawal succedded and
+ # vice versa. Transaction enforce the integrity of the database and guards the data against program errors or database break-downs.
+ # So basically you should use transaction blocks whenever you have a number of statements that must be executed together or
+ # not at all. Example:
+ #
+ # transaction do
+ # david.withdrawal(100)
+ # mary.deposit(100)
+ # end
+ #
+ # This example will only take money from David and give to Mary if neither +withdrawal+ nor +deposit+ raises an exception.
+ # Exceptions will force a ROLLBACK that returns the database to the state before the transaction was begun. Be aware, though,
+ # that the objects by default will _not_ have their instance data returned to their pre-transactional state.
+ #
+ # == Transactions are not distributed across database connections
+ #
+ # A transaction acts on a single database connection. If you have
+ # multiple class-specific databases, the transaction will not protect
+ # interaction among them. One workaround is to begin a transaction
+ # on each class whose models you alter:
+ #
+ # Student.transaction do
+ # Course.transaction do
+ # course.enroll(student)
+ # student.units += course.units
+ # end
+ # end
+ #
+ # This is a poor solution, but full distributed transactions are beyond
+ # the scope of Active Record.
+ #
+ # == Save and destroy are automatically wrapped in a transaction
+ #
+ # Both Base#save and Base#destroy come wrapped in a transaction that ensures that whatever you do in validations or callbacks
+ # will happen under the protected cover of a transaction. So you can use validations to check for values that the transaction
+ # depend on or you can raise exceptions in the callbacks to rollback.
+ #
+ # == Object-level transactions
+ #
+ # You can enable object-level transactions for Active Record objects, though. You do this by naming the each of the Active Records
+ # that you want to enable object-level transactions for, like this:
+ #
+ # Account.transaction(david, mary) do
+ # david.withdrawal(100)
+ # mary.deposit(100)
+ # end
+ #
+ # If the transaction fails, David and Mary will be returned to their pre-transactional state. No money will have changed hands in
+ # neither object nor database.
+ #
+ # == Exception handling
+ #
+ # Also have in mind that exceptions thrown within a transaction block will be propagated (after triggering the ROLLBACK), so you
+ # should be ready to catch those in your application code.
+ #
+ # Tribute: Object-level transactions are implemented by Transaction::Simple by Austin Ziegler.
+ module ClassMethods
+ def transaction(*objects, &block)
+ TRANSACTION_MUTEX.lock
+
+ begin
+ objects.each { |o| o.extend(Transaction::Simple) }
+ objects.each { |o| o.start_transaction }
+
+ result = connection.transaction(&block)
+
+ objects.each { |o| o.commit_transaction }
+ return result
+ rescue Exception => object_transaction_rollback
+ objects.each { |o| o.abort_transaction }
+ raise
+ ensure
+ TRANSACTION_MUTEX.unlock
+ end
+ end
+ end
+
+ def transaction(*objects, &block)
+ self.class.transaction(*objects, &block)
+ end
+
+ def destroy_with_transactions #:nodoc:
+ if TRANSACTION_MUTEX.locked?
+ destroy_without_transactions
+ else
+ transaction { destroy_without_transactions }
+ end
+ end
+
+ def save_with_transactions(perform_validation = true) #:nodoc:
+ if TRANSACTION_MUTEX.locked?
+ save_without_transactions(perform_validation)
+ else
+ transaction { save_without_transactions(perform_validation) }
+ end
+ end
+ end
+end