rubocop-rails 2.6.0 → 2.9.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 (95) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +16 -0
  3. data/config/default.yml +189 -6
  4. data/lib/rubocop/cop/mixin/active_record_helper.rb +12 -3
  5. data/lib/rubocop/cop/mixin/enforce_superclass.rb +40 -0
  6. data/lib/rubocop/cop/mixin/index_method.rb +25 -11
  7. data/lib/rubocop/cop/rails/action_filter.rb +10 -14
  8. data/lib/rubocop/cop/rails/active_record_aliases.rb +13 -17
  9. data/lib/rubocop/cop/rails/active_record_callbacks_order.rb +148 -0
  10. data/lib/rubocop/cop/rails/active_record_override.rb +1 -1
  11. data/lib/rubocop/cop/rails/active_support_aliases.rb +12 -21
  12. data/lib/rubocop/cop/rails/after_commit_override.rb +91 -0
  13. data/lib/rubocop/cop/rails/application_controller.rb +3 -7
  14. data/lib/rubocop/cop/rails/application_job.rb +2 -1
  15. data/lib/rubocop/cop/rails/application_mailer.rb +2 -7
  16. data/lib/rubocop/cop/rails/application_record.rb +2 -7
  17. data/lib/rubocop/cop/rails/arel_star.rb +41 -0
  18. data/lib/rubocop/cop/rails/assert_not.rb +8 -10
  19. data/lib/rubocop/cop/rails/attribute_default_block_value.rb +90 -0
  20. data/lib/rubocop/cop/rails/belongs_to.rb +9 -18
  21. data/lib/rubocop/cop/rails/blank.rb +27 -27
  22. data/lib/rubocop/cop/rails/bulk_change_table.rb +1 -1
  23. data/lib/rubocop/cop/rails/content_tag.rb +20 -33
  24. data/lib/rubocop/cop/rails/create_table_with_timestamps.rb +2 -1
  25. data/lib/rubocop/cop/rails/date.rb +10 -11
  26. data/lib/rubocop/cop/rails/default_scope.rb +61 -0
  27. data/lib/rubocop/cop/rails/delegate.rb +10 -10
  28. data/lib/rubocop/cop/rails/delegate_allow_blank.rb +7 -8
  29. data/lib/rubocop/cop/rails/dynamic_find_by.rb +13 -11
  30. data/lib/rubocop/cop/rails/enum_hash.rb +11 -10
  31. data/lib/rubocop/cop/rails/enum_uniqueness.rb +2 -1
  32. data/lib/rubocop/cop/rails/environment_comparison.rb +18 -14
  33. data/lib/rubocop/cop/rails/exit.rb +4 -10
  34. data/lib/rubocop/cop/rails/file_path.rb +5 -4
  35. data/lib/rubocop/cop/rails/find_by.rb +13 -13
  36. data/lib/rubocop/cop/rails/find_by_id.rb +94 -0
  37. data/lib/rubocop/cop/rails/find_each.rb +16 -14
  38. data/lib/rubocop/cop/rails/has_and_belongs_to_many.rb +3 -2
  39. data/lib/rubocop/cop/rails/has_many_or_has_one_dependent.rb +4 -7
  40. data/lib/rubocop/cop/rails/helper_instance_variable.rb +4 -2
  41. data/lib/rubocop/cop/rails/http_positional_arguments.rb +25 -21
  42. data/lib/rubocop/cop/rails/http_status.rb +7 -9
  43. data/lib/rubocop/cop/rails/ignored_skip_action_filter_option.rb +8 -6
  44. data/lib/rubocop/cop/rails/index_by.rb +11 -2
  45. data/lib/rubocop/cop/rails/index_with.rb +11 -2
  46. data/lib/rubocop/cop/rails/inquiry.rb +39 -0
  47. data/lib/rubocop/cop/rails/inverse_of.rb +3 -2
  48. data/lib/rubocop/cop/rails/lexically_scoped_action_filter.rb +17 -15
  49. data/lib/rubocop/cop/rails/link_to_blank.rb +20 -20
  50. data/lib/rubocop/cop/rails/mailer_name.rb +86 -0
  51. data/lib/rubocop/cop/rails/match_route.rb +120 -0
  52. data/lib/rubocop/cop/rails/negate_include.rb +41 -0
  53. data/lib/rubocop/cop/rails/not_null_column.rb +2 -1
  54. data/lib/rubocop/cop/rails/order_by_id.rb +52 -0
  55. data/lib/rubocop/cop/rails/output.rb +5 -2
  56. data/lib/rubocop/cop/rails/output_safety.rb +3 -2
  57. data/lib/rubocop/cop/rails/pick.rb +21 -15
  58. data/lib/rubocop/cop/rails/pluck.rb +56 -0
  59. data/lib/rubocop/cop/rails/pluck_id.rb +56 -0
  60. data/lib/rubocop/cop/rails/pluck_in_where.rb +70 -0
  61. data/lib/rubocop/cop/rails/pluralization_grammar.rb +10 -14
  62. data/lib/rubocop/cop/rails/presence.rb +12 -13
  63. data/lib/rubocop/cop/rails/present.rb +30 -24
  64. data/lib/rubocop/cop/rails/rake_environment.rb +9 -11
  65. data/lib/rubocop/cop/rails/read_write_attribute.rb +12 -11
  66. data/lib/rubocop/cop/rails/redundant_allow_nil.rb +29 -31
  67. data/lib/rubocop/cop/rails/redundant_foreign_key.rb +9 -12
  68. data/lib/rubocop/cop/rails/redundant_receiver_in_with_options.rb +11 -10
  69. data/lib/rubocop/cop/rails/reflection_class_name.rb +4 -3
  70. data/lib/rubocop/cop/rails/refute_methods.rb +9 -10
  71. data/lib/rubocop/cop/rails/relative_date_constant.rb +20 -9
  72. data/lib/rubocop/cop/rails/render_inline.rb +41 -0
  73. data/lib/rubocop/cop/rails/render_plain_text.rb +71 -0
  74. data/lib/rubocop/cop/rails/request_referer.rb +7 -7
  75. data/lib/rubocop/cop/rails/reversible_migration.rb +82 -7
  76. data/lib/rubocop/cop/rails/safe_navigation.rb +12 -11
  77. data/lib/rubocop/cop/rails/safe_navigation_with_blank.rb +5 -10
  78. data/lib/rubocop/cop/rails/save_bang.rb +19 -22
  79. data/lib/rubocop/cop/rails/scope_args.rb +2 -1
  80. data/lib/rubocop/cop/rails/short_i18n.rb +74 -0
  81. data/lib/rubocop/cop/rails/skips_model_validations.rb +46 -11
  82. data/lib/rubocop/cop/rails/squished_sql_heredocs.rb +82 -0
  83. data/lib/rubocop/cop/rails/time_zone.rb +22 -20
  84. data/lib/rubocop/cop/rails/uniq_before_pluck.rb +10 -10
  85. data/lib/rubocop/cop/rails/unique_validation_without_index.rb +18 -8
  86. data/lib/rubocop/cop/rails/unknown_env.rb +15 -4
  87. data/lib/rubocop/cop/rails/validation.rb +15 -14
  88. data/lib/rubocop/cop/rails/where_equals.rb +94 -0
  89. data/lib/rubocop/cop/rails/where_exists.rb +126 -0
  90. data/lib/rubocop/cop/rails/where_not.rb +97 -0
  91. data/lib/rubocop/cop/rails_cops.rb +22 -0
  92. data/lib/rubocop/rails/schema_loader.rb +4 -4
  93. data/lib/rubocop/rails/schema_loader/schema.rb +5 -5
  94. data/lib/rubocop/rails/version.rb +5 -1
  95. metadata +37 -9
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # This cop looks for uses of `default_scope`.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # default_scope -> { where(hidden: false) }
11
+ #
12
+ # # good
13
+ # scope :published, -> { where(hidden: false) }
14
+ #
15
+ # # bad
16
+ # def self.default_scope
17
+ # where(hidden: false)
18
+ # end
19
+ #
20
+ # # good
21
+ # def self.published
22
+ # where(hidden: false)
23
+ # end
24
+ #
25
+ class DefaultScope < Base
26
+ MSG = 'Avoid use of `default_scope`. It is better to use explicitly named scopes.'
27
+ RESTRICT_ON_SEND = %i[default_scope].freeze
28
+
29
+ def_node_matcher :method_call?, <<~PATTERN
30
+ (send nil? :default_scope ...)
31
+ PATTERN
32
+
33
+ def_node_matcher :class_method_definition?, <<~PATTERN
34
+ (defs _ :default_scope args ...)
35
+ PATTERN
36
+
37
+ def_node_matcher :eigenclass_method_definition?, <<~PATTERN
38
+ (sclass (self) $(def :default_scope args ...))
39
+ PATTERN
40
+
41
+ def on_send(node)
42
+ return unless method_call?(node)
43
+
44
+ add_offense(node.loc.selector)
45
+ end
46
+
47
+ def on_defs(node)
48
+ return unless class_method_definition?(node)
49
+
50
+ add_offense(node.loc.name)
51
+ end
52
+
53
+ def on_sclass(node)
54
+ eigenclass_method_definition?(node) do |default_scope|
55
+ add_offense(default_scope.loc.name)
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -52,7 +52,9 @@ module RuboCop
52
52
  #
53
53
  # # good
54
54
  # delegate :bar, to: :foo, prefix: true
55
- class Delegate < Cop
55
+ class Delegate < Base
56
+ extend AutoCorrector
57
+
56
58
  MSG = 'Use `delegate` to define delegations.'
57
59
 
58
60
  def_node_matcher :delegate?, <<~PATTERN
@@ -64,22 +66,20 @@ module RuboCop
64
66
  return unless trivial_delegate?(node)
65
67
  return if private_or_protected_delegation(node)
66
68
 
67
- add_offense(node, location: :keyword)
69
+ register_offense(node)
68
70
  end
69
71
 
70
- def autocorrect(node)
71
- delegation = ["delegate :#{node.body.method_name}",
72
- "to: :#{node.body.receiver.method_name}"]
72
+ private
73
73
 
74
- delegation << ['prefix: true'] if node.method?(prefixed_method_name(node.body))
74
+ def register_offense(node)
75
+ add_offense(node.loc.keyword) do |corrector|
76
+ delegation = ["delegate :#{node.body.method_name}", "to: :#{node.body.receiver.method_name}"]
77
+ delegation << ['prefix: true'] if node.method?(prefixed_method_name(node.body))
75
78
 
76
- lambda do |corrector|
77
79
  corrector.replace(node.source_range, delegation.join(', '))
78
80
  end
79
81
  end
80
82
 
81
- private
82
-
83
83
  def trivial_delegate?(def_node)
84
84
  delegate?(def_node) &&
85
85
  method_name_matches?(def_node.method_name, def_node.body) &&
@@ -122,7 +122,7 @@ module RuboCop
122
122
  end
123
123
 
124
124
  def private_or_protected_inline(line)
125
- processed_source[line - 1].strip =~ /\A(private )|(protected )/
125
+ processed_source[line - 1].strip.match?(/\A(private )|(protected )/)
126
126
  end
127
127
  end
128
128
  end
@@ -13,22 +13,21 @@ module RuboCop
13
13
  #
14
14
  # # good
15
15
  # delegate :foo, to: :bar, allow_nil: true
16
- class DelegateAllowBlank < Cop
16
+ class DelegateAllowBlank < Base
17
+ extend AutoCorrector
18
+
17
19
  MSG = '`allow_blank` is not a valid option, use `allow_nil`.'
20
+ RESTRICT_ON_SEND = %i[delegate].freeze
18
21
 
19
22
  def_node_matcher :allow_blank_option, <<~PATTERN
20
23
  (send nil? :delegate _ (hash <$(pair (sym :allow_blank) true) ...>))
21
24
  PATTERN
22
25
 
23
26
  def on_send(node)
24
- allow_blank_option(node) do |offending_node|
25
- add_offense(offending_node)
26
- end
27
- end
27
+ return unless (offending_node = allow_blank_option(node))
28
28
 
29
- def autocorrect(pair_node)
30
- lambda do |corrector|
31
- corrector.replace(pair_node.key.source_range, 'allow_nil')
29
+ add_offense(offending_node) do |corrector|
30
+ corrector.replace(offending_node.key.source_range, 'allow_nil')
32
31
  end
33
32
  end
34
33
  end
@@ -31,7 +31,9 @@ module RuboCop
31
31
  #
32
32
  # # good
33
33
  # Gem::Specification.find_by_name('backend').gem_dir
34
- class DynamicFindBy < Cop
34
+ class DynamicFindBy < Base
35
+ extend AutoCorrector
36
+
35
37
  MSG = 'Use `%<static_name>s` instead of dynamic `%<method>s`.'
36
38
  METHOD_PATTERN = /^find_by_(.+?)(!)?$/.freeze
37
39
 
@@ -41,26 +43,26 @@ module RuboCop
41
43
  method_name = node.method_name
42
44
  static_name = static_method_name(method_name)
43
45
  return unless static_name
46
+ return if node.arguments.any?(&:splat_type?)
44
47
 
45
- add_offense(node,
46
- message: format(MSG, static_name: static_name,
47
- method: method_name))
48
+ message = format(MSG, static_name: static_name, method: method_name)
49
+ add_offense(node, message: message) do |corrector|
50
+ autocorrect(corrector, node)
51
+ end
48
52
  end
49
53
  alias on_csend on_send
50
54
 
51
- def autocorrect(node)
55
+ private
56
+
57
+ def autocorrect(corrector, node)
52
58
  keywords = column_keywords(node.method_name)
53
59
 
54
60
  return if keywords.size != node.arguments.size
55
61
 
56
- lambda do |corrector|
57
- autocorrect_method_name(corrector, node)
58
- autocorrect_argument_keywords(corrector, node, keywords)
59
- end
62
+ autocorrect_method_name(corrector, node)
63
+ autocorrect_argument_keywords(corrector, node, keywords)
60
64
  end
61
65
 
62
- private
63
-
64
66
  def allowed_invocation?(node)
65
67
  allowed_method?(node) || allowed_receiver?(node) ||
66
68
  whitelisted?(node)
@@ -17,9 +17,12 @@ module RuboCop
17
17
  # # good
18
18
  # enum status: { active: 0, archived: 1 }
19
19
  #
20
- class EnumHash < Cop
20
+ class EnumHash < Base
21
+ extend AutoCorrector
22
+
21
23
  MSG = 'Enum defined as an array found in `%<enum>s` enum declaration. '\
22
24
  'Use hash syntax instead.'
25
+ RESTRICT_ON_SEND = %i[enum].freeze
23
26
 
24
27
  def_node_matcher :enum?, <<~PATTERN
25
28
  (send nil? :enum (hash $...))
@@ -35,19 +38,17 @@ module RuboCop
35
38
  key, array = array_pair?(pair)
36
39
  next unless key
37
40
 
38
- add_offense(array, message: format(MSG, enum: enum_name(key)))
41
+ add_offense(array, message: format(MSG, enum: enum_name(key))) do |corrector|
42
+ hash = array.children.each_with_index.map do |elem, index|
43
+ "#{source(elem)} => #{index}"
44
+ end.join(', ')
45
+
46
+ corrector.replace(array.loc.expression, "{#{hash}}")
47
+ end
39
48
  end
40
49
  end
41
50
  end
42
51
 
43
- def autocorrect(node)
44
- hash = node.children.each_with_index.map do |elem, index|
45
- "#{source(elem)} => #{index}"
46
- end.join(', ')
47
-
48
- ->(corrector) { corrector.replace(node.loc.expression, "{#{hash}}") }
49
- end
50
-
51
52
  private
52
53
 
53
54
  def enum_name(key)
@@ -17,11 +17,12 @@ module RuboCop
17
17
  #
18
18
  # # good
19
19
  # enum status: [:active, :archived]
20
- class EnumUniqueness < Cop
20
+ class EnumUniqueness < Base
21
21
  include Duplication
22
22
 
23
23
  MSG = 'Duplicate value `%<value>s` found in `%<enum>s` ' \
24
24
  'enum declaration.'
25
+ RESTRICT_ON_SEND = %i[enum].freeze
25
26
 
26
27
  def_node_matcher :enum?, <<~PATTERN
27
28
  (send nil? :enum (hash $...))
@@ -15,12 +15,16 @@ module RuboCop
15
15
  #
16
16
  # # good
17
17
  # Rails.env.production?
18
- class EnvironmentComparison < Cop
18
+ class EnvironmentComparison < Base
19
+ extend AutoCorrector
20
+
19
21
  MSG = 'Favor `%<bang>sRails.env.%<env>s?` over `%<source>s`.'
20
22
 
21
23
  SYM_MSG = 'Do not compare `Rails.env` with a symbol, it will always ' \
22
24
  'evaluate to `false`.'
23
25
 
26
+ RESTRICT_ON_SEND = %i[== !=].freeze
27
+
24
28
  def_node_matcher :comparing_str_env_with_rails_env_on_lhs?, <<~PATTERN
25
29
  (send
26
30
  (send (const {nil? cbase} :Rails) :env)
@@ -62,28 +66,28 @@ module RuboCop
62
66
  comparing_str_env_with_rails_env_on_rhs?(node))
63
67
  env, = *env_node
64
68
  bang = node.method?(:!=) ? '!' : ''
69
+ message = format(MSG, bang: bang, env: env, source: node.source)
65
70
 
66
- add_offense(node, message: format(
67
- MSG, bang: bang, env: env, source: node.source
68
- ))
69
- end
70
-
71
- if comparing_sym_env_with_rails_env_on_lhs?(node) ||
72
- comparing_sym_env_with_rails_env_on_rhs?(node)
73
- add_offense(node, message: SYM_MSG)
71
+ add_offense(node, message: message) do |corrector|
72
+ autocorrect(corrector, node)
73
+ end
74
74
  end
75
- end
76
75
 
77
- def autocorrect(node)
78
- lambda do |corrector|
79
- replacement = build_predicate_method(node)
76
+ return unless comparing_sym_env_with_rails_env_on_lhs?(node) || comparing_sym_env_with_rails_env_on_rhs?(node)
80
77
 
81
- corrector.replace(node.source_range, replacement)
78
+ add_offense(node, message: SYM_MSG) do |corrector|
79
+ autocorrect(corrector, node)
82
80
  end
83
81
  end
84
82
 
85
83
  private
86
84
 
85
+ def autocorrect(corrector, node)
86
+ replacement = build_predicate_method(node)
87
+
88
+ corrector.replace(node.source_range, replacement)
89
+ end
90
+
87
91
  def build_predicate_method(node)
88
92
  if rails_env_on_lhs?(node)
89
93
  build_predicate_method_for_rails_env_on_lhs(node)
@@ -23,27 +23,21 @@ module RuboCop
23
23
  #
24
24
  # # good
25
25
  # raise 'a bad error has happened'
26
- class Exit < Cop
26
+ class Exit < Base
27
27
  include ConfigurableEnforcedStyle
28
28
 
29
29
  MSG = 'Do not use `exit` in Rails applications.'
30
- TARGET_METHODS = %i[exit exit!].freeze
30
+ RESTRICT_ON_SEND = %i[exit exit!].freeze
31
31
  EXPLICIT_RECEIVERS = %i[Kernel Process].freeze
32
32
 
33
33
  def on_send(node)
34
- add_offense(node, location: :selector) if offending_node?(node)
34
+ add_offense(node.loc.selector) if offending_node?(node)
35
35
  end
36
36
 
37
37
  private
38
38
 
39
39
  def offending_node?(node)
40
- right_method_name?(node.method_name) &&
41
- right_argument_count?(node.arguments) &&
42
- right_receiver?(node.receiver)
43
- end
44
-
45
- def right_method_name?(method_name)
46
- TARGET_METHODS.include?(method_name)
40
+ right_argument_count?(node.arguments) && right_receiver?(node.receiver)
47
41
  end
48
42
 
49
43
  # More than 1 argument likely means it is a different
@@ -25,7 +25,7 @@ module RuboCop
25
25
  # # good
26
26
  # Rails.root.join('app/models/goober')
27
27
  #
28
- class FilePath < Cop
28
+ class FilePath < Base
29
29
  include ConfigurableEnforcedStyle
30
30
  include RangeHelp
31
31
 
@@ -33,6 +33,7 @@ module RuboCop
33
33
  'instead.'
34
34
  MSG_ARGUMENTS = 'Please use `Rails.root.join(\'path\', \'to\')` ' \
35
35
  'instead.'
36
+ RESTRICT_ON_SEND = %i[join].freeze
36
37
 
37
38
  def_node_matcher :file_join_nodes?, <<~PATTERN
38
39
  (send (const nil? :File) :join ...)
@@ -90,17 +91,17 @@ module RuboCop
90
91
  end
91
92
 
92
93
  def string_with_slash?(node)
93
- node.str_type? && node.source =~ %r{/}
94
+ node.str_type? && node.source.include?('/')
94
95
  end
95
96
 
96
97
  def register_offense(node)
97
98
  line_range = node.loc.column...node.loc.last_column
98
99
  source_range = source_range(processed_source.buffer, node.first_line,
99
100
  line_range)
100
- add_offense(node, location: source_range)
101
+ add_offense(source_range)
101
102
  end
102
103
 
103
- def message(_node)
104
+ def message(_range)
104
105
  format(style == :arguments ? MSG_ARGUMENTS : MSG_SLASHES)
105
106
  end
106
107
  end
@@ -13,11 +13,12 @@ module RuboCop
13
13
  #
14
14
  # # good
15
15
  # User.find_by(name: 'Bruce')
16
- class FindBy < Cop
16
+ class FindBy < Base
17
17
  include RangeHelp
18
+ extend AutoCorrector
18
19
 
19
20
  MSG = 'Use `find_by` instead of `where.%<method>s`.'
20
- TARGET_SELECTORS = %i[first take].freeze
21
+ RESTRICT_ON_SEND = %i[first take].freeze
21
22
 
22
23
  def_node_matcher :where_first?, <<~PATTERN
23
24
  (send ({send csend} _ :where ...) {:first :take})
@@ -26,28 +27,27 @@ module RuboCop
26
27
  def on_send(node)
27
28
  return unless where_first?(node)
28
29
 
29
- range = range_between(node.receiver.loc.selector.begin_pos,
30
- node.loc.selector.end_pos)
30
+ range = range_between(node.receiver.loc.selector.begin_pos, node.loc.selector.end_pos)
31
31
 
32
- add_offense(node, location: range,
33
- message: format(MSG, method: node.method_name))
32
+ add_offense(range, message: format(MSG, method: node.method_name)) do |corrector|
33
+ autocorrect(corrector, node)
34
+ end
34
35
  end
35
36
  alias on_csend on_send
36
37
 
37
- def autocorrect(node)
38
+ private
39
+
40
+ def autocorrect(corrector, node)
38
41
  # Don't autocorrect where(...).first, because it can return different
39
42
  # results from find_by. (They order records differently, so the
40
43
  # 'first' record can be different.)
41
44
  return if node.method?(:first)
42
45
 
43
46
  where_loc = node.receiver.loc.selector
44
- first_loc = range_between(node.loc.dot.begin_pos,
45
- node.loc.selector.end_pos)
47
+ first_loc = range_between(node.loc.dot.begin_pos, node.loc.selector.end_pos)
46
48
 
47
- lambda do |corrector|
48
- corrector.replace(where_loc, 'find_by')
49
- corrector.replace(first_loc, '')
50
- end
49
+ corrector.replace(where_loc, 'find_by')
50
+ corrector.replace(first_loc, '')
51
51
  end
52
52
  end
53
53
  end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # This cop enforces that `ActiveRecord#find` is used instead of
7
+ # `where.take!`, `find_by!`, and `find_by_id!` to retrieve a single record
8
+ # by primary key when you expect it to be found.
9
+ #
10
+ # @example
11
+ # # bad
12
+ # User.where(id: id).take!
13
+ # User.find_by_id!(id)
14
+ # User.find_by!(id: id)
15
+ #
16
+ # # good
17
+ # User.find(id)
18
+ #
19
+ class FindById < Base
20
+ include RangeHelp
21
+ extend AutoCorrector
22
+
23
+ MSG = 'Use `%<good_method>s` instead of `%<bad_method>s`.'
24
+ RESTRICT_ON_SEND = %i[take! find_by_id! find_by!].freeze
25
+
26
+ def_node_matcher :where_take?, <<~PATTERN
27
+ (send
28
+ $(send _ :where
29
+ (hash
30
+ (pair (sym :id) $_))) :take!)
31
+ PATTERN
32
+
33
+ def_node_matcher :find_by?, <<~PATTERN
34
+ {
35
+ (send _ :find_by_id! $_)
36
+ (send _ :find_by! (hash (pair (sym :id) $_)))
37
+ }
38
+ PATTERN
39
+
40
+ def on_send(node)
41
+ where_take?(node) do |where, id_value|
42
+ range = where_take_offense_range(node, where)
43
+ bad_method = build_where_take_bad_method(id_value)
44
+
45
+ register_offense(range, id_value, bad_method)
46
+ end
47
+
48
+ find_by?(node) do |id_value|
49
+ range = find_by_offense_range(node)
50
+ bad_method = build_find_by_bad_method(node, id_value)
51
+
52
+ register_offense(range, id_value, bad_method)
53
+ end
54
+ end
55
+
56
+ private
57
+
58
+ def register_offense(range, id_value, bad_method)
59
+ good_method = build_good_method(id_value)
60
+ message = format(MSG, good_method: good_method, bad_method: bad_method)
61
+
62
+ add_offense(range, message: message) do |corrector|
63
+ corrector.replace(range, good_method)
64
+ end
65
+ end
66
+
67
+ def where_take_offense_range(node, where)
68
+ range_between(where.loc.selector.begin_pos, node.loc.expression.end_pos)
69
+ end
70
+
71
+ def find_by_offense_range(node)
72
+ range_between(node.loc.selector.begin_pos, node.loc.expression.end_pos)
73
+ end
74
+
75
+ def build_good_method(id_value)
76
+ "find(#{id_value.source})"
77
+ end
78
+
79
+ def build_where_take_bad_method(id_value)
80
+ "where(id: #{id_value.source}).take!"
81
+ end
82
+
83
+ def build_find_by_bad_method(node, id_value)
84
+ case node.method_name
85
+ when :find_by_id!
86
+ "find_by_id!(#{id_value.source})"
87
+ when :find_by!
88
+ "find_by!(id: #{id_value.source})"
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end