Relationship between ActiveRecord with_lock and cache

background

What happened to this code when I wrote this code? So I read the actual Rails code.

What I want to do is make sure to call the external API only once for one Hoge and save the result in Fuga.

class Hoge < ActiveRecord::Base
  has_oen :fuga

  def piyo
    return fuga if fuga.present? # <-I don't want to lock it, so check if it's related in advance
    with_lock do
      reload # <-I want to clear the cache of 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

Initially, there is no reload of * 1 and fuga is cached, so a timing issue may occur. I thought, I couldn't reproduce the error case with spec, so I read the Rails code.

https://api.rubyonrails.org/classes/ActiveRecord/Locking/Pessimistic.html

# 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

So 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

I see At the time of persisted ?, reload is read internally. .. No wonder the test doesn't fail. That's why I realized that I didn't need to add reload, so I added only the spec case.

Recommended Posts

Relationship between ActiveRecord with_lock and cache
Relationship between Controller and View
Relationship between package and class
Relationship between database and model (basic)
[Java] Relationship between H2DB and JDBC
Relationship between Eclipse m2e plugin and Maven
Relationship between UI test and recording, implementation method
About the relationship between HTTP methods, actions and CRUD
Verification of the relationship between Docker images and containers
Switch between JDK 7 and JDK 8
Difference between vh and%
Difference between i ++ and ++ i
Difference between product and variant
Difference between redirect_to and render
[Java] Difference between == and equals
Rails: Difference between resources and resources
Difference between puts and print
Difference between redirect_to and render
Difference between CUI and GUI
Difference between variables and instance variables
Difference between mockito-core and mockito-all
Difference between class and instance
Difference between bundle and bundle install
Connection between ViewModel and XML
Difference between ArrayList and LinkedList
Difference between render and redirect_to
Difference between List and ArrayList
Differences between IndexOutOfBoundsException and ArrayIndexOutOfBoundsException
Difference between .bashrc and .bash_profile
Difference between StringBuilder and StringBuffer
Difference between render and redirect_to
Difference between render and redirect_to
[Ruby] Relationship between parent class and child class. The relationship between a class and an instance.
[Java] Introductory structure Class definition Relationship between class and instance Method definition format
The relationship between strict Java date checking and daylight savings time
About the relationship between the Java String equality operator (==) and initialization. Beginners