class RuboCop::Cop::Rails::TransactionExitStatement


end
return if user.active?
CustomModel.custom_transaction do
# bad
@example TransactionMethods: [“custom_transaction”]
end
next if user.active?
# Commit
ApplicationRecord.transaction do
# good
end
raise “User is active” if user.active?
# Rollback
ApplicationRecord.transaction do
# good
end
break if user.active?
ApplicationRecord.with_lock do
# bad, as ‘with_lock` implicitly opens a transaction too
end
throw if user.active?
user.with_lock do
# bad, as `with_lock` implicitly opens a transaction too
end
throw if user.active?
ApplicationRecord.transaction do
# bad
end
break if user.active?
ApplicationRecord.transaction do
# bad
end
return if user.active?
ApplicationRecord.transaction do
# bad
@example
the config `active_record.commit_transaction_on_non_local_return`.
to their historical behavior. In Rails 7.1, the behavior is controlled with
NOTE: This cop is disabled on Rails >= 7.2 because transactions were restored
If you are defining custom transaction methods, you can configure it with `TransactionMethods`.
desired.
error when rollback is desired, and to use `next` when commit is
As alternatives, it would be more intuitive to explicitly raise an
committed (pre ActiveRecord 7 behavior).
exited using these statements are being rollbacked rather than
unexpected behavior when using ActiveRecord >= 7, where transactions
`break` and `throw`) in transactions. This is due to the eventual
Checks for the use of exit statements (namely `return`,

def in_transaction_block?(node)

def in_transaction_block?(node)
  return false unless transaction_method_name?(node.method_name)
  return false unless (parent = node.parent)
  parent.any_block_type? && parent.body
end

def nested_block?(statement_node)

def nested_block?(statement_node)
  name = statement_node.ancestors.find(&:any_block_type?).children.first.method_name
  !transaction_method_name?(name)
end

def on_send(node)

def on_send(node)
  return if target_rails_version >= 7.2
  return unless in_transaction_block?(node)
  exit_statements(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

def statement(statement_node)

def statement(statement_node)
  if statement_node.return_type?
    'return'
  elsif statement_node.break_type?
    'break'
  else
    statement_node.method_name
  end
end

def transaction_method?(method_name)

def transaction_method?(method_name)
  cop_config.fetch('TransactionMethods', []).include?(method_name.to_s)
end

def transaction_method_name?(method_name)

def transaction_method_name?(method_name)
  BUILT_IN_TRANSACTION_METHODS.include?(method_name) || transaction_method?(method_name)
end