rubocop-rails 2.5.2 → 2.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.md +2 -2
  4. data/config/default.yml +192 -11
  5. data/lib/rubocop/cop/mixin/active_record_helper.rb +9 -3
  6. data/lib/rubocop/cop/mixin/index_method.rb +25 -1
  7. data/lib/rubocop/cop/rails/active_record_callbacks_order.rb +143 -0
  8. data/lib/rubocop/cop/rails/after_commit_override.rb +91 -0
  9. data/lib/rubocop/cop/rails/content_tag.rb +69 -0
  10. data/lib/rubocop/cop/rails/create_table_with_timestamps.rb +1 -3
  11. data/lib/rubocop/cop/rails/default_scope.rb +54 -0
  12. data/lib/rubocop/cop/rails/delegate.rb +2 -4
  13. data/lib/rubocop/cop/rails/dynamic_find_by.rb +41 -15
  14. data/lib/rubocop/cop/rails/exit.rb +2 -2
  15. data/lib/rubocop/cop/rails/file_path.rb +2 -1
  16. data/lib/rubocop/cop/rails/find_by_id.rb +103 -0
  17. data/lib/rubocop/cop/rails/has_many_or_has_one_dependent.rb +1 -5
  18. data/lib/rubocop/cop/rails/helper_instance_variable.rb +2 -0
  19. data/lib/rubocop/cop/rails/http_positional_arguments.rb +7 -1
  20. data/lib/rubocop/cop/rails/http_status.rb +2 -0
  21. data/lib/rubocop/cop/rails/index_by.rb +9 -1
  22. data/lib/rubocop/cop/rails/index_with.rb +9 -1
  23. data/lib/rubocop/cop/rails/inquiry.rb +38 -0
  24. data/lib/rubocop/cop/rails/inverse_of.rb +0 -4
  25. data/lib/rubocop/cop/rails/link_to_blank.rb +3 -3
  26. data/lib/rubocop/cop/rails/mailer_name.rb +80 -0
  27. data/lib/rubocop/cop/rails/match_route.rb +119 -0
  28. data/lib/rubocop/cop/rails/negate_include.rb +39 -0
  29. data/lib/rubocop/cop/rails/order_by_id.rb +53 -0
  30. data/lib/rubocop/cop/rails/pick.rb +55 -0
  31. data/lib/rubocop/cop/rails/pluck.rb +59 -0
  32. data/lib/rubocop/cop/rails/pluck_id.rb +58 -0
  33. data/lib/rubocop/cop/rails/pluck_in_where.rb +70 -0
  34. data/lib/rubocop/cop/rails/presence.rb +2 -6
  35. data/lib/rubocop/cop/rails/rake_environment.rb +17 -0
  36. data/lib/rubocop/cop/rails/redundant_foreign_key.rb +80 -0
  37. data/lib/rubocop/cop/rails/redundant_receiver_in_with_options.rb +0 -3
  38. data/lib/rubocop/cop/rails/reflection_class_name.rb +1 -1
  39. data/lib/rubocop/cop/rails/relative_date_constant.rb +5 -2
  40. data/lib/rubocop/cop/rails/render_inline.rb +40 -0
  41. data/lib/rubocop/cop/rails/render_plain_text.rb +76 -0
  42. data/lib/rubocop/cop/rails/reversible_migration.rb +80 -1
  43. data/lib/rubocop/cop/rails/safe_navigation.rb +1 -1
  44. data/lib/rubocop/cop/rails/save_bang.rb +8 -9
  45. data/lib/rubocop/cop/rails/short_i18n.rb +76 -0
  46. data/lib/rubocop/cop/rails/skips_model_validations.rb +46 -8
  47. data/lib/rubocop/cop/rails/squished_sql_heredocs.rb +83 -0
  48. data/lib/rubocop/cop/rails/time_zone.rb +1 -3
  49. data/lib/rubocop/cop/rails/uniq_before_pluck.rb +14 -12
  50. data/lib/rubocop/cop/rails/unique_validation_without_index.rb +16 -6
  51. data/lib/rubocop/cop/rails/unknown_env.rb +18 -6
  52. data/lib/rubocop/cop/rails/where_exists.rb +131 -0
  53. data/lib/rubocop/cop/rails/where_not.rb +108 -0
  54. data/lib/rubocop/cop/rails_cops.rb +21 -0
  55. data/lib/rubocop/rails/schema_loader/schema.rb +4 -4
  56. data/lib/rubocop/rails/version.rb +1 -1
  57. metadata +31 -10
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # This cop enforces the use of `collection.exclude?(obj)`
7
+ # over `!collection.include?(obj)`.
8
+ #
9
+ # @example
10
+ # # bad
11
+ # !array.include?(2)
12
+ # !hash.include?(:key)
13
+ #
14
+ # # good
15
+ # array.exclude?(2)
16
+ # hash.exclude?(:key)
17
+ #
18
+ class NegateInclude < Cop
19
+ MSG = 'Use `.exclude?` and remove the negation part.'
20
+
21
+ def_node_matcher :negate_include_call?, <<~PATTERN
22
+ (send (send $_ :include? $_) :!)
23
+ PATTERN
24
+
25
+ def on_send(node)
26
+ add_offense(node) if negate_include_call?(node)
27
+ end
28
+
29
+ def autocorrect(node)
30
+ negate_include_call?(node) do |receiver, obj|
31
+ lambda do |corrector|
32
+ corrector.replace(node, "#{receiver.source}.exclude?(#{obj.source})")
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # This cop checks for places where ordering by `id` column is used.
7
+ #
8
+ # Don't use the `id` column for ordering. The sequence of ids is not guaranteed
9
+ # to be in any particular order, despite often (incidentally) being chronological.
10
+ # Use a timestamp column to order chronologically. As a bonus the intent is clearer.
11
+ #
12
+ # NOTE: Make sure the changed order column does not introduce performance
13
+ # bottlenecks and appropriate database indexes are added.
14
+ #
15
+ # @example
16
+ # # bad
17
+ # scope :chronological, -> { order(id: :asc) }
18
+ # scope :chronological, -> { order(primary_key => :asc) }
19
+ #
20
+ # # good
21
+ # scope :chronological, -> { order(created_at: :asc) }
22
+ #
23
+ class OrderById < Base
24
+ include RangeHelp
25
+
26
+ MSG = 'Do not use the `id` column for ordering. '\
27
+ 'Use a timestamp column to order chronologically.'
28
+
29
+ def_node_matcher :order_by_id?, <<~PATTERN
30
+ (send _ :order
31
+ {
32
+ (sym :id)
33
+ (hash (pair (sym :id) _))
34
+ (send _ :primary_key)
35
+ (hash (pair (send _ :primary_key) _))
36
+ })
37
+ PATTERN
38
+
39
+ def on_send(node)
40
+ return unless node.method?(:order)
41
+
42
+ add_offense(offense_range(node)) if order_by_id?(node)
43
+ end
44
+
45
+ private
46
+
47
+ def offense_range(node)
48
+ range_between(node.loc.selector.begin_pos, node.source_range.end_pos)
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # This cop enforces the use of `pick` over `pluck(...).first`.
7
+ #
8
+ # Using `pluck` followed by `first` creates an intermediate array, which
9
+ # `pick` avoids. When called on an Active Record relation, `pick` adds a
10
+ # limit to the query so that only one value is fetched from the database.
11
+ #
12
+ # @example
13
+ # # bad
14
+ # Model.pluck(:a).first
15
+ # [{ a: :b, c: :d }].pluck(:a, :b).first
16
+ #
17
+ # # good
18
+ # Model.pick(:a)
19
+ # [{ a: :b, c: :d }].pick(:a, :b)
20
+ class Pick < Cop
21
+ extend TargetRailsVersion
22
+
23
+ MSG = 'Prefer `pick(%<args>s)` over `pluck(%<args>s).first`.'
24
+
25
+ minimum_target_rails_version 6.0
26
+
27
+ def_node_matcher :pick_candidate?, <<~PATTERN
28
+ (send (send _ :pluck ...) :first)
29
+ PATTERN
30
+
31
+ def on_send(node)
32
+ pick_candidate?(node) do
33
+ range = node.receiver.loc.selector.join(node.loc.selector)
34
+ add_offense(node, location: range)
35
+ end
36
+ end
37
+
38
+ def autocorrect(node)
39
+ first_range = node.receiver.source_range.end.join(node.loc.selector)
40
+
41
+ lambda do |corrector|
42
+ corrector.remove(first_range)
43
+ corrector.replace(node.receiver.loc.selector, 'pick')
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ def message(node)
50
+ format(MSG, args: node.receiver.arguments.map(&:source).join(', '))
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # This cop enforces the use of `pluck` over `map`.
7
+ #
8
+ # `pluck` can be used instead of `map` to extract a single key from each
9
+ # element in an enumerable. When called on an Active Record relation, it
10
+ # results in a more efficient query that only selects the necessary key.
11
+ #
12
+ # @example
13
+ # # bad
14
+ # Post.published.map { |post| post[:title] }
15
+ # [{ a: :b, c: :d }].collect { |el| el[:a] }
16
+ #
17
+ # # good
18
+ # Post.published.pluck(:title)
19
+ # [{ a: :b, c: :d }].pluck(:a)
20
+ class Pluck < Cop
21
+ extend TargetRailsVersion
22
+
23
+ MSG = 'Prefer `pluck(:%<value>s)` over `%<method>s { |%<argument>s| %<element>s[:%<value>s] }`.'
24
+
25
+ minimum_target_rails_version 5.0
26
+
27
+ def_node_matcher :pluck_candidate?, <<~PATTERN
28
+ (block (send _ ${:map :collect}) (args (arg $_argument)) (send (lvar $_element) :[] (sym $_value)))
29
+ PATTERN
30
+
31
+ def on_block(node)
32
+ pluck_candidate?(node) do |method, argument, element, value|
33
+ next unless argument == element
34
+
35
+ add_offense(node, location: offense_range(node), message: message(method, argument, element, value))
36
+ end
37
+ end
38
+
39
+ def autocorrect(node)
40
+ _method, _argument, _element, value = pluck_candidate?(node)
41
+
42
+ lambda do |corrector|
43
+ corrector.replace(offense_range(node), "pluck(:#{value})")
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ def offense_range(node)
50
+ node.send_node.loc.selector.join(node.loc.end)
51
+ end
52
+
53
+ def message(method, argument, element, value)
54
+ format(MSG, method: method, argument: argument, element: element, value: value)
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # This cop enforces the use of `ids` over `pluck(:id)` and `pluck(primary_key)`.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # User.pluck(:id)
11
+ # user.posts.pluck(:id)
12
+ #
13
+ # def self.user_ids
14
+ # pluck(primary_key)
15
+ # end
16
+ #
17
+ # # good
18
+ # User.ids
19
+ # user.posts.ids
20
+ #
21
+ # def self.user_ids
22
+ # ids
23
+ # end
24
+ #
25
+ class PluckId < Cop
26
+ include RangeHelp
27
+ include ActiveRecordHelper
28
+
29
+ MSG = 'Use `ids` instead of `%<bad_method>s`.'
30
+
31
+ def_node_matcher :pluck_id_call?, <<~PATTERN
32
+ (send _ :pluck {(sym :id) (send nil? :primary_key)})
33
+ PATTERN
34
+
35
+ def on_send(node)
36
+ return if !pluck_id_call?(node) || in_where?(node)
37
+
38
+ range = offense_range(node)
39
+ message = format(MSG, bad_method: range.source)
40
+
41
+ add_offense(node, location: range, message: message)
42
+ end
43
+
44
+ def autocorrect(node)
45
+ lambda do |corrector|
46
+ corrector.replace(offense_range(node), 'ids')
47
+ end
48
+ end
49
+
50
+ private
51
+
52
+ def offense_range(node)
53
+ range_between(node.loc.selector.begin_pos, node.loc.expression.end_pos)
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # This cop identifies places where `pluck` is used in `where` query methods
7
+ # and can be replaced with `select`.
8
+ #
9
+ # Since `pluck` is an eager method and hits the database immediately,
10
+ # using `select` helps to avoid additional database queries.
11
+ #
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.
15
+ #
16
+ # When the EnforcedStyle is aggressive then all calls to `pluck` in the
17
+ # `where` is used as offenses. This may lead to false positives
18
+ # as the cop cannot replace to `select` between calls to `pluck` on an
19
+ # `ActiveRecord::Relation` instance vs a call to `pluck` on an `Array` instance.
20
+ #
21
+ # @example
22
+ # # bad
23
+ # Post.where(user_id: User.active.pluck(:id))
24
+ #
25
+ # # good
26
+ # Post.where(user_id: User.active.select(:id))
27
+ # Post.where(user_id: active_users.select(:id))
28
+ #
29
+ # @example EnforcedStyle: conservative (default)
30
+ # # good
31
+ # Post.where(user_id: active_users.pluck(:id))
32
+ #
33
+ # @example EnforcedStyle: aggressive
34
+ # # bad
35
+ # Post.where(user_id: active_users.pluck(:id))
36
+ #
37
+ class PluckInWhere < Cop
38
+ include ActiveRecordHelper
39
+ include ConfigurableEnforcedStyle
40
+
41
+ MSG = 'Use `select` instead of `pluck` within `where` query method.'
42
+
43
+ def on_send(node)
44
+ return unless node.method?(:pluck) && in_where?(node)
45
+ return if style == :conservative && !root_receiver(node)&.const_type?
46
+
47
+ add_offense(node, location: :selector)
48
+ end
49
+
50
+ def autocorrect(node)
51
+ lambda do |corrector|
52
+ corrector.replace(node.loc.selector, 'select')
53
+ end
54
+ end
55
+
56
+ private
57
+
58
+ def root_receiver(node)
59
+ receiver = node.receiver
60
+
61
+ if receiver&.send_type?
62
+ root_receiver(receiver)
63
+ else
64
+ receiver
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -76,15 +76,11 @@ module RuboCop
76
76
  return if ignore_if_node?(node)
