Using Transactions in ActiveRecord
Problem
You want to perform database operations as a group: if one of the operations fails, it should be as though none of them had ever happened.
Solution
Include active_record/ transactions, and youll give each ActiveRecord class a TRansaction method. This method starts a database transaction, runs a code block, then commits the transaction. If the code block throws an exception, the database transaction is rolled back.
Heres some simple initialization code to give ActiveRecord access to the database tables for the weblog system first seen in Recipe 13.11:
require cookbook_dbconnect activerecord_connect # See chapter introduction class User < ActiveRecord::Base has_and_belongs_to_many :blog_posts end class BlogPost < ActiveRecord::Base has_and_belongs_to_many :authors, :class_name => User end
The create_from_new_author method below creates a new entry in the users table, then associates it with a new entry in the blog_posts table. But theres a 50% chance that an exception will be thrown right after the new author is created. If that happens, the author creation is rolled back: in effect, it never happened.
require active_record/ transactions class BlogPost def BlogPost.create_from_new_author(author_name, title, content) transaction do author = User.create(:name => author_name) raise Random failure! if rand(2) == 0 create(:authors => [author], :title => title, :content => content) end end end
Since the whole operation is enclosed within a TRansaction block, an exception won leave the database in a state where the author has been created but the blog entry hasn :
BlogPost.create_from_new_author(Carol, The End Is Near,
A few more facts of doom…)
# => #
# The method succeeded; Carols in the database:
User.find(:first, :conditions=>"name=Carol")
# => #
Discussion
You should use database transactions whenever one database operation puts the database into an inconsistent state, and a second operation brings the database back into consistency. All kinds of things can go wrong between the first and second operation. The database server might crash or your application might throw an exception. The Ruby interpreter might decide to stop running your thread for an arbitrarily long time, giving other threads a chance to marvel at the inconsistent state of the database. An inconsistent database can cause problems that are very difficult to debug and fix.
ActiveRecords transactions piggyback on top of database transactions, so theyll only work if your database supports transactions. Most databases do these days; chances are you won have trouble unless you e using a MySQL database and not using InnoDB tables. However, most of the open source databases don support nested transactions, so you e limited to one transaction at a time with a given database connection.
In addition to a code block, the transaction method can take a number of ActiveRecord objects. These are the objects that participate in the transaction. If the transaction fails, then not only will the database be restored to its previous state, so will the member variables of the objects.
This is useful if you e defining a method that modifies ActiveRecord objects themselves, not just the database representations of those objects. For instance, a shopping cart object might keep a running total thats consulted by the application, but not stored in the database.
See Also
- http://wiki.rubyonrails.com/rails/pages/HowToUseTransactions
- http://rubyonrails.org/api/classes/ActiveRecord/Transactions/ClassMethods.html
Категории