rubocop-rails 2.12.4 → 2.13.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 (40) hide show
  1. checksums.yaml +4 -4
  2. data/config/default.yml +34 -6
  3. data/lib/rubocop/cop/mixin/active_record_migrations_helper.rb +34 -0
  4. data/lib/rubocop/cop/rails/active_record_aliases.rb +4 -0
  5. data/lib/rubocop/cop/rails/application_controller.rb +5 -1
  6. data/lib/rubocop/cop/rails/application_job.rb +5 -1
  7. data/lib/rubocop/cop/rails/application_mailer.rb +5 -1
  8. data/lib/rubocop/cop/rails/application_record.rb +6 -1
  9. data/lib/rubocop/cop/rails/arel_star.rb +6 -0
  10. data/lib/rubocop/cop/rails/blank.rb +5 -4
  11. data/lib/rubocop/cop/rails/compact_blank.rb +98 -0
  12. data/lib/rubocop/cop/rails/content_tag.rb +2 -2
  13. data/lib/rubocop/cop/rails/create_table_with_timestamps.rb +2 -7
  14. data/lib/rubocop/cop/rails/duration_arithmetic.rb +97 -0
  15. data/lib/rubocop/cop/rails/dynamic_find_by.rb +4 -0
  16. data/lib/rubocop/cop/rails/http_positional_arguments.rb +1 -1
  17. data/lib/rubocop/cop/rails/lexically_scoped_action_filter.rb +8 -7
  18. data/lib/rubocop/cop/rails/mailer_name.rb +4 -0
  19. data/lib/rubocop/cop/rails/negate_include.rb +3 -2
  20. data/lib/rubocop/cop/rails/output.rb +4 -0
  21. data/lib/rubocop/cop/rails/pick.rb +7 -0
  22. data/lib/rubocop/cop/rails/pluck_id.rb +3 -0
  23. data/lib/rubocop/cop/rails/pluck_in_where.rb +7 -6
  24. data/lib/rubocop/cop/rails/rake_environment.rb +5 -0
  25. data/lib/rubocop/cop/rails/redundant_presence_validation_on_belongs_to.rb +192 -0
  26. data/lib/rubocop/cop/rails/reflection_class_name.rb +4 -2
  27. data/lib/rubocop/cop/rails/relative_date_constant.rb +3 -0
  28. data/lib/rubocop/cop/rails/reversible_migration.rb +1 -1
  29. data/lib/rubocop/cop/rails/root_join_chain.rb +72 -0
  30. data/lib/rubocop/cop/rails/safe_navigation_with_blank.rb +12 -3
  31. data/lib/rubocop/cop/rails/save_bang.rb +19 -0
  32. data/lib/rubocop/cop/rails/schema_comment.rb +104 -0
  33. data/lib/rubocop/cop/rails/squished_sql_heredocs.rb +4 -2
  34. data/lib/rubocop/cop/rails/time_zone.rb +3 -0
  35. data/lib/rubocop/cop/rails/uniq_before_pluck.rb +29 -35
  36. data/lib/rubocop/cop/rails/where_equals.rb +4 -0
  37. data/lib/rubocop/cop/rails/where_exists.rb +9 -8
  38. data/lib/rubocop/cop/rails_cops.rb +6 -0
  39. data/lib/rubocop/rails/version.rb +1 -1
  40. metadata +11 -4
