rubocop-rails 2.7.1 → 2.10.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/LICENSE.txt +1 -1
- data/README.md +18 -2
- data/config/default.yml +144 -6
- data/config/obsoletion.yml +7 -0
- data/lib/rubocop/cop/mixin/active_record_helper.rb +16 -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 +19 -16
- 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 +10 -19
- data/lib/rubocop/cop/rails/blank.rb +31 -27
- data/lib/rubocop/cop/rails/bulk_change_table.rb +1 -1
- data/lib/rubocop/cop/rails/content_tag.rb +34 -19
- 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 +11 -4
- data/lib/rubocop/cop/rails/delegate.rb +9 -9
- data/lib/rubocop/cop/rails/delegate_allow_blank.rb +7 -8
- data/lib/rubocop/cop/rails/dynamic_find_by.rb +15 -12
- 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/environment_variable_access.rb +67 -0
- data/lib/rubocop/cop/rails/exit.rb +4 -10
- data/lib/rubocop/cop/rails/file_path.rb +7 -8
- data/lib/rubocop/cop/rails/find_by.rb +13 -13
- data/lib/rubocop/cop/rails/find_by_id.rb +12 -21
- data/lib/rubocop/cop/rails/find_each.rb +19 -18
- 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 +37 -10
- data/lib/rubocop/cop/rails/helper_instance_variable.rb +30 -2
- data/lib/rubocop/cop/rails/http_positional_arguments.rb +32 -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 +7 -2
- 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 +25 -23
- data/lib/rubocop/cop/rails/mailer_name.rb +19 -13
- data/lib/rubocop/cop/rails/match_route.rb +14 -13
- data/lib/rubocop/cop/rails/negate_include.rb +10 -8
- 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 +14 -12
- data/lib/rubocop/cop/rails/pluck.rb +6 -9
- data/lib/rubocop/cop/rails/pluck_id.rb +4 -6
- data/lib/rubocop/cop/rails/pluck_in_where.rb +39 -5
- 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 +8 -10
- 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 +18 -4
- data/lib/rubocop/cop/rails/refute_methods.rb +9 -10
- data/lib/rubocop/cop/rails/relative_date_constant.rb +34 -22
- data/lib/rubocop/cop/rails/render_inline.rb +2 -1
- data/lib/rubocop/cop/rails/render_plain_text.rb +9 -14
- data/lib/rubocop/cop/rails/request_referer.rb +7 -7
- data/lib/rubocop/cop/rails/require_dependency.rb +38 -0
- data/lib/rubocop/cop/rails/reversible_migration.rb +83 -8
- data/lib/rubocop/cop/rails/reversible_migration_method_definition.rb +75 -0
- data/lib/rubocop/cop/rails/safe_navigation.rb +30 -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 +7 -9
- data/lib/rubocop/cop/rails/skips_model_validations.rb +4 -4
- data/lib/rubocop/cop/rails/squished_sql_heredocs.rb +82 -0
- data/lib/rubocop/cop/rails/time_zone.rb +35 -25
- data/lib/rubocop/cop/rails/time_zone_assignment.rb +37 -0
- data/lib/rubocop/cop/rails/uniq_before_pluck.rb +6 -6
- data/lib/rubocop/cop/rails/unique_validation_without_index.rb +18 -8
- data/lib/rubocop/cop/rails/unknown_env.rb +3 -3
- data/lib/rubocop/cop/rails/validation.rb +15 -14
- data/lib/rubocop/cop/rails/where_equals.rb +98 -0
- data/lib/rubocop/cop/rails/where_exists.rb +85 -16
- data/lib/rubocop/cop/rails/where_not.rb +101 -0
- data/lib/rubocop/cop/rails_cops.rb +12 -0
- data/lib/rubocop/rails.rb +2 -0
- data/lib/rubocop/rails/schema_loader.rb +4 -4
- data/lib/rubocop/rails/schema_loader/schema.rb +4 -8
- data/lib/rubocop/rails/version.rb +5 -1
- metadata +33 -14
@@ -13,8 +13,9 @@ module RuboCop
|
|
13
13
|
#
|
14
14
|
# # good
|
15
15
|
# scope :something, -> { where(something: true) }
|
16
|
-
class ScopeArgs <
|
16
|
+
class ScopeArgs < Base
|
17
17
|
MSG = 'Use `lambda`/`proc` instead of a plain method call.'
|
18
|
+
RESTRICT_ON_SEND = %i[scope].freeze
|
18
19
|
|
19
20
|
def_node_matcher :scope?, '(send nil? :scope _ $send)'
|
20
21
|
|
@@ -38,8 +38,9 @@ module RuboCop
|
|
38
38
|
# t :key
|
39
39
|
# l Time.now
|
40
40
|
#
|
41
|
-
class ShortI18n <
|
41
|
+
class ShortI18n < Base
|
42
42
|
include ConfigurableEnforcedStyle
|
43
|
+
extend AutoCorrector
|
43
44
|
|
44
45
|
MSG = 'Use `%<good_method>s` instead of `%<bad_method>s`.'
|
45
46
|
|
@@ -48,6 +49,8 @@ module RuboCop
|
|
48
49
|
localize: :l
|
49
50
|
}.freeze
|
50
51
|
|
52
|
+
RESTRICT_ON_SEND = PREFERRED_METHODS.keys.freeze
|
53
|
+
|
51
54
|
def_node_matcher :long_i18n?, <<~PATTERN
|
52
55
|
(send {nil? (const nil? :I18n)} ${:translate :localize} ...)
|
53
56
|
PATTERN
|
@@ -58,15 +61,10 @@ module RuboCop
|
|
58
61
|
long_i18n?(node) do |method_name|
|
59
62
|
good_method = PREFERRED_METHODS[method_name]
|
60
63
|
message = format(MSG, good_method: good_method, bad_method: method_name)
|
64
|
+
range = node.loc.selector
|
61
65
|
|
62
|
-
add_offense(
|
63
|
-
|
64
|
-
end
|
65
|
-
|
66
|
-
def autocorrect(node)
|
67
|
-
long_i18n?(node) do |method_name|
|
68
|
-
lambda do |corrector|
|
69
|
-
corrector.replace(node.loc.selector, PREFERRED_METHODS[method_name])
|
66
|
+
add_offense(range, message: message) do |corrector|
|
67
|
+
corrector.replace(range, PREFERRED_METHODS[method_name])
|
70
68
|
end
|
71
69
|
end
|
72
70
|
end
|
@@ -7,7 +7,7 @@ module RuboCop
|
|
7
7
|
# validations which are listed in
|
8
8
|
# https://guides.rubyonrails.org/active_record_validations.html#skipping-validations
|
9
9
|
#
|
10
|
-
# Methods may be ignored from this rule by configuring a `
|
10
|
+
# Methods may be ignored from this rule by configuring a `AllowedMethods`.
|
11
11
|
#
|
12
12
|
# @example
|
13
13
|
# # bad
|
@@ -26,7 +26,7 @@ module RuboCop
|
|
26
26
|
# user.update(website: 'example.com')
|
27
27
|
# FileUtils.touch('file')
|
28
28
|
#
|
29
|
-
# @example
|
29
|
+
# @example AllowedMethods: ["touch"]
|
30
30
|
# # bad
|
31
31
|
# DiscussionBoard.decrement_counter(:post_count, 5)
|
32
32
|
# DiscussionBoard.increment_counter(:post_count, 5)
|
@@ -35,7 +35,7 @@ module RuboCop
|
|
35
35
|
# # good
|
36
36
|
# user.touch
|
37
37
|
#
|
38
|
-
class SkipsModelValidations <
|
38
|
+
class SkipsModelValidations < Base
|
39
39
|
MSG = 'Avoid using `%<method>s` because it skips validations.'
|
40
40
|
|
41
41
|
METHODS_WITH_ARGUMENTS = %w[decrement!
|
@@ -76,7 +76,7 @@ module RuboCop
|
|
76
76
|
return if good_touch?(node)
|
77
77
|
return if good_insert?(node)
|
78
78
|
|
79
|
-
add_offense(node,
|
79
|
+
add_offense(node.loc.selector, message: message(node))
|
80
80
|
end
|
81
81
|
alias on_csend on_send
|
82
82
|
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Rails
|
6
|
+
#
|
7
|
+
# Checks SQL heredocs to use `.squish`.
|
8
|
+
# Some SQL syntax (e.g. PostgreSQL comments and functions) requires newlines
|
9
|
+
# to be preserved in order to work, thus auto-correction for this cop is not safe.
|
10
|
+
#
|
11
|
+
# @example
|
12
|
+
# # bad
|
13
|
+
# <<-SQL
|
14
|
+
# SELECT * FROM posts;
|
15
|
+
# SQL
|
16
|
+
#
|
17
|
+
# <<-SQL
|
18
|
+
# SELECT * FROM posts
|
19
|
+
# WHERE id = 1
|
20
|
+
# SQL
|
21
|
+
#
|
22
|
+
# execute(<<~SQL, "Post Load")
|
23
|
+
# SELECT * FROM posts
|
24
|
+
# WHERE post_id = 1
|
25
|
+
# SQL
|
26
|
+
#
|
27
|
+
# # good
|
28
|
+
# <<-SQL.squish
|
29
|
+
# SELECT * FROM posts;
|
30
|
+
# SQL
|
31
|
+
#
|
32
|
+
# <<~SQL.squish
|
33
|
+
# SELECT * FROM table
|
34
|
+
# WHERE id = 1
|
35
|
+
# SQL
|
36
|
+
#
|
37
|
+
# execute(<<~SQL.squish, "Post Load")
|
38
|
+
# SELECT * FROM posts
|
39
|
+
# WHERE post_id = 1
|
40
|
+
# SQL
|
41
|
+
#
|
42
|
+
class SquishedSQLHeredocs < Base
|
43
|
+
include Heredoc
|
44
|
+
extend AutoCorrector
|
45
|
+
|
46
|
+
SQL = 'SQL'
|
47
|
+
SQUISH = '.squish'
|
48
|
+
MSG = 'Use `%<expect>s` instead of `%<current>s`.'
|
49
|
+
|
50
|
+
def on_heredoc(node)
|
51
|
+
return unless offense_detected?(node)
|
52
|
+
|
53
|
+
add_offense(node) do |corrector|
|
54
|
+
corrector.insert_after(node, SQUISH)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def offense_detected?(node)
|
61
|
+
sql_heredoc?(node) && !using_squish?(node)
|
62
|
+
end
|
63
|
+
|
64
|
+
def sql_heredoc?(node)
|
65
|
+
delimiter_string(node) == SQL
|
66
|
+
end
|
67
|
+
|
68
|
+
def using_squish?(node)
|
69
|
+
node.parent&.send_type? && node.parent&.method?(:squish)
|
70
|
+
end
|
71
|
+
|
72
|
+
def message(node)
|
73
|
+
format(
|
74
|
+
MSG,
|
75
|
+
expect: "#{node.source}#{SQUISH}",
|
76
|
+
current: node.source
|
77
|
+
)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -19,7 +19,7 @@ module RuboCop
|
|
19
19
|
#
|
20
20
|
# # bad
|
21
21
|
# Time.now
|
22
|
-
# Time.parse('2015-03-
|
22
|
+
# Time.parse('2015-03-02T19:05:37')
|
23
23
|
#
|
24
24
|
# # bad
|
25
25
|
# Time.current
|
@@ -27,24 +27,26 @@ module RuboCop
|
|
27
27
|
#
|
28
28
|
# # good
|
29
29
|
# Time.zone.now
|
30
|
-
# Time.zone.parse('2015-03-
|
30
|
+
# Time.zone.parse('2015-03-02T19:05:37')
|
31
|
+
# Time.zone.parse('2015-03-02T19:05:37Z') # Respect ISO 8601 format with timezone specifier.
|
31
32
|
#
|
32
33
|
# @example EnforcedStyle: flexible (default)
|
33
34
|
# # `flexible` allows usage of `in_time_zone` instead of `zone`.
|
34
35
|
#
|
35
36
|
# # bad
|
36
37
|
# Time.now
|
37
|
-
# Time.parse('2015-03-
|
38
|
+
# Time.parse('2015-03-02T19:05:37')
|
38
39
|
#
|
39
40
|
# # good
|
40
41
|
# Time.zone.now
|
41
|
-
# Time.zone.parse('2015-03-
|
42
|
+
# Time.zone.parse('2015-03-02T19:05:37')
|
42
43
|
#
|
43
44
|
# # good
|
44
45
|
# Time.current
|
45
46
|
# Time.at(timestamp).in_time_zone
|
46
|
-
class TimeZone <
|
47
|
+
class TimeZone < Base
|
47
48
|
include ConfigurableEnforcedStyle
|
49
|
+
extend AutoCorrector
|
48
50
|
|
49
51
|
MSG = 'Do not use `%<current>s` without zone. Use `%<prefer>s` ' \
|
50
52
|
'instead.'
|
@@ -62,6 +64,8 @@ module RuboCop
|
|
62
64
|
ACCEPTED_METHODS = %i[in_time_zone utc getlocal xmlschema iso8601
|
63
65
|
jisx0301 rfc3339 httpdate to_i to_f].freeze
|
64
66
|
|
67
|
+
TIMEZONE_SPECIFIER = /[A-z]/.freeze
|
68
|
+
|
65
69
|
def on_const(node)
|
66
70
|
mod, klass = *node
|
67
71
|
# we should only check core classes
|
@@ -71,26 +75,24 @@ module RuboCop
|
|
71
75
|
check_time_node(klass, node.parent) if klass == :Time
|
72
76
|
end
|
73
77
|
|
74
|
-
|
75
|
-
lambda do |corrector|
|
76
|
-
# add `.zone`: `Time.at` => `Time.zone.at`
|
77
|
-
corrector.insert_after(node.children[0].source_range, '.zone')
|
78
|
+
private
|
78
79
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
corrector.replace(node.loc.selector, 'now')
|
83
|
-
when :new
|
84
|
-
autocorrect_time_new(node, corrector)
|
85
|
-
end
|
80
|
+
def autocorrect(corrector, node)
|
81
|
+
# add `.zone`: `Time.at` => `Time.zone.at`
|
82
|
+
corrector.insert_after(node.children[0].source_range, '.zone')
|
86
83
|
|
87
|
-
|
88
|
-
|
89
|
-
|
84
|
+
case node.method_name
|
85
|
+
when :current
|
86
|
+
# replace `Time.zone.current` => `Time.zone.now`
|
87
|
+
corrector.replace(node.loc.selector, 'now')
|
88
|
+
when :new
|
89
|
+
autocorrect_time_new(node, corrector)
|
90
90
|
end
|
91
|
-
end
|
92
91
|
|
93
|
-
|
92
|
+
# prefer `Time` over `DateTime` class
|
93
|
+
corrector.replace(node.children.first.source_range, 'Time') if strict?
|
94
|
+
remove_redundant_in_time_zone(corrector, node)
|
95
|
+
end
|
94
96
|
|
95
97
|
def autocorrect_time_new(node, corrector)
|
96
98
|
if node.arguments?
|
@@ -117,9 +119,10 @@ module RuboCop
|
|
117
119
|
end
|
118
120
|
|
119
121
|
def check_time_node(klass, node)
|
122
|
+
return if attach_timezone_specifier?(node.first_argument)
|
123
|
+
|
120
124
|
chain = extract_method_chain(node)
|
121
125
|
return if not_danger_chain?(chain)
|
122
|
-
|
123
126
|
return check_localtime(node) if need_check_localtime?(chain)
|
124
127
|
|
125
128
|
method_name = (chain & DANGEROUS_METHODS).join('.')
|
@@ -128,7 +131,13 @@ module RuboCop
|
|
128
131
|
|
129
132
|
message = build_message(klass, method_name, node)
|
130
133
|
|
131
|
-
add_offense(node
|
134
|
+
add_offense(node.loc.selector, message: message) do |corrector|
|
135
|
+
autocorrect(corrector, node)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def attach_timezone_specifier?(date)
|
140
|
+
date.respond_to?(:value) && TIMEZONE_SPECIFIER.match?(date.value.to_s[-1])
|
132
141
|
end
|
133
142
|
|
134
143
|
def build_message(klass, method_name, node)
|
@@ -193,8 +202,9 @@ module RuboCop
|
|
193
202
|
|
194
203
|
return if node.arguments?
|
195
204
|
|
196
|
-
add_offense(selector_node,
|
197
|
-
|
205
|
+
add_offense(selector_node.loc.selector, message: MSG_LOCALTIME) do |corrector|
|
206
|
+
autocorrect(corrector, selector_node)
|
207
|
+
end
|
198
208
|
end
|
199
209
|
|
200
210
|
def not_danger_chain?(chain)
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Rails
|
6
|
+
# This cop checks for the use of `Time.zone=` method.
|
7
|
+
#
|
8
|
+
# The `zone` attribute persists for the rest of the Ruby runtime, potentially causing
|
9
|
+
# unexpected behaviour at a later time.
|
10
|
+
# Using `Time.use_zone` ensures the code passed in block is the only place Time.zone is affected.
|
11
|
+
# It eliminates the possibility of a `zone` sticking around longer than intended.
|
12
|
+
#
|
13
|
+
# @example
|
14
|
+
# # bad
|
15
|
+
# Time.zone = 'EST'
|
16
|
+
#
|
17
|
+
# # good
|
18
|
+
# Time.use_zone('EST') do
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
class TimeZoneAssignment < Base
|
22
|
+
MSG = 'Use `Time.use_zone` with blocks instead of `Time.zone=`.'
|
23
|
+
RESTRICT_ON_SEND = %i[zone=].freeze
|
24
|
+
|
25
|
+
def_node_matcher :time_zone_assignement?, <<~PATTERN
|
26
|
+
(send (const nil? :Time) :zone= ...)
|
27
|
+
PATTERN
|
28
|
+
|
29
|
+
def on_send(node)
|
30
|
+
return unless time_zone_assignement?(node)
|
31
|
+
|
32
|
+
add_offense(node)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -18,6 +18,8 @@ module RuboCop
|
|
18
18
|
# ActiveRecord::Relation vs a call to pluck on an
|
19
19
|
# ActiveRecord::Associations::CollectionProxy.
|
20
20
|
#
|
21
|
+
# This cop is unsafe because the behavior may change depending on the
|
22
|
+
# database collation.
|
21
23
|
# Autocorrect is disabled by default for this cop since it may generate
|
22
24
|
# false positives.
|
23
25
|
#
|
@@ -43,11 +45,13 @@ module RuboCop
|
|
43
45
|
# # good
|
44
46
|
# Model.distinct.pluck(:id)
|
45
47
|
#
|
46
|
-
class UniqBeforePluck <
|
48
|
+
class UniqBeforePluck < Base
|
47
49
|
include ConfigurableEnforcedStyle
|
48
50
|
include RangeHelp
|
51
|
+
extend AutoCorrector
|
49
52
|
|
50
53
|
MSG = 'Use `distinct` before `pluck`.'
|
54
|
+
RESTRICT_ON_SEND = %i[uniq distinct pluck].freeze
|
51
55
|
NEWLINE = "\n"
|
52
56
|
PATTERN = '[!^block (send (send %<type>s :pluck ...) ' \
|
53
57
|
'${:uniq :distinct} ...)]'
|
@@ -67,11 +71,7 @@ module RuboCop
|
|
67
71
|
|
68
72
|
return unless method
|
69
73
|
|
70
|
-
add_offense(node
|
71
|
-
end
|
72
|
-
|
73
|
-
def autocorrect(node)
|
74
|
-
lambda do |corrector|
|
74
|
+
add_offense(node.loc.selector) do |corrector|
|
75
75
|
method = node.method_name
|
76
76
|
|
77
77
|
corrector.remove(dot_method_with_whitespace(method, node))
|
@@ -24,33 +24,37 @@ module RuboCop
|
|
24
24
|
# # good - even if the schema does not have a unique index
|
25
25
|
# validates :account, length: { minimum: MIN_LENGTH }
|
26
26
|
#
|
27
|
-
class UniqueValidationWithoutIndex <
|
27
|
+
class UniqueValidationWithoutIndex < Base
|
28
28
|
include ActiveRecordHelper
|
29
29
|
|
30
30
|
MSG = 'Uniqueness validation should be with a unique index.'
|
31
|
+
RESTRICT_ON_SEND = %i[validates].freeze
|
31
32
|
|
32
33
|
def on_send(node)
|
33
|
-
return unless node.method?(:validates)
|
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
|
@@ -17,7 +17,7 @@ module RuboCop
|
|
17
17
|
# # good
|
18
18
|
# Rails.env.production?
|
19
19
|
# Rails.env == 'production'
|
20
|
-
class UnknownEnv <
|
20
|
+
class UnknownEnv < Base
|
21
21
|
MSG = 'Unknown environment `%<name>s`.'
|
22
22
|
MSG_SIMILAR = 'Unknown environment `%<name>s`. ' \
|
23
23
|
'Did you mean `%<similar>s`?'
|
@@ -41,7 +41,7 @@ module RuboCop
|
|
41
41
|
|
42
42
|
def on_send(node)
|
43
43
|
unknown_environment_predicate?(node) do |name|
|
44
|
-
add_offense(node
|
44
|
+
add_offense(node.loc.selector, message: message(name))
|
45
45
|
end
|
46
46
|
|
47
47
|
unknown_environment_equal?(node) do |str_node|
|
@@ -62,7 +62,7 @@ module RuboCop
|
|
62
62
|
# DidYouMean::SpellChecker is not available in all versions of Ruby,
|
63
63
|
# and even on versions where it *is* available (>= 2.3), it is not
|
64
64
|
# always required correctly. So we do a feature check first. See:
|
65
|
-
# https://github.com/rubocop
|
65
|
+
# https://github.com/rubocop/rubocop/issues/7979
|
66
66
|
similar_names = if defined?(DidYouMean::SpellChecker)
|
67
67
|
spell_checker = DidYouMean::SpellChecker.new(dictionary: environments)
|
68
68
|
spell_checker.correct(name)
|