Skip to content

Commit 5617426

Browse files
authored
Merge pull request #1323 from Earlopain/where-equal-not
[Fix #1199] Make `Rails/WhereEquals` aware of `where.not(...)`
2 parents b676624 + 3885fcc commit 5617426

File tree

4 files changed

+39
-10
lines changed

4 files changed

+39
-10
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
* [#1199](https://github.com/rubocop/rubocop-rails/issues/1199): Make `Rails/WhereEquals` aware of `where.not(...)`. ([@earlopain][])

config/default.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1185,12 +1185,12 @@ Rails/Validation:
11851185
- app/models/**/*.rb
11861186

11871187
Rails/WhereEquals:
1188-
Description: 'Pass conditions to `where` as a hash instead of manually constructing SQL.'
1188+
Description: 'Pass conditions to `where` and `where.not` as a hash instead of manually constructing SQL.'
11891189
StyleGuide: 'https://rails.rubystyle.guide/#hash-conditions'
11901190
Enabled: 'pending'
11911191
SafeAutoCorrect: false
11921192
VersionAdded: '2.9'
1193-
VersionChanged: '2.10'
1193+
VersionChanged: <<next>>
11941194

11951195
Rails/WhereExists:
11961196
Description: 'Prefer `exists?(...)` over `where(...).exists?`.'

lib/rubocop/cop/rails/where_equals.rb

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ module RuboCop
44
module Cop
55
module Rails
66
# Identifies places where manually constructed SQL
7-
# in `where` can be replaced with `where(attribute: value)`.
7+
# in `where` and `where.not` can be replaced with
8+
# `where(attribute: value)` and `where.not(attribute: value)`.
89
#
910
# @safety
1011
# This cop's autocorrection is unsafe because is may change SQL.
@@ -13,6 +14,7 @@ module Rails
1314
# @example
1415
# # bad
1516
# User.where('name = ?', 'Gabe')
17+
# User.where.not('name = ?', 'Gabe')
1618
# User.where('name = :name', name: 'Gabe')
1719
# User.where('name IS NULL')
1820
# User.where('name IN (?)', ['john', 'jane'])
@@ -21,6 +23,7 @@ module Rails
2123
#
2224
# # good
2325
# User.where(name: 'Gabe')
26+
# User.where.not(name: 'Gabe')
2427
# User.where(name: nil)
2528
# User.where(name: ['john', 'jane'])
2629
# User.where(users: { name: 'Gabe' })
@@ -29,16 +32,18 @@ class WhereEquals < Base
2932
extend AutoCorrector
3033

3134
MSG = 'Use `%<good_method>s` instead of manually constructing SQL.'
32-
RESTRICT_ON_SEND = %i[where].freeze
35+
RESTRICT_ON_SEND = %i[where not].freeze
3336

3437
def_node_matcher :where_method_call?, <<~PATTERN
3538
{
36-
(call _ :where (array $str_type? $_ ?))
37-
(call _ :where $str_type? $_ ?)
39+
(call _ {:where :not} (array $str_type? $_ ?))
40+
(call _ {:where :not} $str_type? $_ ?)
3841
}
3942
PATTERN
4043

4144
def on_send(node)
45+
return if node.method?(:not) && !where_not?(node)
46+
4247
where_method_call?(node) do |template_node, value_node|
4348
value_node = value_node.first
4449

@@ -47,7 +52,7 @@ def on_send(node)
4752
column, value = extract_column_and_value(template_node, value_node)
4853
return unless value
4954

50-
good_method = build_good_method(column, value)
55+
good_method = build_good_method(node.method_name, column, value)
5156
message = format(MSG, good_method: good_method)
5257

5358
add_offense(range, message: message) do |corrector|
@@ -88,15 +93,21 @@ def extract_column_and_value(template_node, value_node)
8893
[Regexp.last_match(1), value]
8994
end
9095

91-
def build_good_method(column, value)
96+
def build_good_method(method_name, column, value)
9297
if column.include?('.')
9398
table, column = column.split('.')
9499

95-
"where(#{table}: { #{column}: #{value} })"
100+
"#{method_name}(#{table}: { #{column}: #{value} })"
96101
else
97-
"where(#{column}: #{value})"
102+
"#{method_name}(#{column}: #{value})"
98103
end
99104
end
105+
106+
def where_not?(node)
107+
return false unless (receiver = node.receiver)
108+
109+
receiver.send_type? && receiver.method?(:where)
110+
end
100111
end
101112
end
102113
end

spec/rubocop/cop/rails/where_equals_spec.rb

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,17 @@
1212
RUBY
1313
end
1414

15+
it 'registers an offense and corrects when using `=` and anonymous placeholder with not' do
16+
expect_offense(<<~RUBY)
17+
User.where.not('name = ?', 'Gabe')
18+
^^^^^^^^^^^^^^^^^^^^^^^ Use `not(name: 'Gabe')` instead of manually constructing SQL.
19+
RUBY
20+
21+
expect_correction(<<~RUBY)
22+
User.where.not(name: 'Gabe')
23+
RUBY
24+
end
25+
1526
it 'registers an offense and corrects when using `=` and anonymous placeholder with safe navigation' do
1627
expect_offense(<<~RUBY)
1728
User&.where('name = ?', 'Gabe')
@@ -200,4 +211,10 @@
200211
User.where("name IN (:names)", )
201212
RUBY
202213
end
214+
215+
it 'does not register an offense when using `not` not preceded by `where`' do
216+
expect_no_offenses(<<~RUBY)
217+
users.not('name = ?', 'Gabe')
218+
RUBY
219+
end
203220
end

0 commit comments

Comments
 (0)