rubocop-rails 2.9.1 → 2.11.2
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 +4 -4
- data/config/default.yml +111 -5
- data/config/obsoletion.yml +7 -0
- data/lib/rubocop/cop/mixin/active_record_helper.rb +11 -0
- data/lib/rubocop/cop/rails/add_column_index.rb +64 -0
- data/lib/rubocop/cop/rails/attribute_default_block_value.rb +1 -1
- data/lib/rubocop/cop/rails/belongs_to.rb +1 -1
- data/lib/rubocop/cop/rails/blank.rb +4 -0
- data/lib/rubocop/cop/rails/bulk_change_table.rb +5 -1
- data/lib/rubocop/cop/rails/content_tag.rb +18 -3
- data/lib/rubocop/cop/rails/date.rb +17 -6
- data/lib/rubocop/cop/rails/dynamic_find_by.rb +4 -2
- data/lib/rubocop/cop/rails/eager_evaluation_log_message.rb +78 -0
- data/lib/rubocop/cop/rails/environment_comparison.rb +1 -2
- data/lib/rubocop/cop/rails/environment_variable_access.rb +67 -0
- data/lib/rubocop/cop/rails/expanded_date_range.rb +86 -0
- data/lib/rubocop/cop/rails/file_path.rb +2 -4
- data/lib/rubocop/cop/rails/find_by.rb +31 -12
- data/lib/rubocop/cop/rails/find_each.rb +2 -0
- data/lib/rubocop/cop/rails/has_many_or_has_one_dependent.rb +34 -4
- data/lib/rubocop/cop/rails/http_positional_arguments.rb +8 -1
- data/lib/rubocop/cop/rails/http_status.rb +12 -3
- data/lib/rubocop/cop/rails/i18n_locale_assignment.rb +37 -0
- data/lib/rubocop/cop/rails/inverse_of.rb +1 -2
- data/lib/rubocop/cop/rails/link_to_blank.rb +5 -1
- data/lib/rubocop/cop/rails/reflection_class_name.rb +14 -1
- data/lib/rubocop/cop/rails/relative_date_constant.rb +18 -19
- data/lib/rubocop/cop/rails/require_dependency.rb +38 -0
- data/lib/rubocop/cop/rails/reversible_migration.rb +1 -1
- data/lib/rubocop/cop/rails/reversible_migration_method_definition.rb +75 -0
- data/lib/rubocop/cop/rails/safe_navigation.rb +20 -2
- data/lib/rubocop/cop/rails/time_zone.rb +22 -22
- data/lib/rubocop/cop/rails/time_zone_assignment.rb +37 -0
- data/lib/rubocop/cop/rails/unknown_env.rb +1 -1
- data/lib/rubocop/cop/rails/unused_ignored_columns.rb +69 -0
- data/lib/rubocop/cop/rails/where_exists.rb +11 -0
- data/lib/rubocop/cop/rails/where_not.rb +5 -1
- data/lib/rubocop/cop/rails_cops.rb +9 -0
- data/lib/rubocop/rails.rb +2 -0
- data/lib/rubocop/rails/schema_loader/schema.rb +2 -4
- data/lib/rubocop/rails/version.rb +1 -1
- metadata +21 -11
@@ -12,20 +12,21 @@ module RuboCop
|
|
12
12
|
# The cop also reports warnings when you are using `to_time` method,
|
13
13
|
# because it doesn't know about Rails time zone either.
|
14
14
|
#
|
15
|
-
# Two styles are supported for this cop. When EnforcedStyle is 'strict'
|
15
|
+
# Two styles are supported for this cop. When `EnforcedStyle` is 'strict'
|
16
16
|
# then the Date methods `today`, `current`, `yesterday`, and `tomorrow`
|
17
17
|
# are prohibited and the usage of both `to_time`
|
18
18
|
# and 'to_time_in_current_zone' are reported as warning.
|
19
19
|
#
|
20
|
-
# When EnforcedStyle is
|
21
|
-
#
|
20
|
+
# When `EnforcedStyle` is `flexible` then only `Date.today` is prohibited.
|
21
|
+
#
|
22
|
+
# And you can set a warning for `to_time` with `AllowToTime: false`.
|
23
|
+
# `AllowToTime` is `true` by default to prevent false positive on `DateTime` object.
|
22
24
|
#
|
23
25
|
# @example EnforcedStyle: strict
|
24
26
|
# # bad
|
25
27
|
# Date.current
|
26
28
|
# Date.yesterday
|
27
29
|
# Date.today
|
28
|
-
# date.to_time
|
29
30
|
#
|
30
31
|
# # good
|
31
32
|
# Time.zone.today
|
@@ -34,7 +35,6 @@ module RuboCop
|
|
34
35
|
# @example EnforcedStyle: flexible (default)
|
35
36
|
# # bad
|
36
37
|
# Date.today
|
37
|
-
# date.to_time
|
38
38
|
#
|
39
39
|
# # good
|
40
40
|
# Time.zone.today
|
@@ -43,6 +43,13 @@ module RuboCop
|
|
43
43
|
# Date.yesterday
|
44
44
|
# date.in_time_zone
|
45
45
|
#
|
46
|
+
# @example AllowToTime: true (default)
|
47
|
+
# # good
|
48
|
+
# date.to_time
|
49
|
+
#
|
50
|
+
# @example AllowToTime: false
|
51
|
+
# # bad
|
52
|
+
# date.to_time
|
46
53
|
class Date < Base
|
47
54
|
include ConfigurableEnforcedStyle
|
48
55
|
|
@@ -73,7 +80,7 @@ module RuboCop
|
|
73
80
|
|
74
81
|
def on_send(node)
|
75
82
|
return unless node.receiver && bad_methods.include?(node.method_name)
|
76
|
-
|
83
|
+
return if allow_to_time? && node.method?(:to_time)
|
77
84
|
return if safe_chain?(node) || safe_to_time?(node)
|
78
85
|
|
79
86
|
check_deprecated_methods(node)
|
@@ -139,6 +146,10 @@ module RuboCop
|
|
139
146
|
end
|
140
147
|
end
|
141
148
|
|
149
|
+
def allow_to_time?
|
150
|
+
cop_config.fetch('AllowToTime', true)
|
151
|
+
end
|
152
|
+
|
142
153
|
def good_days
|
143
154
|
style == :strict ? [] : %i[current yesterday tomorrow]
|
144
155
|
end
|
@@ -32,18 +32,20 @@ module RuboCop
|
|
32
32
|
# # good
|
33
33
|
# Gem::Specification.find_by_name('backend').gem_dir
|
34
34
|
class DynamicFindBy < Base
|
35
|
+
include ActiveRecordHelper
|
35
36
|
extend AutoCorrector
|
36
37
|
|
37
38
|
MSG = 'Use `%<static_name>s` instead of dynamic `%<method>s`.'
|
38
39
|
METHOD_PATTERN = /^find_by_(.+?)(!)?$/.freeze
|
40
|
+
IGNORED_ARGUMENT_TYPES = %i[hash splat].freeze
|
39
41
|
|
40
42
|
def on_send(node)
|
41
|
-
return if allowed_invocation?(node)
|
43
|
+
return if node.receiver.nil? && !inherit_active_record_base?(node) || allowed_invocation?(node)
|
42
44
|
|
43
45
|
method_name = node.method_name
|
44
46
|
static_name = static_method_name(method_name)
|
45
47
|
return unless static_name
|
46
|
-
return if node.arguments.any?(
|
48
|
+
return if node.arguments.any? { |argument| IGNORED_ARGUMENT_TYPES.include?(argument.type) }
|
47
49
|
|
48
50
|
message = format(MSG, static_name: static_name, method: method_name)
|
49
51
|
add_offense(node, message: message) do |corrector|
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Rails
|
6
|
+
# This cop checks that blocks are used for interpolated strings passed to
|
7
|
+
# `Rails.logger.debug`.
|
8
|
+
#
|
9
|
+
# By default, Rails production environments use the `:info` log level.
|
10
|
+
# At the `:info` log level, `Rails.logger.debug` statements do not result
|
11
|
+
# in log output. However, Ruby must eagerly evaluate interpolated string
|
12
|
+
# arguments passed as method arguments. Passing a block to
|
13
|
+
# `Rails.logger.debug` prevents costly evaluation of interpolated strings
|
14
|
+
# when no output would be produced anyway.
|
15
|
+
#
|
16
|
+
# @example
|
17
|
+
# #bad
|
18
|
+
# Rails.logger.debug "The time is #{Time.zone.now}."
|
19
|
+
#
|
20
|
+
# #good
|
21
|
+
# Rails.logger.debug { "The time is #{Time.zone.now}." }
|
22
|
+
#
|
23
|
+
class EagerEvaluationLogMessage < Base
|
24
|
+
extend AutoCorrector
|
25
|
+
|
26
|
+
MSG = 'Pass a block to `Rails.logger.debug`.'
|
27
|
+
RESTRICT_ON_SEND = %i[debug].freeze
|
28
|
+
|
29
|
+
def_node_matcher :interpolated_string_passed_to_debug, <<~PATTERN
|
30
|
+
(send
|
31
|
+
(send
|
32
|
+
(const {cbase nil?} :Rails)
|
33
|
+
:logger
|
34
|
+
)
|
35
|
+
:debug
|
36
|
+
$(dstr ...)
|
37
|
+
)
|
38
|
+
PATTERN
|
39
|
+
|
40
|
+
def on_send(node)
|
41
|
+
return if node.parent&.block_type?
|
42
|
+
|
43
|
+
interpolated_string_passed_to_debug(node) do |arguments|
|
44
|
+
message = format(MSG)
|
45
|
+
|
46
|
+
range = replacement_range(node)
|
47
|
+
replacement = replacement_source(node, arguments)
|
48
|
+
|
49
|
+
add_offense(range, message: message) do |corrector|
|
50
|
+
corrector.replace(range, replacement)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def replacement_range(node)
|
58
|
+
stop = node.loc.expression.end
|
59
|
+
start = node.loc.selector.end
|
60
|
+
|
61
|
+
if node.parenthesized_call?
|
62
|
+
stop.with(begin_pos: start.begin_pos)
|
63
|
+
else
|
64
|
+
stop.with(begin_pos: start.begin_pos + 1)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def replacement_source(node, arguments)
|
69
|
+
if node.parenthesized_call?
|
70
|
+
" { #{arguments.source} }"
|
71
|
+
else
|
72
|
+
"{ #{arguments.source} }"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -20,8 +20,7 @@ module RuboCop
|
|
20
20
|
|
21
21
|
MSG = 'Favor `%<bang>sRails.env.%<env>s?` over `%<source>s`.'
|
22
22
|
|
23
|
-
SYM_MSG = 'Do not compare `Rails.env` with a symbol, it will always '
|
24
|
-
'evaluate to `false`.'
|
23
|
+
SYM_MSG = 'Do not compare `Rails.env` with a symbol, it will always evaluate to `false`.'
|
25
24
|
|
26
25
|
RESTRICT_ON_SEND = %i[== !=].freeze
|
27
26
|
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Rails
|
6
|
+
# This cop looks for direct access to environment variables through the
|
7
|
+
# `ENV` variable within the application code. This can lead to runtime
|
8
|
+
# errors due to misconfiguration that could have been discovered at boot
|
9
|
+
# time if the environment variables were loaded as part of initialization
|
10
|
+
# and copied into the application's configuration or secrets. The cop can
|
11
|
+
# be configured to allow either reads or writes if required.
|
12
|
+
#
|
13
|
+
# @example
|
14
|
+
# # good
|
15
|
+
# Rails.application.config.foo
|
16
|
+
# Rails.application.config.x.foo.bar
|
17
|
+
# Rails.application.secrets.foo
|
18
|
+
# Rails.application.config.foo = "bar"
|
19
|
+
#
|
20
|
+
# @example AllowReads: false (default)
|
21
|
+
# # bad
|
22
|
+
# ENV["FOO"]
|
23
|
+
# ENV.fetch("FOO")
|
24
|
+
#
|
25
|
+
# @example AllowReads: true
|
26
|
+
# # good
|
27
|
+
# ENV["FOO"]
|
28
|
+
# ENV.fetch("FOO")
|
29
|
+
#
|
30
|
+
# @example AllowWrites: false (default)
|
31
|
+
# # bad
|
32
|
+
# ENV["FOO"] = "bar"
|
33
|
+
#
|
34
|
+
# @example AllowWrites: true
|
35
|
+
# # good
|
36
|
+
# ENV["FOO"] = "bar"
|
37
|
+
class EnvironmentVariableAccess < Base
|
38
|
+
READ_MSG = 'Do not read from `ENV` directly post initialization.'
|
39
|
+
WRITE_MSG = 'Do not write to `ENV` directly post initialization.'
|
40
|
+
|
41
|
+
def on_const(node)
|
42
|
+
add_offense(node, message: READ_MSG) if env_read?(node) && !allow_reads?
|
43
|
+
add_offense(node, message: WRITE_MSG) if env_write?(node) && !allow_writes?
|
44
|
+
end
|
45
|
+
|
46
|
+
def_node_search :env_read?, <<~PATTERN
|
47
|
+
^(send (const {cbase nil?} :ENV) !:[]= ...)
|
48
|
+
PATTERN
|
49
|
+
|
50
|
+
def_node_search :env_write?, <<~PATTERN
|
51
|
+
{^(indexasgn (const {cbase nil?} :ENV) ...)
|
52
|
+
^(send (const {cbase nil?} :ENV) :[]= ...)}
|
53
|
+
PATTERN
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def allow_reads?
|
58
|
+
cop_config['AllowReads'] == true
|
59
|
+
end
|
60
|
+
|
61
|
+
def allow_writes?
|
62
|
+
cop_config['AllowWrites'] == true
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Rails
|
6
|
+
# This cop checks for expanded date range. It only compatible `..` range is targeted.
|
7
|
+
# Incompatible `...` range is ignored.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# # bad
|
11
|
+
# date.beginning_of_day..date.end_of_day
|
12
|
+
# date.beginning_of_week..date.end_of_week
|
13
|
+
# date.beginning_of_month..date.end_of_month
|
14
|
+
# date.beginning_of_quarter..date.end_of_quarter
|
15
|
+
# date.beginning_of_year..date.end_of_year
|
16
|
+
#
|
17
|
+
# # good
|
18
|
+
# date.all_day
|
19
|
+
# date.all_week
|
20
|
+
# date.all_month
|
21
|
+
# date.all_quarter
|
22
|
+
# date.all_year
|
23
|
+
#
|
24
|
+
class ExpandedDateRange < Base
|
25
|
+
extend AutoCorrector
|
26
|
+
extend TargetRailsVersion
|
27
|
+
|
28
|
+
MSG = 'Use `%<preferred_method>s` instead.'
|
29
|
+
|
30
|
+
minimum_target_rails_version 5.1
|
31
|
+
|
32
|
+
def_node_matcher :expanded_date_range, <<~PATTERN
|
33
|
+
(irange
|
34
|
+
(send
|
35
|
+
$_ {:beginning_of_day :beginning_of_week :beginning_of_month :beginning_of_quarter :beginning_of_year})
|
36
|
+
(send
|
37
|
+
$_ {:end_of_day :end_of_week :end_of_month :end_of_quarter :end_of_year}))
|
38
|
+
PATTERN
|
39
|
+
|
40
|
+
PREFERRED_METHODS = {
|
41
|
+
beginning_of_day: 'all_day',
|
42
|
+
beginning_of_week: 'all_week',
|
43
|
+
beginning_of_month: 'all_month',
|
44
|
+
beginning_of_quarter: 'all_quarter',
|
45
|
+
beginning_of_year: 'all_year'
|
46
|
+
}.freeze
|
47
|
+
|
48
|
+
MAPPED_DATE_RANGE_METHODS = {
|
49
|
+
beginning_of_day: :end_of_day,
|
50
|
+
beginning_of_week: :end_of_week,
|
51
|
+
beginning_of_month: :end_of_month,
|
52
|
+
beginning_of_quarter: :end_of_quarter,
|
53
|
+
beginning_of_year: :end_of_year
|
54
|
+
}.freeze
|
55
|
+
|
56
|
+
def on_irange(node)
|
57
|
+
return unless expanded_date_range(node)
|
58
|
+
|
59
|
+
begin_node = node.begin
|
60
|
+
end_node = node.end
|
61
|
+
return unless same_receiver?(begin_node, end_node)
|
62
|
+
|
63
|
+
beginning_method = begin_node.method_name
|
64
|
+
end_method = end_node.method_name
|
65
|
+
return unless use_mapped_methods?(beginning_method, end_method)
|
66
|
+
|
67
|
+
preferred_method = "#{begin_node.receiver.source}.#{PREFERRED_METHODS[beginning_method]}"
|
68
|
+
|
69
|
+
add_offense(node, message: format(MSG, preferred_method: preferred_method)) do |corrector|
|
70
|
+
corrector.replace(node, preferred_method)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def same_receiver?(begin_node, end_node)
|
77
|
+
begin_node.receiver.source == end_node.receiver.source
|
78
|
+
end
|
79
|
+
|
80
|
+
def use_mapped_methods?(beginning_method, end_method)
|
81
|
+
MAPPED_DATE_RANGE_METHODS[beginning_method] == end_method
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -29,10 +29,8 @@ module RuboCop
|
|
29
29
|
include ConfigurableEnforcedStyle
|
30
30
|
include RangeHelp
|
31
31
|
|
32
|
-
MSG_SLASHES = '
|
33
|
-
|
34
|
-
MSG_ARGUMENTS = 'Please use `Rails.root.join(\'path\', \'to\')` ' \
|
35
|
-
'instead.'
|
32
|
+
MSG_SLASHES = 'Prefer `Rails.root.join(\'path/to\')`.'
|
33
|
+
MSG_ARGUMENTS = 'Prefer `Rails.root.join(\'path\', \'to\')`.'
|
36
34
|
RESTRICT_ON_SEND = %i[join].freeze
|
37
35
|
|
38
36
|
def_node_matcher :file_join_nodes?, <<~PATTERN
|
@@ -3,16 +3,27 @@
|
|
3
3
|
module RuboCop
|
4
4
|
module Cop
|
5
5
|
module Rails
|
6
|
-
# This cop is used to identify usages of `where.
|
7
|
-
#
|
6
|
+
# This cop is used to identify usages of `where.take` and change them to use `find_by` instead.
|
7
|
+
#
|
8
|
+
# And `where(...).first` can return different results from `find_by`.
|
9
|
+
# (They order records differently, so the "first" record can be different.)
|
10
|
+
#
|
11
|
+
# If you also want to detect `where.first`, you can set `IgnoreWhereFirst` to false.
|
8
12
|
#
|
9
13
|
# @example
|
10
14
|
# # bad
|
11
|
-
# User.where(name: 'Bruce').first
|
12
15
|
# User.where(name: 'Bruce').take
|
13
16
|
#
|
14
17
|
# # good
|
15
18
|
# User.find_by(name: 'Bruce')
|
19
|
+
#
|
20
|
+
# @example IgnoreWhereFirst: true (default)
|
21
|
+
# # good
|
22
|
+
# User.where(name: 'Bruce').first
|
23
|
+
#
|
24
|
+
# @example IgnoreWhereFirst: false
|
25
|
+
# # bad
|
26
|
+
# User.where(name: 'Bruce').first
|
16
27
|
class FindBy < Base
|
17
28
|
include RangeHelp
|
18
29
|
extend AutoCorrector
|
@@ -20,14 +31,11 @@ module RuboCop
|
|
20
31
|
MSG = 'Use `find_by` instead of `where.%<method>s`.'
|
21
32
|
RESTRICT_ON_SEND = %i[first take].freeze
|
22
33
|
|
23
|
-
def_node_matcher :where_first?, <<~PATTERN
|
24
|
-
(send ({send csend} _ :where ...) {:first :take})
|
25
|
-
PATTERN
|
26
|
-
|
27
34
|
def on_send(node)
|
28
|
-
return unless
|
35
|
+
return unless node.arguments.empty? && where_method?(node.receiver)
|
36
|
+
return if ignore_where_first? && node.method?(:first)
|
29
37
|
|
30
|
-
range =
|
38
|
+
range = offense_range(node)
|
31
39
|
|
32
40
|
add_offense(range, message: format(MSG, method: node.method_name)) do |corrector|
|
33
41
|
autocorrect(corrector, node)
|
@@ -37,10 +45,17 @@ module RuboCop
|
|
37
45
|
|
38
46
|
private
|
39
47
|
|
48
|
+
def where_method?(receiver)
|
49
|
+
return false unless receiver
|
50
|
+
|
51
|
+
receiver.respond_to?(:method?) && receiver.method?(:where)
|
52
|
+
end
|
53
|
+
|
54
|
+
def offense_range(node)
|
55
|
+
range_between(node.receiver.loc.selector.begin_pos, node.loc.selector.end_pos)
|
56
|
+
end
|
57
|
+
|
40
58
|
def autocorrect(corrector, node)
|
41
|
-
# Don't autocorrect where(...).first, because it can return different
|
42
|
-
# results from find_by. (They order records differently, so the
|
43
|
-
# 'first' record can be different.)
|
44
59
|
return if node.method?(:first)
|
45
60
|
|
46
61
|
where_loc = node.receiver.loc.selector
|
@@ -49,6 +64,10 @@ module RuboCop
|
|
49
64
|
corrector.replace(where_loc, 'find_by')
|
50
65
|
corrector.replace(first_loc, '')
|
51
66
|
end
|
67
|
+
|
68
|
+
def ignore_where_first?
|
69
|
+
cop_config.fetch('IgnoreWhereFirst', true)
|
70
|
+
end
|
52
71
|
end
|
53
72
|
end
|
54
73
|
end
|
@@ -17,6 +17,7 @@ module RuboCop
|
|
17
17
|
# # good
|
18
18
|
# User.order(:foo).each
|
19
19
|
class FindEach < Base
|
20
|
+
include ActiveRecordHelper
|
20
21
|
extend AutoCorrector
|
21
22
|
|
22
23
|
MSG = 'Use `find_each` instead of `each`.'
|
@@ -30,6 +31,7 @@ module RuboCop
|
|
30
31
|
def on_send(node)
|
31
32
|
return unless node.receiver&.send_type?
|
32
33
|
return unless SCOPE_METHODS.include?(node.receiver.method_name)
|
34
|
+
return if node.receiver.receiver.nil? && !inherit_active_record_base?(node)
|
33
35
|
return if ignored?(node)
|
34
36
|
|
35
37
|
range = node.loc.selector
|
@@ -5,7 +5,9 @@ module RuboCop
|
|
5
5
|
module Rails
|
6
6
|
# This cop looks for `has_many` or `has_one` associations that don't
|
7
7
|
# specify a `:dependent` option.
|
8
|
-
#
|
8
|
+
#
|
9
|
+
# It doesn't register an offense if `:through` or `dependent: nil`
|
10
|
+
# is specified, or if the model is read-only.
|
9
11
|
#
|
10
12
|
# @example
|
11
13
|
# # bad
|
@@ -18,8 +20,18 @@ module RuboCop
|
|
18
20
|
# class User < ActiveRecord::Base
|
19
21
|
# has_many :comments, dependent: :restrict_with_exception
|
20
22
|
# has_one :avatar, dependent: :destroy
|
23
|
+
# has_many :articles, dependent: nil
|
21
24
|
# has_many :patients, through: :appointments
|
22
25
|
# end
|
26
|
+
#
|
27
|
+
# class User < ActiveRecord::Base
|
28
|
+
# has_many :comments
|
29
|
+
# has_one :avatar
|
30
|
+
#
|
31
|
+
# def readonly?
|
32
|
+
# true
|
33
|
+
# end
|
34
|
+
# end
|
23
35
|
class HasManyOrHasOneDependent < Base
|
24
36
|
MSG = 'Specify a `:dependent` option.'
|
25
37
|
RESTRICT_ON_SEND = %i[has_many has_one].freeze
|
@@ -37,7 +49,7 @@ module RuboCop
|
|
37
49
|
PATTERN
|
38
50
|
|
39
51
|
def_node_matcher :dependent_option?, <<~PATTERN
|
40
|
-
(pair (sym :dependent) !nil)
|
52
|
+
(pair (sym :dependent) {!nil (nil)})
|
41
53
|
PATTERN
|
42
54
|
|
43
55
|
def_node_matcher :present_option?, <<~PATTERN
|
@@ -51,8 +63,20 @@ module RuboCop
|
|
51
63
|
(args) ...)
|
52
64
|
PATTERN
|
53
65
|
|
66
|
+
def_node_matcher :association_extension_block?, <<~PATTERN
|
67
|
+
(block
|
68
|
+
(send nil? :has_many _)
|
69
|
+
(args) ...)
|
70
|
+
PATTERN
|
71
|
+
|
72
|
+
def_node_matcher :readonly?, <<~PATTERN
|
73
|
+
(def :readonly?
|
74
|
+
(args)
|
75
|
+
(true))
|
76
|
+
PATTERN
|
77
|
+
|
54
78
|
def on_send(node)
|
55
|
-
return if active_resource?(node.parent)
|
79
|
+
return if active_resource?(node.parent) || readonly_model?(node)
|
56
80
|
return if !association_without_options?(node) && valid_options?(association_with_options?(node))
|
57
81
|
return if valid_options_in_with_options_block?(node)
|
58
82
|
|
@@ -61,10 +85,16 @@ module RuboCop
|
|
61
85
|
|
62
86
|
private
|
63
87
|
|
88
|
+
def readonly_model?(node)
|
89
|
+
return false unless (parent = node.parent)
|
90
|
+
|
91
|
+
parent.each_descendant(:def).any? { |def_node| readonly?(def_node) }
|
92
|
+
end
|
93
|
+
|
64
94
|
def valid_options_in_with_options_block?(node)
|
65
95
|
return true unless node.parent
|
66
96
|
|
67
|
-
n = node.parent.begin_type? ? node.parent.parent : node.parent
|
97
|
+
n = node.parent.begin_type? || association_extension_block?(node.parent) ? node.parent.parent : node.parent
|
68
98
|
|
69
99
|
contain_valid_options_in_with_options_block?(n)
|
70
100
|
end
|