rubocop-rails 2.25.0 → 2.29.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.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.md +52 -2
  4. data/config/default.yml +47 -9
  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_order.rb +1 -5
  9. data/lib/rubocop/cop/rails/active_record_callbacks_order.rb +1 -5
  10. data/lib/rubocop/cop/rails/add_column_index.rb +1 -0
  11. data/lib/rubocop/cop/rails/application_record.rb +4 -0
  12. data/lib/rubocop/cop/rails/bulk_change_table.rb +11 -4
  13. data/lib/rubocop/cop/rails/compact_blank.rb +29 -8
  14. data/lib/rubocop/cop/rails/dangerous_column_names.rb +2 -0
  15. data/lib/rubocop/cop/rails/date.rb +2 -2
  16. data/lib/rubocop/cop/rails/enum_hash.rb +31 -8
  17. data/lib/rubocop/cop/rails/enum_syntax.rb +130 -0
  18. data/lib/rubocop/cop/rails/enum_uniqueness.rb +29 -7
  19. data/lib/rubocop/cop/rails/env_local.rb +26 -3
  20. data/lib/rubocop/cop/rails/file_path.rb +4 -1
  21. data/lib/rubocop/cop/rails/http_positional_arguments.rb +7 -0
  22. data/lib/rubocop/cop/rails/ignored_skip_action_filter_option.rb +1 -1
  23. data/lib/rubocop/cop/rails/index_by.rb +28 -12
  24. data/lib/rubocop/cop/rails/index_with.rb +28 -12
  25. data/lib/rubocop/cop/rails/link_to_blank.rb +2 -2
  26. data/lib/rubocop/cop/rails/match_route.rb +1 -9
  27. data/lib/rubocop/cop/rails/multiple_route_paths.rb +50 -0
  28. data/lib/rubocop/cop/rails/not_null_column.rb +8 -2
  29. data/lib/rubocop/cop/rails/pluck.rb +20 -0
  30. data/lib/rubocop/cop/rails/pluck_in_where.rb +17 -8
  31. data/lib/rubocop/cop/rails/pluralization_grammar.rb +29 -15
  32. data/lib/rubocop/cop/rails/present.rb +0 -2
  33. data/lib/rubocop/cop/rails/redundant_active_record_all_method.rb +1 -30
  34. data/lib/rubocop/cop/rails/redundant_foreign_key.rb +1 -1
  35. data/lib/rubocop/cop/rails/redundant_presence_validation_on_belongs_to.rb +9 -0
  36. data/lib/rubocop/cop/rails/redundant_receiver_in_with_options.rb +1 -1
  37. data/lib/rubocop/cop/rails/reflection_class_name.rb +1 -1
  38. data/lib/rubocop/cop/rails/render_plain_text.rb +6 -3
  39. data/lib/rubocop/cop/rails/request_referer.rb +1 -1
  40. data/lib/rubocop/cop/rails/reversible_migration.rb +3 -1
  41. data/lib/rubocop/cop/rails/root_pathname_methods.rb +15 -11
  42. data/lib/rubocop/cop/rails/save_bang.rb +1 -1
  43. data/lib/rubocop/cop/rails/schema_comment.rb +1 -0
  44. data/lib/rubocop/cop/rails/select_map.rb +3 -2
  45. data/lib/rubocop/cop/rails/skips_model_validations.rb +7 -2
  46. data/lib/rubocop/cop/rails/squished_sql_heredocs.rb +1 -1
  47. data/lib/rubocop/cop/rails/strong_parameters_expect.rb +104 -0
  48. data/lib/rubocop/cop/rails/three_state_boolean_column.rb +2 -1
  49. data/lib/rubocop/cop/rails/time_zone.rb +13 -6
  50. data/lib/rubocop/cop/rails/transaction_exit_statement.rb +5 -0
  51. data/lib/rubocop/cop/rails/validation.rb +4 -1
  52. data/lib/rubocop/cop/rails/where_equals.rb +28 -12
  53. data/lib/rubocop/cop/rails/where_not.rb +11 -6
  54. data/lib/rubocop/cop/rails/where_range.rb +89 -43
  55. data/lib/rubocop/cop/rails_cops.rb +4 -0
  56. data/lib/rubocop/rails/migration_file_skippable.rb +54 -0
  57. data/lib/rubocop/rails/schema_loader/schema.rb +1 -1
  58. data/lib/rubocop/rails/version.rb +1 -1
  59. data/lib/rubocop/rails.rb +0 -1
  60. data/lib/rubocop-rails.rb +3 -0
  61. metadata +11 -9
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # Looks for enums written with keyword arguments syntax.
7
+ #
8
+ # Defining enums with keyword arguments syntax is deprecated and will be removed in Rails 8.0.
9
+ # Positional arguments should be used instead:
10
+ #
11
+ # @example
12
+ # # bad
13
+ # enum status: { active: 0, archived: 1 }, _prefix: true
14
+ #
15
+ # # good
16
+ # enum :status, { active: 0, archived: 1 }, prefix: true
17
+ #
18
+ class EnumSyntax < Base
19
+ extend AutoCorrector
20
+ extend TargetRubyVersion
21
+ extend TargetRailsVersion
22
+
23
+ minimum_target_ruby_version 3.0
24
+ minimum_target_rails_version 7.0
25
+
26
+ MSG = 'Enum defined with keyword arguments in `%<enum>s` enum declaration. Use positional arguments instead.'
27
+ MSG_OPTIONS = 'Enum defined with deprecated options in `%<enum>s` enum declaration. Remove the `_` prefix.'
28
+ RESTRICT_ON_SEND = %i[enum].freeze
29
+
30
+ # From https://github.com/rails/rails/blob/v7.2.1/activerecord/lib/active_record/enum.rb#L231
31
+ OPTION_NAMES = %w[prefix suffix scopes default instance_methods].freeze
32
+ UNDERSCORED_OPTION_NAMES = OPTION_NAMES.map { |option| "_#{option}" }.freeze
33
+
34
+ def_node_matcher :enum?, <<~PATTERN
35
+ (send nil? :enum (hash $...))
36
+ PATTERN
37
+
38
+ def_node_matcher :enum_with_options?, <<~PATTERN
39
+ (send nil? :enum $_ ${array hash} $_)
40
+ PATTERN
41
+
42
+ def on_send(node)
43
+ check_and_correct_keyword_args(node)
44
+ check_enum_options(node)
45
+ end
46
+
47
+ private
48
+
49
+ def check_and_correct_keyword_args(node)
50
+ enum?(node) do |pairs|
51
+ pairs.each do |pair|
52
+ next if option_key?(pair)
53
+
54
+ correct_keyword_args(node, pair.key, pair.value, pairs[1..])
55
+ end
56
+ end
57
+ end
58
+
59
+ def check_enum_options(node)
60
+ enum_with_options?(node) do |key, _, options|
61
+ options.children.each do |option|
62
+ next unless option_key?(option)
63
+
64
+ add_offense(option.key, message: format(MSG_OPTIONS, enum: enum_name_value(key))) do |corrector|
65
+ corrector.replace(option.key, option.key.source.delete_prefix('_'))
66
+ end
67
+ end
68
+ end
69
+ end
70
+
71
+ def correct_keyword_args(node, key, values, options)
72
+ add_offense(values, message: format(MSG, enum: enum_name_value(key))) do |corrector|
73
+ # TODO: Multi-line autocorrect could be implemented in the future.
74
+ next if multiple_enum_definitions?(node)
75
+
76
+ preferred_syntax = "enum #{enum_name(key)}, #{values.source}#{correct_options(options)}"
77
+
78
+ corrector.replace(node, preferred_syntax)
79
+ end
80
+ end
81
+
82
+ def multiple_enum_definitions?(node)
83
+ keys = node.first_argument.keys.map { |key| key.source.delete_prefix('_') }
84
+ filterred_keys = keys.filter { |key| !OPTION_NAMES.include?(key) }
85
+ filterred_keys.size >= 2
86
+ end
87
+
88
+ def enum_name_value(key)
89
+ case key.type
90
+ when :sym, :str
91
+ key.value
92
+ else
93
+ key.source
94
+ end
95
+ end
96
+
97
+ def enum_name(elem)
98
+ case elem.type
99
+ when :str
100
+ elem.value.dump
101
+ when :sym
102
+ elem.value.inspect
103
+ else
104
+ elem.source
105
+ end
106
+ end
107
+
108
+ def option_key?(pair)
109
+ return false unless pair.respond_to?(:key)
110
+
111
+ UNDERSCORED_OPTION_NAMES.include?(pair.key.source)
112
+ end
113
+
114
+ def correct_options(options)
115
+ corrected_options = options.map do |pair|
116
+ name = if pair.key.source[0] == '_'
117
+ pair.key.source[1..]
118
+ else
119
+ pair.key.source
120
+ end
121
+
122
+ "#{name}: #{pair.value.source}"
123
+ end.join(', ')
124
+
125
+ ", #{corrected_options}" unless corrected_options.empty?
126
+ end
127
+ end
128
+ end
129
+ end
130
+ end
@@ -7,6 +7,18 @@ module RuboCop
7
7
  #
