rubocop-rails 2.26.0 → 2.29.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.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.md +51 -1
  4. data/config/default.yml +30 -6
  5. data/lib/rubocop/cop/mixin/index_method.rb +12 -5
  6. data/lib/rubocop/cop/mixin/routes_helper.rb +20 -0
  7. data/lib/rubocop/cop/mixin/target_rails_version.rb +3 -5
  8. data/lib/rubocop/cop/rails/action_controller_flash_before_render.rb +5 -5
  9. data/lib/rubocop/cop/rails/action_order.rb +1 -5
  10. data/lib/rubocop/cop/rails/active_record_callbacks_order.rb +1 -5
  11. data/lib/rubocop/cop/rails/add_column_index.rb +1 -0
  12. data/lib/rubocop/cop/rails/application_record.rb +4 -0
  13. data/lib/rubocop/cop/rails/bulk_change_table.rb +1 -0
  14. data/lib/rubocop/cop/rails/compact_blank.rb +8 -4
  15. data/lib/rubocop/cop/rails/dangerous_column_names.rb +2 -0
  16. data/lib/rubocop/cop/rails/duplicate_association.rb +8 -4
  17. data/lib/rubocop/cop/rails/enum_syntax.rb +18 -14
  18. data/lib/rubocop/cop/rails/env_local.rb +26 -3
  19. data/lib/rubocop/cop/rails/file_path.rb +26 -3
  20. data/lib/rubocop/cop/rails/http_positional_arguments.rb +7 -0
  21. data/lib/rubocop/cop/rails/index_by.rb +28 -12
  22. data/lib/rubocop/cop/rails/index_with.rb +28 -12
  23. data/lib/rubocop/cop/rails/match_route.rb +1 -9
  24. data/lib/rubocop/cop/rails/multiple_route_paths.rb +50 -0
  25. data/lib/rubocop/cop/rails/not_null_column.rb +6 -2
  26. data/lib/rubocop/cop/rails/pluck.rb +20 -0
  27. data/lib/rubocop/cop/rails/pluralization_grammar.rb +1 -1
  28. data/lib/rubocop/cop/rails/present.rb +0 -2
  29. data/lib/rubocop/cop/rails/redundant_active_record_all_method.rb +1 -30
  30. data/lib/rubocop/cop/rails/redundant_foreign_key.rb +1 -1
  31. data/lib/rubocop/cop/rails/redundant_receiver_in_with_options.rb +1 -1
  32. data/lib/rubocop/cop/rails/reflection_class_name.rb +1 -1
  33. data/lib/rubocop/cop/rails/request_referer.rb +1 -1
  34. data/lib/rubocop/cop/rails/reversible_migration.rb +3 -1
  35. data/lib/rubocop/cop/rails/root_pathname_methods.rb +17 -8
  36. data/lib/rubocop/cop/rails/save_bang.rb +4 -3
  37. data/lib/rubocop/cop/rails/schema_comment.rb +1 -0
  38. data/lib/rubocop/cop/rails/select_map.rb +3 -2
  39. data/lib/rubocop/cop/rails/skips_model_validations.rb +4 -2
  40. data/lib/rubocop/cop/rails/squished_sql_heredocs.rb +1 -1
  41. data/lib/rubocop/cop/rails/strong_parameters_expect.rb +104 -0
  42. data/lib/rubocop/cop/rails/three_state_boolean_column.rb +2 -1
  43. data/lib/rubocop/cop/rails/time_zone.rb +13 -6
  44. data/lib/rubocop/cop/rails/transaction_exit_statement.rb +5 -0
  45. data/lib/rubocop/cop/rails/where_equals.rb +6 -1
  46. data/lib/rubocop/cop/rails/where_not.rb +6 -1
  47. data/lib/rubocop/cop/rails/where_range.rb +7 -2
  48. data/lib/rubocop/cop/rails_cops.rb +3 -0
  49. data/lib/rubocop/rails/migration_file_skippable.rb +54 -0
  50. data/lib/rubocop/rails/version.rb +1 -1
  51. data/lib/rubocop/rails.rb +0 -1
  52. data/lib/rubocop-rails.rb +3 -0
  53. metadata +8 -7