77
77
 
78
78
  redundant_receiver_and_other(node) do |receiver, other|
79
- unless ignore_other_node?(other) || receiver.nil?
80
- add_offense(node, message: message(node, receiver, other))
81
- end
79
+ add_offense(node, message: message(node, receiver, other)) unless ignore_other_node?(other) || receiver.nil?
82
80
  end
83
81
 
84
82
  redundant_negative_receiver_and_other(node) do |receiver, other|
85
- unless ignore_other_node?(other) || receiver.nil?
86
- add_offense(node, message: message(node, receiver, other))
87
- end
83
+ add_offense(node, message: message(node, receiver, other)) unless ignore_other_node?(other) || receiver.nil?
88
84
  end
89
85
  end
90
86
 
@@ -41,8 +41,25 @@ module RuboCop
41
41
  end
42
42
  end
43
43
 
44
+ def autocorrect(node)
45
+ lambda do |corrector|
46
+ task_name = node.arguments[0]
47
+ task_dependency = correct_task_dependency(task_name)
48
+
49
+ corrector.replace(task_name.loc.expression, task_dependency)
50
+ end
51
+ end
52
+
44
53
  private
45
54
 
55
+ def correct_task_dependency(task_name)
56
+ if task_name.sym_type?
57
+ "#{task_name.source.delete(':|\'|"')}: :environment"
58
+ else
59
+ "#{task_name.source} => :environment"
60
+ end
61
+ end
62
+
46
63
  def task_name(node)
