Skip to content

Instantly share code, notes, and snippets.

@ikaruga777
Last active November 10, 2024 06:20
Show Gist options
  • Save ikaruga777/b8b637f8db25a4d6ec8a4e40753fbc05 to your computer and use it in GitHub Desktop.
Save ikaruga777/b8b637f8db25a4d6ec8a4e40753fbc05 to your computer and use it in GitHub Desktop.
トランザクションの中でperform_laterするのを検知するCopのたたき
module RuboCop
module Cop
module ActiveJob
class PerformInTransactions < Base
MSG = 'トランザクションの中でperform_laterすなー'
BUILT_IN_TRANSACTION_METHODS = %i[transaction with_lock].freeze
def_node_search :perform_later_call, <<~PATTERN
{
# 標準的なperform_later呼び出し
(send _ :perform_later ...)
# ActiveJob::Baseを継承したクラスメソッドとしての呼び出し
(send (const _ _) :perform_later ...)
# キューイング関連のメソッド
(send _ {:enqueue_at :enqueue_in} ...)
# Sidekiq::Workerを使用している場合
(send _ {:perform_async :perform_at :perform_in} ...)
# delayed_jobを使用している場合
(send _ :delay ...)
(send (send _ :delay) :perform ...)
}
PATTERN
def on_send(node)
return unless in_transaction_block?(node)
perform_later_call(node.parent.body).each do |statement_node|
next if statement_node.break_type? && nested_block?(statement_node)
statement = statement(statement_node)
message = format(MSG, statement: statement)
add_offense(statement_node, message: message)
end
end
private
def in_transaction_block?(node)
return false unless transaction_method_name?(node.method_name)
return false unless (parent = node.parent)
parent.block_type? && parent.body
end
def statement(statement_node)
if statement_node.return_type?
'return'
elsif statement_node.break_type?
'break'
else
statement_node.method_name
end
end
def nested_block?(statement_node)
name = statement_node.ancestors.find(&:block_type?).children.first.method_name
!transaction_method_name?(name)
end
def transaction_method_name?(method_name)
BUILT_IN_TRANSACTION_METHODS.include?(method_name) || transaction_method?(method_name)
end
def transaction_method?(method_name)
cop_config.fetch('TransactionMethods', []).include?(method_name.to_s)
end
end
end
end
end
Model.transaction do
Job.set(:wait_until).perform_later
end
class User < ApplicationRecord
def some_method
transaction do
save!
SomeJob.perform_later(id) # Copが警告を出す
end
end
end
class User < ApplicationRecord
def some_method
transaction do
save!
end
SomeJob.perform_later(id) # トランザクションの外なのでsafe
end
end
$ rubocop example.rb
Inspecting 1 file
C

Offenses:

example.rb:2:3: C: ActiveJob/PerformInTransactions: トランザクションの中でperform_laterすなー
  Job.set(:wait_until).perform_later
  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
example.rb:9:7: C: ActiveJob/PerformInTransactions: トランザクションの中でperform_laterすなー
      SomeJob.perform_later(id)  # Copが警告を出す
      ^^^^^^^^^^^^^^^^^^^^^^^^^

1 file inspected, 2 offenses detected
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment