rubocop-rails 2.19.1 → 2.30.3

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 (111) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.md +70 -16
  4. data/config/default.yml +173 -28
  5. data/lib/rubocop/cop/mixin/active_record_helper.rb +16 -4
  6. data/lib/rubocop/cop/mixin/active_record_migrations_helper.rb +2 -2
  7. data/lib/rubocop/cop/mixin/database_type_resolvable.rb +66 -0
  8. data/lib/rubocop/cop/mixin/index_method.rb +68 -61
  9. data/lib/rubocop/cop/mixin/routes_helper.rb +20 -0
  10. data/lib/rubocop/cop/mixin/target_rails_version.rb +27 -2
  11. data/lib/rubocop/cop/rails/action_controller_flash_before_render.rb +3 -1
  12. data/lib/rubocop/cop/rails/action_controller_test_case.rb +2 -2
  13. data/lib/rubocop/cop/rails/action_filter.rb +3 -0
  14. data/lib/rubocop/cop/rails/action_order.rb +1 -5
  15. data/lib/rubocop/cop/rails/active_record_aliases.rb +2 -2
  16. data/lib/rubocop/cop/rails/active_record_callbacks_order.rb +1 -5
  17. data/lib/rubocop/cop/rails/active_support_aliases.rb +6 -5
  18. data/lib/rubocop/cop/rails/active_support_on_load.rb +21 -1
  19. data/lib/rubocop/cop/rails/add_column_index.rb +1 -0
  20. data/lib/rubocop/cop/rails/after_commit_override.rb +1 -1
  21. data/lib/rubocop/cop/rails/application_record.rb +4 -0
  22. data/lib/rubocop/cop/rails/assert_not.rb +0 -1
  23. data/lib/rubocop/cop/rails/belongs_to.rb +1 -1
  24. data/lib/rubocop/cop/rails/blank.rb +1 -1
  25. data/lib/rubocop/cop/rails/bulk_change_table.rb +19 -45
  26. data/lib/rubocop/cop/rails/compact_blank.rb +29 -8
  27. data/lib/rubocop/cop/rails/content_tag.rb +2 -2
  28. data/lib/rubocop/cop/rails/dangerous_column_names.rb +448 -0
  29. data/lib/rubocop/cop/rails/date.rb +14 -5
  30. data/lib/rubocop/cop/rails/delegate.rb +53 -7
  31. data/lib/rubocop/cop/rails/duplicate_association.rb +71 -10
  32. data/lib/rubocop/cop/rails/dynamic_find_by.rb +3 -3
  33. data/lib/rubocop/cop/rails/eager_evaluation_log_message.rb +2 -2
  34. data/lib/rubocop/cop/rails/enum_hash.rb +31 -8
  35. data/lib/rubocop/cop/rails/enum_syntax.rb +130 -0
  36. data/lib/rubocop/cop/rails/enum_uniqueness.rb +29 -7
  37. data/lib/rubocop/cop/rails/env_local.rb +69 -0
  38. data/lib/rubocop/cop/rails/expanded_date_range.rb +1 -1
  39. data/lib/rubocop/cop/rails/file_path.rb +186 -18
  40. data/lib/rubocop/cop/rails/find_by.rb +3 -3
  41. data/lib/rubocop/cop/rails/find_by_id.rb +9 -23
  42. data/lib/rubocop/cop/rails/find_each.rb +1 -1
  43. data/lib/rubocop/cop/rails/freeze_time.rb +1 -1
  44. data/lib/rubocop/cop/rails/has_many_or_has_one_dependent.rb +1 -1
  45. data/lib/rubocop/cop/rails/helper_instance_variable.rb +1 -1
  46. data/lib/rubocop/cop/rails/http_positional_arguments.rb +7 -0
  47. data/lib/rubocop/cop/rails/http_status.rb +16 -5
  48. data/lib/rubocop/cop/rails/i18n_lazy_lookup.rb +63 -13
  49. data/lib/rubocop/cop/rails/i18n_locale_texts.rb +5 -1
  50. data/lib/rubocop/cop/rails/ignored_skip_action_filter_option.rb +23 -3
  51. data/lib/rubocop/cop/rails/index_by.rb +28 -12
  52. data/lib/rubocop/cop/rails/index_with.rb +28 -12
  53. data/lib/rubocop/cop/rails/inquiry.rb +2 -1
  54. data/lib/rubocop/cop/rails/inverse_of.rb +1 -1
  55. data/lib/rubocop/cop/rails/lexically_scoped_action_filter.rb +19 -10
  56. data/lib/rubocop/cop/rails/link_to_blank.rb +2 -2
  57. data/lib/rubocop/cop/rails/match_route.rb +1 -9
  58. data/lib/rubocop/cop/rails/multiple_route_paths.rb +50 -0
  59. data/lib/rubocop/cop/rails/not_null_column.rb +100 -6
  60. data/lib/rubocop/cop/rails/output.rb +3 -2
  61. data/lib/rubocop/cop/rails/pick.rb +10 -5
  62. data/lib/rubocop/cop/rails/pluck.rb +21 -1
  63. data/lib/rubocop/cop/rails/pluck_id.rb +2 -1
  64. data/lib/rubocop/cop/rails/pluck_in_where.rb +35 -13
  65. data/lib/rubocop/cop/rails/pluralization_grammar.rb +30 -16
  66. data/lib/rubocop/cop/rails/presence.rb +1 -1
  67. data/lib/rubocop/cop/rails/present.rb +1 -3
  68. data/lib/rubocop/cop/rails/rake_environment.rb +22 -6
  69. data/lib/rubocop/cop/rails/redundant_active_record_all_method.rb +190 -0
  70. data/lib/rubocop/cop/rails/redundant_foreign_key.rb +1 -1
  71. data/lib/rubocop/cop/rails/redundant_presence_validation_on_belongs_to.rb +16 -0
  72. data/lib/rubocop/cop/rails/redundant_receiver_in_with_options.rb +2 -2
  73. data/lib/rubocop/cop/rails/reflection_class_name.rb +2 -2
  74. data/lib/rubocop/cop/rails/refute_methods.rb +0 -1
  75. data/lib/rubocop/cop/rails/relative_date_constant.rb +1 -1
  76. data/lib/rubocop/cop/rails/render_plain_text.rb +6 -3
  77. data/lib/rubocop/cop/rails/request_referer.rb +1 -1
  78. data/lib/rubocop/cop/rails/response_parsed_body.rb +52 -10
  79. data/lib/rubocop/cop/rails/reversible_migration.rb +7 -5
  80. data/lib/rubocop/cop/rails/root_pathname_methods.rb +58 -15
  81. data/lib/rubocop/cop/rails/save_bang.rb +22 -14
  82. data/lib/rubocop/cop/rails/schema_comment.rb +17 -10
  83. data/lib/rubocop/cop/rails/select_map.rb +79 -0
  84. data/lib/rubocop/cop/rails/skips_model_validations.rb +9 -4
  85. data/lib/rubocop/cop/rails/squished_sql_heredocs.rb +1 -2
  86. data/lib/rubocop/cop/rails/strip_heredoc.rb +1 -1
  87. data/lib/rubocop/cop/rails/strong_parameters_expect.rb +104 -0
  88. data/lib/rubocop/cop/rails/three_state_boolean_column.rb +4 -5
  89. data/lib/rubocop/cop/rails/time_zone.rb +26 -11
  90. data/lib/rubocop/cop/rails/transaction_exit_statement.rb +40 -9
  91. data/lib/rubocop/cop/rails/uniq_before_pluck.rb +11 -26
  92. data/lib/rubocop/cop/rails/unique_validation_without_index.rb +17 -21
  93. data/lib/rubocop/cop/rails/unknown_env.rb +5 -1
  94. data/lib/rubocop/cop/rails/unused_ignored_columns.rb +6 -0
  95. data/lib/rubocop/cop/rails/unused_render_content.rb +67 -0
  96. data/lib/rubocop/cop/rails/validation.rb +9 -4
  97. data/lib/rubocop/cop/rails/where_equals.rb +29 -12
  98. data/lib/rubocop/cop/rails/where_exists.rb +9 -9
  99. data/lib/rubocop/cop/rails/where_missing.rb +6 -2
  100. data/lib/rubocop/cop/rails/where_not.rb +18 -11
  101. data/lib/rubocop/cop/rails/where_range.rb +203 -0
  102. data/lib/rubocop/cop/rails_cops.rb +11 -0
  103. data/lib/rubocop/rails/migration_file_skippable.rb +54 -0
  104. data/lib/rubocop/rails/plugin.rb +48 -0
  105. data/lib/rubocop/rails/schema_loader/schema.rb +8 -7
  106. data/lib/rubocop/rails/schema_loader.rb +5 -15
  107. data/lib/rubocop/rails/version.rb +1 -1
  108. data/lib/rubocop/rails.rb +1 -8
  109. data/lib/rubocop-rails.rb +12 -4
  110. metadata +55 -11
  111. data/lib/rubocop/rails/inject.rb +0 -18