47
64
  first_arg = node.arguments[0]
48
65
  case first_arg&.type
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # This cop detects cases where the `:foreign_key` option on associations
7
+ # is redundant.
8
+ #
9
+ # @example
10
+ # # bad
11
+ # class Post
12
+ # has_many :comments, foreign_key: 'post_id'
13
+ # end
14
+ #
15
+ # class Comment
16
+ # belongs_to :post, foreign_key: 'post_id'
17
+ # end
18
+ #
19
+ # # good
20
+ # class Post
21
+ # has_many :comments
22
+ # end
23
+ #
24
+ # class Comment
25
+ # belongs_to :author, foreign_key: 'user_id'
26
+ # end
27
+ class RedundantForeignKey < Cop
28
+ include RangeHelp
29
+
30
+ MSG = 'Specifying the default value for `foreign_key` is redundant.'
31
+
32
+ def_node_matcher :association_with_foreign_key, <<~PATTERN
33
+ (send nil? ${:belongs_to :has_one :has_many :has_and_belongs_to_many} ({sym str} $_)
34
+ $(hash <$(pair (sym :foreign_key) ({sym str} $_)) ...>)
35
+ )
36
+ PATTERN
37
+
38
+ def on_send(node)
39
+ association_with_foreign_key(node) do |type, name, options, foreign_key_pair, foreign_key|
40
+ if redundant?(node, type, name, options, foreign_key)
41
+ add_offense(node, location: foreign_key_pair.loc.expression)
42
+ end
43
+ end
44
+ end
45
+
46
+ def autocorrect(node)
47
+ _type, _name, _options, foreign_key_pair, _foreign_key = association_with_foreign_key(node)
48
+ range = range_with_surrounding_space(range: foreign_key_pair.source_range, side: :left)
49
+ range = range_with_surrounding_comma(range, :left)
50
+
51
+ lambda do |corrector|
52
+ corrector.remove(range)
53
+ end
54
+ end
55
+
56
+ private
57
+
58
+ def redundant?(node, association_type, association_name, options, foreign_key)
59
+ foreign_key.to_s == default_foreign_key(node, association_type, association_name, options)
60
+ end
61
+
62
+ def default_foreign_key(node, association_type, association_name, options)
63
+ if association_type == :belongs_to
64
+ "#{association_name}_id"
65
+ elsif (as = find_as_option(options))
66
+ "#{as}_id"
67
+ else
68
+ node.parent_module_name&.foreign_key
69
+ end
70
+ end
71
+
72
+ def find_as_option(options)
73
+ options.pairs.find do |pair|
74
+ pair.key.sym_type? && pair.key.value == :as
75
+ end&.value&.value
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end