PageRenderTime 44ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/integration-tests/apps/rails2/frozen/vendor/rails/activerecord/lib/active_record/transactions.rb

https://gitlab.com/meetly/torquebox
Ruby | 235 lines | 55 code | 10 blank | 170 comment | 1 complexity | 9553e5507efbd5dbd2e4bbc9c152df57 MD5 | raw file
  1. require 'thread'
  2. module ActiveRecord
  3. # See ActiveRecord::Transactions::ClassMethods for documentation.
  4. module Transactions
  5. class TransactionError < ActiveRecordError # :nodoc:
  6. end
  7. def self.included(base)
  8. base.extend(ClassMethods)
  9. base.class_eval do
  10. [:destroy, :save, :save!].each do |method|
  11. alias_method_chain method, :transactions
  12. end
  13. end
  14. end
  15. # Transactions are protective blocks where SQL statements are only permanent
  16. # if they can all succeed as one atomic action. The classic example is a
  17. # transfer between two accounts where you can only have a deposit if the
  18. # withdrawal succeeded and vice versa. Transactions enforce the integrity of
  19. # the database and guard the data against program errors or database
  20. # break-downs. So basically you should use transaction blocks whenever you
  21. # have a number of statements that must be executed together or not at all.
  22. # Example:
  23. #
  24. # ActiveRecord::Base.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
  30. # +withdrawal+ nor +deposit+ raises an exception. Exceptions will force a
  31. # ROLLBACK that returns the database to the state before the transaction was
  32. # begun. Be aware, though, that the objects will _not_ have their instance
  33. # data returned to their pre-transactional state.
  34. #
  35. # == Different Active Record classes in a single transaction
  36. #
  37. # Though the transaction class method is called on some Active Record class,
  38. # the objects within the transaction block need not all be instances of
  39. # that class. This is because transactions are per-database connection, not
  40. # per-model.
  41. #
  42. # In this example a <tt>Balance</tt> record is transactionally saved even
  43. # though <tt>transaction</tt> is called on the <tt>Account</tt> class:
  44. #
  45. # Account.transaction do
  46. # balance.save!
  47. # account.save!
  48. # end
  49. #
  50. # Note that the +transaction+ method is also available as a model instance
  51. # method. For example, you can also do this:
  52. #
  53. # balance.transaction do
  54. # balance.save!
  55. # account.save!
  56. # end
  57. #
  58. # == Transactions are not distributed across database connections
  59. #
  60. # A transaction acts on a single database connection. If you have
  61. # multiple class-specific databases, the transaction will not protect
  62. # interaction among them. One workaround is to begin a transaction
  63. # on each class whose models you alter:
  64. #
  65. # Student.transaction do
  66. # Course.transaction do
  67. # course.enroll(student)
  68. # student.units += course.units
  69. # end
  70. # end
  71. #
  72. # This is a poor solution, but full distributed transactions are beyond
  73. # the scope of Active Record.
  74. #
  75. # == Save and destroy are automatically wrapped in a transaction
  76. #
  77. # Both Base#save and Base#destroy come wrapped in a transaction that ensures
  78. # that whatever you do in validations or callbacks will happen under the
  79. # protected cover of a transaction. So you can use validations to check for
  80. # values that the transaction depends on or you can raise exceptions in the
  81. # callbacks to rollback, including <tt>after_*</tt> callbacks.
  82. #
  83. # == Exception handling and rolling back
  84. #
  85. # Also have in mind that exceptions thrown within a transaction block will
  86. # be propagated (after triggering the ROLLBACK), so you should be ready to
  87. # catch those in your application code.
  88. #
  89. # One exception is the ActiveRecord::Rollback exception, which will trigger
  90. # a ROLLBACK when raised, but not be re-raised by the transaction block.
  91. #
  92. # *Warning*: one should not catch ActiveRecord::StatementInvalid exceptions
  93. # inside a transaction block. StatementInvalid exceptions indicate that an
  94. # error occurred at the database level, for example when a unique constraint
  95. # is violated. On some database systems, such as PostgreSQL, database errors
  96. # inside a transaction causes the entire transaction to become unusable
  97. # until it's restarted from the beginning. Here is an example which
  98. # demonstrates the problem:
  99. #
  100. # # Suppose that we have a Number model with a unique column called 'i'.
  101. # Number.transaction do
  102. # Number.create(:i => 0)
  103. # begin
  104. # # This will raise a unique constraint error...
  105. # Number.create(:i => 0)
  106. # rescue ActiveRecord::StatementInvalid
  107. # # ...which we ignore.
  108. # end
  109. #
  110. # # On PostgreSQL, the transaction is now unusable. The following
  111. # # statement will cause a PostgreSQL error, even though the unique
  112. # # constraint is no longer violated:
  113. # Number.create(:i => 1)
  114. # # => "PGError: ERROR: current transaction is aborted, commands
  115. # # ignored until end of transaction block"
  116. # end
  117. #
  118. # One should restart the entire transaction if a StatementError occurred.
  119. #
  120. # == Nested transactions
  121. #
  122. # #transaction calls can be nested. By default, this makes all database
  123. # statements in the nested transaction block become part of the parent
  124. # transaction. For example:
  125. #
  126. # User.transaction do
  127. # User.create(:username => 'Kotori')
  128. # User.transaction do
  129. # User.create(:username => 'Nemu')
  130. # raise ActiveRecord::Rollback
  131. # end
  132. # end
  133. #
  134. # User.find(:all) # => empty
  135. #
  136. # It is also possible to requires a sub-transaction by passing
  137. # <tt>:requires_new => true</tt>. If anything goes wrong, the
  138. # database rolls back to the beginning of the sub-transaction
  139. # without rolling back the parent transaction. For example:
  140. #
  141. # User.transaction do
  142. # User.create(:username => 'Kotori')
  143. # User.transaction(:requires_new => true) do
  144. # User.create(:username => 'Nemu')
  145. # raise ActiveRecord::Rollback
  146. # end
  147. # end
  148. #
  149. # User.find(:all) # => Returns only Kotori
  150. #
  151. # Most databases don't support true nested transactions. At the time of
  152. # writing, the only database that we're aware of that supports true nested
  153. # transactions, is MS-SQL. Because of this, Active Record emulates nested
  154. # transactions by using savepoints. See
  155. # http://dev.mysql.com/doc/refman/5.0/en/savepoints.html
  156. # for more information about savepoints.
  157. #
  158. # === Caveats
  159. #
  160. # If you're on MySQL, then do not use DDL operations in nested transactions
  161. # blocks that are emulated with savepoints. That is, do not execute statements
  162. # like 'CREATE TABLE' inside such blocks. This is because MySQL automatically
  163. # releases all savepoints upon executing a DDL operation. When #transaction
  164. # is finished and tries to release the savepoint it created earlier, a
  165. # database error will occur because the savepoint has already been
  166. # automatically released. The following example demonstrates the problem:
  167. #
  168. # Model.connection.transaction do # BEGIN
  169. # Model.connection.transaction(:requires_new => true) do # CREATE SAVEPOINT active_record_1
  170. # Model.connection.create_table(...) # active_record_1 now automatically released
  171. # end # RELEASE savepoint active_record_1
  172. # # ^^^^ BOOM! database error!
  173. # end
  174. module ClassMethods
  175. # See ActiveRecord::Transactions::ClassMethods for detailed documentation.
  176. def transaction(options = {}, &block)
  177. # See the ConnectionAdapters::DatabaseStatements#transaction API docs.
  178. connection.transaction(options, &block)
  179. end
  180. end
  181. # See ActiveRecord::Transactions::ClassMethods for detailed documentation.
  182. def transaction(&block)
  183. self.class.transaction(&block)
  184. end
  185. def destroy_with_transactions #:nodoc:
  186. with_transaction_returning_status(:destroy_without_transactions)
  187. end
  188. def save_with_transactions(perform_validation = true) #:nodoc:
  189. rollback_active_record_state! { with_transaction_returning_status(:save_without_transactions, perform_validation) }
  190. end
  191. def save_with_transactions! #:nodoc:
  192. rollback_active_record_state! { self.class.transaction { save_without_transactions! } }
  193. end
  194. # Reset id and @new_record if the transaction rolls back.
  195. def rollback_active_record_state!
  196. id_present = has_attribute?(self.class.primary_key)
  197. previous_id = id
  198. previous_new_record = new_record?
  199. yield
  200. rescue Exception
  201. @new_record = previous_new_record
  202. if id_present
  203. self.id = previous_id
  204. else
  205. @attributes.delete(self.class.primary_key)
  206. @attributes_cache.delete(self.class.primary_key)
  207. end
  208. raise
  209. end
  210. # Executes +method+ within a transaction and captures its return value as a
  211. # status flag. If the status is true the transaction is committed, otherwise
  212. # a ROLLBACK is issued. In any case the status flag is returned.
  213. #
  214. # This method is available within the context of an ActiveRecord::Base
  215. # instance.
  216. def with_transaction_returning_status(method, *args)
  217. status = nil
  218. self.class.transaction do
  219. status = send(method, *args)
  220. raise ActiveRecord::Rollback unless status
  221. end
  222. status
  223. end
  224. end
  225. end