@@ -29,18 +29,28 @@ module RuboCop
29
29
  PATTERN
30
30
 
31
31
  def_node_matcher :on_bad_to_h, <<~PATTERN
32
- (block
33
- (call _ :to_h)
34
- (args (arg $_el))
35
- (array $_ (lvar _el)))
32
+ {
33
+ (block
34
+ (call _ :to_h)
35
+ (args (arg $_el))
36
+ (array $_ (lvar _el)))
37
+ (numblock
38
+ (call _ :to_h) $1
39
+ (array $_ (lvar :_1)))
40
+ }
36
41
  PATTERN
37
42
 
38
43
  def_node_matcher :on_bad_map_to_h, <<~PATTERN
39
44
  (call
40
- (block
41
- (call _ {:map :collect})
42
- (args (arg $_el))
43
- (array $_ (lvar _el)))
45
+ {
46
+ (block
47
+ (call _ {:map :collect})
48
+ (args (arg $_el))
49
+ (array $_ (lvar _el)))
50
+ (numblock
51
+ (call _ {:map :collect}) $1
52
+ (array $_ (lvar :_1)))
53
+ }
44
54
  :to_h)
45
55
  PATTERN
46
56
 
@@ -48,10 +58,16 @@ module RuboCop
48
58
  (send
49
59
  (const {nil? cbase} :Hash)
50
60
  :[]
51
- (block
52
- (call _ {:map :collect})
53
- (args (arg $_el))
54
- (array $_ (lvar _el))))
61
+ {
62
+ (block
63
+ (call _ {:map :collect})
64
+ (args (arg $_el))
65
+ (array $_ (lvar _el)))
66
+ (numblock
67
+ (call _ {:map :collect}) $1
68
+ (array $_ (lvar :_1)))
69
+ }
70
+ )
55
71
  PATTERN
56
72
 
57
73
  private
@@ -32,18 +32,28 @@ module RuboCop
32
32
  PATTERN
33
33
 
34
34
  def_node_matcher :on_bad_to_h, <<~PATTERN
35
- (block
36
- (call _ :to_h)
37
- (args (arg $_el))
38
- (array (lvar _el) $_))
35
+ {
36
+ (block
37
+ (call _ :to_h)
38
+ (args (arg $_el))
39
+ (array (lvar _el) $_))
40
+ (numblock
41
+ (call _ :to_h) $1
42
+ (array (lvar :_1) $_))
43
+ }
39
44
  PATTERN
40
45
 
41
46
  def_node_matcher :on_bad_map_to_h, <<~PATTERN
42
47
  (call
43
- (block
44
- (call _ {:map :collect})
45
- (args (arg $_el))
46
- (array (lvar _el) $_))
48
+ {
49
+ (block
50
+ (call _ {:map :collect})
51
+ (args (arg $_el))
52
+ (array (lvar _el) $_))
53
+ (numblock
54
+ (call _ {:map :collect}) $1
55
+ (array (lvar :_1) $_))
56
+ }
47
57
  :to_h)
48
58
  PATTERN
49
59
 
@@ -51,10 +61,16 @@ module RuboCop
51
61
  (send
52
62
  (const {nil? cbase} :Hash)
53
63
  :[]
54
- (block
55
- (call _ {:map :collect})
56
- (args (arg $_el))
57
- (array (lvar _el) $_)))
64
+ {
65
+ (block
66
+ (call _ {:map :collect})
67
+ (args (arg $_el))
68
+ (array (lvar _el) $_))
69
+ (numblock
70
+ (call _ {:map :collect}) $1
71
+ (array (lvar :_1) $_))
72
+ }
73
+ )
58
74
  PATTERN
59
75
 
60
76
  private
@@ -21,11 +21,11 @@ module RuboCop
21
21
  # match 'photos/:id', to: 'photos#show', via: :all
22
22
  #
23
23
  class MatchRoute < Base
24
+ include RoutesHelper
24
25
  extend AutoCorrector
25
26
 
26
27
  MSG = 'Use `%<http_method>s` instead of `match` to define a route.'
27
28
  RESTRICT_ON_SEND = %i[match].freeze
28
- HTTP_METHODS = %i[get post put patch delete].freeze
29
29
 
30
30
  def_node_matcher :match_method_call?, <<~PATTERN
31
31
  (send nil? :match $_ $(hash ...) ?)
@@ -60,14 +60,6 @@ module RuboCop
60
60
  end
61
61
  end
62
62
 
63
- def_node_matcher :routes_draw?, <<~PATTERN
64
- (send (send _ :routes) :draw)
65
- PATTERN
66
-
67
- def within_routes?(node)
68
- node.each_ancestor(:block).any? { |a| routes_draw?(a.send_node) }
69
- end
70
-
71
63
  def extract_via(node)
72
64
  via_pair = via_pair(node)
73
65
  return %i[get] unless via_pair
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # Checks for mapping a route with multiple paths, which is deprecated and will be removed in Rails 8.1.
7
+ #
8
+ # @example
9
+ #
10
+ # # bad
11
+ # get '/users', '/other_path', to: 'users#index'
12
+ #
13
+ # # good
14
+ # get '/users', to: 'users#index'
15
+ # get '/other_path', to: 'users#index'
16
+ #
17
+ class MultipleRoutePaths < Base
18
+ include RoutesHelper
19
+ extend AutoCorrector
20
+
21
+ MSG = 'Use separate routes instead of combining multiple route paths in a single route.'
22
+ RESTRICT_ON_SEND = HTTP_METHODS
23
+
24
+ IGNORED_ARGUMENT_TYPES = %i[array hash].freeze
25
+
26
+ def on_send(node)
27
+ return unless within_routes?(node)
28
+
29
+ route_paths = node.arguments.reject { |argument| IGNORED_ARGUMENT_TYPES.include?(argument.type) }
30
+ return if route_paths.count < 2
31
+
32
+ add_offense(node) do |corrector|
33
+ corrector.replace(node, migrate_to_multiple_routes(node, route_paths))
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ def migrate_to_multiple_routes(node, route_paths)
40
+ rest = route_paths.last.source_range.end.join(node.source_range.end).source
41
+ indentation = ' ' * node.source_range.column
42
+
43
+ route_paths.map do |route_path|
44
+ "#{node.method_name} #{route_path.source}#{rest}"
45
+ end.join("\n#{indentation}")
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -41,10 +41,14 @@ module RuboCop
41
41
  # change_column_null :products, :category_id, false
42
42
  class NotNullColumn < Base
43
43
  include DatabaseTypeResolvable
44
+ include MigrationsHelper
44
45
 
45
46
  MSG = 'Do not add a NOT NULL column without a default value.'
46
47
  RESTRICT_ON_SEND = %i[add_column add_reference].freeze
47
48
 
49
+ VIRTUAL_TYPE_VALUES = [:virtual, 'virtual'].freeze
50
+ TEXT_TYPE_VALUES = [:text, 'text'].freeze
51
+
48
52
  def_node_matcher :add_not_null_column?, <<~PATTERN
49
53
  (send nil? :add_column _ _ $_ (hash $...))
50
54
  PATTERN
@@ -91,8 +95,8 @@ module RuboCop
91
95
 
92
96
  def check_column(type, pairs)
93
97
  if type.respond_to?(:value)
94
- return if type.value == :virtual || type.value == 'virtual'
95
- return if (type.value == :text || type.value == 'text') && database == MYSQL
98
+ return if VIRTUAL_TYPE_VALUES.include?(type.value)
99
+ return if TEXT_TYPE_VALUES.include?(type.value) && database == MYSQL
96
100
  end
97
101
 
98
102
  check_pairs(pairs)
@@ -9,6 +9,24 @@ module RuboCop
9
9
  # element in an enumerable. When called on an Active Record relation, it
10
10
  # results in a more efficient query that only selects the necessary key.
11
11
  #