8
8
  # @example
9
9
  # # bad
10
+ # enum :status, { active: 0, archived: 0 }
11
+ #
12
+ # # good
13
+ # enum :status, { active: 0, archived: 1 }
14
+ #
15
+ # # bad
16
+ # enum :status, [:active, :archived, :active]
17
+ #
18
+ # # good
19
+ # enum :status, [:active, :archived]
20
+ #
21
+ # # bad
10
22
  # enum status: { active: 0, archived: 0 }
11
23
  #
12
24
  # # good
@@ -24,6 +36,10 @@ module RuboCop
24
36
  RESTRICT_ON_SEND = %i[enum].freeze
25
37
 
26
38
  def_node_matcher :enum?, <<~PATTERN
39
+ (send nil? :enum $_ ${array hash} ...)
40
+ PATTERN
41
+
42
+ def_node_matcher :enum_with_old_syntax?, <<~PATTERN
27
43
  (send nil? :enum (hash $...))
28
44
  PATTERN
29
45
 
@@ -32,15 +48,17 @@ module RuboCop
32
48
  PATTERN
33
49
 
34
50
  def on_send(node)
35
- enum?(node) do |pairs|
51
+ enum?(node) do |key, args|
52
+ consecutive_duplicates(args.values).each do |item|
53
+ add_offense(item, message: message(key, item))
54
+ end
55
+ end
56
+
57
+ enum_with_old_syntax?(node) do |pairs|
36
58
  pairs.each do |pair|
