rubocop-rails 2.5.1 → 2.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) 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 +22 -0
  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 +84 -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 +40 -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 +1 -1
  20. data/lib/rubocop/cop/rails/http_status.rb +2 -0
  21. data/lib/rubocop/cop/rails/index_by.rb +8 -0
  22. data/lib/rubocop/cop/rails/index_with.rb +8 -0
  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 +79 -0
  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 +15 -11
  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 +106 -0
  54. data/lib/rubocop/cop/rails_cops.rb +21 -0
  55. data/lib/rubocop/rails/schema_loader.rb +10 -10
  56. data/lib/rubocop/rails/schema_loader/schema.rb +4 -4
  57. data/lib/rubocop/rails/version.rb +1 -1
  58. 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