12
+ # NOTE: If the receiver's relation is not loaded and `pluck` is used inside an iteration,
13
+ # it may result in N+1 queries because `pluck` queries the database on each iteration.
14
+ # This cop ignores offenses for `map/collect` when they are suspected to be part of an iteration
15
+ # to prevent such potential issues.
16
+ #
17
+ # [source,ruby]
18
+ # ----
19
+ # users = User.all
20
+ # 5.times do
21
+ # users.map { |user| user[:foo] } # Only one query is executed
22
+ # end
23
+ #
24
+ # users = User.all
25
+ # 5.times do
26
+ # users.pluck(:id) # A query is executed on every iteration
27
+ # end
28
+ # ----
29
+ #
12
30
  # @safety
13
31
  # This cop is unsafe because model can use column aliases.
14
32
  #
@@ -42,6 +60,8 @@ module RuboCop
42
60
  PATTERN
43
61
 
44
62
  def on_block(node)
63
+ return if node.each_ancestor(:block, :numblock).any?
64
+
45
65
  pluck_candidate?(node) do |argument, key|
46
66
  next if key.regexp_type? || !use_one_block_argument?(argument)
47
67
 
@@ -11,7 +11,7 @@ module RuboCop
11
11
  # 3.day.ago
12
12
  # 1.months.ago
13
13
  # 5.megabyte
14
- # 1.gigabyte
14
+ # 1.gigabytes
15
15
  #
16
16
  # # good
17
17
  # 3.days.ago
@@ -98,8 +98,6 @@ module RuboCop
98
98
  end
99
99
 
100
100
  def on_or(node)
101
- return unless cop_config['NilOrEmpty']
102
-
103
101
  exists_and_not_empty?(node) do |var1, var2|
104
102
  return unless var1 == var2
105
103
 
@@ -3,35 +3,6 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # TODO: In the future, please support only RuboCop 1.52+ and use `RuboCop::Cop::AllowedReceivers`:
7
- # https://github.com/rubocop/rubocop/blob/v1.52.0/lib/rubocop/cop/mixin/allowed_receivers.rb
8
- # At that time, this duplicated module implementation can be removed.
9
- module AllowedReceivers
10
- def allowed_receiver?(receiver)
11
- receiver_name = receiver_name(receiver)
12
-
13
- allowed_receivers.include?(receiver_name)
14
- end
15
-
16
- def receiver_name(receiver)
17
- return receiver_name(receiver.receiver) if receiver.receiver && !receiver.receiver.const_type?
18
-
19
- if receiver.send_type?
20
- if receiver.receiver
21
- "#{receiver_name(receiver.receiver)}.#{receiver.method_name}"
22
- else
23
- receiver.method_name.to_s
24
- end
25
- else
26
- receiver.source
27
- end
28
- end
29
-
30
- def allowed_receivers
31
- cop_config.fetch('AllowedReceivers', [])
32
- end
33
- end
34
-
35
6
  # Detect redundant `all` used as a receiver for Active Record query methods.
36
7
  #
37
8
  # For the methods `delete_all` and `destroy_all`, this cop will only check cases where the receiver is a model.
@@ -203,7 +174,7 @@ module RuboCop
203
174
  parent = node.parent
204
175
  return false unless POSSIBLE_ENUMERABLE_BLOCK_METHODS.include?(parent.method_name)
205
176
 
206
- parent.parent&.block_type? || parent.parent&.numblock_type? || parent.first_argument&.block_pass_type?
177
+ parent.block_literal? || parent.first_argument&.block_pass_type?
207
178
  end
208
179
 
209
180
  def sensitive_association_method?(node)
@@ -40,7 +40,7 @@ module RuboCop
40
40
  def on_send(node)
41
41
  association_with_foreign_key(node) do |type, name, options, foreign_key_pair, foreign_key|
42
42
  if redundant?(node, type, name, options, foreign_key)
43
- add_offense(foreign_key_pair.source_range) do |corrector|
43
+ add_offense(foreign_key_pair) do |corrector|
44
44
  range = range_with_surrounding_space(foreign_key_pair.source_range, side: :left)
45
45
  range = range_with_surrounding_comma(range, :left)
46
46
 
@@ -78,7 +78,7 @@ module RuboCop
78
78
 
79
79
  send_nodes.each do |send_node|
