rubocop-rails 2.5.2 → 2.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/LICENSE.txt +1 -1
- data/README.md +2 -2
- data/config/default.yml +192 -11
- data/lib/rubocop/cop/mixin/active_record_helper.rb +9 -3
- data/lib/rubocop/cop/mixin/index_method.rb +25 -1
- data/lib/rubocop/cop/rails/active_record_callbacks_order.rb +143 -0
- data/lib/rubocop/cop/rails/after_commit_override.rb +91 -0
- data/lib/rubocop/cop/rails/content_tag.rb +69 -0
- data/lib/rubocop/cop/rails/create_table_with_timestamps.rb +1 -3
- data/lib/rubocop/cop/rails/default_scope.rb +54 -0
- data/lib/rubocop/cop/rails/delegate.rb +2 -4
- data/lib/rubocop/cop/rails/dynamic_find_by.rb +41 -15
- data/lib/rubocop/cop/rails/exit.rb +2 -2
- data/lib/rubocop/cop/rails/file_path.rb +2 -1
- data/lib/rubocop/cop/rails/find_by_id.rb +103 -0
- data/lib/rubocop/cop/rails/has_many_or_has_one_dependent.rb +1 -5
- data/lib/rubocop/cop/rails/helper_instance_variable.rb +2 -0
- data/lib/rubocop/cop/rails/http_positional_arguments.rb +7 -1
- data/lib/rubocop/cop/rails/http_status.rb +2 -0
- data/lib/rubocop/cop/rails/index_by.rb +9 -1
- data/lib/rubocop/cop/rails/index_with.rb +9 -1
- data/lib/rubocop/cop/rails/inquiry.rb +38 -0
- data/lib/rubocop/cop/rails/inverse_of.rb +0 -4
- data/lib/rubocop/cop/rails/link_to_blank.rb +3 -3
- data/lib/rubocop/cop/rails/mailer_name.rb +80 -0
- data/lib/rubocop/cop/rails/match_route.rb +119 -0
- data/lib/rubocop/cop/rails/negate_include.rb +39 -0
- data/lib/rubocop/cop/rails/order_by_id.rb +53 -0
- data/lib/rubocop/cop/rails/pick.rb +55 -0
- data/lib/rubocop/cop/rails/pluck.rb +59 -0
- data/lib/rubocop/cop/rails/pluck_id.rb +58 -0
- data/lib/rubocop/cop/rails/pluck_in_where.rb +70 -0
- data/lib/rubocop/cop/rails/presence.rb +2 -6
- data/lib/rubocop/cop/rails/rake_environment.rb +17 -0
- data/lib/rubocop/cop/rails/redundant_foreign_key.rb +80 -0
- data/lib/rubocop/cop/rails/redundant_receiver_in_with_options.rb +0 -3
- data/lib/rubocop/cop/rails/reflection_class_name.rb +1 -1
- data/lib/rubocop/cop/rails/relative_date_constant.rb +5 -2
- data/lib/rubocop/cop/rails/render_inline.rb +40 -0
- data/lib/rubocop/cop/rails/render_plain_text.rb +76 -0
- data/lib/rubocop/cop/rails/reversible_migration.rb +80 -1
- data/lib/rubocop/cop/rails/safe_navigation.rb +1 -1
- data/lib/rubocop/cop/rails/save_bang.rb +8 -9
- data/lib/rubocop/cop/rails/short_i18n.rb +76 -0
- data/lib/rubocop/cop/rails/skips_model_validations.rb +46 -8
- data/lib/rubocop/cop/rails/squished_sql_heredocs.rb +83 -0
- data/lib/rubocop/cop/rails/time_zone.rb +1 -3
- data/lib/rubocop/cop/rails/uniq_before_pluck.rb +14 -12
- data/lib/rubocop/cop/rails/unique_validation_without_index.rb +16 -6
- data/lib/rubocop/cop/rails/unknown_env.rb +18 -6
- data/lib/rubocop/cop/rails/where_exists.rb +131 -0
- data/lib/rubocop/cop/rails/where_not.rb +108 -0
- data/lib/rubocop/cop/rails_cops.rb +21 -0
- data/lib/rubocop/rails/schema_loader/schema.rb +4 -4
- data/lib/rubocop/rails/version.rb +1 -1
- metadata +31 -10
@@ -0,0 +1,83 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Rails
|
6
|
+
#
|
7
|
+
# Checks SQL heredocs to use `.squish`.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# # bad
|
11
|
+
# <<-SQL
|
12
|
+
# SELECT * FROM posts;
|
13
|
+
# SQL
|
14
|
+
#
|
15
|
+
# <<-SQL
|
16
|
+
# SELECT * FROM posts
|
17
|
+
# WHERE id = 1
|
18
|
+
# SQL
|
19
|
+
#
|
20
|
+
# execute(<<~SQL, "Post Load")
|
21
|
+
# SELECT * FROM posts
|
22
|
+
# WHERE post_id = 1
|
23
|
+
# SQL
|
24
|
+
#
|
25
|
+
# # good
|
26
|
+
# <<-SQL.squish
|
27
|
+
# SELECT * FROM posts;
|
28
|
+
# SQL
|
29
|
+
#
|
30
|
+
# <<~SQL.squish
|
31
|
+
# SELECT * FROM table
|
32
|
+
# WHERE id = 1
|
33
|
+
# SQL
|
34
|
+
#
|
35
|
+
# execute(<<~SQL.squish, "Post Load")
|
36
|
+
# SELECT * FROM posts
|
37
|
+
# WHERE post_id = 1
|
38
|
+
# SQL
|
39
|
+
#
|
40
|
+
class SquishedSQLHeredocs < Cop
|
41
|
+
include Heredoc
|
42
|
+
|
43
|
+
SQL = 'SQL'
|
44
|
+
SQUISH = '.squish'
|
45
|
+
MSG = 'Use `%<expect>s` instead of `%<current>s`.'
|
46
|
+
|
47
|
+
def on_heredoc(node)
|
48
|
+
return unless offense_detected?(node)
|
49
|
+
|
50
|
+
add_offense(node)
|
51
|
+
end
|
52
|
+
|
53
|
+
def autocorrect(node)
|
54
|
+
lambda do |corrector|
|
55
|
+
corrector.insert_after(node, SQUISH)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def offense_detected?(node)
|
62
|
+
sql_heredoc?(node) && !using_squish?(node)
|
63
|
+
end
|
64
|
+
|
65
|
+
def sql_heredoc?(node)
|
66
|
+
delimiter_string(node) == SQL
|
67
|
+
end
|
68
|
+
|
69
|
+
def using_squish?(node)
|
70
|
+
node.parent&.send_type? && node.parent&.method?(:squish)
|
71
|
+
end
|
72
|
+
|
73
|
+
def message(node)
|
74
|
+
format(
|
75
|
+
MSG,
|
76
|
+
expect: "#{node.source}#{SQUISH}",
|
77
|
+
current: node.source
|
78
|
+
)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -85,9 +85,7 @@ module RuboCop
|
|
85
85
|
end
|
86
86
|
|
87
87
|
# prefer `Time` over `DateTime` class
|
88
|
-
if strict?
|
89
|
-
corrector.replace(node.children.first.source_range, 'Time')
|
90
|
-
end
|
88
|
+
corrector.replace(node.children.first.source_range, 'Time') if strict?
|
91
89
|
remove_redundant_in_time_zone(corrector, node)
|
92
90
|
end
|
93
91
|
end
|
@@ -3,20 +3,23 @@
|
|
3
3
|
module RuboCop
|
4
4
|
module Cop
|
5
5
|
module Rails
|
6
|
-
# Prefer the use of
|
6
|
+
# Prefer the use of distinct, before pluck instead of after.
|
7
7
|
#
|
8
|
-
# The use of
|
8
|
+
# The use of distinct before pluck is preferred because it executes within
|
9
9
|
# the database.
|
10
10
|
#
|
11
11
|
# This cop has two different enforcement modes. When the EnforcedStyle
|
12
12
|
# is conservative (the default) then only calls to pluck on a constant
|
13
|
-
# (i.e. a model class) before
|
13
|
+
# (i.e. a model class) before distinct are added as offenses.
|
14
14
|
#
|
15
15
|
# When the EnforcedStyle is aggressive then all calls to pluck before
|
16
|
-
#
|
17
|
-
# cannot distinguish between calls to pluck on an
|
18
|
-
# vs a call to pluck on an
|
16
|
+
# distinct are added as offenses. This may lead to false positives
|
17
|
+
# as the cop cannot distinguish between calls to pluck on an
|
18
|
+
# ActiveRecord::Relation vs a call to pluck on an
|
19
|
+
# ActiveRecord::Associations::CollectionProxy.
|
19
20
|
#
|
21
|
+
# This cop is unsafe because the behavior may change depending on the
|
22
|
+
# database collation.
|
20
23
|
# Autocorrect is disabled by default for this cop since it may generate
|
21
24
|
# false positives.
|
22
25
|
#
|
@@ -25,7 +28,7 @@ module RuboCop
|
|
25
28
|
# Model.pluck(:id).uniq
|
26
29
|
#
|
27
30
|
# # good
|
28
|
-
# Model.
|
31
|
+
# Model.distinct.pluck(:id)
|
29
32
|
#
|
30
33
|
# @example EnforcedStyle: aggressive
|
31
34
|
# # bad
|
@@ -40,13 +43,13 @@ module RuboCop
|
|
40
43
|
# Model.pluck(:id).uniq
|
41
44
|
#
|
42
45
|
# # good
|
43
|
-
# Model.
|
46
|
+
# Model.distinct.pluck(:id)
|
44
47
|
#
|
45
48
|
class UniqBeforePluck < RuboCop::Cop::Cop
|
46
49
|
include ConfigurableEnforcedStyle
|
47
50
|
include RangeHelp
|
48
51
|
|
49
|
-
MSG = 'Use
|
52
|
+
MSG = 'Use `distinct` before `pluck`.'
|
50
53
|
NEWLINE = "\n"
|
51
54
|
PATTERN = '[!^block (send (send %<type>s :pluck ...) ' \
|
52
55
|
'${:uniq :distinct} ...)]'
|
@@ -66,8 +69,7 @@ module RuboCop
|
|
66
69
|
|
67
70
|
return unless method
|
68
71
|
|
69
|
-
add_offense(node, location: :selector
|
70
|
-
message: format(MSG, method: method))
|
72
|
+
add_offense(node, location: :selector)
|
71
73
|
end
|
72
74
|
|
73
75
|
def autocorrect(node)
|
@@ -75,7 +77,7 @@ module RuboCop
|
|
75
77
|
method = node.method_name
|
76
78
|
|
77
79
|
corrector.remove(dot_method_with_whitespace(method, node))
|
78
|
-
corrector.insert_before(node.receiver.loc.dot.begin,
|
80
|
+
corrector.insert_before(node.receiver.loc.dot.begin, '.distinct')
|
79
81
|
end
|
80
82
|
end
|
81
83
|
|
@@ -34,23 +34,27 @@ module RuboCop
|
|
34
34
|
return unless uniqueness_part(node)
|
35
35
|
return if condition_part?(node)
|
36
36
|
return unless schema
|
37
|
-
|
37
|
+
|
38
|
+
klass, table, names = find_schema_information(node)
|
39
|
+
return unless names
|
40
|
+
return if with_index?(klass, table, names)
|
38
41
|
|
39
42
|
add_offense(node)
|
40
43
|
end
|
41
44
|
|
42
45
|
private
|
43
46
|
|
44
|
-
def
|
47
|
+
def find_schema_information(node)
|
45
48
|
klass = class_node(node)
|
46
|
-
return
|
49
|
+
return unless klass
|
47
50
|
|
48
51
|
table = schema.table_by(name: table_name(klass))
|
49
|
-
return true unless table # Skip analysis if it can't find the table
|
50
|
-
|
51
52
|
names = column_names(node)
|
52
|
-
return true unless names
|
53
53
|
|
54
|
+
[klass, table, names]
|
55
|
+
end
|
56
|
+
|
57
|
+
def with_index?(klass, table, names)
|
54
58
|
# Compatibility for Rails 4.2.
|
55
59
|
add_indicies = schema.add_indicies_by(table_name: table_name(klass))
|
56
60
|
|
@@ -95,6 +99,8 @@ module RuboCop
|
|
95
99
|
scope = find_scope(uniq)
|
96
100
|
return unless scope
|
97
101
|
|
102
|
+
scope = unfreeze_scope(scope)
|
103
|
+
|
98
104
|
case scope.type
|
99
105
|
when :sym, :str
|
100
106
|
[scope.value]
|
@@ -112,6 +118,10 @@ module RuboCop
|
|
112
118
|
end
|
113
119
|
end
|
114
120
|
|
121
|
+
def unfreeze_scope(scope)
|
122
|
+
scope.send_type? && scope.method?(:freeze) ? scope.children.first : scope
|
123
|
+
end
|
124
|
+
|
115
125
|
def class_node(node)
|
116
126
|
node.each_ancestor.find(&:class_type?)
|
117
127
|
end
|
@@ -5,6 +5,9 @@ module RuboCop
|
|
5
5
|
module Rails
|
6
6
|
# This cop checks that environments called with `Rails.env` predicates
|
7
7
|
# exist.
|
8
|
+
# By default the cop allows three environments which Rails ships with:
|
9
|
+
# `development`, `test`, and `production`.
|
10
|
+
# More can be added to the `Environments` config parameter.
|
8
11
|
#
|
9
12
|
# @example
|
10
13
|
# # bad
|
@@ -15,8 +18,6 @@ module RuboCop
|
|
15
18
|
# Rails.env.production?
|
16
19
|
# Rails.env == 'production'
|
17
20
|
class UnknownEnv < Cop
|
18
|
-
include NameSimilarity
|
19
|
-
|
20
21
|
MSG = 'Unknown environment `%<name>s`.'
|
21
22
|
MSG_SIMILAR = 'Unknown environment `%<name>s`. ' \
|
22
23
|
'Did you mean `%<similar>s`?'
|
@@ -57,11 +58,22 @@ module RuboCop
|
|
57
58
|
|
58
59
|
def message(name)
|
59
60
|
name = name.to_s.chomp('?')
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
61
|
+
|
62
|
+
# DidYouMean::SpellChecker is not available in all versions of Ruby,
|
63
|
+
# and even on versions where it *is* available (>= 2.3), it is not
|
64
|
+
# always required correctly. So we do a feature check first. See:
|
65
|
+
# https://github.com/rubocop-hq/rubocop/issues/7979
|
66
|
+
similar_names = if defined?(DidYouMean::SpellChecker)
|
67
|
+
spell_checker = DidYouMean::SpellChecker.new(dictionary: environments)
|
68
|
+
spell_checker.correct(name)
|
69
|
+
else
|
70
|
+
[]
|
71
|
+
end
|
72
|
+
|
73
|
+
if similar_names.empty?
|
64
74
|
format(MSG, name: name)
|
75
|
+
else
|
76
|
+
format(MSG_SIMILAR, name: name, similar: similar_names.join(', '))
|
65
77
|
end
|
66
78
|
end
|
67
79
|
|
@@ -0,0 +1,131 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Rails
|
6
|
+
# This cop enforces consistent style when using `exists?`.
|
7
|
+
#
|
8
|
+
# Two styles are supported for this cop. When EnforcedStyle is 'exists'
|
9
|
+
# then the cop enforces `exists?(...)` over `where(...).exists?`.
|
10
|
+
#
|
11
|
+
# When EnforcedStyle is 'where' then the cop enforces
|
12
|
+
# `where(...).exists?` over `exists?(...)`.
|
13
|
+
#
|
14
|
+
# @example EnforcedStyle: exists (default)
|
15
|
+
# # bad
|
16
|
+
# User.where(name: 'john').exists?
|
17
|
+
# User.where(['name = ?', 'john']).exists?
|
18
|
+
# User.where('name = ?', 'john').exists?
|
19
|
+
# user.posts.where(published: true).exists?
|
20
|
+
#
|
21
|
+
# # good
|
22
|
+
# User.exists?(name: 'john')
|
23
|
+
# User.where('length(name) > 10').exists?
|
24
|
+
# user.posts.exists?(published: true)
|
25
|
+
#
|
26
|
+
# @example EnforcedStyle: where
|
27
|
+
# # bad
|
28
|
+
# User.exists?(name: 'john')
|
29
|
+
# User.exists?(['name = ?', 'john'])
|
30
|
+
# User.exists?('name = ?', 'john')
|
31
|
+
# user.posts.exists?(published: true)
|
32
|
+
#
|
33
|
+
# # good
|
34
|
+
# User.where(name: 'john').exists?
|
35
|
+
# User.where(['name = ?', 'john']).exists?
|
36
|
+
# User.where('name = ?', 'john').exists?
|
37
|
+
# user.posts.where(published: true).exists?
|
38
|
+
# User.where('length(name) > 10').exists?
|
39
|
+
class WhereExists < Cop
|
40
|
+
include ConfigurableEnforcedStyle
|
41
|
+
|
42
|
+
MSG = 'Prefer `%<good_method>s` over `%<bad_method>s`.'
|
43
|
+
|
44
|
+
def_node_matcher :where_exists_call?, <<~PATTERN
|
45
|
+
(send (send _ :where $...) :exists?)
|
46
|
+
PATTERN
|
47
|
+
|
48
|
+
def_node_matcher :exists_with_args?, <<~PATTERN
|
49
|
+
(send _ :exists? $...)
|
50
|
+
PATTERN
|
51
|
+
|
52
|
+
def on_send(node)
|
53
|
+
find_offenses(node) do |args|
|
54
|
+
return unless convertable_args?(args)
|
55
|
+
|
56
|
+
range = correction_range(node)
|
57
|
+
message = format(MSG, good_method: build_good_method(args), bad_method: range.source)
|
58
|
+
add_offense(node, location: range, message: message)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def autocorrect(node)
|
63
|
+
args = find_offenses(node)
|
64
|
+
|
65
|
+
lambda do |corrector|
|
66
|
+
corrector.replace(
|
67
|
+
correction_range(node),
|
68
|
+
build_good_method(args)
|
69
|
+
)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def where_style?
|
76
|
+
style == :where
|
77
|
+
end
|
78
|
+
|
79
|
+
def exists_style?
|
80
|
+
style == :exists
|
81
|
+
end
|
82
|
+
|
83
|
+
def find_offenses(node, &block)
|
84
|
+
if exists_style?
|
85
|
+
where_exists_call?(node, &block)
|
86
|
+
elsif where_style?
|
87
|
+
exists_with_args?(node, &block)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def convertable_args?(args)
|
92
|
+
return false if args.empty?
|
93
|
+
|
94
|
+
args.size > 1 || args[0].hash_type? || args[0].array_type?
|
95
|
+
end
|
96
|
+
|
97
|
+
def correction_range(node)
|
98
|
+
if exists_style?
|
99
|
+
node.receiver.loc.selector.join(node.loc.selector)
|
100
|
+
elsif where_style?
|
101
|
+
node.loc.selector.with(end_pos: node.loc.expression.end_pos)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def build_good_method(args)
|
106
|
+
if exists_style?
|
107
|
+
build_good_method_exists(args)
|
108
|
+
elsif where_style?
|
109
|
+
build_good_method_where(args)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def build_good_method_exists(args)
|
114
|
+
if args.size > 1
|
115
|
+
"exists?([#{args.map(&:source).join(', ')}])"
|
116
|
+
else
|
117
|
+
"exists?(#{args[0].source})"
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def build_good_method_where(args)
|
122
|
+
if args.size > 1
|
123
|
+
"where(#{args.map(&:source).join(', ')}).exists?"
|
124
|
+
else
|
125
|
+
"where(#{args[0].source}).exists?"
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Rails
|
6
|
+
# This cop identifies places where manually constructed SQL
|
7
|
+
# in `where` can be replaced with `where.not(...)`.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# # bad
|
11
|
+
# User.where('name != ?', 'Gabe')
|
12
|
+
# User.where('name != :name', name: 'Gabe')
|
13
|
+
# User.where('name <> ?', 'Gabe')
|
14
|
+
# User.where('name <> :name', name: 'Gabe')
|
15
|
+
# User.where('name IS NOT NULL')
|
16
|
+
# User.where('name NOT IN (?)', ['john', 'jane'])
|
17
|
+
# User.where('name NOT IN (:names)', names: ['john', 'jane'])
|
18
|
+
#
|
19
|
+
# # good
|
20
|
+
# User.where.not(name: 'Gabe')
|
21
|
+
# User.where.not(name: nil)
|
22
|
+
# User.where.not(name: ['john', 'jane'])
|
23
|
+
#
|
24
|
+
class WhereNot < Cop
|
25
|
+
include RangeHelp
|
26
|
+
|
27
|
+
MSG = 'Use `%<good_method>s` instead of manually constructing negated SQL in `where`.'
|
28
|
+
|
29
|
+
def_node_matcher :where_method_call?, <<~PATTERN
|
30
|
+
{
|
31
|
+
(send _ :where (array $str_type? $_ ?))
|
32
|
+
(send _ :where $str_type? $_ ?)
|
33
|
+
}
|
34
|
+
PATTERN
|
35
|
+
|
36
|
+
def on_send(node)
|
37
|
+
where_method_call?(node) do |template_node, value_node|
|
38
|
+
value_node = value_node.first
|
39
|
+
|
40
|
+
range = offense_range(node)
|
41
|
+
|
42
|
+
column_and_value = extract_column_and_value(template_node, value_node)
|
43
|
+
return unless column_and_value
|
44
|
+
|
45
|
+
good_method = build_good_method(*column_and_value)
|
46
|
+
message = format(MSG, good_method: good_method)
|
47
|
+
|
48
|
+
add_offense(node, location: range, message: message)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def autocorrect(node)
|
53
|
+
where_method_call?(node) do |template_node, value_node|
|
54
|
+
value_node = value_node.first
|
55
|
+
|
56
|
+
lambda do |corrector|
|
57
|
+
range = offense_range(node)
|
58
|
+
|
59
|
+
column, value = *extract_column_and_value(template_node, value_node)
|
60
|
+
replacement = build_good_method(column, value)
|
61
|
+
|
62
|
+
corrector.replace(range, replacement)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
NOT_EQ_ANONYMOUS_RE = /\A([\w.]+)\s+(?:!=|<>)\s+\?\z/.freeze # column != ?, column <> ?
|
68
|
+
NOT_IN_ANONYMOUS_RE = /\A([\w.]+)\s+NOT\s+IN\s+\(\?\)\z/i.freeze # column NOT IN (?)
|
69
|
+
NOT_EQ_NAMED_RE = /\A([\w.]+)\s+(?:!=|<>)\s+:(\w+)\z/.freeze # column != :column, column <> :column
|
70
|
+
NOT_IN_NAMED_RE = /\A([\w.]+)\s+NOT\s+IN\s+\(:(\w+)\)\z/i.freeze # column NOT IN (:column)
|
71
|
+
IS_NOT_NULL_RE = /\A([\w.]+)\s+IS\s+NOT\s+NULL\z/i.freeze # column IS NOT NULL
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def offense_range(node)
|
76
|
+
range_between(node.loc.selector.begin_pos, node.loc.expression.end_pos)
|
77
|
+
end
|
78
|
+
|
79
|
+
def extract_column_and_value(template_node, value_node)
|
80
|
+
value =
|
81
|
+
case template_node.value
|
82
|
+
when NOT_EQ_ANONYMOUS_RE, NOT_IN_ANONYMOUS_RE
|
83
|
+
value_node.source
|
84
|
+
when NOT_EQ_NAMED_RE, NOT_IN_NAMED_RE
|
85
|
+
return unless value_node.hash_type?
|
86
|
+
|
87
|
+
pair = value_node.pairs.find { |p| p.key.value.to_sym == Regexp.last_match(2).to_sym }
|
88
|
+
pair.value.source
|
89
|
+
when IS_NOT_NULL_RE
|
90
|
+
'nil'
|
91
|
+
else
|
92
|
+
return
|
93
|
+
end
|
94
|
+
|
95
|
+
[Regexp.last_match(1), value]
|
96
|
+
end
|
97
|
+
|
98
|
+
def build_good_method(column, value)
|
99
|
+
if column.include?('.')
|
100
|
+
"where.not('#{column}' => #{value})"
|
101
|
+
else
|
102
|
+
"where.not(#{column}: #{value})"
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|