37
59
  enum_values(pair) do |key, args|
38
- items = args.values
39
-
40
- next unless duplicates?(items)
41
-
42
- consecutive_duplicates(items).each do |item|
43
- add_offense(item, message: format(MSG, value: item.source, enum: enum_name(key)))
60
+ consecutive_duplicates(args.values).each do |item|
61
+ add_offense(item, message: message(key, item))
44
62
  end
45
63
  end
46
64
  end
@@ -57,6 +75,10 @@ module RuboCop
57
75
  key.source
58
76
  end
59
77
  end
78
+
79
+ def message(key, item)
80
+ format(MSG, value: item.source, enum: enum_name(key))
81
+ end
60
82
  end
61
83
  end
62
84
  end
@@ -19,20 +19,33 @@ module RuboCop
19
19
  extend TargetRailsVersion
20
20
 
21
21
  MSG = 'Use `Rails.env.local?` instead.'
22
+ MSG_NEGATED = 'Use `!Rails.env.local?` instead.'
22
23
  LOCAL_ENVIRONMENTS = %i[development? test?].to_set.freeze
23
24
 
24
25
  minimum_target_rails_version 7.1
25
26
 
26
- # @!method rails_env_local_candidate?(node)
27
- def_node_matcher :rails_env_local_candidate?, <<~PATTERN
27
+ # @!method rails_env_local_or?(node)
28
+ def_node_matcher :rails_env_local_or?, <<~PATTERN
28
29
  (or
29
30
  (send (send (const {cbase nil? } :Rails) :env) $%LOCAL_ENVIRONMENTS)
30
31
  (send (send (const {cbase nil? } :Rails) :env) $%LOCAL_ENVIRONMENTS)
31
32
  )
32
33
  PATTERN
33
34
 
35
+ # @!method rails_env_local_and?(node)
36
+ def_node_matcher :rails_env_local_and?, <<~PATTERN
37
+ (and
38
+ (send
39
+ (send (send (const {cbase nil? } :Rails) :env) $%LOCAL_ENVIRONMENTS)
40
+ :!)
41
+ (send
42
+ (send (send (const {cbase nil? } :Rails) :env) $%LOCAL_ENVIRONMENTS)
43
+ :!)
44
+ )
45
+ PATTERN
46
+
34
47
  def on_or(node)
35
- rails_env_local_candidate?(node) do |*environments|
48
+ rails_env_local_or?(node) do |*environments|
36
49
  next unless environments.to_set == LOCAL_ENVIRONMENTS
37
50
 
38
51
  add_offense(node) do |corrector|
@@ -40,6 +53,16 @@ module RuboCop
40
53
  end
41
54
  end
42
55
  end
56
+
57
+ def on_and(node)
58
+ rails_env_local_and?(node) do |*environments|
59
+ next unless environments.to_set == LOCAL_ENVIRONMENTS
60
+
61
+ add_offense(node, message: MSG_NEGATED) do |corrector|
62
+ corrector.replace(node, '!Rails.env.local?')
63
+ end
64
+ end
65
+ end
43
66
  end
44
67
  end
45
68
  end
@@ -66,6 +66,8 @@ module RuboCop
66
66
 
67
67
  def on_send(node)
68
68
  check_for_file_join_with_rails_root(node)
69
+ return unless node.receiver
70
+
69
71
  check_for_rails_root_join_with_slash_separated_path(node)
70
72
  check_for_rails_root_join_with_string_arguments(node)
71
73
  end
@@ -76,6 +78,7 @@ module RuboCop
76
78
  rails_root_index = find_rails_root_index(node)
