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