rubocop-rails 2.12.4 → 2.13.0

Sign up to get free protection for your applications and to get access to all the features.
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'