2
1

More than 3 years have passed since last update.

ActiveRecordのwith_lockとキャッシュの関係

Posted at

背景

こんなコードを書いた時にふとこのコードってどうなっているんだ?
となったので実際のRailsのコードを読んでみました。

やりたいことは1つのHogeに対して外部APIを必ず1回しか呼ばないようにして、その結果をFugaに保存する。

class Hoge < ActiveRecord::Base
  has_oen :fuga

  def piyo
    return fuga if fuga.present? # <- ロック取りたくないから事前に関連があるか確認
    with_lock do
      reload # <- fugaのキャッシュを消したい ※1
      return fuga if fuga.present?

      result = call_api
      create_fuga(**result)
    end
  end

  private

  def call_api
    # call api
  end
end

class Fuga < ActiveRecord::Base
  belongs_to :hoge
end

当初※1のreloadがなくてこれってfugaがキャッシュされていてタイミングissue発生するのでは?と思ったのですが、
specでどうしてもエラーケースを再現できずRailsのコードを読んでみた。

# https://github.com/rails/rails/blob/914caca2d31bd753f47f9168f2a375921d9e91cc/activerecord/lib/active_record/locking/pessimistic.rb#L82-L90

# Wraps the passed block in a transaction, locking the object
# before yielding. You can pass the SQL locking clause
# as argument (see <tt>lock!</tt>).
def with_lock(lock = true)
  transaction do
    lock!(lock)
    yield
  end
end

というわけでsee lock!

# https://github.com/rails/rails/blob/914caca2d31bd753f47f9168f2a375921d9e91cc/activerecord/lib/active_record/locking/pessimistic.rb#L63-L80

# Obtain a row lock on this record. Reloads the record to obtain the requested
# lock. Pass an SQL locking clause to append the end of the SELECT statement
# or pass true for "FOR UPDATE" (the default, an exclusive row lock). Returns
# the locked record.
def lock!(lock = true)
  if persisted?
    if has_changes_to_save?
      raise(<<-MSG.squish)
        Locking a record with unpersisted changes is not supported. Use
        `save` to persist the changes, or `reload` to discard them
        explicitly.
      MSG
    end

    reload(lock: lock)
  end
  self
end

なるほど〜ん
persisted?の時には内部でreload読んでくれてるんですね。。
どうりでテスト落ちないわけですわ
というわけでreload追加する必要がないとわかってspecのケースだけ追加しましたとさ

2
1
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
2
1