@@ -36,6 +36,10 @@ module RuboCop
36
36
  # if: -> { trusted_origin? && action_name != "admin" }
37
37
  # end
38
38
  class IgnoredSkipActionFilterOption < Base
39
+ extend AutoCorrector
40
+
41
+ include RangeHelp
42
+
39
43
  MSG = <<~MSG.chomp.freeze
40
44
  `%<ignore>s` option will be ignored when `%<prefer>s` and `%<ignore>s` are used together.
41
45
  MSG
@@ -48,7 +52,7 @@ module RuboCop
48
52
  (send
49
53
  nil?
50
54
  {#{FILTERS.join(' ')}}
51
- _
55
+ ...
52
56
  $_)
53
57
  PATTERN
54
58
 
@@ -60,9 +64,13 @@ module RuboCop
60
64
  options = options_hash(options)
61
65
 
62
66
  if if_and_only?(options)
63
- add_offense(options[:if], message: format(MSG, prefer: :only, ignore: :if))
67
+ add_offense(options[:if], message: format(MSG, prefer: :only, ignore: :if)) do |corrector|
68
+ remove_node_with_left_space_and_comma(corrector, options[:if])
69
+ end
64
70
  elsif if_and_except?(options)
65
- add_offense(options[:except], message: format(MSG, prefer: :if, ignore: :except))
71
+ add_offense(options[:except], message: format(MSG, prefer: :if, ignore: :except)) do |corrector|
72
+ remove_node_with_left_space_and_comma(corrector, options[:except])
73
+ end
66
74
  end
67
75
  end
68
76
 
@@ -81,6 +89,18 @@ module RuboCop
81
89
  def if_and_except?(options)
82
90
  options.key?(:if) && options.key?(:except)
83
91
  end
92
+
93
+ def remove_node_with_left_space_and_comma(corrector, node)
94
+ corrector.remove(
95
+ range_with_surrounding_comma(
96
+ range_with_surrounding_space(
97
+ node.source_range,
98
+ side: :left
99
+ ),
100
+ :left
101
+ )
102
+ )
103
+ end
84
104
  end
85
105
  end
86
106
  end
@@ -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
@@ -29,10 +29,11 @@ module RuboCop
29
29
  def on_send(node)
30
30
  return unless node.arguments.empty?
31
31
  return unless (receiver = node.receiver)
32
- return if !receiver.str_type? && !receiver.array_type?
32
+ return unless receiver.type?(:str, :array)
33
33
 
34
34
  add_offense(node.loc.selector)
35
35
  end
36
+ alias on_csend on_send
36
37
  end
37
38
  end
38
39
  end
@@ -222,7 +222,7 @@ module RuboCop
222
222
 
223
223
  def with_options_arguments(recv, node)
224
224
  blocks = node.each_ancestor(:block).select do |block|
225
- block.send_node.command?(:with_options) && same_context_in_with_options?(block.arguments.first, recv)
225
+ block.send_node.command?(:with_options) && same_context_in_with_options?(block.first_argument, recv)
226
226
  end
227
227
  blocks.flat_map { |n| n.send_node.arguments }
228
228
  end
@@ -115,6 +115,10 @@ module RuboCop
115
115
  $_)))
116
116
  PATTERN
117
117
 
118
+ def_node_matcher :delegated_methods, <<~PATTERN
119
+ (send nil? :delegate (sym $_)+ (hash <(pair (sym :to) _) ...>))
120
+ PATTERN
121
+
118
122
  def on_send(node)
119
123
  methods_node = only_or_except_filter_methods(node)
120
124
  return unless methods_node
@@ -122,25 +126,30 @@ module RuboCop
122
126
  parent = node.each_ancestor(:class, :module).first
123
127
  return unless parent
124
128
 
129
+ # NOTE: a `:begin` node may not exist if the class/module consists of a single statement
125
130
  block = parent.each_child_node(:begin).first
126
- return unless block
127
-
128
131
  defined_action_methods = defined_action_methods(block)
129
132
 
130
- methods = array_values(methods_node).reject do |method|
131
- defined_action_methods.include?(method)
132
- end
133
+ unmatched_methods = array_values(methods_node) - defined_action_methods
134
+ return if unmatched_methods.empty?
133
135
 
134
- message = message(methods, parent)
135
- add_offense(node, message: message) unless methods.empty?
136
+ message = message(unmatched_methods, parent)
137
+ add_offense(node, message: message)
136
138
  end
137
139
 
138
140
  private
139
141
 
140
142
  def defined_action_methods(block)
