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

root/tags/rel_2-0-2/activerecord/lib/active_record/transactions.rb

Revision 8240, 5.0 kB (checked in by xal, 1 year ago)

Removed documentation for the removed rollback! method on transactions and mention ActiveRecord::Rollback [cody]

Line 
1 require 'thread'
2
3 module ActiveRecord
4   module Transactions # :nodoc:
5     class TransactionError < ActiveRecordError # :nodoc:
6     end
7
8     def self.included(base)
9       base.extend(ClassMethods)
10
11       base.class_eval do
12         [:destroy, :save, :save!].each do |method|
13           alias_method_chain method, :transactions
14         end
15       end
16     end
17
18     # Transactions are protective blocks where SQL statements are only permanent if they can all succeed as one atomic action.
19     # The classic example is a transfer between two accounts where you can only have a deposit if the withdrawal succeeded and
20     # vice versa. Transactions enforce the integrity of the database and guard the data against program errors or database break-downs.
21     # So basically you should use transaction blocks whenever you have a number of statements that must be executed together or
22     # not at all. Example:
23     #
24     #   transaction do
25     #     david.withdrawal(100)
26     #     mary.deposit(100)
27     #   end
28     #
29     # This example will only take money from David and give to Mary if neither +withdrawal+ nor +deposit+ raises an exception.
30     # Exceptions will force a ROLLBACK that returns the database to the state before the transaction was begun. Be aware, though,
31     # that the objects by default will _not_ have their instance data returned to their pre-transactional state.
32     #
33     # == Different ActiveRecord classes in a single transaction
34     #
35     # Though the transaction class method is called on some ActiveRecord class,
36     # the objects within the transaction block need not all be instances of
37     # that class.
38     # In this example a <tt>Balance</tt> record is transactionally saved even
39     # though <tt>transaction</tt> is called on the <tt>Account</tt> class:
40     #
41     #   Account.transaction do
42     #     balance.save!
43     #     account.save!
44     #   end
45     #
46     # == Transactions are not distributed across database connections
47     #
48     # A transaction acts on a single database connection.  If you have
49     # multiple class-specific databases, the transaction will not protect
50     # interaction among them.  One workaround is to begin a transaction
51     # on each class whose models you alter:
52     #
53     #   Student.transaction do
54     #     Course.transaction do
55     #       course.enroll(student)
56     #       student.units += course.units
57     #     end
58     #   end
59     #
60     # This is a poor solution, but full distributed transactions are beyond
61     # the scope of Active Record.
62     #
63     # == Save and destroy are automatically wrapped in a transaction
64     #
65     # Both Base#save and Base#destroy come wrapped in a transaction that ensures that whatever you do in validations or callbacks
66     # will happen under the protected cover of a transaction. So you can use validations to check for values that the transaction
67     # depends on or you can raise exceptions in the callbacks to rollback.
68     #
69     # == Exception handling
70     #
71     # Also have in mind that exceptions thrown within a transaction block will be propagated (after triggering the ROLLBACK), so you
72     # should be ready to catch those in your application code. One exception is the ActiveRecord::Rollback exception, which will
73     # trigger a ROLLBACK when raised, but not be re-raised by the transaction block.
74     module ClassMethods
75       def transaction(&block)
76         previous_handler = trap('TERM') { raise TransactionError, "Transaction aborted" }
77         increment_open_transactions
78
79         begin
80           connection.transaction(Thread.current['start_db_transaction'], &block)
81         ensure
82           decrement_open_transactions
83           trap('TERM', previous_handler)
84         end
85       end
86
87       private
88         def increment_open_transactions #:nodoc:
89           open = Thread.current['open_transactions'] ||= 0
90           Thread.current['start_db_transaction'] = open.zero?
91           Thread.current['open_transactions'] = open + 1
92         end
93
94         def decrement_open_transactions #:nodoc:
95           Thread.current['open_transactions'] -= 1
96         end
97     end
98
99     def transaction(&block)
100       self.class.transaction(&block)
101     end
102
103     def destroy_with_transactions #:nodoc:
104       transaction { destroy_without_transactions }
105     end
106
107     def save_with_transactions(perform_validation = true) #:nodoc:
108       rollback_active_record_state! { transaction { save_without_transactions(perform_validation) } }
109     end
110
111     def save_with_transactions! #:nodoc:
112       rollback_active_record_state! { transaction { save_without_transactions! } }
113     end
114
115     # Reset id and @new_record if the transaction rolls back.
116     def rollback_active_record_state!
117       id_present = has_attribute?(self.class.primary_key)
118       previous_id = id
119       previous_new_record = @new_record
120       yield
121     rescue Exception
122       @new_record = previous_new_record
123       if id_present
124         self.id = previous_id
125       else
126         @attributes.delete(self.class.primary_key)
127         @attributes_cache.delete(self.class.primary_key)
128       end 
129       raise
130     end
131   end
132 end
Note: See TracBrowser for help on using the browser.