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