143
+ return [] unless block
144
+
141
145
  defined_methods = block.each_child_node(:def).map(&:method_name)
146
+ defined_methods + delegated_action_methods(block) + aliased_action_methods(block, defined_methods)
147
+ end
142
148
 
143
- defined_methods + aliased_action_methods(block, defined_methods)
149
+ def delegated_action_methods(node)
150
+ node.each_child_node(:send).flat_map do |child_node|
151
+ delegated_methods(child_node) || []
152
+ end
144
153
  end
145
154
 
146
155
  def aliased_action_methods(node, defined_methods)
@@ -176,14 +185,14 @@ module RuboCop
176
185
  when :sym
177
186
  [node.value]
178
187
  when :array
179
- node.values.map do |v|
188
+ node.values.filter_map do |v|
180
189
  case v.type
181
190
  when :str
182
191
  v.str_content.to_sym
183
192
  when :sym
184
193
  v.value
185
194
  end
186
- end.compact
195
+ end
187
196
  else
188
197
  []
189
198
  end
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # Checks for calls to `link_to` that contain a
6
+ # Checks for calls to `link_to`, `link_to_if`, and `link_to_unless` methods that contain a
7
7
  # `target: '_blank'` but no `rel: 'noopener'`. This can be a security
8
8
  # risk as the loaded page will have control over the previous page
9
9
  # and could change its location for phishing purposes.
@@ -24,7 +24,7 @@ module RuboCop
24
24
  extend AutoCorrector
25
25
 
26
26
  MSG = 'Specify a `:rel` option containing noopener.'
27
- RESTRICT_ON_SEND = %i[link_to].freeze
27
+ RESTRICT_ON_SEND = %i[link_to link_to_if link_to_unless].freeze
28
28
 
29
29
  def_node_matcher :blank_target?, <<~PATTERN
30
30
  (pair {(sym :target) (str "target")} {(str "_blank") (sym :_blank)})
@@ -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
@@ -3,23 +3,52 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # Checks for add_column call with NOT NULL constraint
7
- # in migration file.
6
+ # Checks for add_column calls with a NOT NULL constraint without a default
7
+ # value.
8
+ #
9
+ # This cop only applies when adding a column to an existing table, since
10
+ # existing records will not have a value for the new column. New tables
11
+ # can freely use NOT NULL columns without defaults, since there are no
12
+ # records that could violate the constraint.
13
+ #
14
+ # If you need to add a NOT NULL column to an existing table, you must add
15
+ # it as nullable first, back-fill the data, and then use
16
+ # `change_column_null`. Alternatively, you could add the column with a
17
+ # default first to have the database automatically backfill existing rows,
18
+ # and then use `change_column_default` to remove the default.
19
+ #
20
+ # `TEXT` cannot have a default value in MySQL.
21
+ # The cop will automatically detect an adapter from `development`
22
+ # environment in `config/database.yml` or the environment variable
23
+ # `DATABASE_URL` when the `Database` option is not set. If the database
24
+ # is MySQL, this cop ignores offenses for `TEXT` columns.
8
25
  #
9
26
  # @example
10
27
  # # bad
11
28
  # add_column :users, :name, :string, null: false
12
29
  # add_reference :products, :category, null: false
30
+ # change_table :users do |t|
31
+ # t.string :name, null: false
32
+ # end
13
33
  #
14
34
  # # good
15
35
  # add_column :users, :name, :string, null: true
16
36
  # add_column :users, :name, :string, null: false, default: ''
37
+ # change_table :users do |t|
38
+ # t.string :name, null: false, default: ''
39
+ # end
17
40
  # add_reference :products, :category
18
- # add_reference :products, :category, null: false, default: 1
41
+ # change_column_null :products, :category_id, false
19
42
  class NotNullColumn < Base
43
+ include DatabaseTypeResolvable
44
+ include MigrationsHelper
45
+
20
46
  MSG = 'Do not add a NOT NULL column without a default value.'