77
79
  slash_node = node.children[rails_root_index + 1]
78
80
  return unless slash_node&.str_type? && slash_node.source.start_with?(File::SEPARATOR)
81
+ return unless node.children[rails_root_index].children.first.send_type?
79
82
 
80
83
  register_offense(node, require_to_s: false) do |corrector|
81
84
  autocorrect_slash_after_rails_root_in_dstr(corrector, node, rails_root_index)
@@ -97,7 +100,7 @@ module RuboCop
97
100
  return unless node.arguments.any? { |e| rails_root_nodes?(e) }
98
101
 
99
102
  register_offense(node, require_to_s: true) do |corrector|
100
- autocorrect_file_join(corrector, node)
103
+ autocorrect_file_join(corrector, node) unless node.first_argument.array_type?
101
104
  end
102
105
  end
103
106
 
@@ -40,6 +40,10 @@ module RuboCop
40
40
  (hash (kwsplat _))
41
41
  PATTERN
42
42
 
43
+ def_node_matcher :forwarded_kwrestarg?, <<~PATTERN
44
+ (hash (forwarded-kwrestarg))
45
+ PATTERN
46
+
43
47
  def_node_matcher :include_rack_test_methods?, <<~PATTERN
44
48
  (send nil? :include
45
49
  (const
@@ -83,7 +87,9 @@ module RuboCop
83
87
  end
84
88
  end
85
89
 
90
+ # rubocop:disable Metrics/CyclomaticComplexity
86
91
  def needs_conversion?(data)
92
+ return false if data.forwarded_args_type? || forwarded_kwrestarg?(data)
87
93
  return true unless data.hash_type?
88
94
  return false if kwsplat_hash?(data)
89
95
 
@@ -91,6 +97,7 @@ module RuboCop
91
97
  special_keyword_arg?(pair.key) || (format_arg?(pair.key) && data.pairs.one?)
92
98
  end
93
99
  end
100
+ # rubocop:enable Metrics/CyclomaticComplexity
94
101
 
95
102
  def special_keyword_arg?(node)
96
103
  node.sym_type? && KEYWORD_ARGS.include?(node.value)
@@ -52,7 +52,7 @@ module RuboCop
52
52
  (send
53
53
  nil?
54
54
  {#{FILTERS.join(' ')}}
55
- _
55
+ ...
56
56
  $_)
57
57
  PATTERN
58
58
 
@@ -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
@@ -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
@@ -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)
@@ -136,6 +140,8 @@ module RuboCop
136
140
 
137
141
  def check_change_table(node)
138
142
  change_table?(node) do |table|
143
+ next unless node.body
144
+
139
145
  children = node.body.begin_type? ? node.body.children : [node.body]
140
146
  children.each do |child|
141
147
  check_add_column_in_change_table(child, table)
@@ -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
 
@@ -7,17 +7,26 @@ module RuboCop
7
7
  # and can be replaced with `select`.
8
8
  #
9
9
  # Since `pluck` is an eager method and hits the database immediately,
10
- # using `select` helps to avoid additional database queries.
10
+ # using `select` helps to avoid additional database queries by running as
11
+ # a subquery.
11
12
  #
12
- # This cop has two different enforcement modes. When the `EnforcedStyle`
13
- # is `conservative` (the default) then only calls to `pluck` on a constant
14
- # (i.e. a model class) in the `where` is used as offenses.
13
+ # This cop has two modes of enforcement. When the `EnforcedStyle` is set
14
+ # to `conservative` (the default), only calls to `pluck` on a constant
15
+ # (e.g. a model class) within `where` are considered offenses.
15
16
  #
16
17
  # @safety
17
- # When the `EnforcedStyle` is `aggressive` then all calls to `pluck` in the
18
- # `where` is used as offenses. This may lead to false positives
19
- # as the cop cannot replace to `select` between calls to `pluck` on an
20
- # `ActiveRecord::Relation` instance vs a call to `pluck` on an `Array` instance.
18
+ # When `EnforcedStyle` is set to `aggressive`, all calls to `pluck`
19
+ # within `where` are considered offenses. This might lead to false
20
+ # positives because the check cannot distinguish between calls to
21
+ # `pluck` on an `ActiveRecord::Relation` instance and calls to `pluck`
22
+ # on an `Array` instance.
23
+ #
24
+ # Additionally, when using a subquery with the SQL `IN` operator,
25
+ # databases like PostgreSQL and MySQL can't optimize complex queries as
26
+ # well. They need to scan all records of the outer table against the
27
+ # subquery result sequentially, rather than using an index. This can
28
+ # cause significant performance issues compared to writing the query
29
+ # differently or using `pluck`.
21
30
  #
22
31
  # @example
23
32
  # # bad