Class: RuboCop::Cop::Rails::TransactionExitStatement

Inherits:
Base
  • Object
show all
Defined in:
lib/rubocop/cop/rails/transaction_exit_statement.rb

Overview

Checks for the use of exit statements (namely return, break and throw) in transactions. This is due to the eventual unexpected behavior when using ActiveRecord >= 7, where transactions exited using these statements are being rollbacked rather than committed (pre ActiveRecord 7 behavior).

As alternatives, it would be more intuitive to explicitly raise an error when rollback is desired, and to use next when commit is desired.

If you are defining custom transaction methods, you can configure it with TransactionMethods.

NOTE: This cop is disabled on Rails >= 7.2 because transactions were restored to their historical behavior. In Rails 7.1, the behavior is controlled with the config active_record.commit_transaction_on_non_local_return.

Examples:

# bad
ApplicationRecord.transaction do
  return if user.active?
end

# bad
ApplicationRecord.transaction do
  break if user.active?
end

# bad
ApplicationRecord.transaction do
  throw if user.active?
end

# bad, as `with_lock` implicitly opens a transaction too
user.with_lock do
  throw if user.active?
end

# bad, as `with_lock` implicitly opens a transaction too
ApplicationRecord.with_lock do
  break if user.active?
end

# good
ApplicationRecord.transaction do
  # Rollback
  raise "User is active" if user.active?
end

# good
ApplicationRecord.transaction do
  # Commit
  next if user.active?
end

TransactionMethods: [“custom_transaction”]

# bad
CustomModel.custom_transaction do
  return if user.active?
end

Constant Summary collapse

MSG =
'Exit statement `%<statement>s` is not allowed. Use `raise` (rollback) or `next` (commit).'
BUILT_IN_TRANSACTION_METHODS =
i[transaction with_lock].freeze

Instance Method Summary collapse

Instance Method Details

#on_send(node) ⇒ Object



82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/rubocop/cop/rails/transaction_exit_statement.rb', line 82

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