80
80
  receiver = send_node.receiver
81
- add_offense(receiver.source_range) do |corrector|
81
+ add_offense(receiver) do |corrector|
82
82
  autocorrect(corrector, send_node, node)
83
83
  end
84
84
  end
@@ -43,7 +43,7 @@ module RuboCop
43
43
  return if reflection_class_name.value.send_type? && reflection_class_name.value.receiver.nil?
44
44
  return if reflection_class_name.value.lvar_type? && str_assigned?(reflection_class_name)
45
45
 
46
- add_offense(reflection_class_name.source_range) do |corrector|
46
+ add_offense(reflection_class_name) do |corrector|
47
47
  autocorrect(corrector, reflection_class_name)
48
48
  end
49
49
  end
@@ -34,7 +34,7 @@ module RuboCop
34
34
  referer?(node) do
35
35
  return unless node.method?(wrong_method_name)
36
36
 
37
- add_offense(node.source_range) do |corrector|
37
+ add_offense(node) do |corrector|
38
38
  corrector.replace(node, "request.#{style}")
39
39
  end
40
40
  end
@@ -215,8 +215,10 @@ module RuboCop
215
215
  end
216
216
 
217
217
  def check_drop_table_node(node)
218
+ return unless (last_argument = node.last_argument)
219
+
218
220
  drop_table_call(node) do
219
- unless node.parent.block_type? || node.last_argument.block_pass_type?
221
+ unless node.parent.block_type? || last_argument.block_pass_type?
220
222
  add_offense(node, message: format(MSG, action: 'drop_table(without block)'))
221
223
  end
222
224
  end
@@ -23,6 +23,8 @@ module RuboCop
23
23
  # File.binread(Rails.root.join('db', 'schema.rb'))
24
24
  # File.write(Rails.root.join('db', 'schema.rb'), content)
25
25
  # File.binwrite(Rails.root.join('db', 'schema.rb'), content)
26
+ # Dir.glob(Rails.root.join('db', 'schema.rb'))
27
+ # Dir[Rails.root.join('db', 'schema.rb')]
26
28
  #
27
29
  # # good
28
30
  # Rails.root.join('db', 'schema.rb').open
@@ -31,12 +33,13 @@ module RuboCop
31
33
  # Rails.root.join('db', 'schema.rb').binread
32
34
  # Rails.root.join('db', 'schema.rb').write(content)
33
35
  # Rails.root.join('db', 'schema.rb').binwrite(content)
36
+ # Rails.root.glob("db/schema.rb")
34
37
  #
35
38
  class RootPathnameMethods < Base # rubocop:disable Metrics/ClassLength
36
39
  extend AutoCorrector
37
40
  include RangeHelp
38
41
 
39
- MSG = '`%<rails_root>s` is a `Pathname` so you can just append `#%<method>s`.'
42
+ MSG = '`%<rails_root>s` is a `Pathname`, so you can use `%<replacement>s`.'
40
43
 
41
44
  DIR_GLOB_METHODS = %i[[] glob].to_set.freeze
42
45
 
@@ -188,13 +191,14 @@ module RuboCop
188
191
 
189
192
  def on_send(node)
190
193
  evidence(node) do |method, path, args, rails_root|
191
- add_offense(node, message: format(MSG, method: method, rails_root: rails_root.source)) do |corrector|
192
- replacement = if dir_glob?(node)
193
- build_path_glob_replacement(path)
194
- else
195
- build_path_replacement(path, method, args)
196
- end
194
+ replacement = if dir_glob?(node)
195
+ build_path_glob_replacement(path)
196
+ else
197
+ build_path_replacement(path, method, args)
198
+ end
197
199
 
200
+ message = format(MSG, rails_root: rails_root.source, replacement: replacement)
201
+ add_offense(node, message: message) do |corrector|
198
202
  corrector.replace(node, replacement)
199
203
  end
200
204
  end
@@ -233,7 +237,12 @@ module RuboCop
233
237
  end
234
238
 
235
239
  replacement = "#{path_replacement}.#{method}"
