Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

root/trunk/activerecord/lib/active_record/transactions.rb

Revision 4312, 4.9 kB (checked in by marcel, 4 years ago)

Replace alias method chaining with Module#alias_method_chain. [Marcel Molina Jr.]

Line 
1 require 'active_record/vendor/simple.rb'
2 Transaction::Simple.send(:remove_method, :transaction)
3 require 'thread'
4
5 module ActiveRecord
6   module Transactions # :nodoc:
7     TRANSACTION_MUTEX = Mutex.new
8
9     class TransactionError < ActiveRecordError # :nodoc:
10     end
11
12     def self.included(base)
13       base.extend(ClassMethods)
14
15       base.class_eval do
16         [:destroy, :save].each do |method|
17           alias_method_chain method, :transactions
18         end
19       end
20     end
21
22     # Transactions are protective blocks where SQL statements are only permanent if they can all succeed as one atomic action.
23     # The classic example is a transfer between two accounts where you can only have a deposit if the withdrawal succeeded and
24     # vice versa. Transactions enforce the integrity of the database and guard the data against program errors or database break-downs.
25     # So basically you should use transaction blocks whenever you have a number of statements that must be executed together or
26     # not at all. Example:
27     #
28     #   transaction do
29     #     david.withdrawal(100)
30     #     mary.deposit(100)
31     #   end
32     #
33     # This example will only take money from David and give to Mary if neither +withdrawal+ nor +deposit+ raises an exception.
34     # Exceptions will force a ROLLBACK that returns the database to the state before the transaction was begun. Be aware, though,
35     # that the objects by default will _not_ have their instance data returned to their pre-transactional state.
36     #
37     # == Transactions are not distributed across database connections
38     #
39     # A transaction acts on a single database connection.  If you have
40     # multiple class-specific databases, the transaction will not protect
41     # interaction among them.  One workaround is to begin a transaction
42     # on each class whose models you alter:
43     #
44     #   Student.transaction do
45     #     Course.transaction do
46     #       course.enroll(student)
47     #       student.units += course.units
48     #     end
49     #   end
50     #
51     # This is a poor solution, but full distributed transactions are beyond
52     # the scope of Active Record.
53     #
54     # == Save and destroy are automatically wrapped in a transaction
55     #
56     # Both Base#save and Base#destroy come wrapped in a transaction that ensures that whatever you do in validations or callbacks
57     # will happen under the protected cover of a transaction. So you can use validations to check for values that the transaction
58     # depend on or you can raise exceptions in the callbacks to rollback.
59     #
60     # == Object-level transactions
61     #
62     # You can enable object-level transactions for Active Record objects, though. You do this by naming each of the Active Records
63     # that you want to enable object-level transactions for, like this:
64     #
65     #   Account.transaction(david, mary) do
66     #     david.withdrawal(100)
67     #     mary.deposit(100)
68     #   end
69     #
70     # If the transaction fails, David and Mary will be returned to their pre-transactional state. No money will have changed hands in
71     # neither object nor database.
72     #
73     # == Exception handling
74     #
75     # Also have in mind that exceptions thrown within a transaction block will be propagated (after triggering the ROLLBACK), so you
76     # should be ready to catch those in your application code.
77     #
78     # Tribute: Object-level transactions are implemented by Transaction::Simple by Austin Ziegler.
79     module ClassMethods
80       def transaction(*objects, &block)
81         previous_handler = trap('TERM') { raise TransactionError, "Transaction aborted" }
82         lock_mutex
83        
84         begin
85           objects.each { |o| o.extend(Transaction::Simple) }
86           objects.each { |o| o.start_transaction }
87
88           result = connection.transaction(Thread.current['start_db_transaction'], &block)
89
90           objects.each { |o| o.commit_transaction }
91           return result
92         rescue Exception => object_transaction_rollback
93           objects.each { |o| o.abort_transaction }
94           raise
95         ensure
96           unlock_mutex
97           trap('TERM', previous_handler)
98         end
99       end
100      
101       def lock_mutex#:nodoc:
102         Thread.current['open_transactions'] ||= 0
103         TRANSACTION_MUTEX.lock if Thread.current['open_transactions'] == 0
104         Thread.current['start_db_transaction'] = (Thread.current['open_transactions'] == 0)
105         Thread.current['open_transactions'] += 1
106       end
107      
108       def unlock_mutex#:nodoc:
109         Thread.current['open_transactions'] -= 1
110         TRANSACTION_MUTEX.unlock if Thread.current['open_transactions'] == 0
111       end
112     end
113
114     def transaction(*objects, &block)
115       self.class.transaction(*objects, &block)
116     end
117
118     def destroy_with_transactions #:nodoc:
119       transaction { destroy_without_transactions }
120     end
121    
122     def save_with_transactions(perform_validation = true) #:nodoc:
123       transaction { save_without_transactions(perform_validation) }
124     end
125   end
126 end
Note: See TracBrowser for help on using the browser.