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 new Rails/PrivateTransactionOption cop #1236

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

wata727
Copy link
Contributor

@wata727 wata727 commented Feb 10, 2024

This PR adds a new cop called Rails/PrivateTransactionOption.

This cop checks whether ActiveRecord::Base.transaction(joinable: _) is used. The joinable option is a private API and is not intended to be called from outside Active Record core.

Passing joinable: false may cause unexpected behavior such as the after_commit callback not firing at the appropriate time.

See also kufu/activerecord-bitemporal#156

The motivation for wanting to add this is that even though the joinable is undocumented, it was recommended in some articles, so it may be inadvertently widely used. It is possible that it is being used incorrectly to simply enable requires_new implicitly without realizing that it affects the behavior of after_commit.


Before submitting the PR make sure the following are checked:

  • The PR relates to only one subject with a clear title and description in grammatically correct, complete sentences.
  • Wrote good commit messages.
  • Commit message starts with [Fix #issue-number] (if the related issue exists).
  • Feature branch is up-to-date with master (if not - rebase it).
  • Squashed related commits together.
  • Added tests.
  • Ran bundle exec rake default. It executes all tests and runs RuboCop on its own code.
  • Added an entry (file) to the changelog folder named {change_type}_{change_description}.md if the new code introduces user-observable changes. See changelog entry format for details.
  • If this is a new cop, consider making a corresponding update to the Rails Style Guide.

This PR adds a new cop called `Rails/PrivateTransactionOption`.

This cop checks whether `ActiveRecord::Base.transaction(joinable: _)`
is used. The `joinable` option is a private API and is not intended
to be called from outside Active Record core.

rails/rails#39912 (comment)
rails/rails#46182 (comment)

Passing `joinable: false` may cause unexpected behavior such as the
`after_commit` callback not firing at the appropriate time.
@koic
Copy link
Member

koic commented Feb 10, 2024

Can you first propose an addition to the Rails Style Guide?
https://github.com/rubocop/rails-style-guide

I'm not sure which articles have made incorrect references, but including a description in the Style Guide that RuboCop Rails follows would serve as one countermeasure to them.

@wata727
Copy link
Contributor Author

wata727 commented Feb 11, 2024

Opened rubocop/rails-style-guide#355.

I'm not sure which articles have made incorrect references

A quick Google search shows the following articles:

Some articles mention unintended side effects, while others do not.

@pirj
Copy link
Member

pirj commented Feb 11, 2024

@pirj
Copy link
Member

pirj commented Feb 11, 2024

#414 Related

@wata727
Copy link
Contributor Author

wata727 commented Feb 11, 2024

documents joinable while not the recent. What happened?

See rails/rails#46186

@pirj
Copy link
Member

pirj commented Feb 11, 2024

Some more context rspec/rspec-rails#2598 (comment) - I withdraw my statement that adding joinable: false is a good practice. With requires_new: true and all explicit transactions (apart from implicit update/create/etc transactions), joinable: false is redundant. And considering the side effects (due to joinable? checks?) it’s plain bad.

@pirj
Copy link
Member

pirj commented Feb 11, 2024

Related cop suggestion #400

@wata727
Copy link
Contributor Author

wata727 commented Feb 11, 2024

Agreed with your opinion. I don't believe it's always right to require requires_new: true in all transactions, but I do believe that using joinable: false is probably wrong most of the time.

In reality, there may be situations where you have to use joinable: false because the transactions used inside a third-party gem do not use requires_new: true, but considering the side effects, it should be a last resort. This cop helps you understand that it must be used with caution and its behavior is not guaranteed by Rails core team.

@pirj
Copy link
Member

pirj commented Feb 12, 2024

Should it be possible to wrap a call to a gem that doesn’t require_new in a transaction to isolate it instead of a wrapping joinable: false?

It is worth mentioning that when reasoning about nested transactions and business logic, the error kernel design pattern comes handy.

@wata727
Copy link
Contributor Author

wata727 commented Feb 12, 2024

As far as I know, there's no way around it. You need to fix the gem.
It might be useful to add some notes on how to fix such things, but I think that is outside the scope of this cop.

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

Successfully merging this pull request may close these issues.

None yet

3 participants