21
47
  RESTRICT_ON_SEND = %i[add_column add_reference].freeze
22
48
 
49
+ VIRTUAL_TYPE_VALUES = [:virtual, 'virtual'].freeze
50
+ TEXT_TYPE_VALUES = [:text, 'text'].freeze
51
+
23
52
  def_node_matcher :add_not_null_column?, <<~PATTERN
24
53
  (send nil? :add_column _ _ $_ (hash $...))
25
54
  PATTERN
@@ -28,6 +57,22 @@ module RuboCop
28
57
  (send nil? :add_reference _ _ (hash $...))
29
58
  PATTERN
30
59
 
60
+ def_node_matcher :change_table?, <<~PATTERN
61
+ (block (send nil? :change_table ...) (args (arg $_)) _)
62
+ PATTERN
63
+
64
+ def_node_matcher :add_not_null_column_in_change_table?, <<~PATTERN
65
+ (send (lvar $_) :column _ $_ (hash $...))
66
+ PATTERN
67
+
68
+ def_node_matcher :add_not_null_column_via_shortcut_in_change_table?, <<~PATTERN
69
+ (send (lvar $_) $_ _ (hash $...))
70
+ PATTERN
71
+
72
+ def_node_matcher :add_not_null_reference_in_change_table?, <<~PATTERN
73
+ (send (lvar $_) :add_reference _ _ (hash $...))
74
+ PATTERN
75
+
31
76
  def_node_matcher :null_false?, <<~PATTERN
32
77
  (pair (sym :null) (false))
33
78
  PATTERN
@@ -41,13 +86,25 @@ module RuboCop
41
86
  check_add_reference(node)
42
87
  end
43
88
 
89
+ def on_block(node)
90
+ check_change_table(node)
91
+ end
92
+ alias on_numblock on_block
93
+
44
94
  private
45
95
 
96
+ def check_column(type, pairs)
97
+ if type.respond_to?(:value)
98
+ return if VIRTUAL_TYPE_VALUES.include?(type.value)
99
+ return if TEXT_TYPE_VALUES.include?(type.value) && database == MYSQL
100
+ end
101
+
102
+ check_pairs(pairs)
103
+ end
104
+
46
105
  def check_add_column(node)
47
106
  add_not_null_column?(node) do |type, pairs|
48
- return if type.value == :virtual || type.value == 'virtual'
49
-
50
- check_pairs(pairs)
107
+ check_column(type, pairs)
51
108
  end
52
109
  end
53
110
 
@@ -57,6 +114,43 @@ module RuboCop
57
114
  end
58
115
  end
59
116
 
117
+ def check_add_column_in_change_table(node, table)
118
+ add_not_null_column_in_change_table?(node) do |receiver, type, pairs|
119
+ next unless receiver == table
120
+
121
+ check_column(type, pairs)
122
+ end
123
+ end
124
+
125
+ def check_add_column_via_shortcut_in_change_table(node, table)
126
+ add_not_null_column_via_shortcut_in_change_table?(node) do |receiver, type, pairs|
127
+ next unless receiver == table
128
+
129
+ check_column(type, pairs)
130
+ end
131
+ end
132
+
133
+ def check_add_reference_in_change_table(node, table)
134
+ add_not_null_reference_in_change_table?(node) do |receiver, pairs|
135
+ next unless receiver == table
136
+
137
+ check_pairs(pairs)
138
+ end
139
+ end
140
+
141
+ def check_change_table(node)
142
+ change_table?(node) do |table|
143
+ next unless node.body
144
+
145
+ children = node.body.begin_type? ? node.body.children : [node.body]
146
+ children.each do |child|
147
+ check_add_column_in_change_table(child, table)
148
+ check_add_column_via_shortcut_in_change_table(child, table)
149
+ check_add_reference_in_change_table(child, table)
150
+ end
151
+ end
152
+ end
153
+
60
154
  def check_pairs(pairs)
61
155
  return if pairs.any? { |pair| default_option?(pair) }
62
156
 
@@ -23,6 +23,7 @@ module RuboCop
23
23
 
24
24
  MSG = "Do not write to stdout. Use Rails's logger if you want to log."
25
25
  RESTRICT_ON_SEND = %i[ap p pp pretty_print print puts binwrite syswrite write write_nonblock].freeze
