LoginSignup
5
0

More than 5 years have passed since last update.

バグ? callbackを使うときの注意点

Posted at

はじめに

callbackを利用する際に自分が予想していた挙動と異なる動きをすることがあったのでメモとして残しておきます。

問題

今回扱う問題を簡単にまとめると、「callback内で子要素をアソシエーションを用いて作成すると子要素のbefore_updateが呼ばれる」というバグになります。

作成のみするので、子要素のbefore_createだけが呼ばれるのを期待していたところ、なんとbefore_updateまで呼ばれてしまったということです。

具体的なコードを見ながら確認したほうがわかりやすいので以下に示します。
※ 例は適当です

point.rb
class Point < ApplicationRecord
  after_create do
    point_histories.create!
  end

  has_many :point_histories
end
point_history.rb
class PointHistory < ApplicationRecord
  before_create do
    # この部分だけ呼ばれると予想していたが...
  end

  before_update do
    # この部分も呼ばれる
  end

  belongs_to :point
end

この状態で Point.create するとその子要素であるPointHistoryクラスのbefore_updateも呼ばれます。

updateなんてどこにも書いてないのになんでbefore_updateがよばれるんだろう...って感じですね。

原因

この動きになる原因なのですが、Railsにはモデル間がアソシエーションで関連付けられている際、関連づけられているモデルを自動で保存する機能があるのでそれが悪さをしているようでした。

(そもそもcallbackではなくこれをつかってcreateすればって感じですが割愛で笑)

こういうやつですね。

point = Point.new
point.point_histories.build
point.save # pointとpoint_history両方作られる

これを今回の例にあてはめてみると、after_create内部でアソシエーションを用いて、関連づけられているモデルのレコードを作成しているのがわかります。

point.rb
class Point < ApplicationRecord
  after_create do
    # アソシエーションを用いてレコードを作成
    point_histories.create!
  end

  has_many :point_histories
end

アソシエーションを用いてレコードを作成しているため、上記で説明した関連づけられたモデルを自動で保存する機能も動き、(すでにレコードがあるため)before_updateが呼ばれるということになります。

解決策

callbackを利用したい場合の解決策としては「アソシエーションを用いずレコードを作成する」、「autosave: falseオプションを使用する」方法があります。

アソシエーションを用いずレコードを作成する

point.rb
class Point < ApplicationRecord
  after_create do
    PointHistory.create!(point_id: id)
  end

  has_many :point_histories
end

autosave: falseオプションを使用する

point.rb
class Point < ApplicationRecord
  after_create do
    point_histories.create!
  end

  has_many :point_histories, autosave: false
end

おわりに

考えてみればたしかにそうだなーという感じの挙動なのですが、意識していないとバグを生むことにもなるかもしれないのでみなさんも気をつけてください。

なお、本記事で扱ったバグはすでにRailsのissueにもあげられていますが修正には及ばずcloseされてしまったようです。

5
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
0