rubocop-rails 2.6.0 → 2.9.0
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/README.md +16 -0
- data/config/default.yml +189 -6
- data/lib/rubocop/cop/mixin/active_record_helper.rb +12 -3
- data/lib/rubocop/cop/mixin/enforce_superclass.rb +40 -0
- data/lib/rubocop/cop/mixin/index_method.rb +25 -11
- data/lib/rubocop/cop/rails/action_filter.rb +10 -14
- data/lib/rubocop/cop/rails/active_record_aliases.rb +13 -17
- data/lib/rubocop/cop/rails/active_record_callbacks_order.rb +148 -0
- data/lib/rubocop/cop/rails/active_record_override.rb +1 -1
- data/lib/rubocop/cop/rails/active_support_aliases.rb +12 -21
- data/lib/rubocop/cop/rails/after_commit_override.rb +91 -0
- data/lib/rubocop/cop/rails/application_controller.rb +3 -7
- data/lib/rubocop/cop/rails/application_job.rb +2 -1
- data/lib/rubocop/cop/rails/application_mailer.rb +2 -7
- data/lib/rubocop/cop/rails/application_record.rb +2 -7
- data/lib/rubocop/cop/rails/arel_star.rb +41 -0
- data/lib/rubocop/cop/rails/assert_not.rb +8 -10
- data/lib/rubocop/cop/rails/attribute_default_block_value.rb +90 -0
- data/lib/rubocop/cop/rails/belongs_to.rb +9 -18
- data/lib/rubocop/cop/rails/blank.rb +27 -27
- data/lib/rubocop/cop/rails/bulk_change_table.rb +1 -1
- data/lib/rubocop/cop/rails/content_tag.rb +20 -33
- data/lib/rubocop/cop/rails/create_table_with_timestamps.rb +2 -1
- data/lib/rubocop/cop/rails/date.rb +10 -11
- data/lib/rubocop/cop/rails/default_scope.rb +61 -0
- data/lib/rubocop/cop/rails/delegate.rb +10 -10
- data/lib/rubocop/cop/rails/delegate_allow_blank.rb +7 -8
- data/lib/rubocop/cop/rails/dynamic_find_by.rb +13 -11
- data/lib/rubocop/cop/rails/enum_hash.rb +11 -10
- data/lib/rubocop/cop/rails/enum_uniqueness.rb +2 -1
- data/lib/rubocop/cop/rails/environment_comparison.rb +18 -14
- data/lib/rubocop/cop/rails/exit.rb +4 -10
- data/lib/rubocop/cop/rails/file_path.rb +5 -4
- data/lib/rubocop/cop/rails/find_by.rb +13 -13
- data/lib/rubocop/cop/rails/find_by_id.rb +94 -0
- data/lib/rubocop/cop/rails/find_each.rb +16 -14
- data/lib/rubocop/cop/rails/has_and_belongs_to_many.rb +3 -2
- data/lib/rubocop/cop/rails/has_many_or_has_one_dependent.rb +4 -7
- data/lib/rubocop/cop/rails/helper_instance_variable.rb +4 -2
- data/lib/rubocop/cop/rails/http_positional_arguments.rb +25 -21
- data/lib/rubocop/cop/rails/http_status.rb +7 -9
- data/lib/rubocop/cop/rails/ignored_skip_action_filter_option.rb +8 -6
- data/lib/rubocop/cop/rails/index_by.rb +11 -2
- data/lib/rubocop/cop/rails/index_with.rb +11 -2
- data/lib/rubocop/cop/rails/inquiry.rb +39 -0
- data/lib/rubocop/cop/rails/inverse_of.rb +3 -2
- data/lib/rubocop/cop/rails/lexically_scoped_action_filter.rb +17 -15
- data/lib/rubocop/cop/rails/link_to_blank.rb +20 -20
- data/lib/rubocop/cop/rails/mailer_name.rb +86 -0
- data/lib/rubocop/cop/rails/match_route.rb +120 -0
- data/lib/rubocop/cop/rails/negate_include.rb +41 -0
- data/lib/rubocop/cop/rails/not_null_column.rb +2 -1
- data/lib/rubocop/cop/rails/order_by_id.rb +52 -0
- data/lib/rubocop/cop/rails/output.rb +5 -2
- data/lib/rubocop/cop/rails/output_safety.rb +3 -2
- data/lib/rubocop/cop/rails/pick.rb +21 -15
- data/lib/rubocop/cop/rails/pluck.rb +56 -0
- data/lib/rubocop/cop/rails/pluck_id.rb +56 -0
- data/lib/rubocop/cop/rails/pluck_in_where.rb +70 -0
- data/lib/rubocop/cop/rails/pluralization_grammar.rb +10 -14
- data/lib/rubocop/cop/rails/presence.rb +12 -13
- data/lib/rubocop/cop/rails/present.rb +30 -24
- data/lib/rubocop/cop/rails/rake_environment.rb +9 -11
- data/lib/rubocop/cop/rails/read_write_attribute.rb +12 -11
- data/lib/rubocop/cop/rails/redundant_allow_nil.rb +29 -31
- data/lib/rubocop/cop/rails/redundant_foreign_key.rb +9 -12
- data/lib/rubocop/cop/rails/redundant_receiver_in_with_options.rb +11 -10
- data/lib/rubocop/cop/rails/reflection_class_name.rb +4 -3
- data/lib/rubocop/cop/rails/refute_methods.rb +9 -10
- data/lib/rubocop/cop/rails/relative_date_constant.rb +20 -9
- data/lib/rubocop/cop/rails/render_inline.rb +41 -0
- data/lib/rubocop/cop/rails/render_plain_text.rb +71 -0
- data/lib/rubocop/cop/rails/request_referer.rb +7 -7
- data/lib/rubocop/cop/rails/reversible_migration.rb +82 -7
- data/lib/rubocop/cop/rails/safe_navigation.rb +12 -11
- data/lib/rubocop/cop/rails/safe_navigation_with_blank.rb +5 -10
- data/lib/rubocop/cop/rails/save_bang.rb +19 -22
- data/lib/rubocop/cop/rails/scope_args.rb +2 -1
- data/lib/rubocop/cop/rails/short_i18n.rb +74 -0
- data/lib/rubocop/cop/rails/skips_model_validations.rb +46 -11
- data/lib/rubocop/cop/rails/squished_sql_heredocs.rb +82 -0
- data/lib/rubocop/cop/rails/time_zone.rb +22 -20
- data/lib/rubocop/cop/rails/uniq_before_pluck.rb +10 -10
- data/lib/rubocop/cop/rails/unique_validation_without_index.rb +18 -8
- data/lib/rubocop/cop/rails/unknown_env.rb +15 -4
- data/lib/rubocop/cop/rails/validation.rb +15 -14
- data/lib/rubocop/cop/rails/where_equals.rb +94 -0
- data/lib/rubocop/cop/rails/where_exists.rb +126 -0
- data/lib/rubocop/cop/rails/where_not.rb +97 -0
- data/lib/rubocop/cop/rails_cops.rb +22 -0
- data/lib/rubocop/rails/schema_loader.rb +4 -4
- data/lib/rubocop/rails/schema_loader/schema.rb +5 -5
- data/lib/rubocop/rails/version.rb +5 -1
- metadata +37 -9
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Rails
|
6
|
+
# This cop looks for uses of `default_scope`.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# # bad
|
10
|
+
# default_scope -> { where(hidden: false) }
|
11
|
+
#
|
12
|
+
# # good
|
13
|
+
# scope :published, -> { where(hidden: false) }
|
14
|
+
#
|
15
|
+
# # bad
|
16
|
+
# def self.default_scope
|
17
|
+
# where(hidden: false)
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# # good
|
21
|
+
# def self.published
|
22
|
+
# where(hidden: false)
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
class DefaultScope < Base
|
26
|
+
MSG = 'Avoid use of `default_scope`. It is better to use explicitly named scopes.'
|
27
|
+
RESTRICT_ON_SEND = %i[default_scope].freeze
|
28
|
+
|
29
|
+
def_node_matcher :method_call?, <<~PATTERN
|
30
|
+
(send nil? :default_scope ...)
|
31
|
+
PATTERN
|
32
|
+
|
33
|
+
def_node_matcher :class_method_definition?, <<~PATTERN
|
34
|
+
(defs _ :default_scope args ...)
|
35
|
+
PATTERN
|
36
|
+
|
37
|
+
def_node_matcher :eigenclass_method_definition?, <<~PATTERN
|
38
|
+
(sclass (self) $(def :default_scope args ...))
|
39
|
+
PATTERN
|
40
|
+
|
41
|
+
def on_send(node)
|
42
|
+
return unless method_call?(node)
|
43
|
+
|
44
|
+
add_offense(node.loc.selector)
|
45
|
+
end
|
46
|
+
|
47
|
+
def on_defs(node)
|
48
|
+
return unless class_method_definition?(node)
|
49
|
+
|
50
|
+
add_offense(node.loc.name)
|
51
|
+
end
|
52
|
+
|
53
|
+
def on_sclass(node)
|
54
|
+
eigenclass_method_definition?(node) do |default_scope|
|
55
|
+
add_offense(default_scope.loc.name)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -52,7 +52,9 @@ module RuboCop
|
|
52
52
|
#
|
53
53
|
# # good
|
54
54
|
# delegate :bar, to: :foo, prefix: true
|
55
|
-
class Delegate <
|
55
|
+
class Delegate < Base
|
56
|
+
extend AutoCorrector
|
57
|
+
|
56
58
|
MSG = 'Use `delegate` to define delegations.'
|
57
59
|
|
58
60
|
def_node_matcher :delegate?, <<~PATTERN
|
@@ -64,22 +66,20 @@ module RuboCop
|
|
64
66
|
return unless trivial_delegate?(node)
|
65
67
|
return if private_or_protected_delegation(node)
|
66
68
|
|
67
|
-
|
69
|
+
register_offense(node)
|
68
70
|
end
|
69
71
|
|
70
|
-
|
71
|
-
delegation = ["delegate :#{node.body.method_name}",
|
72
|
-
"to: :#{node.body.receiver.method_name}"]
|
72
|
+
private
|
73
73
|
|
74
|
-
|
74
|
+
def register_offense(node)
|
75
|
+
add_offense(node.loc.keyword) do |corrector|
|
76
|
+
delegation = ["delegate :#{node.body.method_name}", "to: :#{node.body.receiver.method_name}"]
|
77
|
+
delegation << ['prefix: true'] if node.method?(prefixed_method_name(node.body))
|
75
78
|
|
76
|
-
lambda do |corrector|
|
77
79
|
corrector.replace(node.source_range, delegation.join(', '))
|
78
80
|
end
|
79
81
|
end
|
80
82
|
|
81
|
-
private
|
82
|
-
|
83
83
|
def trivial_delegate?(def_node)
|
84
84
|
delegate?(def_node) &&
|
85
85
|
method_name_matches?(def_node.method_name, def_node.body) &&
|
@@ -122,7 +122,7 @@ module RuboCop
|
|
122
122
|
end
|
123
123
|
|
124
124
|
def private_or_protected_inline(line)
|
125
|
-
processed_source[line - 1].strip
|
125
|
+
processed_source[line - 1].strip.match?(/\A(private )|(protected )/)
|
126
126
|
end
|
127
127
|
end
|
128
128
|
end
|
@@ -13,22 +13,21 @@ module RuboCop
|
|
13
13
|
#
|
14
14
|
# # good
|
15
15
|
# delegate :foo, to: :bar, allow_nil: true
|
16
|
-
class DelegateAllowBlank <
|
16
|
+
class DelegateAllowBlank < Base
|
17
|
+
extend AutoCorrector
|
18
|
+
|
17
19
|
MSG = '`allow_blank` is not a valid option, use `allow_nil`.'
|
20
|
+
RESTRICT_ON_SEND = %i[delegate].freeze
|
18
21
|
|
19
22
|
def_node_matcher :allow_blank_option, <<~PATTERN
|
20
23
|
(send nil? :delegate _ (hash <$(pair (sym :allow_blank) true) ...>))
|
21
24
|
PATTERN
|
22
25
|
|
23
26
|
def on_send(node)
|
24
|
-
allow_blank_option(node)
|
25
|
-
add_offense(offending_node)
|
26
|
-
end
|
27
|
-
end
|
27
|
+
return unless (offending_node = allow_blank_option(node))
|
28
28
|
|
29
|
-
|
30
|
-
|
31
|
-
corrector.replace(pair_node.key.source_range, 'allow_nil')
|
29
|
+
add_offense(offending_node) do |corrector|
|
30
|
+
corrector.replace(offending_node.key.source_range, 'allow_nil')
|
32
31
|
end
|
33
32
|
end
|
34
33
|
end
|
@@ -31,7 +31,9 @@ module RuboCop
|
|
31
31
|
#
|
32
32
|
# # good
|
33
33
|
# Gem::Specification.find_by_name('backend').gem_dir
|
34
|
-
class DynamicFindBy <
|
34
|
+
class DynamicFindBy < Base
|
35
|
+
extend AutoCorrector
|
36
|
+
|
35
37
|
MSG = 'Use `%<static_name>s` instead of dynamic `%<method>s`.'
|
36
38
|
METHOD_PATTERN = /^find_by_(.+?)(!)?$/.freeze
|
37
39
|
|
@@ -41,26 +43,26 @@ module RuboCop
|
|
41
43
|
method_name = node.method_name
|
42
44
|
static_name = static_method_name(method_name)
|
43
45
|
return unless static_name
|
46
|
+
return if node.arguments.any?(&:splat_type?)
|
44
47
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
+
message = format(MSG, static_name: static_name, method: method_name)
|
49
|
+
add_offense(node, message: message) do |corrector|
|
50
|
+
autocorrect(corrector, node)
|
51
|
+
end
|
48
52
|
end
|
49
53
|
alias on_csend on_send
|
50
54
|
|
51
|
-
|
55
|
+
private
|
56
|
+
|
57
|
+
def autocorrect(corrector, node)
|
52
58
|
keywords = column_keywords(node.method_name)
|
53
59
|
|
54
60
|
return if keywords.size != node.arguments.size
|
55
61
|
|
56
|
-
|
57
|
-
|
58
|
-
autocorrect_argument_keywords(corrector, node, keywords)
|
59
|
-
end
|
62
|
+
autocorrect_method_name(corrector, node)
|
63
|
+
autocorrect_argument_keywords(corrector, node, keywords)
|
60
64
|
end
|
61
65
|
|
62
|
-
private
|
63
|
-
|
64
66
|
def allowed_invocation?(node)
|
65
67
|
allowed_method?(node) || allowed_receiver?(node) ||
|
66
68
|
whitelisted?(node)
|
@@ -17,9 +17,12 @@ module RuboCop
|
|
17
17
|
# # good
|
18
18
|
# enum status: { active: 0, archived: 1 }
|
19
19
|
#
|
20
|
-
class EnumHash <
|
20
|
+
class EnumHash < Base
|
21
|
+
extend AutoCorrector
|
22
|
+
|
21
23
|
MSG = 'Enum defined as an array found in `%<enum>s` enum declaration. '\
|
22
24
|
'Use hash syntax instead.'
|
25
|
+
RESTRICT_ON_SEND = %i[enum].freeze
|
23
26
|
|
24
27
|
def_node_matcher :enum?, <<~PATTERN
|
25
28
|
(send nil? :enum (hash $...))
|
@@ -35,19 +38,17 @@ module RuboCop
|
|
35
38
|
key, array = array_pair?(pair)
|
36
39
|
next unless key
|
37
40
|
|
38
|
-
add_offense(array, message: format(MSG, enum: enum_name(key)))
|
41
|
+
add_offense(array, message: format(MSG, enum: enum_name(key))) do |corrector|
|
42
|
+
hash = array.children.each_with_index.map do |elem, index|
|
43
|
+
"#{source(elem)} => #{index}"
|
44
|
+
end.join(', ')
|
45
|
+
|
46
|
+
corrector.replace(array.loc.expression, "{#{hash}}")
|
47
|
+
end
|
39
48
|
end
|
40
49
|
end
|
41
50
|
end
|
42
51
|
|
43
|
-
def autocorrect(node)
|
44
|
-
hash = node.children.each_with_index.map do |elem, index|
|
45
|
-
"#{source(elem)} => #{index}"
|
46
|
-
end.join(', ')
|
47
|
-
|
48
|
-
->(corrector) { corrector.replace(node.loc.expression, "{#{hash}}") }
|
49
|
-
end
|
50
|
-
|
51
52
|
private
|
52
53
|
|
53
54
|
def enum_name(key)
|
@@ -17,11 +17,12 @@ module RuboCop
|
|
17
17
|
#
|
18
18
|
# # good
|
19
19
|
# enum status: [:active, :archived]
|
20
|
-
class EnumUniqueness <
|
20
|
+
class EnumUniqueness < Base
|
21
21
|
include Duplication
|
22
22
|
|
23
23
|
MSG = 'Duplicate value `%<value>s` found in `%<enum>s` ' \
|
24
24
|
'enum declaration.'
|
25
|
+
RESTRICT_ON_SEND = %i[enum].freeze
|
25
26
|
|
26
27
|
def_node_matcher :enum?, <<~PATTERN
|
27
28
|
(send nil? :enum (hash $...))
|
@@ -15,12 +15,16 @@ module RuboCop
|
|
15
15
|
#
|
16
16
|
# # good
|
17
17
|
# Rails.env.production?
|
18
|
-
class EnvironmentComparison <
|
18
|
+
class EnvironmentComparison < Base
|
19
|
+
extend AutoCorrector
|
20
|
+
|
19
21
|
MSG = 'Favor `%<bang>sRails.env.%<env>s?` over `%<source>s`.'
|
20
22
|
|
21
23
|
SYM_MSG = 'Do not compare `Rails.env` with a symbol, it will always ' \
|
22
24
|
'evaluate to `false`.'
|
23
25
|
|
26
|
+
RESTRICT_ON_SEND = %i[== !=].freeze
|
27
|
+
|
24
28
|
def_node_matcher :comparing_str_env_with_rails_env_on_lhs?, <<~PATTERN
|
25
29
|
(send
|
26
30
|
(send (const {nil? cbase} :Rails) :env)
|
@@ -62,28 +66,28 @@ module RuboCop
|
|
62
66
|
comparing_str_env_with_rails_env_on_rhs?(node))
|
63
67
|
env, = *env_node
|
64
68
|
bang = node.method?(:!=) ? '!' : ''
|
69
|
+
message = format(MSG, bang: bang, env: env, source: node.source)
|
65
70
|
|
66
|
-
add_offense(node, message:
|
67
|
-
|
68
|
-
|
69
|
-
end
|
70
|
-
|
71
|
-
if comparing_sym_env_with_rails_env_on_lhs?(node) ||
|
72
|
-
comparing_sym_env_with_rails_env_on_rhs?(node)
|
73
|
-
add_offense(node, message: SYM_MSG)
|
71
|
+
add_offense(node, message: message) do |corrector|
|
72
|
+
autocorrect(corrector, node)
|
73
|
+
end
|
74
74
|
end
|
75
|
-
end
|
76
75
|
|
77
|
-
|
78
|
-
lambda do |corrector|
|
79
|
-
replacement = build_predicate_method(node)
|
76
|
+
return unless comparing_sym_env_with_rails_env_on_lhs?(node) || comparing_sym_env_with_rails_env_on_rhs?(node)
|
80
77
|
|
81
|
-
|
78
|
+
add_offense(node, message: SYM_MSG) do |corrector|
|
79
|
+
autocorrect(corrector, node)
|
82
80
|
end
|
83
81
|
end
|
84
82
|
|
85
83
|
private
|
86
84
|
|
85
|
+
def autocorrect(corrector, node)
|
86
|
+
replacement = build_predicate_method(node)
|
87
|
+
|
88
|
+
corrector.replace(node.source_range, replacement)
|
89
|
+
end
|
90
|
+
|
87
91
|
def build_predicate_method(node)
|
88
92
|
if rails_env_on_lhs?(node)
|
89
93
|
build_predicate_method_for_rails_env_on_lhs(node)
|
@@ -23,27 +23,21 @@ module RuboCop
|
|
23
23
|
#
|
24
24
|
# # good
|
25
25
|
# raise 'a bad error has happened'
|
26
|
-
class Exit <
|
26
|
+
class Exit < Base
|
27
27
|
include ConfigurableEnforcedStyle
|
28
28
|
|
29
29
|
MSG = 'Do not use `exit` in Rails applications.'
|
30
|
-
|
30
|
+
RESTRICT_ON_SEND = %i[exit exit!].freeze
|
31
31
|
EXPLICIT_RECEIVERS = %i[Kernel Process].freeze
|
32
32
|
|
33
33
|
def on_send(node)
|
34
|
-
add_offense(node
|
34
|
+
add_offense(node.loc.selector) if offending_node?(node)
|
35
35
|
end
|
36
36
|
|
37
37
|
private
|
38
38
|
|
39
39
|
def offending_node?(node)
|
40
|
-
|
41
|
-
right_argument_count?(node.arguments) &&
|
42
|
-
right_receiver?(node.receiver)
|
43
|
-
end
|
44
|
-
|
45
|
-
def right_method_name?(method_name)
|
46
|
-
TARGET_METHODS.include?(method_name)
|
40
|
+
right_argument_count?(node.arguments) && right_receiver?(node.receiver)
|
47
41
|
end
|
48
42
|
|
49
43
|
# More than 1 argument likely means it is a different
|
@@ -25,7 +25,7 @@ module RuboCop
|
|
25
25
|
# # good
|
26
26
|
# Rails.root.join('app/models/goober')
|
27
27
|
#
|
28
|
-
class FilePath <
|
28
|
+
class FilePath < Base
|
29
29
|
include ConfigurableEnforcedStyle
|
30
30
|
include RangeHelp
|
31
31
|
|
@@ -33,6 +33,7 @@ module RuboCop
|
|
33
33
|
'instead.'
|
34
34
|
MSG_ARGUMENTS = 'Please use `Rails.root.join(\'path\', \'to\')` ' \
|
35
35
|
'instead.'
|
36
|
+
RESTRICT_ON_SEND = %i[join].freeze
|
36
37
|
|
37
38
|
def_node_matcher :file_join_nodes?, <<~PATTERN
|
38
39
|
(send (const nil? :File) :join ...)
|
@@ -90,17 +91,17 @@ module RuboCop
|
|
90
91
|
end
|
91
92
|
|
92
93
|
def string_with_slash?(node)
|
93
|
-
node.str_type? && node.source
|
94
|
+
node.str_type? && node.source.include?('/')
|
94
95
|
end
|
95
96
|
|
96
97
|
def register_offense(node)
|
97
98
|
line_range = node.loc.column...node.loc.last_column
|
98
99
|
source_range = source_range(processed_source.buffer, node.first_line,
|
99
100
|
line_range)
|
100
|
-
add_offense(
|
101
|
+
add_offense(source_range)
|
101
102
|
end
|
102
103
|
|
103
|
-
def message(
|
104
|
+
def message(_range)
|
104
105
|
format(style == :arguments ? MSG_ARGUMENTS : MSG_SLASHES)
|
105
106
|
end
|
106
107
|
end
|
@@ -13,11 +13,12 @@ module RuboCop
|
|
13
13
|
#
|
14
14
|
# # good
|
15
15
|
# User.find_by(name: 'Bruce')
|
16
|
-
class FindBy <
|
16
|
+
class FindBy < Base
|
17
17
|
include RangeHelp
|
18
|
+
extend AutoCorrector
|
18
19
|
|
19
20
|
MSG = 'Use `find_by` instead of `where.%<method>s`.'
|
20
|
-
|
21
|
+
RESTRICT_ON_SEND = %i[first take].freeze
|
21
22
|
|
22
23
|
def_node_matcher :where_first?, <<~PATTERN
|
23
24
|
(send ({send csend} _ :where ...) {:first :take})
|
@@ -26,28 +27,27 @@ module RuboCop
|
|
26
27
|
def on_send(node)
|
27
28
|
return unless where_first?(node)
|
28
29
|
|
29
|
-
range = range_between(node.receiver.loc.selector.begin_pos,
|
30
|
-
node.loc.selector.end_pos)
|
30
|
+
range = range_between(node.receiver.loc.selector.begin_pos, node.loc.selector.end_pos)
|
31
31
|
|
32
|
-
add_offense(
|
33
|
-
|
32
|
+
add_offense(range, message: format(MSG, method: node.method_name)) do |corrector|
|
33
|
+
autocorrect(corrector, node)
|
34
|
+
end
|
34
35
|
end
|
35
36
|
alias on_csend on_send
|
36
37
|
|
37
|
-
|
38
|
+
private
|
39
|
+
|
40
|
+
def autocorrect(corrector, node)
|
38
41
|
# Don't autocorrect where(...).first, because it can return different
|
39
42
|
# results from find_by. (They order records differently, so the
|
40
43
|
# 'first' record can be different.)
|
41
44
|
return if node.method?(:first)
|
42
45
|
|
43
46
|
where_loc = node.receiver.loc.selector
|
44
|
-
first_loc = range_between(node.loc.dot.begin_pos,
|
45
|
-
node.loc.selector.end_pos)
|
47
|
+
first_loc = range_between(node.loc.dot.begin_pos, node.loc.selector.end_pos)
|
46
48
|
|
47
|
-
|
48
|
-
|
49
|
-
corrector.replace(first_loc, '')
|
50
|
-
end
|
49
|
+
corrector.replace(where_loc, 'find_by')
|
50
|
+
corrector.replace(first_loc, '')
|
51
51
|
end
|
52
52
|
end
|
53
53
|
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Rails
|
6
|
+
# This cop enforces that `ActiveRecord#find` is used instead of
|
7
|
+
# `where.take!`, `find_by!`, and `find_by_id!` to retrieve a single record
|
8
|
+
# by primary key when you expect it to be found.
|
9
|
+
#
|
10
|
+
# @example
|
11
|
+
# # bad
|
12
|
+
# User.where(id: id).take!
|
13
|
+
# User.find_by_id!(id)
|
14
|
+
# User.find_by!(id: id)
|
15
|
+
#
|
16
|
+
# # good
|
17
|
+
# User.find(id)
|
18
|
+
#
|
19
|
+
class FindById < Base
|
20
|
+
include RangeHelp
|
21
|
+
extend AutoCorrector
|
22
|
+
|
23
|
+
MSG = 'Use `%<good_method>s` instead of `%<bad_method>s`.'
|
24
|
+
RESTRICT_ON_SEND = %i[take! find_by_id! find_by!].freeze
|
25
|
+
|
26
|
+
def_node_matcher :where_take?, <<~PATTERN
|
27
|
+
(send
|
28
|
+
$(send _ :where
|
29
|
+
(hash
|
30
|
+
(pair (sym :id) $_))) :take!)
|
31
|
+
PATTERN
|
32
|
+
|
33
|
+
def_node_matcher :find_by?, <<~PATTERN
|
34
|
+
{
|
35
|
+
(send _ :find_by_id! $_)
|
36
|
+
(send _ :find_by! (hash (pair (sym :id) $_)))
|
37
|
+
}
|
38
|
+
PATTERN
|
39
|
+
|
40
|
+
def on_send(node)
|
41
|
+
where_take?(node) do |where, id_value|
|
42
|
+
range = where_take_offense_range(node, where)
|
43
|
+
bad_method = build_where_take_bad_method(id_value)
|
44
|
+
|
45
|
+
register_offense(range, id_value, bad_method)
|
46
|
+
end
|
47
|
+
|
48
|
+
find_by?(node) do |id_value|
|
49
|
+
range = find_by_offense_range(node)
|
50
|
+
bad_method = build_find_by_bad_method(node, id_value)
|
51
|
+
|
52
|
+
register_offense(range, id_value, bad_method)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def register_offense(range, id_value, bad_method)
|
59
|
+
good_method = build_good_method(id_value)
|
60
|
+
message = format(MSG, good_method: good_method, bad_method: bad_method)
|
61
|
+
|
62
|
+
add_offense(range, message: message) do |corrector|
|
63
|
+
corrector.replace(range, good_method)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def where_take_offense_range(node, where)
|
68
|
+
range_between(where.loc.selector.begin_pos, node.loc.expression.end_pos)
|
69
|
+
end
|
70
|
+
|
71
|
+
def find_by_offense_range(node)
|
72
|
+
range_between(node.loc.selector.begin_pos, node.loc.expression.end_pos)
|
73
|
+
end
|
74
|
+
|
75
|
+
def build_good_method(id_value)
|
76
|
+
"find(#{id_value.source})"
|
77
|
+
end
|
78
|
+
|
79
|
+
def build_where_take_bad_method(id_value)
|
80
|
+
"where(id: #{id_value.source}).take!"
|
81
|
+
end
|
82
|
+
|
83
|
+
def build_find_by_bad_method(node, id_value)
|
84
|
+
case node.method_name
|
85
|
+
when :find_by_id!
|
86
|
+
"find_by_id!(#{id_value.source})"
|
87
|
+
when :find_by!
|
88
|
+
"find_by!(id: #{id_value.source})"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|