18435


## 引用

[ぢみへんプログラミング日誌 ActiveRecord::Base.transaction
はネストしたトランザクションに対応しているのか確認した。](http://jimiprg.blog.shinobi.jp/ruby%20on%20rails/activerecord
--base.transac)

> 細かい説明は抜きにして、以下のirb (rails console -s で起動するコンソールモード)での実行内容を見てみよう。  
>  まずはUserというモデルをsaveメソッド単体で保存してみる。
[code]

>     [root@localhost base]# rails console -s
>     Loading development environment in sandbox (Rails 4.0.0.beta1)
>     Any modifications you make will be rolled back on exit
>     irb(main):001:0> a  = User.new
>     => #
>     irb(main):002:0> a.user_no = "123"
>     => "123"
>     irb(main):003:0> a.user_name = "aiueo"
>     => "aiueo"
>     irb(main):004:0> a.save
>       (0.3ms)  BEGIN
>       User Exists (1.6ms)  SELECT 1 AS one FROM "users" WHERE
> "users"."user_no" = '123' LIMIT 1
>       SQL (13.1ms)  INSERT INTO "users" ("updated_at", "user_name",
> "user_no") VALUES ($1, $2, $3, $4) RETURNING "id"  [ ["created_at", Wed, 13
> Mar 2013 15:10:12 UTC +00:00], ["updated_at", Wed, 13 Mar 2013 15:10:12 UTC
> +00:00],["user_name", "aiueo"], ["user_no", "123"]]
>        (3.3ms)  COMMIT
>     => true
>  
[/code]

>
> 見ての通り、saveメソッドは単体でトランザクションも起動している。

  

## 前置き

上記からわかりますが、railsは明示的に指定しなくてもトランザクションを勝手に行ってくれます。

トランザクションの中で例外が起こると自動でロールバックしてくれるわけです。

（上記引用では、`save`なので例えDBの書き込みに失敗してもロールバックはしてくれません。`save!`ならしてくれる。補足：上記サイトは後半で自分でraiseして動作確認を試みている）

## railsが自動で行うトランザクションはなんのためか？

ここからは推測ですが、  
レコードを1件更新するだけのような場合は失敗してもそもそもDBに更新されたデータがないのでロールバックの必要がありません（そもそもロールバックできる対象がない）。

そこで ではどういうときにロールバックが必要かと考えた場合、  
`has_manyな関係`の処理ではないかと考えました。

[Rails4 - 複数の子レコードを作成・更新する. accepts_nested_attributes_for -
Qiita](http://qiita.com/hmuronaka/items/818c421dc632e3efb7a6)

つまり上記サイト（引用）にある

[code]

    class Member < ActiveRecord::Base
      has_many :posts
      accepts_nested_attributes_for :posts
    end
    
[/code]

* * *
[code]

    params = { member: {
      name: 'joe', posts_attributes: [
        { title: 'Kari, the awesome Ruby documentation browser!' },
        { title: 'The egalitarian assumption of the modern citizen' },
        { title: '', _destroy: '1' } # this will be ignored
      ]
    }}
    
    member = Member.create(params[:member])
    member.posts.length # => 2
    member.posts.first.title # => 'Kari, the awesome Ruby documentation browser!'
    member.posts.second.title # => 'The egalitarian assumption of the modern citizen'
    
[/code]

のようなデータをDBに書き込みにいくときは、Membersテーブルと、Postsテーブルに書き込みにいく処理があるわけでして、どちらか片方のテーブルでエラーがあるとロールバックして欲しいということになります。

この考えから導いた推測は、

  * has_manyな関係の更新のようにrailsが内部で複数テーブルを更新しにいくときは自動トランザクションは`!メソッド`を使っていた場合、特に意識していなかったがロールバックしてくれてうれしい

  * 1レコードの更新のときは、トランザクションの必要はないのにとりあえず自動でトランザクションが走っている

ということになりました。  
しかし、1レコードのときに無駄な処理が走っているという自分なりの結論が何か腑に落ちません。無駄な処理が走るようなものなのでしょうか？


CC BY-SA 3.0
2015-11-05T04:19:39.717
18419
2015-11-05T09:49:40.807
2020-06-17T08:14:45.997
-1
9008
question
ruby-on-rails rails-activerecord database
railsが自動で行うトランザクションはhas_manyな関係のためでしょうか？


（これかなというのを見つけてしまいましたので自己回答です...）

## save, save!, destroyのコールバック群はトランザクション内で処理されるからです

[ActiveRecordのコールバックの順番・コールバック内のロールバック処理 - Hack Your
Design!](http://blog.toshimaru.net/active-record-callbacks/)

> ドキュメントにはこう書いてある  
>  
>  The entire callback chain of a save, save!, or destroy call runs within a
> transaction. That includes after_* hooks. If everything goes fine a COMMIT
> is executed once the chain has been completed.  
>  
>  save, save!, destroyのコールバック群はトランザクション内で処理されますよ、と。

  
  
コールバックではプログラマが任意にロールバックさせることが可能です。

> before_*のタイミングで false を返すと処理はロールバックされる。
>
> after_* のタイミングでロールバックしたい場合は、明示的にRollbackをraiseしてやれば ok

  
  
注目は  
`after_save`というコールバックでしょう。  
`save`はしたけれどもまだ`commit`していないので、ロールバック可能です。

つまりたった1件のレコード更新でもコールバックでプログラマにロールバックを起こせるチャンスをrailsは提供してくれているのです。

(`save`したけどやっぱり戻してという処理は基本的に書きたいということはないように思いますが、要件的に必要という場面は存在する可能性はありそうです）

単純にrailsはコールバック群をトランザクション内で処理する仕様として覚えてもよいかもしれませんが、1件でもトランザクションを走らせている理由はコールバック群でロールバックをできるようにするためと思われます。


CC BY-SA 3.0
2015-11-05T09:49:40.807
18435
2015-11-05T09:49:40.807
9008
18419
answer