236
- replacement += "(#{args.map(&:source).join(', ')})" unless args.empty?
240
+
241
+ if args.any?
242
+ formatted_args = args.map { |arg| arg.array_type? ? "*#{arg.source}" : arg.source }
243
+ replacement += "(#{formatted_args.join(', ')})"
244
+ end
245
+
237
246
  replacement
238
247
  end
239
248
 
@@ -244,7 +244,7 @@ module RuboCop
244
244
  end
245
245
 
246
246
  def operator_or_single_negative?(node)
247
- node.or_type? || node.and_type? || single_negative?(node)
247
+ node.operator_keyword? || single_negative?(node)
248
248
  end
249
249
 
250
250
  def conditional?(parent)
@@ -328,8 +328,9 @@ module RuboCop
328
328
  end
329
329
 
330
330
  def return_value_assigned?(node)
331
- assignment = assignable_node(node).parent
332
- assignment&.lvasgn_type?
331
+ return false unless (assignment = assignable_node(node).parent)
332
+
333
+ assignment.assignment?
333
334
  end
334
335
 
335
336
  def persist_method?(node, methods = RESTRICT_ON_SEND)
@@ -23,6 +23,7 @@ module RuboCop
23
23
  #
24
24
  class SchemaComment < Base
25
25
  include ActiveRecordMigrationsHelper
26
+ include MigrationsHelper
26
27
 
27
28
  COLUMN_MSG = 'New database column without `comment`.'
28
29
  TABLE_MSG = 'New database table without `comment`.'
@@ -40,12 +40,13 @@ module RuboCop
40
40
  autocorrect(corrector, select_node, node, preferred_method)
41
41
  end
42
42
  end
43
+ alias on_csend on_send
43
44
 
44
45
  private
45
46
 
46
47
  def find_select_node(node, column_name)
47
48
  node.descendants.detect do |select_candidate|
48
- next if !select_candidate.send_type? || !select_candidate.method?(:select)
49
+ next if !select_candidate.call_type? || !select_candidate.method?(:select)
49
50
 
50
51
  match_column_name?(select_candidate, column_name)
51
52
  end
@@ -53,7 +54,7 @@ module RuboCop
53
54
 
54
55
  # rubocop:disable Metrics/AbcSize
55
56
  def autocorrect(corrector, select_node, node, preferred_method)
56
- corrector.remove(select_node.loc.dot || node.loc.dot)
57
+ corrector.remove(select_node.parent.loc.dot)
57
58
  corrector.remove(select_node.loc.selector.begin.join(select_node.source_range.end))
58
59
  corrector.replace(node.loc.selector.begin.join(node.source_range.end), preferred_method)
59
60
  end
@@ -100,7 +100,8 @@ module RuboCop
100
100
  end
101
101
 
102
102
  def forbidden_methods
103
- obsolete_result = cop_config['Blacklist']
103
+ # TODO: Remove when RuboCop Rails 3 releases.
104
+ obsolete_result = cop_config['Blacklist'] # rubocop:disable InternalAffairs/UndefinedConfig
104
105
  if obsolete_result
105
106
  warn '`Blacklist` has been renamed to `ForbiddenMethods`.' unless @displayed_forbidden_warning
106
107
  @displayed_forbidden_warning = true
@@ -111,7 +112,8 @@ module RuboCop
111
112
  end
112
113
 
113
114
  def allowed_methods
114
- obsolete_result = cop_config['Whitelist']
115
+ # TODO: Remove when RuboCop Rails 3 releases.
116
+ obsolete_result = cop_config['Whitelist'] # rubocop:disable InternalAffairs/UndefinedConfig
115
117
  if obsolete_result
116
118
  warn '`Whitelist` has been renamed to `AllowedMethods`.' unless @displayed_allowed_warning
117
119
  @displayed_allowed_warning = true
@@ -68,7 +68,7 @@ module RuboCop
68
68
  end
69
69
 
70
70
  def using_squish?(node)
71
- node.parent&.send_type? && node.parent&.method?(:squish)
71
+ node.parent&.send_type? && node.parent.method?(:squish)
72
72
  end
73
73
 
