rails:更新系処理の悲観的ロックのサンプル

掲題を実現する場合のサンプル。

処理で更新されるテーブルのモデルに、以下のようなメソッドを実装。

class BatchExecUpdateDate < ApplicationRecord
  # batch_exec_update_dates
  #
  # id        :bigint(8)  not null, primary key
  # proc_type :integer    not null
  # upated_at :datetime   not null

  enum proc_type : { daily: 0, monthly: 1}

  class << self
    def transaction_with_lock(proc_type, &block)
      target_rec = find_or_create_by(proc_type: proc_type)

      ActiveRecord::Base transacrion do
        # 行ロック取得
        lock_rec = BatchExecUpdateDate.lock('FOR UPDATE NOWAIT').find(target_rec.id)
        # 引数で渡されたブロックを処理する
        block.call
        lock_rec.update(updated_at: Time.zone.now)
      end
      true
    rescue ActiveRecord::StatementInvalid => e
      return false if e.cause&.kind_of?(PG::LockNotAvailable)

      raise e
    end
  end
end

modelでupdate_atを必須valideteかけている場合は、
明示的にupdated_atをセットしないとエラーになる
validate指定を外すと、自動でセットしてcreateしてくれる

validates :updated_at, presence: true

target_rec = find_or_create_by(proc_type: proc_type) {|beud| beud.updated_at = Time.zone.now}

以下のような処理があったとして

# 処理クラス
class AaaBatch
  class << self
    def update_process
      # 更新処理
    end
  end
end

以下のように呼び出すことで、実現できる。

# 起動するとき
BatchExecUpdateDate.transaction_with_lock(proc_type) do
  AaaBatch.update_process(0)
  BbbBatch.update_process(0)
  :
end