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,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Rails
|
6
|
+
# This cop enforces the use of `collection.exclude?(obj)`
|
7
|
+
# over `!collection.include?(obj)`.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# # bad
|
11
|
+
# !array.include?(2)
|
12
|
+
# !hash.include?(:key)
|
13
|
+
#
|
14
|
+
# # good
|
15
|
+
# array.exclude?(2)
|
16
|
+
# hash.exclude?(:key)
|
17
|
+
#
|
18
|
+
class NegateInclude < Cop
|
19
|
+
MSG = 'Use `.exclude?` and remove the negation part.'
|
20
|
+
|
21
|
+
def_node_matcher :negate_include_call?, <<~PATTERN
|
22
|
+
(send (send $_ :include? $_) :!)
|
23
|
+
PATTERN
|
24
|
+
|
25
|
+
def on_send(node)
|
26
|
+
add_offense(node) if negate_include_call?(node)
|
27
|
+
end
|
28
|
+
|
29
|
+
def autocorrect(node)
|
30
|
+
negate_include_call?(node) do |receiver, obj|
|
31
|
+
lambda do |corrector|
|
32
|
+
corrector.replace(node, "#{receiver.source}.exclude?(#{obj.source})")
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Rails
|
6
|
+
# This cop checks for places where ordering by `id` column is used.
|
7
|
+
#
|
8
|
+
# Don't use the `id` column for ordering. The sequence of ids is not guaranteed
|
9
|
+
# to be in any particular order, despite often (incidentally) being chronological.
|
10
|
+
# Use a timestamp column to order chronologically. As a bonus the intent is clearer.
|
11
|
+
#
|
12
|
+
# NOTE: Make sure the changed order column does not introduce performance
|
13
|
+
# bottlenecks and appropriate database indexes are added.
|
14
|
+
#
|
15
|
+
# @example
|
16
|
+
# # bad
|
17
|
+
# scope :chronological, -> { order(id: :asc) }
|
18
|
+
# scope :chronological, -> { order(primary_key => :asc) }
|
19
|
+
#
|
20
|
+
# # good
|
21
|
+
# scope :chronological, -> { order(created_at: :asc) }
|
22
|
+
#
|
23
|
+
class OrderById < Base
|
24
|
+
include RangeHelp
|
25
|
+
|
26
|
+
MSG = 'Do not use the `id` column for ordering. '\
|
27
|
+
'Use a timestamp column to order chronologically.'
|
28
|
+
|
29
|
+
def_node_matcher :order_by_id?, <<~PATTERN
|
30
|
+
(send _ :order
|
31
|
+
{
|
32
|
+
(sym :id)
|
33
|
+
(hash (pair (sym :id) _))
|
34
|
+
(send _ :primary_key)
|
35
|
+
(hash (pair (send _ :primary_key) _))
|
36
|
+
})
|
37
|
+
PATTERN
|
38
|
+
|
39
|
+
def on_send(node)
|
40
|
+
return unless node.method?(:order)
|
41
|
+
|
42
|
+
add_offense(offense_range(node)) if order_by_id?(node)
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def offense_range(node)
|
48
|
+
range_between(node.loc.selector.begin_pos, node.source_range.end_pos)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Rails
|
6
|
+
# This cop enforces the use of `pick` over `pluck(...).first`.
|
7
|
+
#
|
8
|
+
# Using `pluck` followed by `first` creates an intermediate array, which
|
9
|
+
# `pick` avoids. When called on an Active Record relation, `pick` adds a
|
10
|
+
# limit to the query so that only one value is fetched from the database.
|
11
|
+
#
|
12
|
+
# @example
|
13
|
+
# # bad
|
14
|
+
# Model.pluck(:a).first
|
15
|
+
# [{ a: :b, c: :d }].pluck(:a, :b).first
|
16
|
+
#
|
17
|
+
# # good
|
18
|
+
# Model.pick(:a)
|
19
|
+
# [{ a: :b, c: :d }].pick(:a, :b)
|
20
|
+
class Pick < Cop
|
21
|
+
extend TargetRailsVersion
|
22
|
+
|
23
|
+
MSG = 'Prefer `pick(%<args>s)` over `pluck(%<args>s).first`.'
|
24
|
+
|
25
|
+
minimum_target_rails_version 6.0
|
26
|
+
|
27
|
+
def_node_matcher :pick_candidate?, <<~PATTERN
|
28
|
+
(send (send _ :pluck ...) :first)
|
29
|
+
PATTERN
|
30
|
+
|
31
|
+
def on_send(node)
|
32
|
+
pick_candidate?(node) do
|
33
|
+
range = node.receiver.loc.selector.join(node.loc.selector)
|
34
|
+
add_offense(node, location: range)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def autocorrect(node)
|
39
|
+
first_range = node.receiver.source_range.end.join(node.loc.selector)
|
40
|
+
|
41
|
+
lambda do |corrector|
|
42
|
+
corrector.remove(first_range)
|
43
|
+
corrector.replace(node.receiver.loc.selector, 'pick')
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def message(node)
|
50
|
+
format(MSG, args: node.receiver.arguments.map(&:source).join(', '))
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Rails
|
6
|
+
# This cop enforces the use of `pluck` over `map`.
|
7
|
+
#
|
8
|
+
# `pluck` can be used instead of `map` to extract a single key from each
|
9
|
+
# element in an enumerable. When called on an Active Record relation, it
|
10
|
+
# results in a more efficient query that only selects the necessary key.
|
11
|
+
#
|
12
|
+
# @example
|
13
|
+
# # bad
|
14
|
+
# Post.published.map { |post| post[:title] }
|
15
|
+
# [{ a: :b, c: :d }].collect { |el| el[:a] }
|
16
|
+
#
|
17
|
+
# # good
|
18
|
+
# Post.published.pluck(:title)
|
19
|
+
# [{ a: :b, c: :d }].pluck(:a)
|
20
|
+
class Pluck < Cop
|
21
|
+
extend TargetRailsVersion
|
22
|
+
|
23
|
+
MSG = 'Prefer `pluck(:%<value>s)` over `%<method>s { |%<argument>s| %<element>s[:%<value>s] }`.'
|
24
|
+
|
25
|
+
minimum_target_rails_version 5.0
|
26
|
+
|
27
|
+
def_node_matcher :pluck_candidate?, <<~PATTERN
|
28
|
+
(block (send _ ${:map :collect}) (args (arg $_argument)) (send (lvar $_element) :[] (sym $_value)))
|
29
|
+
PATTERN
|
30
|
+
|
31
|
+
def on_block(node)
|
32
|
+
pluck_candidate?(node) do |method, argument, element, value|
|
33
|
+
next unless argument == element
|
34
|
+
|
35
|
+
add_offense(node, location: offense_range(node), message: message(method, argument, element, value))
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def autocorrect(node)
|
40
|
+
_method, _argument, _element, value = pluck_candidate?(node)
|
41
|
+
|
42
|
+
lambda do |corrector|
|
43
|
+
corrector.replace(offense_range(node), "pluck(:#{value})")
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def offense_range(node)
|
50
|
+
node.send_node.loc.selector.join(node.loc.end)
|
51
|
+
end
|
52
|
+
|
53
|
+
def message(method, argument, element, value)
|
54
|
+
format(MSG, method: method, argument: argument, element: element, value: value)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Rails
|
6
|
+
# This cop enforces the use of `ids` over `pluck(:id)` and `pluck(primary_key)`.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# # bad
|
10
|
+
# User.pluck(:id)
|
11
|
+
# user.posts.pluck(:id)
|
12
|
+
#
|
13
|
+
# def self.user_ids
|
14
|
+
# pluck(primary_key)
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# # good
|
18
|
+
# User.ids
|
19
|
+
# user.posts.ids
|
20
|
+
#
|
21
|
+
# def self.user_ids
|
22
|
+
# ids
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
class PluckId < Cop
|
26
|
+
include RangeHelp
|
27
|
+
include ActiveRecordHelper
|
28
|
+
|
29
|
+
MSG = 'Use `ids` instead of `%<bad_method>s`.'
|
30
|
+
|
31
|
+
def_node_matcher :pluck_id_call?, <<~PATTERN
|
32
|
+
(send _ :pluck {(sym :id) (send nil? :primary_key)})
|
33
|
+
PATTERN
|
34
|
+
|
35
|
+
def on_send(node)
|
36
|
+
return if !pluck_id_call?(node) || in_where?(node)
|
37
|
+
|
38
|
+
range = offense_range(node)
|
39
|
+
message = format(MSG, bad_method: range.source)
|
40
|
+
|
41
|
+
add_offense(node, location: range, message: message)
|
42
|
+
end
|
43
|
+
|
44
|
+
def autocorrect(node)
|
45
|
+
lambda do |corrector|
|
46
|
+
corrector.replace(offense_range(node), 'ids')
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def offense_range(node)
|
53
|
+
range_between(node.loc.selector.begin_pos, node.loc.expression.end_pos)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Rails
|
6
|
+
# This cop identifies places where `pluck` is used in `where` query methods
|
7
|
+
# and can be replaced with `select`.
|
8
|
+
#
|
9
|
+
# Since `pluck` is an eager method and hits the database immediately,
|
10
|
+
# using `select` helps to avoid additional database queries.
|
11
|
+
#
|
12
|
+
# This cop has two different enforcement modes. When the EnforcedStyle
|
13
|
+
# is conservative (the default) then only calls to `pluck` on a constant
|
14
|
+
# (i.e. a model class) in the `where` is used as offenses.
|
15
|
+
#
|
16
|
+
# When the EnforcedStyle is aggressive then all calls to `pluck` in the
|
17
|
+
# `where` is used as offenses. This may lead to false positives
|
18
|
+
# as the cop cannot replace to `select` between calls to `pluck` on an
|
19
|
+
# `ActiveRecord::Relation` instance vs a call to `pluck` on an `Array` instance.
|
20
|
+
#
|
21
|
+
# @example
|
22
|
+
# # bad
|
23
|
+
# Post.where(user_id: User.active.pluck(:id))
|
24
|
+
#
|
25
|
+
# # good
|
26
|
+
# Post.where(user_id: User.active.select(:id))
|
27
|
+
# Post.where(user_id: active_users.select(:id))
|
28
|
+
#
|
29
|
+
# @example EnforcedStyle: conservative (default)
|
30
|
+
# # good
|
31
|
+
# Post.where(user_id: active_users.pluck(:id))
|
32
|
+
#
|
33
|
+
# @example EnforcedStyle: aggressive
|
34
|
+
# # bad
|
35
|
+
# Post.where(user_id: active_users.pluck(:id))
|
36
|
+
#
|
37
|
+
class PluckInWhere < Cop
|
38
|
+
include ActiveRecordHelper
|
39
|
+
include ConfigurableEnforcedStyle
|
40
|
+
|
41
|
+
MSG = 'Use `select` instead of `pluck` within `where` query method.'
|
42
|
+
|
43
|
+
def on_send(node)
|
44
|
+
return unless node.method?(:pluck) && in_where?(node)
|
45
|
+
return if style == :conservative && !root_receiver(node)&.const_type?
|
46
|
+
|
47
|
+
add_offense(node, location: :selector)
|
48
|
+
end
|
49
|
+
|
50
|
+
def autocorrect(node)
|
51
|
+
lambda do |corrector|
|
52
|
+
corrector.replace(node.loc.selector, 'select')
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def root_receiver(node)
|
59
|
+
receiver = node.receiver
|
60
|
+
|
61
|
+
if receiver&.send_type?
|
62
|
+
root_receiver(receiver)
|
63
|
+
else
|
64
|
+
receiver
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -76,15 +76,11 @@ module RuboCop
|
|
76
76
|
return if ignore_if_node?(node)
|
77
77
|
|
78
78
|
redundant_receiver_and_other(node) do |receiver, other|
|
79
|
-
unless ignore_other_node?(other) || receiver.nil?
|
80
|
-
add_offense(node, message: message(node, receiver, other))
|
81
|
-
end
|
79
|
+
add_offense(node, message: message(node, receiver, other)) unless ignore_other_node?(other) || receiver.nil?
|
82
80
|
end
|
83
81
|
|
84
82
|
redundant_negative_receiver_and_other(node) do |receiver, other|
|
85
|
-
unless ignore_other_node?(other) || receiver.nil?
|
86
|
-
add_offense(node, message: message(node, receiver, other))
|
87
|
-
end
|
83
|
+
add_offense(node, message: message(node, receiver, other)) unless ignore_other_node?(other) || receiver.nil?
|
88
84
|
end
|
89
85
|
end
|
90
86
|
|
@@ -41,8 +41,25 @@ module RuboCop
|
|
41
41
|
end
|
42
42
|
end
|
43
43
|
|
44
|
+
def autocorrect(node)
|
45
|
+
lambda do |corrector|
|
46
|
+
task_name = node.arguments[0]
|
47
|
+
task_dependency = correct_task_dependency(task_name)
|
48
|
+
|
49
|
+
corrector.replace(task_name.loc.expression, task_dependency)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
44
53
|
private
|
45
54
|
|
55
|
+
def correct_task_dependency(task_name)
|
56
|
+
if task_name.sym_type?
|
57
|
+
"#{task_name.source.delete(':|\'|"')}: :environment"
|
58
|
+
else
|
59
|
+
"#{task_name.source} => :environment"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
46
63
|
def task_name(node)
|
47
64
|
first_arg = node.arguments[0]
|
48
65
|
case first_arg&.type
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Rails
|
6
|
+
# This cop detects cases where the `:foreign_key` option on associations
|
7
|
+
# is redundant.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# # bad
|
11
|
+
# class Post
|
12
|
+
# has_many :comments, foreign_key: 'post_id'
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# class Comment
|
16
|
+
# belongs_to :post, foreign_key: 'post_id'
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# # good
|
20
|
+
# class Post
|
21
|
+
# has_many :comments
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# class Comment
|
25
|
+
# belongs_to :author, foreign_key: 'user_id'
|
26
|
+
# end
|
27
|
+
class RedundantForeignKey < Cop
|
28
|
+
include RangeHelp
|
29
|
+
|
30
|
+
MSG = 'Specifying the default value for `foreign_key` is redundant.'
|
31
|
+
|
32
|
+
def_node_matcher :association_with_foreign_key, <<~PATTERN
|
33
|
+
(send nil? ${:belongs_to :has_one :has_many :has_and_belongs_to_many} ({sym str} $_)
|
34
|
+
$(hash <$(pair (sym :foreign_key) ({sym str} $_)) ...>)
|
35
|
+
)
|
36
|
+
PATTERN
|
37
|
+
|
38
|
+
def on_send(node)
|
39
|
+
association_with_foreign_key(node) do |type, name, options, foreign_key_pair, foreign_key|
|
40
|
+
if redundant?(node, type, name, options, foreign_key)
|
41
|
+
add_offense(node, location: foreign_key_pair.loc.expression)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def autocorrect(node)
|
47
|
+
_type, _name, _options, foreign_key_pair, _foreign_key = association_with_foreign_key(node)
|
48
|
+
range = range_with_surrounding_space(range: foreign_key_pair.source_range, side: :left)
|
49
|
+
range = range_with_surrounding_comma(range, :left)
|
50
|
+
|
51
|
+
lambda do |corrector|
|
52
|
+
corrector.remove(range)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def redundant?(node, association_type, association_name, options, foreign_key)
|
59
|
+
foreign_key.to_s == default_foreign_key(node, association_type, association_name, options)
|
60
|
+
end
|
61
|
+
|
62
|
+
def default_foreign_key(node, association_type, association_name, options)
|
63
|
+
if association_type == :belongs_to
|
64
|
+
"#{association_name}_id"
|
65
|
+
elsif (as = find_as_option(options))
|
66
|
+
"#{as}_id"
|
67
|
+
else
|
68
|
+
node.parent_module_name&.foreign_key
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def find_as_option(options)
|
73
|
+
options.pairs.find do |pair|
|
74
|
+
pair.key.sym_type? && pair.key.value == :as
|
75
|
+
end&.value&.value
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|