74
74
  def singleline_comments_present?(node)
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # Enforces the use of `ActionController::Parameters#expect` as a method for strong parameter handling.
7
+ #
8
+ # @safety
9
+ # This cop's autocorrection is considered unsafe because there are cases where the HTTP status may change
10
+ # from 500 to 400 when handling invalid parameters. This change, however, reflects an intentional
11
+ # incompatibility introduced for valid reasons by the `expect` method, which aligns better with
12
+ # strong parameter conventions.
13
+ #
14
+ # @example
15
+ #
16
+ # # bad
17
+ # params.require(:user).permit(:name, :age)
18
+ # params.permit(user: [:name, :age]).require(:user)
19
+ #
20
+ # # good
21
+ # params.expect(user: [:name, :age])
22
+ #
23
+ class StrongParametersExpect < Base
24
+ extend AutoCorrector
25
+ extend TargetRailsVersion
26
+
27
+ MSG = 'Use `%<prefer>s` instead.'
28
+ RESTRICT_ON_SEND = %i[require permit].freeze
29
+
30
+ minimum_target_rails_version 8.0
31
+
32
+ def_node_matcher :params_require_permit, <<~PATTERN
33
+ $(call
34
+ $(call
35
+ (send nil? :params) :require _) :permit _+)
36
+ PATTERN
37
+
38
+ def_node_matcher :params_permit_require, <<~PATTERN
39
+ $(call
40
+ $(call
41
+ (send nil? :params) :permit (hash (pair _require_param_name _ )))
42
+ :require _require_param_name)
43
+ PATTERN
44
+
45
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
46
+ def on_send(node)
47
+ return if part_of_ignored_node?(node)
48
+
49
+ if (permit_method, require_method = params_require_permit(node))
50
+ range = offense_range(require_method, node)
51
+ prefer = expect_method(require_method, permit_method)
52
+ replace_argument = true
53
+ elsif (require_method, permit_method = params_permit_require(node))
54
+ range = offense_range(permit_method, node)
55
+ prefer = "expect(#{permit_method.arguments.map(&:source).join(', ')})"
56
+ replace_argument = false
57
+ else
58
+ return
59
+ end
60
+
61
+ add_offense(range, message: format(MSG, prefer: prefer)) do |corrector|
62
+ corrector.remove(require_method.receiver.source_range.end.join(require_method.source_range.end))
63
+ corrector.replace(permit_method.loc.selector, 'expect')
64
+ if replace_argument
65
+ corrector.insert_before(permit_method.first_argument, "#{require_key(require_method)}[")
66
+ corrector.insert_after(permit_method.last_argument, ']')
67
+ end
68
+ end
69
+
70
+ ignore_node(node)
71
+ end
72
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
73
+ alias on_csend on_send
74
+
75
+ private
76
+
77
+ def offense_range(method_node, node)
78
+ method_node.loc.selector.join(node.source_range.end)
79
+ end
80
+
81
+ def expect_method(require_method, permit_method)
82
+ require_key = require_key(require_method)
83
+ permit_args = permit_method.arguments.map(&:source).join(', ')
84
+
85
+ arguments = "#{require_key}[#{permit_args}]"
86
+
87
+ "expect(#{arguments})"
88
+ end
89
+
90
+ def require_key(require_method)
91
+ if (first_argument = require_method.first_argument).respond_to?(:value)
92
+ require_arg = first_argument.value
93
+ separator = ': '
94
+ else
95
+ require_arg = first_argument.source
96
+ separator = ' => '
97
+ end
98
+
99
+ "#{require_arg}#{separator}"
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
@@ -18,8 +18,9 @@ module RuboCop
18
18
  # t.boolean :active, default: true, null: false
19
19
  #
20
20
  class ThreeStateBooleanColumn < Base
21
- MSG = 'Boolean columns should always have a default value and a `NOT NULL` constraint.'
21
+ include MigrationsHelper
22
22
 
23
+ MSG = 'Boolean columns should always have a default value and a `NOT NULL` constraint.'
23
24
  RESTRICT_ON_SEND = %i[add_column column boolean].freeze
24
25
 
25
26
  def_node_matcher :three_state_boolean?, <<~PATTERN