@@ -0,0 +1,192 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # Since Rails 5.0 the default for `belongs_to` is `optional: false`
7
+ # unless `config.active_record.belongs_to_required_by_default` is
8
+ # explicitly set to `false`. The presence validator is added
9
+ # automatically, and explicit presence validation is redundant.
10
+ #
11
+ # @example
12
+ # # bad
13
+ # belongs_to :user
14
+ # validates :user, presence: true
15
+ #
16
+ # # bad
17
+ # belongs_to :user
18
+ # validates :user_id, presence: true
19
+ #
20
+ # # bad
21
+ # belongs_to :author, foreign_key: :user_id
22
+ # validates :user_id, presence: true
23
+ #
24
+ # # good
25
+ # belongs_to :user
26
+ #
27
+ # # good
28
+ # belongs_to :author, foreign_key: :user_id
29
+ #
30
+ class RedundantPresenceValidationOnBelongsTo < Base
31
+ include RangeHelp
32
+ extend AutoCorrector
33
+ extend TargetRailsVersion
34
+
35
+ MSG = 'Remove explicit presence validation for `%<association>s`.'
36
+ RESTRICT_ON_SEND = %i[validates].freeze
37
+
38
+ minimum_target_rails_version 5.0
39
+
40
+ # @!method presence_validation?(node)
41
+ # Match a `validates` statement with a presence check
42
+ #
43
+ # @example source that matches - by association
44
+ # validates :user, presence: true
45
+ #
46
+ # @example source that matches - with presence options
47
+ # validates :user, presence: { message: 'duplicate' }
48
+ #
49
+ # @example source that matches - by a foreign key
50
+ # validates :user_id, presence: true
51
+ def_node_matcher :presence_validation?, <<~PATTERN
52
+ $(
53
+ send nil? :validates
54
+ (sym $_)
55
+ ...
56
+ $(hash <$(pair (sym :presence) {true hash}) ...>)
57
+ )
58
+ PATTERN
59
+
60
+ # @!method optional_option?(node)
61
+ # Match a `belongs_to` association with an optional option in a hash
62
+ def_node_matcher :optional?, <<~PATTERN
63
+ (send nil? :belongs_to _ ... #optional_option?)
64
+ PATTERN
65
+
66
+ # @!method optional_option?(node)
67
+ # Match an optional option in a hash
68
+ def_node_matcher :optional_option?, <<~PATTERN
69
+ {
70
+ (hash <(pair (sym :optional) true) ...>) # optional: true
71
+ (hash <(pair (sym :required) false) ...>) # required: false
72
+ }
73
+ PATTERN
74
+
75
+ # @!method any_belongs_to?(node, association:)
76
+ # Match a class with `belongs_to` with no regard to `foreign_key` option
77
+ #
78
+ # @example source that matches
79
+ # belongs_to :user
80
+ #
81
+ # @example source that matches - regardless of `foreign_key`
82
+ # belongs_to :author, foreign_key: :user_id
83
+ #
84
+ # @param node [RuboCop::AST::Node]
85
+ # @param association [Symbol]
86
+ # @return [Array<RuboCop::AST::Node>, nil] matching node
87
+ def_node_matcher :any_belongs_to?, <<~PATTERN
88
+ (begin
89
+ <
90
+ $(send nil? :belongs_to (sym %association) ...)
91
+ ...
92
+ >
93
+ )
94
+ PATTERN
95
+
96
+ # @!method belongs_to?(node, key:, fk:)
97
+ # Match a class with a matching association, either by name or an explicit
98
+ # `foreign_key` option
99
+ #
100
+ # @example source that matches - fk matches `foreign_key` option
101
+ # belongs_to :author, foreign_key: :user_id
102
+ #
103
+ # @example source that matches - key matches association name
104
+ # belongs_to :user
105
+ #
106
+ # @example source that does not match - explicit `foreign_key` does not match
107
+ # belongs_to :user, foreign_key: :account_id
108
+ #
109
+ # @param node [RuboCop::AST::Node]
110
+ # @param key [Symbol] e.g. `:user`
111
+ # @param fk [Symbol] e.g. `:user_id`
112
+ # @return [Array<RuboCop::AST::Node>] matching nodes
113
+ def_node_matcher :belongs_to?, <<~PATTERN
114
+ (begin
115
+ <
116
+ ${
117
+ #belongs_to_without_fk?(%key) # belongs_to :user
118
+ #belongs_to_with_a_matching_fk?(%fk) # belongs_to :author, foreign_key: :user_id
119
+ }
120
+ ...
121
+ >
122
+ )
123
+ PATTERN
124
+
125
+ # @!method belongs_to_without_fk?(node, fk)
126
+ # Match a matching `belongs_to` association, without an explicit `foreign_key` option
127
+ #
128
+ # @param node [RuboCop::AST::Node]
129
+ # @param key [Symbol] e.g. `:user`
130
+ # @return [Array<RuboCop::AST::Node>] matching nodes
131
+ def_node_matcher :belongs_to_without_fk?, <<~PATTERN
132
+ {
133
+ (send nil? :belongs_to (sym %1)) # belongs_to :user
134
+ (send nil? :belongs_to (sym %1) !hash) # belongs_to :user, -> { not_deleted }
135
+ (send nil? :belongs_to (sym %1) !(hash <(pair (sym :foreign_key) _) ...>))
136
+ }
137
+ PATTERN
138
+
139
+ # @!method belongs_to_with_a_matching_fk?(node, fk)
140
+ # Match a matching `belongs_to` association with a matching explicit `foreign_key` option
141
+ #
142
+ # @example source that matches
143
+ # belongs_to :author, foreign_key: :user_id
144
+ #
145
+ # @param node [RuboCop::AST::Node]
146
+ # @param fk [Symbol] e.g. `:user_id`
147
+ # @return [Array<RuboCop::AST::Node>] matching nodes
148
+ def_node_matcher :belongs_to_with_a_matching_fk?, <<~PATTERN
149
+ (send nil? :belongs_to ... (hash <(pair (sym :foreign_key) (sym %1)) ...>))
150
+ PATTERN
151
+
152
+ def on_send(node)
153
+ validation, key, options, presence = presence_validation?(node)
154
+ return unless validation
155
+
156
+ belongs_to = belongs_to_for(node.parent, key)
157
+ return unless belongs_to
158
+ return if optional?(belongs_to)
159
+
160
+ message = format(MSG, association: key.to_s)
161
+
162
+ add_offense(presence, message: message) do |corrector|
163
+ remove_presence_validation(corrector, node, options, presence)
164
+ end
165
+ end
166
+
167
+ private
168
+
169
+ def belongs_to_for(model_class_node, key)
170
+ if key.to_s.end_with?('_id')
171
+ normalized_key = key.to_s.delete_suffix('_id').to_sym
172
+ belongs_to?(model_class_node, key: normalized_key, fk: key)
173
+ else
174
+ any_belongs_to?(model_class_node, association: key)
175
+ end
176
+ end
177
+
178
+ def remove_presence_validation(corrector, node, options, presence)
179
+ if options.children.one?
180
+ corrector.remove(range_by_whole_lines(node.source_range, include_final_newline: true))
181
+ else
182
+ range = range_with_surrounding_comma(
183
+ range_with_surrounding_space(range: presence.source_range, side: :left),
184
+ :left
185
+ )
186
+ corrector.remove(range)
187
+ end
188
+ end
189
+ end
190
+ end
191
+ end
192
+ end
@@ -5,8 +5,10 @@ module RuboCop
5
5
  module Rails
6
6
  # This cop checks if the value of the option `class_name`, in
7
7
  # the definition of a reflection is a string.
8
- # It is marked as unsafe because it cannot be determined whether
9
- # constant or method return value specified to `class_name` is a string.
8
+ #
9
+ # @safety
10
+ # This cop is unsafe because it cannot be determined whether
11
+ # constant or method return value specified to `class_name` is a string.
10
12
  #
11
13
  # @example
12
14
  # # bad
@@ -6,6 +6,9 @@ module RuboCop
6
6
  # This cop checks whether constant value isn't relative date.
7
7
  # Because the relative date will be evaluated only once.
8
8
  #
9
+ # @safety
10
+ # This cop's autocorrection is unsafe because its dependence on the constant is not corrected.
11
+ #
9
12
  # @example
10
13
  # # bad
11
14
  # class SomeClass
@@ -179,7 +179,7 @@ module RuboCop
179
179
  MSG = '%<action>s is not reversible.'
180
180
 
181
181
  def_node_matcher :irreversible_schema_statement_call, <<~PATTERN
182
- (send nil? ${:execute :remove_belongs_to} ...)
182
+ (send nil? ${:change_column :execute :remove_belongs_to :remove_reference} ...)
183
183
  PATTERN
184
184
 
185
185
  def_node_matcher :drop_table_call, <<~PATTERN
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # Use a single `#join` instead of chaining on `Rails.root` or `Rails.public_path`.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # Rails.root.join('db').join('schema.rb')
11
+ # Rails.root.join('db').join(migrate).join('migration.rb')
12
+ # Rails.public_path.join('path').join('file.pdf')
13
+ # Rails.public_path.join('path').join(to).join('file.pdf')
14
+ #
15
+ # # good
16
+ # Rails.root.join('db', 'schema.rb')
17
+ # Rails.root.join('db', migrate, 'migration.rb')
18
+ # Rails.public_path.join('path', 'file.pdf')
19
+ # Rails.public_path.join('path', to, 'file.pdf')
20
+ #
21
+ class RootJoinChain < Base
22
+ extend AutoCorrector
23
+ include RangeHelp
24
+
25
+ MSG = 'Use `%<root>s.join(...)` instead of chaining `#join` calls.'
26
+
27
+ RESTRICT_ON_SEND = %i[join].to_set.freeze
28
+
29
+ # @!method rails_root?(node)
30
+ def_node_matcher :rails_root?, <<~PATTERN
31
+ (send (const {nil? cbase} :Rails) {:root :public_path})
32
+ PATTERN
33
+
34
+ # @!method join?(node)
35
+ def_node_matcher :join?, <<~PATTERN
36
+ (send _ :join $...)
37
+ PATTERN
38
+
39
+ def on_send(node)
40
+ evidence(node) do |rails_node, args|
41
+ add_offense(node, message: format(MSG, root: rails_node.source)) do |corrector|
42
+ range = range_between(rails_node.loc.selector.end_pos, node.loc.expression.end_pos)
43
+ replacement = ".join(#{args.map(&:source).join(', ')})"
44
+
45
+ corrector.replace(range, replacement)
46
+ end
47
+ end
48
+ end
49
+
50
+ private
51
+
52
+ def evidence(node)
53
+ # Are we at the *end* of the join chain?
54
+ return if join?(node.parent)
55
+ # Is there only one join?
56
+ return if rails_root?(node.receiver)
57
+
58
+ all_args = []
59
+
60
+ while (args = join?(node))
61
+ all_args = args + all_args
62
+ node = node.receiver
63
+ end
64
+
65
+ rails_root?(node) do
66
+ yield(node, all_args)
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -6,9 +6,18 @@ module RuboCop
6
6
  # This cop checks to make sure safe navigation isn't used with `blank?` in
7
7
  # a conditional.
8
8
  #
9
- # While the safe navigation operator is generally a good idea, when
10
- # checking `foo&.blank?` in a conditional, `foo` being `nil` will actually
11
- # do the opposite of what the author intends.
9
+ # @safety
10
+ # While the safe navigation operator is generally a good idea, when
11
+ # checking `foo&.blank?` in a conditional, `foo` being `nil` will actually
12
+ # do the opposite of what the author intends.
13
+ #
14
+ # For example:
15
+ #
16
+ # [source,ruby]
17
+ # ----
18
+ # foo&.blank? #=> nil
19
+ # foo.blank? #=> true
20
+ # ----
12
21
  #
13
22
  # @example
14
23
  # # bad
@@ -25,6 +25,25 @@ module RuboCop
25
25
  # You can permit receivers that are giving false positives with
26
26
  # `AllowedReceivers: []`
27
27
  #
28
+ # @safety
29
+ # This cop's autocorrection is unsafe because a custom `update` method call would be changed to `update!`,
30
+ # but the method name in the definition would be unchanged.
31
+ #
32
+ # [source,ruby]
33
+ # ----
34
+ # # Original code
35
+ # def update_attributes
36
+ # end
37
+ #
38
+ # update_attributes
39
+ #
40
+ # # After running rubocop --safe-auto-correct
41
+ # def update_attributes
42
+ # end
43
+ #
44
+ # update
45
+ # ----
46
+ #
28
47
  # @example
29
48
  #
30
49
  # # bad
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # This cop enforces the use of the `comment` option when adding a new table or column
7
+ # to the database during a migration.
8
+ #
9
+ # @example
10
+ # # bad (no comment for a new column or table)
11
+ # add_column :table, :column, :integer
12
+ #
13
+ # create_table :table do |t|
14
+ # t.type :column
15
+ # end
16
+ #
17
+ # # good
18
+ # add_column :table, :column, :integer, comment: 'Number of offenses'
19
+ #
20
+ # create_table :table, comment: 'Table of offenses data' do |t|
21
+ # t.type :column, comment: 'Number of offenses'
22
+ # end
23
+ #
24
+ class SchemaComment < Base
25
+ include ActiveRecordMigrationsHelper
26
+
27
+ COLUMN_MSG = 'New database column without `comment`.'
28
+ TABLE_MSG = 'New database table without `comment`.'
29
+ RESTRICT_ON_SEND = %i[add_column create_table].freeze
30
+ CREATE_TABLE_COLUMN_METHODS = Set[
31
+ *(
32
+ RAILS_ABSTRACT_SCHEMA_DEFINITIONS |
33
+ RAILS_ABSTRACT_SCHEMA_DEFINITIONS_HELPERS |
34
+ POSTGRES_SCHEMA_DEFINITIONS |
35
+ MYSQL_SCHEMA_DEFINITIONS
36
+ )
37
+ ].freeze
38
+
39
+ # @!method comment_present?(node)
40
+ def_node_matcher :comment_present?, <<~PATTERN
41
+ (hash <(pair {(sym :comment) (str "comment")} (_ [present?])) ...>)
42
+ PATTERN
43
+
44
+ # @!method add_column?(node)
45
+ def_node_matcher :add_column?, <<~PATTERN
46
+ (send nil? :add_column _table _column _type _?)
47
+ PATTERN
48
+
49
+ # @!method add_column_with_comment?(node)
50
+ def_node_matcher :add_column_with_comment?, <<~PATTERN
51
+ (send nil? :add_column _table _column _type #comment_present?)
52
+ PATTERN
53
+
54
+ # @!method create_table?(node)
55
+ def_node_matcher :create_table?, <<~PATTERN
56
+ (send nil? :create_table _table _?)
57
+ PATTERN
58
+
59
+ # @!method create_table?(node)
60
+ def_node_matcher :create_table_with_comment?, <<~PATTERN
61
+ (send nil? :create_table _table #comment_present? ...)
62
+ PATTERN
63
+
64
+ # @!method t_column?(node)
65
+ def_node_matcher :t_column?, <<~PATTERN
66
+ (send _var CREATE_TABLE_COLUMN_METHODS ...)
67
+ PATTERN
68
+
69
+ # @!method t_column_with_comment?(node)
70
+ def_node_matcher :t_column_with_comment?, <<~PATTERN
71
+ (send _var CREATE_TABLE_COLUMN_METHODS _column _type? #comment_present?)
72
+ PATTERN
73
+
74
+ def on_send(node)
75
+ if add_column_without_comment?(node)
76
+ add_offense(node, message: COLUMN_MSG)
77
+ elsif create_table?(node)
78
+ if create_table_without_comment?(node)
79
+ add_offense(node, message: TABLE_MSG)
80
+ elsif create_table_column_call_without_comment?(node)
81
+ add_offense(node.parent.body, message: COLUMN_MSG)
82
+ end
83
+ end
84
+ end
85
+
86
+ private
87
+
88
+ def add_column_without_comment?(node)
89
+ add_column?(node) && !add_column_with_comment?(node)
90
+ end
91
+
92
+ def create_table_without_comment?(node)
93
+ create_table?(node) && !create_table_with_comment?(node)
94
+ end
95
+
96
+ def create_table_column_call_without_comment?(node)
97
+ create_table_with_block?(node.parent) &&
98
+ t_column?(node.parent.body) &&
99
+ !t_column_with_comment?(node.parent.body)
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
@@ -5,8 +5,10 @@ module RuboCop
5
5
  module Rails
6
6
  #
7
7
  # Checks SQL heredocs to use `.squish`.
8
- # Some SQL syntax (e.g. PostgreSQL comments and functions) requires newlines
9
- # to be preserved in order to work, thus auto-correction for this cop is not safe.
8
+ #
9
+ # @safety
10
+ # Some SQL syntax (e.g. PostgreSQL comments and functions) requires newlines
11
+ # to be preserved in order to work, thus auto-correction for this cop is not safe.
10
12
  #
11
13
  # @example
12
14
  # # bad
@@ -14,6 +14,9 @@ module RuboCop
14
14
  # When EnforcedStyle is 'flexible' then it's also allowed
15
15
  # to use `Time#in_time_zone`.
16
16
  #
17
+ # @safety
18
+ # This cop's autocorrection is unsafe because it may change handling time.
19
+ #
17
20
  # @example
18
21
  # # bad
19
22
  # Time.now
@@ -3,47 +3,46 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # Prefer the use of distinct, before pluck instead of after.
6
+ # Prefer using `distinct` before `pluck` instead of `uniq` after `pluck`.
7
7
  #
8
- # The use of distinct before pluck is preferred because it executes within
8
+ # The use of distinct before pluck is preferred because it executes by
9
9
  # the database.
10
10
  #
11
11
  # This cop has two different enforcement modes. When the EnforcedStyle
12
- # is conservative (the default) then only calls to pluck on a constant
13
- # (i.e. a model class) before distinct are added as offenses.
12
+ # is `conservative` (the default), then only calls to `pluck` on a constant
13
+ # (i.e. a model class) before `uniq` are added as offenses.
14
14
  #
15
- # When the EnforcedStyle is aggressive then all calls to pluck before
15
+ # When the EnforcedStyle is `aggressive` then all calls to `pluck` before
16
16
  # distinct are added as offenses. This may lead to false positives
17
- # as the cop cannot distinguish between calls to pluck on an
17
+ # as the cop cannot distinguish between calls to `pluck` on an
18
18
  # ActiveRecord::Relation vs a call to pluck on an
19
19
  # ActiveRecord::Associations::CollectionProxy.
20
20
  #
21
- # This cop is unsafe because the behavior may change depending on the
22
- # database collation.
23
- # Autocorrect is disabled by default for this cop since it may generate
24
- # false positives.
21
+ # @safety
22
+ # This cop is unsafe for autocorrection because the behavior may change
23
+ # depending on the database collation.
25
24
  #
26
25
  # @example EnforcedStyle: conservative (default)
27
- # # bad
28
- # Model.pluck(:id).uniq
26
+ # # bad - redundantly fetches duplicate values
27
+ # Album.pluck(:band_name).uniq
29
28
  #
30
29
  # # good
31
- # Model.distinct.pluck(:id)
30
+ # Album.distinct.pluck(:band_name)
32
31
  #
33
32
  # @example EnforcedStyle: aggressive
34
- # # bad
35
- # # this will return a Relation that pluck is called on
36
- # Model.where(cond: true).pluck(:id).uniq
33
+ # # bad - redundantly fetches duplicate values
34
+ # Album.pluck(:band_name).uniq
37
35
  #
38
- # # bad
39
- # # an association on an instance will return a CollectionProxy
40
- # instance.assoc.pluck(:id).uniq
36
+ # # bad - redundantly fetches duplicate values
37
+ # Album.where(year: 1985).pluck(:band_name).uniq
41
38
  #
42
- # # bad
43
- # Model.pluck(:id).uniq
39
+ # # bad - redundantly fetches duplicate values
40
+ # customer.favourites.pluck(:color).uniq
44
41
  #
45
42
  # # good
46
- # Model.distinct.pluck(:id)
43
+ # Album.distinct.pluck(:band_name)
44
+ # Album.distinct.where(year: 1985).pluck(:band_name)
45
+ # customer.favourites.distinct.pluck(:color)
47
46
  #
48
47
  class UniqBeforePluck < Base
49
48
  include ConfigurableEnforcedStyle
@@ -51,10 +50,9 @@ module RuboCop
51
50
  extend AutoCorrector
52
51
 
53
52
  MSG = 'Use `distinct` before `pluck`.'
54
- RESTRICT_ON_SEND = %i[uniq distinct pluck].freeze
53
+ RESTRICT_ON_SEND = %i[uniq].freeze
55
54
  NEWLINE = "\n"
56
- PATTERN = '[!^block (send (send %<type>s :pluck ...) ' \
57
- '${:uniq :distinct} ...)]'
55
+ PATTERN = '[!^block (send (send %<type>s :pluck ...) :uniq ...)]'
58
56
 
59
57
  def_node_matcher :conservative_node_match,
60
58
  format(PATTERN, type: 'const')
@@ -63,13 +61,13 @@ module RuboCop
63
61
  format(PATTERN, type: '_')
64
62
 
65
63
  def on_send(node)
66
- method = if style == :conservative
67
- conservative_node_match(node)
68
- else
69
- aggressive_node_match(node)
70
- end
64
+ uniq = if style == :conservative
65
+ conservative_node_match(node)
66
+ else
67
+ aggressive_node_match(node)
68
+ end
71
69
 
72
- return unless method
70
+ return unless uniq
73
71
 
74
72
  add_offense(node.loc.selector) do |corrector|
75
73
  method = node.method_name
@@ -81,10 +79,6 @@ module RuboCop
81
79
 
82
80
  private
83
81
 
84
- def style_parameter_name
85
- 'EnforcedStyle'
86
- end
87
-
88
82
  def dot_method_with_whitespace(method, node)
89
83
  range_between(dot_method_begin_pos(method, node),
90
84
  node.loc.selector.end_pos)
@@ -6,6 +6,10 @@ module RuboCop
6
6
  # This cop identifies places where manually constructed SQL
7
7
  # in `where` can be replaced with `where(attribute: value)`.
8
8
  #
9
+ # @safety
10
+ # This cop's autocorrection is unsafe because is may change SQL.
11
+ # See: https://github.com/rubocop/rubocop-rails/issues/403
12
+ #
9
13
  # @example
10
14
  # # bad
11
15
  # User.where('name = ?', 'Gabe')
@@ -11,16 +11,17 @@ module RuboCop
11
11
  # When EnforcedStyle is 'where' then the cop enforces
12
12
  # `where(...).exists?` over `exists?(...)`.
13
13
  #
14
- # This cop is unsafe for auto-correction because the behavior may change on the following case:
14
+ # @safety
15
+ # This cop is unsafe for auto-correction because the behavior may change on the following case:
15
16
  #
16
- # [source,ruby]
17
- # ----
18
- # Author.includes(:articles).where(articles: {id: id}).exists?
19
- # #=> Perform `eager_load` behavior (`LEFT JOIN` query) and get result.
17
+ # [source,ruby]
18
+ # ----
19
+ # Author.includes(:articles).where(articles: {id: id}).exists?
20
+ # #=> Perform `eager_load` behavior (`LEFT JOIN` query) and get result.
20
21
  #
21
- # Author.includes(:articles).exists?(articles: {id: id})
22
- # #=> Perform `preload` behavior and `ActiveRecord::StatementInvalid` error occurs.
23
- # ----
22
+ # Author.includes(:articles).exists?(articles: {id: id})
23
+ # #=> Perform `preload` behavior and `ActiveRecord::StatementInvalid` error occurs.
24
+ # ----
24
25
  #
25
26
  # @example EnforcedStyle: exists (default)
26
27
  # # bad
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'mixin/active_record_helper'
4
+ require_relative 'mixin/active_record_migrations_helper'
4
5
  require_relative 'mixin/enforce_superclass'
5
6
  require_relative 'mixin/index_method'
6
7
  require_relative 'mixin/target_rails_version'
@@ -22,12 +23,14 @@ require_relative 'rails/attribute_default_block_value'
22
23
  require_relative 'rails/belongs_to'
23
24
  require_relative 'rails/blank'
24
25
  require_relative 'rails/bulk_change_table'
26
+ require_relative 'rails/compact_blank'
25
27
  require_relative 'rails/content_tag'
26
28
  require_relative 'rails/create_table_with_timestamps'
27
29
  require_relative 'rails/date'
28
30
  require_relative 'rails/default_scope'
29
31
  require_relative 'rails/delegate'
30
32
  require_relative 'rails/delegate_allow_blank'
33
+ require_relative 'rails/duration_arithmetic'
31
34
  require_relative 'rails/dynamic_find_by'
32
35
  require_relative 'rails/eager_evaluation_log_message'
33
36
  require_relative 'rails/enum_hash'
@@ -71,6 +74,7 @@ require_relative 'rails/rake_environment'
71
74
  require_relative 'rails/read_write_attribute'
72
75
  require_relative 'rails/redundant_allow_nil'
73
76
  require_relative 'rails/redundant_foreign_key'
77
+ require_relative 'rails/redundant_presence_validation_on_belongs_to'
74
78
  require_relative 'rails/redundant_receiver_in_with_options'
75
79
  require_relative 'rails/redundant_travel_back'
76
80
  require_relative 'rails/reflection_class_name'
@@ -82,9 +86,11 @@ require_relative 'rails/request_referer'
82
86
  require_relative 'rails/require_dependency'
83
87
  require_relative 'rails/reversible_migration'
84
88
  require_relative 'rails/reversible_migration_method_definition'
89
+ require_relative 'rails/root_join_chain'
85
90
  require_relative 'rails/safe_navigation'
86
91
  require_relative 'rails/safe_navigation_with_blank'
87
92
  require_relative 'rails/save_bang'
93
+ require_relative 'rails/schema_comment'
88
94
  require_relative 'rails/scope_args'
89
95
  require_relative 'rails/short_i18n'
90
96
  require_relative 'rails/skips_model_validations'