Skip to content

Commit

Permalink
Add new cop BangPersistence
Browse files Browse the repository at this point in the history
  • Loading branch information
aliismayilov committed Feb 17, 2025
1 parent 2d257f2 commit e3fa25b
Show file tree
Hide file tree
Showing 7 changed files with 172 additions and 0 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

- Handle unknown HTTP status codes for `RSpecRails/HttpStatus` cop. ([@viralpraxis])
- Fix a false negative for `RSpecRails/TravelAround` cop when passed as a proc to a travel method. ([@ydah])
- Introduce new cop `RSpecRails/BangPersistence` cop. ([@aliismayilov])

## 2.30.0 (2024-06-12)

Expand Down Expand Up @@ -72,6 +73,7 @@
<!-- Contributors (alphabetically) -->

[@akiomik]: https://github.com/akiomik
[@aliismayilov]: https://github.com/aliismayilov
[@anthony-robin]: https://github.com/anthony-robin
[@bquorning]: https://github.com/bquorning
[@corydiamand]: https://github.com/corydiamand
Expand Down
7 changes: 7 additions & 0 deletions config/default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@ RSpecRails/AvoidSetupHook:
VersionAdded: '2.4'
Reference: https://www.rubydoc.info/gems/rubocop-rspec_rails/RuboCop/Cop/RSpecRails/AvoidSetupHook

RSpecRails/BangPersistence:
Description: Prefer bang methods for persistence.
Enabled: pending
SafeAutoCorrect: false
VersionAdded: "<<next>>"
Reference: https://www.rubydoc.info/gems/rubocop-rspec_rails/RuboCop/Cop/RSpecRails/BangPersistence

RSpecRails/HaveHttpStatus:
Description: Checks that tests use `have_http_status` instead of equality matchers.
Enabled: pending
Expand Down
1 change: 1 addition & 0 deletions docs/modules/ROOT/pages/cops.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
=== Department xref:cops_rspecrails.adoc[RSpecRails]

* xref:cops_rspecrails.adoc#rspecrailsavoidsetuphook[RSpecRails/AvoidSetupHook]
* xref:cops_rspecrails.adoc#rspecrailsbangpersistence[RSpecRails/BangPersistence]
* xref:cops_rspecrails.adoc#rspecrailshavehttpstatus[RSpecRails/HaveHttpStatus]
* xref:cops_rspecrails.adoc#rspecrailshttpstatus[RSpecRails/HttpStatus]
* xref:cops_rspecrails.adoc#rspecrailsinferredspectype[RSpecRails/InferredSpecType]
Expand Down
45 changes: 45 additions & 0 deletions docs/modules/ROOT/pages/cops_rspecrails.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,51 @@ end
* https://www.rubydoc.info/gems/rubocop-rspec_rails/RuboCop/Cop/RSpecRails/AvoidSetupHook
[#rspecrailsbangpersistence]
== RSpecRails/BangPersistence
|===
| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed
| Pending
| Yes
| Always (Unsafe)
| <<next>>
| -
|===
Prefer bang methods for persistence.
[#safety-rspecrailsbangpersistence]
=== Safety
This cop is unsafe because by replacing non-bang versions
of the persistence methods, you might:
1. Catch persistence failures. This is what you want to catch.
2. Might replace wrong object calls.
The cop is unaware if the object is ActiveRecord instance
[#examples-rspecrailsbangpersistence]
=== Examples
[source,ruby]
----
# bad
before do
post.update(title: "new title")
end
# good
before do
post.update!(type: "new title")
end
----
[#references-rspecrailsbangpersistence]
=== References
* https://www.rubydoc.info/gems/rubocop-rspec_rails/RuboCop/Cop/RSpecRails/BangPersistence
[#rspecrailshavehttpstatus]
== RSpecRails/HaveHttpStatus
Expand Down
67 changes: 67 additions & 0 deletions lib/rubocop/cop/rspec_rails/bang_persistence.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# frozen_string_literal: true

module RuboCop
module Cop
module RSpecRails
# Prefer bang methods for persistence.
#
# @safety
# This cop is unsafe because by replacing non-bang versions
# of the persistence methods, you might:
# 1. Catch persistence failures. This is what you want to catch.
# 2. Might replace wrong object calls.
# The cop is unaware if the object is ActiveRecord instance
#
#
# @example
# # bad
# before do
# post.update(title: "new title")
# end
#
# # good
# before do
# post.update!(type: "new title")
# end
#
class BangPersistence < ::RuboCop::Cop::Base
extend AutoCorrector

MSG = 'Prefer bang versions of the persistence methods.'

PERSISTENCE_METHODS = %i[
create
destroy
save
update
update_attribute
].to_set.freeze

# Match `before` blocks
# @!method before_block?(node)
def_node_matcher :before_block?, <<-PATTERN
(block (send nil? :before) ...)
PATTERN

# Match `save` and `update` method calls
# @!method peristence_calls(node)
def_node_search :peristence_calls, <<-PATTERN
(send _ {#{PERSISTENCE_METHODS.map { |method| ":#{method}" }.join(' ')}} ...)
PATTERN

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

peristence_calls(node) do |method_call|
add_offense(method_call) do |corrector|
corrector.replace(method_call.loc.selector,
"#{method_call.method_name}!")
end
end
end

alias on_numblock on_block
end
end
end
end
1 change: 1 addition & 0 deletions lib/rubocop/cop/rspec_rails_cops.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# frozen_string_literal: true

require_relative 'rspec_rails/avoid_setup_hook'
require_relative 'rspec_rails/bang_persistence'
require_relative 'rspec_rails/have_http_status'
require_relative 'rspec_rails/http_status'
require_relative 'rspec_rails/inferred_spec_type'
Expand Down
49 changes: 49 additions & 0 deletions spec/rubocop/cop/rspec_rails/bang_persistence_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# frozen_string_literal: true

RSpec.describe RuboCop::Cop::RSpecRails::BangPersistence do
context 'with `save!` in `before`' do
it 'registers no offense' do
expect_no_offenses(<<~RUBY)
before { post.save! }
RUBY
end
end

context 'with `update!` in `before`' do
it 'registers no offense' do
expect_no_offenses(<<~RUBY)
before { post.update!(title: "new title") }
RUBY
end
end

context 'with `save` in `before`' do
it 'registers offense' do
expect_offense(<<~RUBY)
before { post.save }
^^^^^^^^^ Prefer bang versions of the persistence methods.
RUBY

expect_correction(<<~RUBY)
before { post.save! }
RUBY
end
end

context 'with `update_attribute` in `before`' do
it 'registers offense' do
expect_offense(<<~RUBY)
before do
post.update_attribute(title: "new title")
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer bang versions of the persistence methods.
end
RUBY

expect_correction(<<~RUBY)
before do
post.update_attribute!(title: "new title")
end
RUBY
end
end
end

0 comments on commit e3fa25b

Please sign in to comment.