26
+ ALLOWED_TYPES = %i[send csend block numblock].freeze
26
27
 
27
28
  def_node_matcher :output?, <<~PATTERN
28
29
  (send nil? {:ap :p :pp :pretty_print :print :puts} ...)
@@ -39,8 +40,8 @@ module RuboCop
39
40
  PATTERN
40
41
 
41
42
  def on_send(node)
42
- return if node.parent&.call_type?
43
- return unless output?(node) || io_output?(node)
43
+ return if ALLOWED_TYPES.include?(node.parent&.type)
44
+ return if !output?(node) && !io_output?(node)
44
45
 
45
46
  range = offense_range(node)
46
47
 
@@ -9,6 +9,10 @@ module RuboCop
9
9
  # `pick` avoids. When called on an Active Record relation, `pick` adds a
10
10
  # limit to the query so that only one value is fetched from the database.
11
11
  #
12
+ # Note that when `pick` is added to a relation with an existing limit, it
13
+ # causes a subquery to be added. In most cases this is undesirable, and
14
+ # care should be taken while resolving this violation.
15
+ #
12
16
  # @safety
13
17
  # This cop is unsafe because `pluck` is defined on both `ActiveRecord::Relation` and `Enumerable`,
14
18
  # whereas `pick` is only defined on `ActiveRecord::Relation` in Rails 6.0. This was addressed
@@ -28,13 +32,13 @@ module RuboCop
28
32
  extend AutoCorrector
29
33
  extend TargetRailsVersion
30
34
 
31
- MSG = 'Prefer `pick(%<args>s)` over `pluck(%<args>s).first`.'
35
+ MSG = 'Prefer `pick(%<args>s)` over `%<current>s`.'
32
36
  RESTRICT_ON_SEND = %i[first].freeze
33
37
 
34
38
  minimum_target_rails_version 6.0
35
39
 
36
40
  def_node_matcher :pick_candidate?, <<~PATTERN
37
- (send (send _ :pluck ...) :first)
41
+ (call (call _ :pluck ...) :first)
38
42
  PATTERN
39
43
 
40
44
  def on_send(node)
@@ -44,7 +48,7 @@ module RuboCop
44
48
  node_selector = node.loc.selector
45
49
  range = receiver_selector.join(node_selector)
46
50
 
47
- add_offense(range, message: message(receiver)) do |corrector|
51
+ add_offense(range, message: message(receiver, range)) do |corrector|
48
52
  first_range = receiver.source_range.end.join(node_selector)
49
53
 
50
54
  corrector.remove(first_range)
@@ -52,11 +56,12 @@ module RuboCop
52
56
  end
53
57
  end
54
58
  end
59
+ alias on_csend on_send
55
60
 
56
61
  private
57
62
 
58
- def message(receiver)
59
- format(MSG, args: receiver.arguments.map(&:source).join(', '))
63
+ def message(receiver, current)
64
+ format(MSG, args: receiver.arguments.map(&:source).join(', '), current: current.source)
60
65
  end
61
66
  end
62
67
  end
@@ -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
  #
@@ -38,10 +56,12 @@ module RuboCop
38
56
  minimum_target_rails_version 5.0
39
57
 
40
58
  def_node_matcher :pluck_candidate?, <<~PATTERN
41
- ({block numblock} (send _ {:map :collect}) $_argument (send lvar :[] $_key))
59
+ (any_block (call _ {:map :collect}) $_argument (send lvar :[] $_key))
42
60
  PATTERN
43
61
 
44
62
  def on_block(node)
63
+ return if node.each_ancestor(:any_block).any?
64
+
45
65
  pluck_candidate?(node) do |argument, key|
46
66
  next if key.regexp_type? || !use_one_block_argument?(argument)
47
67
 
@@ -34,7 +34,7 @@ module RuboCop
34
34
  RESTRICT_ON_SEND = %i[pluck].freeze
35
35
 
36
36
  def_node_matcher :pluck_id_call?, <<~PATTERN
37
- (send _ :pluck {(sym :id) (send nil? :primary_key)})
37
+ (call _ :pluck {(sym :id) (send nil? :primary_key)})
38
38
  PATTERN
39
39
 
40
40
  def on_send(node)
@@ -47,6 +47,7 @@ module RuboCop
47
47
  corrector.replace(offense_range(node), 'ids')
48
48
  end
49
49
  end
50
+ alias on_csend on_send
50
51
 
51
52
  private
52
53