「RailsによるアジャイルWebアプリケーション開発」19章まで読了
トランザクションのお話。
本書中では、一テーブルに対するトランザクション(よくある残高の問題)の例だったのですが、1トランザクション内での複数テーブルへの同時更新というのも良くある話なので、こちらをサンプルとして実装することにしました。
例として、以前扱った、注文(Order)と請求書(Invoice)のサンプルを使います。
で、トランザクションを実装する基本形は、
modelClass.transaction do ... end
なのですが、複数テーブルにまたがる場合は、どちらのモデルクラスを書けば良いのかが不明だったので、両方使ってみました。
結果的には、どちらで書いても良いようです。
require 'rubygems' require 'activerecord' ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => "../db/development.sqlite3") class Order < ActiveRecord::Base has_one :invoice end class Invoice < ActiveRecord::Base belongs_to :order validates_presence_of :name end order1 = Order.new( :name => "taka_2", :address => "Kanagawa prefecture", :email => "taka_2@test.com", :payment_type_id => 1) order2 = Order.new( :name => "taka_22", :address => "Kanagawa prefecture", :email => "taka_22@test.com", :payment_type_id => 1) inv1 = Invoice.new( :order_id => order1.id, :name => "taka_22") inv2 = Invoice.new( :order_id => order2.id, :name => "") puts("Order.transactionを使う") begin Order.transaction do order1.save! inv1.save! end rescue puts 'order1 / inv1の保存に失敗しました。' end # 後始末 Order.delete(order1.id) Invoice.delete(inv1.id) puts("Invoice.transactionを使う") begin Invoice.transaction do order1.save! inv1.save! end rescue puts 'order1 / inv1の保存に失敗しました。' end # 後始末 Order.delete(order1.id) Invoice.delete(inv1.id) puts("Order.transactionを使う(Fail)") begin Order.transaction do order2.save! inv2.save! end rescue puts 'order2 / inv2の保存に失敗しました。' end # 後始末 #Order.delete(order2.id) #Invoice.delete(inv2.id) puts("Invoice.transactionを使う(Fail)") begin Invoice.transaction do order2.save! inv2.save! end rescue puts 'order2 / inv2の保存に失敗しました。' end # 後始末 #Order.delete(order2.id) #Invoice.delete(inv2.id)
結果はこう。
Order.transactionを使う Invoice.transactionを使う Order.transactionを使う(Fail) order2 / inv2の保存に失敗しました。 Invoice.transactionを使う(Fail) order2 / inv2の保存に失敗しました。
データベースの中身は示しませんが、Orderテーブルが保存されていないことが確認できました。
1データベース内のトランザクションは、コネクション単位だと思うのですが、あえて各モデルオブジェクトからアクセスできるようにしているのは、ある種の冗長を持たせているようです。