Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add rule to prevent bad rescue usage inside a transaction #1386

Open
deanylev opened this issue Nov 19, 2024 · 1 comment
Open

Add rule to prevent bad rescue usage inside a transaction #1386

deanylev opened this issue Nov 19, 2024 · 1 comment

Comments

@deanylev
Copy link

Is your feature request related to a problem? Please describe.

A common mistake made in our Rails codebase is:

    ActiveRecord::Base.transaction do
      # create a few records
    rescue StandardError => e
      # we've rescued too early, meaning no rollback will occur!
    end

Instead of

    begin
      ActiveRecord::Base.transaction do
        # create a few records
      end
    rescue StandardError => e
      # the rollback has already occurred by this point, so we're good to go
    end

Describe the solution you'd like

A RuboCop rule to prevent this would be brilliant, as the effects to data integrity in production can be devastating

Describe alternatives you've considered

I have tried writing a rule myself, to no avail

Additional context

Add any other context or screenshots about the feature request here.

@Earlopain
Copy link
Contributor

I'm not sure if a generic rule for this can be added. The user may want to supress the rollback in which case this seems fine to do. There could also be a raise ActiveRecord::Rollback in the rescue in which case it too would be fine (although a cop could handle that case).

What if a more specific exception like ActiveRecord::RecordNotUnique is rescued. Would that be ok?

That said, a cop for this would not be too complex. Here is what I have:

module RuboCop
  module Cop
    module Rails
      class TransactionRescue < Base
        MSG = 'Do not use beginless rescue inside of transactions.'

        # @!method transaction_rescue?(node)
        def_node_matcher :transaction_rescue?, <<~PATTERN
          (block
            (send _ :transaction)
            _
            (rescue
              {(...) nil?}
              (resbody (array ...) nil? ...)
            nil?)
          )
        PATTERN

        def on_block(node)
          return unless transaction_rescue?(node)

          add_offense(node)
        end
        alias on_numblock on_block
      end
    end
  end
end

It will only detect beginless rescue (not when it was started with an explicit begin) but overall should be about what you want. I didn't test it much though.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants