rubocop-rails 2.14.2 → 2.19.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 (142) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.md +23 -2
  4. data/config/default.yml +190 -12
  5. data/config/obsoletion.yml +10 -0
  6. data/lib/rubocop/cop/mixin/active_record_helper.rb +3 -6
  7. data/lib/rubocop/cop/mixin/active_record_migrations_helper.rb +1 -3
  8. data/lib/rubocop/cop/mixin/enforce_superclass.rb +1 -1
  9. data/lib/rubocop/cop/mixin/index_method.rb +7 -17
  10. data/lib/rubocop/cop/mixin/migrations_helper.rb +1 -1
  11. data/lib/rubocop/cop/rails/action_controller_flash_before_render.rb +112 -0
  12. data/lib/rubocop/cop/rails/action_controller_test_case.rb +2 -2
  13. data/lib/rubocop/cop/rails/action_filter.rb +2 -2
  14. data/lib/rubocop/cop/rails/action_order.rb +116 -0
  15. data/lib/rubocop/cop/rails/active_record_aliases.rb +3 -4
  16. data/lib/rubocop/cop/rails/active_record_callbacks_order.rb +7 -4
  17. data/lib/rubocop/cop/rails/active_record_override.rb +2 -5
  18. data/lib/rubocop/cop/rails/active_support_aliases.rb +1 -1
  19. data/lib/rubocop/cop/rails/active_support_on_load.rb +70 -0
  20. data/lib/rubocop/cop/rails/add_column_index.rb +3 -6
  21. data/lib/rubocop/cop/rails/after_commit_override.rb +1 -1
  22. data/lib/rubocop/cop/rails/application_controller.rb +2 -2
  23. data/lib/rubocop/cop/rails/application_job.rb +3 -3
  24. data/lib/rubocop/cop/rails/application_mailer.rb +2 -2
  25. data/lib/rubocop/cop/rails/application_record.rb +2 -2
  26. data/lib/rubocop/cop/rails/arel_star.rb +2 -2
  27. data/lib/rubocop/cop/rails/assert_not.rb +1 -1
  28. data/lib/rubocop/cop/rails/attribute_default_block_value.rb +1 -1
  29. data/lib/rubocop/cop/rails/belongs_to.rb +2 -5
  30. data/lib/rubocop/cop/rails/blank.rb +10 -11
  31. data/lib/rubocop/cop/rails/bulk_change_table.rb +8 -25
  32. data/lib/rubocop/cop/rails/compact_blank.rb +6 -2
  33. data/lib/rubocop/cop/rails/content_tag.rb +6 -7
  34. data/lib/rubocop/cop/rails/create_table_with_timestamps.rb +16 -3
  35. data/lib/rubocop/cop/rails/date.rb +12 -17
  36. data/lib/rubocop/cop/rails/default_scope.rb +1 -1
  37. data/lib/rubocop/cop/rails/delegate.rb +24 -18
  38. data/lib/rubocop/cop/rails/delegate_allow_blank.rb +2 -2
  39. data/lib/rubocop/cop/rails/deprecated_active_model_errors_methods.rb +63 -3
  40. data/lib/rubocop/cop/rails/dot_separated_keys.rb +71 -0
  41. data/lib/rubocop/cop/rails/duplicate_association.rb +2 -2
  42. data/lib/rubocop/cop/rails/duplicate_scope.rb +1 -1
  43. data/lib/rubocop/cop/rails/duration_arithmetic.rb +4 -4
  44. data/lib/rubocop/cop/rails/dynamic_find_by.rb +26 -14
  45. data/lib/rubocop/cop/rails/eager_evaluation_log_message.rb +6 -2
  46. data/lib/rubocop/cop/rails/enum_hash.rb +3 -4
  47. data/lib/rubocop/cop/rails/enum_uniqueness.rb +3 -6
  48. data/lib/rubocop/cop/rails/environment_comparison.rb +3 -4
  49. data/lib/rubocop/cop/rails/environment_variable_access.rb +1 -1
  50. data/lib/rubocop/cop/rails/exit.rb +1 -1
  51. data/lib/rubocop/cop/rails/expanded_date_range.rb +39 -23
  52. data/lib/rubocop/cop/rails/file_path.rb +41 -24
  53. data/lib/rubocop/cop/rails/find_by.rb +1 -1
  54. data/lib/rubocop/cop/rails/find_by_id.rb +3 -3
  55. data/lib/rubocop/cop/rails/find_each.rb +14 -4
  56. data/lib/rubocop/cop/rails/freeze_time.rb +79 -0
  57. data/lib/rubocop/cop/rails/has_and_belongs_to_many.rb +1 -1
  58. data/lib/rubocop/cop/rails/has_many_or_has_one_dependent.rb +14 -8
  59. data/lib/rubocop/cop/rails/helper_instance_variable.rb +3 -3
  60. data/lib/rubocop/cop/rails/http_positional_arguments.rb +23 -12
  61. data/lib/rubocop/cop/rails/http_status.rb +6 -11
  62. data/lib/rubocop/cop/rails/i18n_lazy_lookup.rb +3 -1
  63. data/lib/rubocop/cop/rails/i18n_locale_assignment.rb +1 -1
  64. data/lib/rubocop/cop/rails/i18n_locale_texts.rb +2 -2
  65. data/lib/rubocop/cop/rails/ignored_columns_assignment.rb +50 -0
  66. data/lib/rubocop/cop/rails/ignored_skip_action_filter_option.rb +5 -14
  67. data/lib/rubocop/cop/rails/index_by.rb +2 -2
  68. data/lib/rubocop/cop/rails/index_with.rb +2 -2
  69. data/lib/rubocop/cop/rails/inquiry.rb +1 -1
  70. data/lib/rubocop/cop/rails/inverse_of.rb +4 -10
  71. data/lib/rubocop/cop/rails/lexically_scoped_action_filter.rb +22 -16
  72. data/lib/rubocop/cop/rails/link_to_blank.rb +2 -5
  73. data/lib/rubocop/cop/rails/mailer_name.rb +5 -5
  74. data/lib/rubocop/cop/rails/match_route.rb +1 -1
  75. data/lib/rubocop/cop/rails/migration_class_name.rb +2 -2
  76. data/lib/rubocop/cop/rails/negate_include.rb +2 -2
  77. data/lib/rubocop/cop/rails/not_null_column.rb +10 -7
  78. data/lib/rubocop/cop/rails/order_by_id.rb +2 -3
  79. data/lib/rubocop/cop/rails/output.rb +7 -9
  80. data/lib/rubocop/cop/rails/output_safety.rb +6 -2
  81. data/lib/rubocop/cop/rails/pick.rb +1 -1
  82. data/lib/rubocop/cop/rails/pluck.rb +45 -13
  83. data/lib/rubocop/cop/rails/pluck_id.rb +2 -2
  84. data/lib/rubocop/cop/rails/pluck_in_where.rb +1 -1
  85. data/lib/rubocop/cop/rails/pluralization_grammar.rb +2 -3
  86. data/lib/rubocop/cop/rails/presence.rb +22 -13
  87. data/lib/rubocop/cop/rails/present.rb +10 -13
  88. data/lib/rubocop/cop/rails/rake_environment.rb +3 -3
  89. data/lib/rubocop/cop/rails/read_write_attribute.rb +2 -2
  90. data/lib/rubocop/cop/rails/redundant_allow_nil.rb +5 -7
  91. data/lib/rubocop/cop/rails/redundant_foreign_key.rb +3 -3
  92. data/lib/rubocop/cop/rails/redundant_presence_validation_on_belongs_to.rb +3 -3
  93. data/lib/rubocop/cop/rails/redundant_receiver_in_with_options.rb +31 -27
  94. data/lib/rubocop/cop/rails/redundant_travel_back.rb +1 -1
  95. data/lib/rubocop/cop/rails/reflection_class_name.rb +35 -2
  96. data/lib/rubocop/cop/rails/refute_methods.rb +1 -5
  97. data/lib/rubocop/cop/rails/relative_date_constant.rb +5 -8
  98. data/lib/rubocop/cop/rails/render_inline.rb +1 -1
  99. data/lib/rubocop/cop/rails/render_plain_text.rb +1 -1
  100. data/lib/rubocop/cop/rails/request_referer.rb +2 -3
  101. data/lib/rubocop/cop/rails/require_dependency.rb +2 -2
  102. data/lib/rubocop/cop/rails/response_parsed_body.rb +57 -0
  103. data/lib/rubocop/cop/rails/reversible_migration.rb +15 -63
  104. data/lib/rubocop/cop/rails/reversible_migration_method_definition.rb +5 -6
  105. data/lib/rubocop/cop/rails/root_join_chain.rb +1 -1
  106. data/lib/rubocop/cop/rails/root_pathname_methods.rb +238 -0
  107. data/lib/rubocop/cop/rails/root_public_path.rb +59 -0
  108. data/lib/rubocop/cop/rails/safe_navigation.rb +8 -13
  109. data/lib/rubocop/cop/rails/safe_navigation_with_blank.rb +2 -4
  110. data/lib/rubocop/cop/rails/save_bang.rb +12 -24
  111. data/lib/rubocop/cop/rails/schema_comment.rb +1 -1
  112. data/lib/rubocop/cop/rails/scope_args.rb +1 -1
  113. data/lib/rubocop/cop/rails/short_i18n.rb +3 -6
  114. data/lib/rubocop/cop/rails/skips_model_validations.rb +3 -4
  115. data/lib/rubocop/cop/rails/squished_sql_heredocs.rb +10 -7
  116. data/lib/rubocop/cop/rails/strip_heredoc.rb +56 -0
  117. data/lib/rubocop/cop/rails/table_name_assignment.rb +1 -1
  118. data/lib/rubocop/cop/rails/three_state_boolean_column.rb +73 -0
  119. data/lib/rubocop/cop/rails/time_zone.rb +34 -32
  120. data/lib/rubocop/cop/rails/time_zone_assignment.rb +4 -4
  121. data/lib/rubocop/cop/rails/to_formatted_s.rb +46 -0
  122. data/lib/rubocop/cop/rails/to_s_with_argument.rb +78 -0
  123. data/lib/rubocop/cop/rails/top_level_hash_with_indifferent_access.rb +49 -0
  124. data/lib/rubocop/cop/rails/transaction_exit_statement.rb +17 -12
  125. data/lib/rubocop/cop/rails/uniq_before_pluck.rb +3 -6
  126. data/lib/rubocop/cop/rails/unique_validation_without_index.rb +14 -7
  127. data/lib/rubocop/cop/rails/unknown_env.rb +3 -5
  128. data/lib/rubocop/cop/rails/unused_ignored_columns.rb +7 -2
  129. data/lib/rubocop/cop/rails/validation.rb +5 -13
  130. data/lib/rubocop/cop/rails/where_equals.rb +2 -2
  131. data/lib/rubocop/cop/rails/where_exists.rb +3 -3
  132. data/lib/rubocop/cop/rails/where_missing.rb +118 -0
  133. data/lib/rubocop/cop/rails/where_not.rb +2 -2
  134. data/lib/rubocop/cop/rails/where_not_with_multiple_conditions.rb +55 -0
  135. data/lib/rubocop/cop/rails_cops.rb +16 -0
  136. data/lib/rubocop/rails/schema_loader/schema.rb +8 -5
  137. data/lib/rubocop/rails/version.rb +1 -1
  138. data/lib/rubocop/rails.rb +1 -1
  139. data/lib/rubocop-rails.rb +19 -0
  140. metadata +23 -9
  141. data/bin/console +0 -11
  142. data/bin/setup +0 -7
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # Checks for consistent uses of `to_fs` or `to_formatted_s`,
7
+ # depending on the cop's configuration.
8
+ #
9
+ # @example EnforcedStyle: to_fs (default)
10
+ #
11
+ # # bad
12
+ # time.to_formatted_s(:db)
13
+ #
14
+ # # good
15
+ # time.to_fs(:db)
16
+ #
17
+ # @example EnforcedStyle: to_formatted_s
18
+ #
19
+ # # bad
20
+ # time.to_fs(:db)
21
+ #
22
+ # # good
23
+ # time.to_formatted_s(:db)
24
+ #
25
+ class ToFormattedS < Base
26
+ include ConfigurableEnforcedStyle
27
+ extend AutoCorrector
28
+ extend TargetRailsVersion
29
+
30
+ minimum_target_rails_version 7.0
31
+
32
+ MSG = 'Use `%<prefer>s` instead.'
33
+ RESTRICT_ON_SEND = %i[to_formatted_s to_fs].freeze
34
+
35
+ def on_send(node)
36
+ return if node.method?(style)
37
+
38
+ add_offense(node.loc.selector, message: format(MSG, prefer: style)) do |corrector|
39
+ corrector.replace(node.loc.selector, style)
40
+ end
41
+ end
42
+ alias on_csend on_send
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # Identifies passing any argument to `#to_s`.
7
+ #
8
+ # @safety
9
+ # This cop is marked as unsafe because it may detect `#to_s` calls
10
+ # that are not related to Active Support implementation.
11
+ #
12
+ # @example
13
+ #
14
+ # # bad
15
+ # obj.to_s(:delimited)
16
+ #
17
+ # # good
18
+ # obj.to_formatted_s(:delimited)
19
+ #
20
+ class ToSWithArgument < Base
21
+ extend AutoCorrector
22
+ extend TargetRailsVersion
23
+
24
+ # These types are defined by the following files in ActiveSupport:
25
+ # lib/active_support/core_ext/array/conversions.rb
26
+ # lib/active_support/core_ext/date/conversions.rb
27
+ # lib/active_support/core_ext/date_time/conversions.rb
28
+ # lib/active_support/core_ext/numeric/conversions.rb
29
+ # lib/active_support/core_ext/range/conversions.rb
30
+ # lib/active_support/core_ext/time/conversions.rb
31
+ # lib/active_support/time_with_zone.rb
32
+ EXTENDED_FORMAT_TYPES = Set.new(
33
+ %i[
34
+ currency
35
+ db
36
+ delimited
37
+ human
38
+ human_size
39
+ inspect
40
+ iso8601
41
+ long
42
+ long_ordinal
43
+ nsec
44
+ number
45
+ percentage
46
+ phone
47
+ rfc822
48
+ rounded
49
+ short
50
+ time
51
+ usec
52
+ ]
53
+ )
54
+
55
+ MSG = 'Use `to_formatted_s` instead.'
56
+
57
+ RESTRICT_ON_SEND = %i[to_s].freeze
58
+
59
+ minimum_target_rails_version 7.0
60
+
61
+ def on_send(node)
62
+ return unless rails_extended_to_s?(node)
63
+
64
+ add_offense(node.loc.selector) do |corrector|
65
+ corrector.replace(node.loc.selector, 'to_formatted_s')
66
+ end
67
+ end
68
+ alias on_csend on_send
69
+
70
+ private
71
+
72
+ def rails_extended_to_s?(node)
73
+ node.first_argument&.sym_type? && EXTENDED_FORMAT_TYPES.include?(node.first_argument.value)
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # Identifies top-level `HashWithIndifferentAccess`.
7
+ # This has been soft-deprecated since Rails 5.1.
8
+ #
9
+ # @example
10
+ # # bad
11
+ # HashWithIndifferentAccess.new(foo: 'bar')
12
+ #
13
+ # # good
14
+ # ActiveSupport::HashWithIndifferentAccess.new(foo: 'bar')
15
+ #
16
+ class TopLevelHashWithIndifferentAccess < Base
17
+ extend AutoCorrector
18
+ extend TargetRailsVersion
19
+
20
+ minimum_target_rails_version 5.1
21
+
22
+ MSG = 'Avoid top-level `HashWithIndifferentAccess`.'
23
+
24
+ # @!method top_level_hash_with_indifferent_access?(node)
25
+ # @param [RuboCop::AST::ConstNode] node
26
+ # @return [Boolean]
27
+ def_node_matcher :top_level_hash_with_indifferent_access?, <<~PATTERN
28
+ (const {nil? cbase} :HashWithIndifferentAccess)
29
+ PATTERN
30
+
31
+ # @param [RuboCop::AST::ConstNode] node
32
+ def on_const(node)
33
+ return unless top_level_hash_with_indifferent_access?(node)
34
+ return if node.parent&.class_type? && node.parent.ancestors.any?(&:module_type?)
35
+
36
+ add_offense(node) do |corrector|
37
+ autocorrect(corrector, node)
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ def autocorrect(corrector, node)
44
+ corrector.insert_before(node.location.name, 'ActiveSupport::')
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -3,10 +3,10 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop checks for the use of exit statements (namely `return`,
6
+ # Checks for the use of exit statements (namely `return`,
7
7
  # `break` and `throw`) in transactions. This is due to the eventual
8
8
  # unexpected behavior when using ActiveRecord >= 7, where transactions
9
- # exitted using these statements are being rollbacked rather than
9
+ # exited using these statements are being rollbacked rather than
10
10
  # committed (pre ActiveRecord 7 behavior).
11
11
  #
12
12
  # As alternatives, it would be more intuitive to explicitly raise an
@@ -29,6 +29,11 @@ module RuboCop
29
29
  # throw if user.active?
30
30
  # end
31
31
  #
32
+ # # bad, as `with_lock` implicitly opens a transaction too
33
+ # user.with_lock do
34
+ # throw if user.active?
35
+ # end
36
+ #
32
37
  # # good
33
38
  # ApplicationRecord.transaction do
34
39
  # # Rollback
@@ -40,25 +45,31 @@ module RuboCop
40
45
  # # Commit
41
46
  # next if user.active?
42
47
  # end
43
- #
44
- # @see https://github.com/rails/rails/commit/15aa4200e083
45
48
  class TransactionExitStatement < Base
46
49
  MSG = <<~MSG.chomp
47
50
  Exit statement `%<statement>s` is not allowed. Use `raise` (rollback) or `next` (commit).
48
51
  MSG
49
52
 
50
- RESTRICT_ON_SEND = %i[transaction].freeze
53
+ RESTRICT_ON_SEND = %i[transaction with_lock].freeze
51
54
 
52
55
  def_node_search :exit_statements, <<~PATTERN
53
56
  ({return | break | send nil? :throw} ...)
54
57
  PATTERN
55
58
 
59
+ def_node_matcher :rescue_body_return_node?, <<~PATTERN
60
+ (:resbody ...
61
+ ...
62
+ ({return | break | send nil? :throw} ...)
63
+ ...
64
+ )
65
+ PATTERN
66
+
56
67
  def on_send(node)
57
68
  return unless (parent = node.parent)
58
69
  return unless parent.block_type? && parent.body
59
70
 
60
71
  exit_statements(parent.body).each do |statement_node|
61
- next if in_rescue?(statement_node) || nested_block?(statement_node)
72
+ next if statement_node.break_type? && nested_block?(statement_node)
62
73
 
63
74
  statement = statement(statement_node)
64
75
  message = format(MSG, statement: statement)
@@ -79,13 +90,7 @@ module RuboCop
79
90
  end
80
91
  end
81
92
 
82
- def in_rescue?(statement_node)
83
- statement_node.ancestors.find(&:rescue_type?)
84
- end
85
-
86
93
  def nested_block?(statement_node)
87
- return false unless statement_node.break_type?
88
-
89
94
  !statement_node.ancestors.find(&:block_type?).method?(:transaction)
90
95
  end
91
96
  end
@@ -54,11 +54,9 @@ module RuboCop
54
54
  NEWLINE = "\n"
55
55
  PATTERN = '[!^block (send (send %<type>s :pluck ...) :uniq ...)]'
56
56
 
57
- def_node_matcher :conservative_node_match,
58
- format(PATTERN, type: 'const')
57
+ def_node_matcher :conservative_node_match, format(PATTERN, type: 'const')
59
58
 
60
- def_node_matcher :aggressive_node_match,
61
- format(PATTERN, type: '_')
59
+ def_node_matcher :aggressive_node_match, format(PATTERN, type: '_')
62
60
 
63
61
  def on_send(node)
64
62
  uniq = if style == :conservative
@@ -80,8 +78,7 @@ module RuboCop
80
78
  private
81
79
 
82
80
  def dot_method_with_whitespace(method, node)
83
- range_between(dot_method_begin_pos(method, node),
84
- node.loc.selector.end_pos)
81
+ range_between(dot_method_begin_pos(method, node), node.loc.selector.end_pos)
85
82
  end
86
83
 
87
84
  def dot_method_begin_pos(method, node)
@@ -4,7 +4,7 @@ module RuboCop
4
4
  module Cop
5
5
  module Rails
6
6
  # When you define a uniqueness validation in Active Record model,
7
- # you also should add a unique index for the column. There are two reasons
7
+ # you also should add a unique index for the column. There are two reasons.
8
8
  # First, duplicated records may occur even if Active Record's validation
9
9
  # is defined.
10
10
  # Second, it will cause slow queries. The validation executes a `SELECT`
@@ -56,12 +56,10 @@ module RuboCop
56
56
 
57
57
  def with_index?(klass, table, names)
58
58
  # Compatibility for Rails 4.2.
59
- add_indicies = schema.add_indicies_by(table_name: table_name(klass))
59
+ add_indices = schema.add_indices_by(table_name: table_name(klass))
60
60
 
61
- (table.indices + add_indicies).any? do |index|
62
- index.unique &&
63
- (index.columns.to_set == names ||
64
- include_column_names_in_expression_index?(index, names))
61
+ (table.indices + add_indices).any? do |index|
62
+ index.unique && (index.columns.to_set == names || include_column_names_in_expression_index?(index, names))
65
63
  end
66
64
  end
67
65
 
@@ -141,11 +139,20 @@ module RuboCop
141
139
  pairs = node.arguments.last
142
140
  return unless pairs.hash_type?
143
141
 
142
+ return true if condition_hash_part?(pairs, keys: %i[if unless])
143
+
144
+ uniqueness_node = uniqueness_part(node)
145
+ return unless uniqueness_node&.hash_type?
146
+
147
+ condition_hash_part?(uniqueness_node, keys: %i[if unless conditions])
148
+ end
149
+
150
+ def condition_hash_part?(pairs, keys:)
144
151
  pairs.each_pair.any? do |pair|
145
152
  key = pair.key
146
153
  next unless key.sym_type?
147
154
 
148
- key.value == :if || key.value == :unless
155
+ keys.include?(key.value)
149
156
  end
150
157
  end
151
158
 
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop checks that environments called with `Rails.env` predicates
6
+ # Checks that environments called with `Rails.env` predicates
7
7
  # exist.
8
8
  # By default the cop allows three environments which Rails ships with:
9
9
  # `development`, `test`, and `production`.
@@ -19,8 +19,7 @@ module RuboCop
19
19
  # Rails.env == 'production'
20
20
  class UnknownEnv < Base
21
21
  MSG = 'Unknown environment `%<name>s`.'
22
- MSG_SIMILAR = 'Unknown environment `%<name>s`. ' \
23
- 'Did you mean `%<similar>s`?'
22
+ MSG_SIMILAR = 'Unknown environment `%<name>s`. Did you mean `%<similar>s`?'
24
23
 
25
24
  def_node_matcher :rails_env?, <<~PATTERN
26
25
  (send
@@ -79,8 +78,7 @@ module RuboCop
79
78
 
80
79
  def unknown_env_predicate?(name)
81
80
  name = name.to_s
82
- name.end_with?('?') &&
83
- !environments.include?(name[0..-2])
81
+ name.end_with?('?') && !environments.include?(name[0..-2])
84
82
  end
85
83
 
86
84
  def unknown_env_name?(name)
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop suggests you remove a column that does not exist in the schema from `ignored_columns`.
6
+ # Suggests you remove a column that does not exist in the schema from `ignored_columns`.
7
7
  # `ignored_columns` is necessary to drop a column from RDBMS, but you don't need it after the migration
8
8
  # to drop the column. You avoid forgetting to remove `ignored_columns` by this cop.
9
9
  #
@@ -28,12 +28,16 @@ module RuboCop
28
28
  (send self :ignored_columns= $array)
29
29
  PATTERN
30
30
 
31
+ def_node_matcher :appended_ignored_columns, <<~PATTERN
32
+ (op-asgn (send self :ignored_columns) :+ $array)
33
+ PATTERN
34
+
31
35
  def_node_matcher :column_name, <<~PATTERN
32
36
  ({str sym} $_)
33
37
  PATTERN
34
38
 
35
39
  def on_send(node)
36
- return unless (columns = ignored_columns(node))
40
+ return unless (columns = ignored_columns(node) || appended_ignored_columns(node))
37
41
  return unless schema
38
42
 
39
43
  table = table(node)
@@ -43,6 +47,7 @@ module RuboCop
43
47
  check_column_existence(column_node, table)
44
48
  end
45
49
  end
50
+ alias on_op_asgn on_send
46
51
 
47
52
  private
48
53
 
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop checks for the use of old-style attribute validation macros.
6
+ # Checks for the use of old-style attribute validation macros.
7
7
  #
8
8
  # @example
9
9
  # # bad
@@ -35,8 +35,7 @@ module RuboCop
35
35
  class Validation < Base
36
36
  extend AutoCorrector
37
37
 
38
- MSG = 'Prefer the new style validations `%<prefer>s` over ' \
39
- '`%<current>s`.'
38
+ MSG = 'Prefer the new style validations `%<prefer>s` over `%<current>s`.'
40
39
 
41
40
  TYPES = %w[
42
41
  acceptance
@@ -62,8 +61,7 @@ module RuboCop
62
61
 
63
62
  add_offense(range, message: message(node)) do |corrector|
64
63
  last_argument = node.arguments.last
65
- return if !last_argument.literal? && !last_argument.splat_type? &&
66
- !frozen_array_argument?(last_argument)
64
+ return if !last_argument.literal? && !last_argument.splat_type? && !frozen_array_argument?(last_argument)
67
65
 
68
66
  corrector.replace(range, 'validates')
69
67
  correct_validate_type(corrector, node)
@@ -104,10 +102,7 @@ module RuboCop
104
102
  end
105
103
 
106
104
  def correct_validate_type_for_hash(corrector, node, arguments)
107
- corrector.replace(
108
- arguments.loc.expression,
109
- "#{validate_type(node)}: #{braced_options(arguments)}"
110
- )
105
+ corrector.replace(arguments, "#{validate_type(node)}: #{braced_options(arguments)}")
111
106
  end
112
107
 
113
108
  def correct_validate_type_for_array(corrector, node, arguments, loc)
@@ -121,10 +116,7 @@ module RuboCop
121
116
  end
122
117
  end
123
118
 
124
- corrector.replace(
125
- loc.expression,
126
- "#{attributes.join(', ')}, #{validate_type(node)}: true"
127
- )
119
+ corrector.replace(loc.expression, "#{attributes.join(', ')}, #{validate_type(node)}: true")
128
120
  end
129
121
 
130
122
  def validate_type(node)
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop identifies places where manually constructed SQL
6
+ # Identifies places where manually constructed SQL
7
7
  # in `where` can be replaced with `where(attribute: value)`.
8
8
  #
9
9
  # @safety
@@ -65,7 +65,7 @@ module RuboCop
65
65
  private
66
66
 
67
67
  def offense_range(node)
68
- range_between(node.loc.selector.begin_pos, node.loc.expression.end_pos)
68
+ range_between(node.loc.selector.begin_pos, node.source_range.end_pos)
69
69
  end
70
70
 
71
71
  def extract_column_and_value(template_node, value_node)
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop enforces consistent style when using `exists?`.
6
+ # Enforces consistent style when using `exists?`.
7
7
  #
8
8
  # Two styles are supported for this cop. When EnforcedStyle is 'exists'
9
9
  # then the cop enforces `exists?(...)` over `where(...).exists?`.
@@ -12,7 +12,7 @@ module RuboCop
12
12
  # `where(...).exists?` over `exists?(...)`.
13
13
  #
14
14
  # @safety
15
- # This cop is unsafe for auto-correction because the behavior may change on the following case:
15
+ # This cop is unsafe for autocorrection because the behavior may change on the following case:
16
16
  #
17
17
  # [source,ruby]
18
18
  # ----
@@ -105,7 +105,7 @@ module RuboCop
105
105
  if exists_style?
106
106
  node.receiver.loc.selector.join(node.loc.selector)
107
107
  elsif where_style?
108
- node.loc.selector.with(end_pos: node.loc.expression.end_pos)
108
+ node.loc.selector.with(end_pos: node.source_range.end_pos)
109
109
  end
110
110
  end
111
111
 
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # Use `where.missing(...)` to find missing relationship records.
7
+ #
8
+ # This cop is enabled in Rails 6.1 or higher.
9
+ #
10
+ # @example
11
+ # # bad
12
+ # Post.left_joins(:author).where(authors: { id: nil })
13
+ #
14
+ # # good
15
+ # Post.where.missing(:author)
16
+ #
17
+ class WhereMissing < Base
18
+ include RangeHelp
19
+ extend AutoCorrector
20
+ extend TargetRailsVersion
21
+
22
+ MSG = 'Use `where.missing(:%<left_joins_association>s)` instead of ' \
23
+ '`%<left_joins_method>s(:%<left_joins_association>s).where(%<where_association>s: { id: nil })`.'
24
+ RESTRICT_ON_SEND = %i[left_joins left_outer_joins].freeze
25
+
26
+ minimum_target_rails_version 6.1
27
+
28
+ # @!method where_node_and_argument(node)
29
+ def_node_search :where_node_and_argument, <<~PATTERN
30
+ $(send ... :where (hash <(pair $(sym _) (hash (pair (sym :id) (nil))))...> ))
31
+ PATTERN
32
+
33
+ # @!method missing_relationship(node)
34
+ def_node_search :missing_relationship, <<~PATTERN
35
+ (pair (sym _) (hash (pair (sym :id) (nil))))
36
+ PATTERN
37
+
38
+ def on_send(node)
39
+ return unless node.first_argument.sym_type?
40
+
41
+ root_receiver = root_receiver(node)
42
+ where_node_and_argument(root_receiver) do |where_node, where_argument|
43
+ next unless root_receiver == root_receiver(where_node)
44
+ next unless same_relationship?(where_argument, node.first_argument)
45
+
46
+ range = range_between(node.loc.selector.begin_pos, node.source_range.end_pos)
47
+ register_offense(node, where_node, where_argument, range)
48
+ break
49
+ end
50
+ end
51
+
52
+ private
53
+
54
+ def root_receiver(node)
55
+ parent = node.parent
56
+ if !parent&.send_type? || parent.method?(:or) || parent.method?(:and)
57
+ node
58
+ else
59
+ root_receiver(parent)
60
+ end
61
+ end
62
+
63
+ def same_relationship?(where, left_joins)
64
+ where.value.to_s.match?(/^#{left_joins.value}s?$/)
65
+ end
66
+
67
+ def register_offense(node, where_node, where_argument, range)
68
+ add_offense(range, message: message(node, where_argument)) do |corrector|
69
+ corrector.replace(node.loc.selector, 'where.missing')
70
+ if multi_condition?(where_node.first_argument)
71
+ replace_where_method(corrector, where_node)
72
+ else
73
+ remove_where_method(corrector, node, where_node)
74
+ end
75
+ end
76
+ end
77
+
78
+ def replace_where_method(corrector, where_node)
79
+ missing_relationship(where_node) do |where_clause|
80
+ corrector.remove(replace_range(where_clause))
81
+ end
82
+ end
83
+
84
+ def replace_range(child)
85
+ if (right_sibling = child.right_sibling)
86
+ range_between(child.source_range.begin_pos, right_sibling.source_range.begin_pos)
87
+ else
88
+ range_between(child.left_sibling.source_range.end_pos, child.source_range.end_pos)
89
+ end
90
+ end
91
+
92
+ def remove_where_method(corrector, node, where_node)
93
+ range = range_between(where_node.loc.selector.begin_pos, where_node.loc.end.end_pos)
94
+ if node.multiline? && !same_line?(node, where_node)
95
+ range = range_by_whole_lines(range, include_final_newline: true)
96
+ else
97
+ corrector.remove(where_node.loc.dot)
98
+ end
99
+
100
+ corrector.remove(range)
101
+ end
102
+
103
+ def same_line?(left_joins_node, where_node)
104
+ left_joins_node.loc.selector.line == where_node.loc.selector.line
105
+ end
106
+
107
+ def multi_condition?(where_arg)
108
+ where_arg.children.count > 1
109
+ end
110
+
111
+ def message(node, where_argument)
112
+ format(MSG, left_joins_association: node.first_argument.value, left_joins_method: node.method_name,
113
+ where_association: where_argument.value)
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop identifies places where manually constructed SQL
6
+ # Identifies places where manually constructed SQL
7
7
  # in `where` can be replaced with `where.not(...)`.
8
8
  #
9
9
  # @example
@@ -64,7 +64,7 @@ module RuboCop
64
64
  private
65
65
 
66
66
  def offense_range(node)
67
- range_between(node.loc.selector.begin_pos, node.loc.expression.end_pos)
67
+ range_between(node.loc.selector.begin_pos, node.source_range.end_pos)
68
68
  end
69
69
 
70
70
  def extract_column_and_value(template_node, value_node)
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # Identifies calls to `where.not` with multiple hash arguments.
7
+ #
8
+ # The behavior of `where.not` changed in Rails 6.1. Prior to the change,
9
+ # `.where.not(trashed: true, role: 'admin')` evaluated to
10
+ # `WHERE trashed != TRUE AND role != 'admin'`.
11
+ # From Rails 6.1 onwards, this executes the query
12
+ # `WHERE NOT (trashed == TRUE AND roles == 'admin')`.
13
+ #
14
+ # @example
15
+ # # bad
16
+ # User.where.not(trashed: true, role: 'admin')
17
+ # User.where.not(trashed: true, role: ['moderator', 'admin'])
18
+ # User.joins(:posts).where.not(posts: { trashed: true, title: 'Rails' })
19
+ #
20
+ # # good
21
+ # User.where.not(trashed: true)
22
+ # User.where.not(role: ['moderator', 'admin'])
23
+ # User.where.not(trashed: true).where.not(role: ['moderator', 'admin'])
24
+ # User.where.not('trashed = ? OR role = ?', true, 'admin')
25
+ class WhereNotWithMultipleConditions < Base
26
+ MSG = 'Use a SQL statement instead of `where.not` with multiple conditions.'
27
+ RESTRICT_ON_SEND = %i[not].freeze
28
+
29
+ def_node_matcher :where_not_call?, <<~PATTERN
30
+ (send (send _ :where) :not $...)
31
+ PATTERN
32
+
33
+ def on_send(node)
34
+ where_not_call?(node) do |args|
35
+ next unless args[0]&.hash_type?
36
+ next unless multiple_arguments_hash? args[0]
37
+
38
+ range = node.receiver.loc.selector.with(end_pos: node.source_range.end_pos)
39
+
40
+ add_offense(range)
41
+ end
42
+ end
43
+
44
+ private
45
+
46
+ def multiple_arguments_hash?(hash)
47
+ return true if hash.pairs.size >= 2
48
+ return false unless hash.values[0]&.hash_type?
49
+
50
+ multiple_arguments_hash?(hash.values[0])
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end