rubocop-rails 2.0.1 → 2.19.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (144) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.md +52 -5
  4. data/config/default.yml +726 -32
  5. data/config/obsoletion.yml +17 -0
  6. data/lib/rubocop/cop/mixin/active_record_helper.rb +106 -0
  7. data/lib/rubocop/cop/mixin/active_record_migrations_helper.rb +32 -0
  8. data/lib/rubocop/cop/mixin/class_send_node_helper.rb +20 -0
  9. data/lib/rubocop/cop/mixin/enforce_superclass.rb +40 -0
  10. data/lib/rubocop/cop/mixin/index_method.rb +165 -0
  11. data/lib/rubocop/cop/mixin/migrations_helper.rb +26 -0
  12. data/lib/rubocop/cop/rails/action_controller_flash_before_render.rb +112 -0
  13. data/lib/rubocop/cop/rails/action_controller_test_case.rb +47 -0
  14. data/lib/rubocop/cop/rails/action_filter.rb +11 -21
  15. data/lib/rubocop/cop/rails/action_order.rb +116 -0
  16. data/lib/rubocop/cop/rails/active_record_aliases.rb +23 -24
  17. data/lib/rubocop/cop/rails/active_record_callbacks_order.rb +143 -0
  18. data/lib/rubocop/cop/rails/active_record_override.rb +3 -6
  19. data/lib/rubocop/cop/rails/active_support_aliases.rb +13 -22
  20. data/lib/rubocop/cop/rails/active_support_on_load.rb +70 -0
  21. data/lib/rubocop/cop/rails/add_column_index.rb +61 -0
  22. data/lib/rubocop/cop/rails/after_commit_override.rb +81 -0
  23. data/lib/rubocop/cop/rails/application_controller.rb +36 -0
  24. data/lib/rubocop/cop/rails/application_job.rb +9 -4
  25. data/lib/rubocop/cop/rails/application_mailer.rb +39 -0
  26. data/lib/rubocop/cop/rails/application_record.rb +9 -9
  27. data/lib/rubocop/cop/rails/arel_star.rb +47 -0
  28. data/lib/rubocop/cop/rails/assert_not.rb +8 -10
  29. data/lib/rubocop/cop/rails/attribute_default_block_value.rb +90 -0
  30. data/lib/rubocop/cop/rails/belongs_to.rb +12 -24
  31. data/lib/rubocop/cop/rails/blank.rb +40 -36
  32. data/lib/rubocop/cop/rails/bulk_change_table.rb +40 -35
  33. data/lib/rubocop/cop/rails/compact_blank.rb +111 -0
  34. data/lib/rubocop/cop/rails/content_tag.rb +93 -0
  35. data/lib/rubocop/cop/rails/create_table_with_timestamps.rb +22 -15
  36. data/lib/rubocop/cop/rails/date.rb +41 -36
  37. data/lib/rubocop/cop/rails/default_scope.rb +61 -0
  38. data/lib/rubocop/cop/rails/delegate.rb +33 -29
  39. data/lib/rubocop/cop/rails/delegate_allow_blank.rb +9 -10
  40. data/lib/rubocop/cop/rails/deprecated_active_model_errors_methods.rb +168 -0
  41. data/lib/rubocop/cop/rails/dot_separated_keys.rb +71 -0
  42. data/lib/rubocop/cop/rails/duplicate_association.rb +56 -0
  43. data/lib/rubocop/cop/rails/duplicate_scope.rb +46 -0
  44. data/lib/rubocop/cop/rails/duration_arithmetic.rb +98 -0
  45. data/lib/rubocop/cop/rails/dynamic_find_by.rb +76 -31
  46. data/lib/rubocop/cop/rails/eager_evaluation_log_message.rb +82 -0
  47. data/lib/rubocop/cop/rails/enum_hash.rb +75 -0
  48. data/lib/rubocop/cop/rails/enum_uniqueness.rb +30 -12
  49. data/lib/rubocop/cop/rails/environment_comparison.rb +70 -22
  50. data/lib/rubocop/cop/rails/environment_variable_access.rb +67 -0
  51. data/lib/rubocop/cop/rails/exit.rb +7 -13
  52. data/lib/rubocop/cop/rails/expanded_date_range.rb +102 -0
  53. data/lib/rubocop/cop/rails/file_path.rb +48 -31
  54. data/lib/rubocop/cop/rails/find_by.rb +43 -24
  55. data/lib/rubocop/cop/rails/find_by_id.rb +94 -0
  56. data/lib/rubocop/cop/rails/find_each.rb +42 -18
  57. data/lib/rubocop/cop/rails/freeze_time.rb +79 -0
  58. data/lib/rubocop/cop/rails/has_and_belongs_to_many.rb +4 -3
  59. data/lib/rubocop/cop/rails/has_many_or_has_one_dependent.rb +62 -25
  60. data/lib/rubocop/cop/rails/helper_instance_variable.rb +32 -4
  61. data/lib/rubocop/cop/rails/http_positional_arguments.rb +61 -32
  62. data/lib/rubocop/cop/rails/http_status.rb +27 -23
  63. data/lib/rubocop/cop/rails/i18n_lazy_lookup.rb +96 -0
  64. data/lib/rubocop/cop/rails/i18n_locale_assignment.rb +37 -0
  65. data/lib/rubocop/cop/rails/i18n_locale_texts.rb +110 -0
  66. data/lib/rubocop/cop/rails/ignored_columns_assignment.rb +50 -0
  67. data/lib/rubocop/cop/rails/ignored_skip_action_filter_option.rb +9 -16
  68. data/lib/rubocop/cop/rails/index_by.rb +65 -0
  69. data/lib/rubocop/cop/rails/index_with.rb +68 -0
  70. data/lib/rubocop/cop/rails/inquiry.rb +39 -0
  71. data/lib/rubocop/cop/rails/inverse_of.rb +33 -27
  72. data/lib/rubocop/cop/rails/lexically_scoped_action_filter.rb +62 -32
  73. data/lib/rubocop/cop/rails/link_to_blank.rb +31 -32
  74. data/lib/rubocop/cop/rails/mailer_name.rb +90 -0
  75. data/lib/rubocop/cop/rails/match_route.rb +120 -0
  76. data/lib/rubocop/cop/rails/migration_class_name.rb +63 -0
  77. data/lib/rubocop/cop/rails/negate_include.rb +42 -0
  78. data/lib/rubocop/cop/rails/not_null_column.rb +16 -12
  79. data/lib/rubocop/cop/rails/order_by_id.rb +51 -0
  80. data/lib/rubocop/cop/rails/output.rb +29 -10
  81. data/lib/rubocop/cop/rails/output_safety.rb +9 -4
  82. data/lib/rubocop/cop/rails/pick.rb +64 -0
  83. data/lib/rubocop/cop/rails/pluck.rb +96 -0
  84. data/lib/rubocop/cop/rails/pluck_id.rb +59 -0
  85. data/lib/rubocop/cop/rails/pluck_in_where.rb +71 -0
  86. data/lib/rubocop/cop/rails/pluralization_grammar.rb +14 -19
  87. data/lib/rubocop/cop/rails/presence.rb +54 -26
  88. data/lib/rubocop/cop/rails/present.rb +40 -37
  89. data/lib/rubocop/cop/rails/rake_environment.rb +112 -0
  90. data/lib/rubocop/cop/rails/read_write_attribute.rb +56 -18
  91. data/lib/rubocop/cop/rails/redundant_allow_nil.rb +33 -45
  92. data/lib/rubocop/cop/rails/redundant_foreign_key.rb +77 -0
  93. data/lib/rubocop/cop/rails/redundant_presence_validation_on_belongs_to.rb +257 -0
  94. data/lib/rubocop/cop/rails/redundant_receiver_in_with_options.rb +34 -32
  95. data/lib/rubocop/cop/rails/redundant_travel_back.rb +57 -0
  96. data/lib/rubocop/cop/rails/reflection_class_name.rb +56 -7
  97. data/lib/rubocop/cop/rails/refute_methods.rb +56 -35
  98. data/lib/rubocop/cop/rails/relative_date_constant.rb +52 -33
  99. data/lib/rubocop/cop/rails/render_inline.rb +41 -0
  100. data/lib/rubocop/cop/rails/render_plain_text.rb +71 -0
  101. data/lib/rubocop/cop/rails/request_referer.rb +10 -11
  102. data/lib/rubocop/cop/rails/require_dependency.rb +38 -0
  103. data/lib/rubocop/cop/rails/response_parsed_body.rb +57 -0
  104. data/lib/rubocop/cop/rails/reversible_migration.rb +122 -82
  105. data/lib/rubocop/cop/rails/reversible_migration_method_definition.rb +66 -0
  106. data/lib/rubocop/cop/rails/root_join_chain.rb +72 -0
  107. data/lib/rubocop/cop/rails/root_pathname_methods.rb +238 -0
  108. data/lib/rubocop/cop/rails/root_public_path.rb +59 -0
  109. data/lib/rubocop/cop/rails/safe_navigation.rb +55 -43
  110. data/lib/rubocop/cop/rails/safe_navigation_with_blank.rb +50 -0
  111. data/lib/rubocop/cop/rails/save_bang.rb +89 -63
  112. data/lib/rubocop/cop/rails/schema_comment.rb +104 -0
  113. data/lib/rubocop/cop/rails/scope_args.rb +8 -3
  114. data/lib/rubocop/cop/rails/short_i18n.rb +71 -0
  115. data/lib/rubocop/cop/rails/skips_model_validations.rb +53 -16
  116. data/lib/rubocop/cop/rails/squished_sql_heredocs.rb +87 -0
  117. data/lib/rubocop/cop/rails/strip_heredoc.rb +56 -0
  118. data/lib/rubocop/cop/rails/table_name_assignment.rb +44 -0
  119. data/lib/rubocop/cop/rails/three_state_boolean_column.rb +73 -0
  120. data/lib/rubocop/cop/rails/time_zone.rb +83 -67
  121. data/lib/rubocop/cop/rails/time_zone_assignment.rb +37 -0
  122. data/lib/rubocop/cop/rails/to_formatted_s.rb +46 -0
  123. data/lib/rubocop/cop/rails/to_s_with_argument.rb +78 -0
  124. data/lib/rubocop/cop/rails/top_level_hash_with_indifferent_access.rb +49 -0
  125. data/lib/rubocop/cop/rails/transaction_exit_statement.rb +99 -0
  126. data/lib/rubocop/cop/rails/uniq_before_pluck.rb +40 -49
  127. data/lib/rubocop/cop/rails/unique_validation_without_index.rb +172 -0
  128. data/lib/rubocop/cop/rails/unknown_env.rb +52 -21
  129. data/lib/rubocop/cop/rails/unused_ignored_columns.rb +76 -0
  130. data/lib/rubocop/cop/rails/validation.rb +54 -23
  131. data/lib/rubocop/cop/rails/where_equals.rb +102 -0
  132. data/lib/rubocop/cop/rails/where_exists.rb +138 -0
  133. data/lib/rubocop/cop/rails/where_missing.rb +118 -0
  134. data/lib/rubocop/cop/rails/where_not.rb +101 -0
  135. data/lib/rubocop/cop/rails/where_not_with_multiple_conditions.rb +55 -0
  136. data/lib/rubocop/cop/rails_cops.rb +78 -8
  137. data/lib/rubocop/rails/inject.rb +1 -1
  138. data/lib/rubocop/rails/schema_loader/schema.rb +191 -0
  139. data/lib/rubocop/rails/schema_loader.rb +61 -0
  140. data/lib/rubocop/rails/version.rb +5 -1
  141. data/lib/rubocop/rails.rb +3 -1
  142. data/lib/rubocop-rails.rb +22 -0
  143. metadata +120 -19
  144. data/bin/setup +0 -7
@@ -3,51 +3,70 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop is used to identify usages of `where.first` and
7
- # change them to use `find_by` instead.
6
+ # Identifies usages of `where.take` and change them to use `find_by` instead.
7
+ #
8
+ # And `where(...).first` can return different results from `find_by`.
9
+ # (They order records differently, so the "first" record can be different.)
10
+ #
11
+ # If you also want to detect `where.first`, you can set `IgnoreWhereFirst` to false.
8
12
  #
9
13
  # @example
10
14
  # # bad
11
- # User.where(name: 'Bruce').first
12
15
  # User.where(name: 'Bruce').take
13
16
  #
14
17
  # # good
15
18
  # User.find_by(name: 'Bruce')
16
- class FindBy < Cop
19
+ #
20
+ # @example IgnoreWhereFirst: true (default)
21
+ # # good
22
+ # User.where(name: 'Bruce').first
23
+ #
24
+ # @example IgnoreWhereFirst: false
25
+ # # bad
26
+ # User.where(name: 'Bruce').first
27
+ class FindBy < Base
17
28
  include RangeHelp
29
+ extend AutoCorrector
18
30
 
19
31
  MSG = 'Use `find_by` instead of `where.%<method>s`.'
20
- TARGET_SELECTORS = %i[first take].freeze
21
-
22
- def_node_matcher :where_first?, <<-PATTERN
23
- (send ({send csend} _ :where ...) {:first :take})
24
- PATTERN
32
+ RESTRICT_ON_SEND = %i[first take].freeze
25
33
 
26
34
  def on_send(node)
27
- return unless where_first?(node)
35
+ return unless node.arguments.empty? && where_method?(node.receiver)
36
+ return if ignore_where_first? && node.method?(:first)
28
37
 
29
- range = range_between(node.receiver.loc.selector.begin_pos,
30
- node.loc.selector.end_pos)
38
+ range = offense_range(node)
31
39
 
32
- add_offense(node, location: range,
33
- message: format(MSG, method: node.method_name))
40
+ add_offense(range, message: format(MSG, method: node.method_name)) do |corrector|
41
+ autocorrect(corrector, node)
42
+ end
34
43
  end
35
44
  alias on_csend on_send
36
45
 
37
- def autocorrect(node)
38
- # Don't autocorrect where(...).first, because it can return different
39
- # results from find_by. (They order records differently, so the
40
- # 'first' record can be different.)
46
+ private
47
+
48
+ def where_method?(receiver)
49
+ return false unless receiver
50
+
51
+ receiver.respond_to?(:method?) && receiver.method?(:where)
52
+ end
53
+
54
+ def offense_range(node)
55
+ range_between(node.receiver.loc.selector.begin_pos, node.loc.selector.end_pos)
56
+ end
57
+
58
+ def autocorrect(corrector, node)
41
59
  return if node.method?(:first)
42
60
 
43
61
  where_loc = node.receiver.loc.selector
44
- first_loc = range_between(node.loc.dot.begin_pos,
45
- node.loc.selector.end_pos)
62
+ first_loc = range_between(node.loc.dot.begin_pos, node.loc.selector.end_pos)
46
63
 
47
- lambda do |corrector|
48
- corrector.replace(where_loc, 'find_by')
49
- corrector.replace(first_loc, '')
50
- end
64
+ corrector.replace(where_loc, 'find_by')
65
+ corrector.replace(first_loc, '')
66
+ end
67
+
68
+ def ignore_where_first?
69
+ cop_config.fetch('IgnoreWhereFirst', true)
51
70
  end
52
71
  end
53
72
  end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # 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.source_range.end_pos)
69
+ end
70
+
71
+ def find_by_offense_range(node)
72
+ range_between(node.loc.selector.begin_pos, node.source_range.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
@@ -3,8 +3,12 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop is used to identify usages of `all.each` and
7
- # change them to use `all.find_each` instead.
6
+ # Identifies usages of `all.each` and change them to use `all.find_each` instead.
7
+ #
8
+ # @safety
9
+ # This cop is unsafe if the receiver object is not an Active Record object.
10
+ # Also, `all.each` returns an `Array` instance and `all.find_each` returns nil,
11
+ # so the return values are different.
8
12
  #
9
13
  # @example
10
14
  # # bad
@@ -12,38 +16,58 @@ module RuboCop
12
16
  #
13
17
  # # good
14
18
  # User.all.find_each
15
- class FindEach < Cop
19
+ #
20
+ # @example AllowedMethods: ['order']
21
+ # # good
22
+ # User.order(:foo).each
23
+ #
24
+ # @example AllowedPattern: ['order']
25
+ # # good
26
+ # User.order(:foo).each
27
+ class FindEach < Base
28
+ include ActiveRecordHelper
29
+ include AllowedMethods
30
+ include AllowedPattern
31
+ extend AutoCorrector
32
+
16
33
  MSG = 'Use `find_each` instead of `each`.'
34
+ RESTRICT_ON_SEND = %i[each].freeze
17
35
 
18
36
  SCOPE_METHODS = %i[
19
37
  all eager_load includes joins left_joins left_outer_joins not preload
20
38
  references unscoped where
21
39
  ].freeze
22
- IGNORED_METHODS = %i[order limit select].freeze
23
40
 
24
41
  def on_send(node)
25
- return unless node.receiver&.send_type? &&
26
- node.method?(:each)
27
-
42
+ return unless node.receiver&.send_type?
28
43
  return unless SCOPE_METHODS.include?(node.receiver.method_name)
29
- return if method_chain(node).any? { |m| ignored_by_find_each?(m) }
44
+ return if node.receiver.receiver.nil? && !inherit_active_record_base?(node)
45
+ return if ignored?(node)
30
46
 
31
- add_offense(node, location: :selector)
32
- end
33
-
34
- def autocorrect(node)
35
- ->(corrector) { corrector.replace(node.loc.selector, 'find_each') }
47
+ range = node.loc.selector
48
+ add_offense(range) do |corrector|
49
+ corrector.replace(range, 'find_each')
50
+ end
36
51
  end
37
52
 
38
53
  private
39
54
 
40
- def method_chain(node)
41
- node.each_node(:send).map(&:method_name)
55
+ def ignored?(node)
56
+ return true if active_model_error_where?(node.receiver)
57
+
58
+ method_chain = node.each_node(:send).map(&:method_name)
59
+
60
+ method_chain.any? { |method_name| allowed_method?(method_name) || matches_allowed_pattern?(method_name) }
42
61
  end
43
62
 
44
- def ignored_by_find_each?(relation_method)
45
- # Active Record's #find_each ignores various extra parameters
46
- IGNORED_METHODS.include?(relation_method)
63
+ def active_model_error_where?(node)
64
+ node.method?(:where) && active_model_error?(node.receiver)
65
+ end
66
+
67
+ def active_model_error?(node)
68
+ return false if node.nil?
69
+
70
+ node.send_type? && node.method?(:errors)
47
71
  end
48
72
  end
49
73
  end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # Identifies usages of `travel_to` with an argument of the current time and
7
+ # change them to use `freeze_time` instead.
8
+ #
9
+ # @safety
10
+ # This cop’s autocorrection is unsafe because `freeze_time` just delegates to
11
+ # `travel_to` with a default `Time.now`, it is not strictly equivalent to `Time.now`
12
+ # if the argument of `travel_to` is the current time considering time zone.
13
+ #
14
+ # @example
15
+ # # bad
16
+ # travel_to(Time.now)
17
+ # travel_to(Time.new)
18
+ # travel_to(DateTime.now)
19
+ # travel_to(Time.current)
20
+ # travel_to(Time.zone.now)
21
+ # travel_to(Time.now.in_time_zone)
22
+ # travel_to(Time.current.to_time)
23
+ #
24
+ # # good
25
+ # freeze_time
26
+ #
27
+ class FreezeTime < Base
28
+ extend AutoCorrector
29
+ extend TargetRailsVersion
30
+
31
+ minimum_target_rails_version 5.2
32
+
33
+ MSG = 'Use `freeze_time` instead of `travel_to`.'
34
+ NOW_METHODS = %i[now new current].freeze
35
+ CONVERT_METHODS = %i[to_time in_time_zone].freeze
36
+ RESTRICT_ON_SEND = %i[travel_to].freeze
37
+
38
+ # @!method time_now?(node)
39
+ def_node_matcher :time_now?, <<~PATTERN
40
+ (const {nil? cbase} {:Time :DateTime})
41
+ PATTERN
42
+
43
+ # @!method zoned_time_now?(node)
44
+ def_node_matcher :zoned_time_now?, <<~PATTERN
45
+ (send (const {nil? cbase} :Time) :zone)
46
+ PATTERN
47
+
48
+ def on_send(node)
49
+ child_node, method_name, time_argument = *node.first_argument&.children
50
+ return if time_argument || !child_node
51
+ return unless current_time?(child_node, method_name) || current_time_with_convert?(child_node, method_name)
52
+
53
+ add_offense(node) do |corrector|
54
+ last_argument = node.last_argument
55
+ freeze_time_method = last_argument.block_pass_type? ? "freeze_time(#{last_argument.source})" : 'freeze_time'
56
+ corrector.replace(node, freeze_time_method)
57
+ end
58
+ end
59
+
60
+ private
61
+
62
+ def current_time?(node, method_name)
63
+ return false unless NOW_METHODS.include?(method_name)
64
+
65
+ node.send_type? ? zoned_time_now?(node) : time_now?(node)
66
+ end
67
+
68
+ def current_time_with_convert?(node, method_name)
69
+ return false unless CONVERT_METHODS.include?(method_name)
70
+
71
+ child_node, child_method_name, time_argument = *node.children
72
+ return if time_argument
73
+
74
+ current_time?(child_node, child_method_name)
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop checks for the use of the has_and_belongs_to_many macro.
6
+ # Checks for the use of the has_and_belongs_to_many macro.
7
7
  #
8
8
  # @example
9
9
  # # bad
@@ -11,13 +11,14 @@ module RuboCop
11
11
  #
12
12
  # # good
13
13
  # # has_many :ingredients, through: :recipe_ingredients
14
- class HasAndBelongsToMany < Cop
14
+ class HasAndBelongsToMany < Base
15
15
  MSG = 'Prefer `has_many :through` to `has_and_belongs_to_many`.'
16
+ RESTRICT_ON_SEND = %i[has_and_belongs_to_many].freeze
16
17
 
17
18
  def on_send(node)
18
19
  return unless node.command?(:has_and_belongs_to_many)
19
20
 
20
- add_offense(node, location: :selector)
21
+ add_offense(node.loc.selector)
21
22
  end
22
23
  end
23
24
  end
@@ -3,9 +3,11 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop looks for `has_many` or `has_one` associations that don't
6
+ # Looks for `has_many` or `has_one` associations that don't
7
7
  # specify a `:dependent` option.
8
- # It doesn't register an offense if `:through` option was specified.
8
+ #
9
+ # It doesn't register an offense if `:through` or `dependent: nil`
10
+ # is specified, or if the model is read-only.
9
11
  #
10
12
  # @example
11
13
  # # bad
@@ -18,75 +20,102 @@ module RuboCop
18
20
  # class User < ActiveRecord::Base
19
21
  # has_many :comments, dependent: :restrict_with_exception
20
22
  # has_one :avatar, dependent: :destroy
23
+ # has_many :articles, dependent: nil
21
24
  # has_many :patients, through: :appointments
22
25
  # end
23
- class HasManyOrHasOneDependent < Cop
26
+ #
27
+ # class User < ActiveRecord::Base
28
+ # has_many :comments
29
+ # has_one :avatar
30
+ #
31
+ # def readonly?
32
+ # true
33
+ # end
34
+ # end
35
+ class HasManyOrHasOneDependent < Base
24
36
  MSG = 'Specify a `:dependent` option.'
37
+ RESTRICT_ON_SEND = %i[has_many has_one].freeze
25
38
 
26
- def_node_search :active_resource_class?, <<-PATTERN
27
- (const (const nil? :ActiveResource) :Base)
39
+ def_node_search :active_resource_class?, <<~PATTERN
40
+ (const (const {nil? cbase} :ActiveResource) :Base)
28
41
  PATTERN
29
42
 
30
- def_node_matcher :association_without_options?, <<-PATTERN
31
- (send nil? {:has_many :has_one} _)
43
+ def_node_matcher :association_without_options?, <<~PATTERN
44
+ (send _ {:has_many :has_one} _)
32
45
  PATTERN
33
46
 
34
- def_node_matcher :association_with_options?, <<-PATTERN
35
- (send nil? {:has_many :has_one} _ (hash $...))
47
+ def_node_matcher :association_with_options?, <<~PATTERN
48
+ (send _ {:has_many :has_one} ... (hash $...))
36
49
  PATTERN
37
50
 
38
- def_node_matcher :dependent_option?, <<-PATTERN
39
- (pair (sym :dependent) !nil)
51
+ def_node_matcher :dependent_option?, <<~PATTERN
52
+ (pair (sym :dependent) {!nil (nil)})
40
53
  PATTERN
41
54
 
42
- def_node_matcher :present_option?, <<-PATTERN
55
+ def_node_matcher :present_option?, <<~PATTERN
43
56
  (pair (sym :through) !nil)
44
57
  PATTERN
45
58
 
46
- def_node_matcher :with_options_block, <<-PATTERN
59
+ def_node_matcher :with_options_block, <<~PATTERN
47
60
  (block
48
61
  (send nil? :with_options
49
62
  (hash $...))
50
63
  (args) ...)
51
64
  PATTERN
52
65
 
53
- def on_send(node)
54
- return if active_resource?(node.parent)
66
+ def_node_matcher :association_extension_block?, <<~PATTERN
67
+ (block
68
+ (send nil? :has_many _)
69
+ (args) ...)
70
+ PATTERN
55
71
 
56
- unless association_without_options?(node)
57
- return if valid_options?(association_with_options?(node))
58
- end
72
+ def_node_matcher :readonly?, <<~PATTERN
73
+ (def :readonly?
74
+ (args)
75
+ (true))
76
+ PATTERN
59
77
 
78
+ def on_send(node)
79
+ return if active_resource?(node.parent) || readonly_model?(node)
80
+ return if !association_without_options?(node) && valid_options?(association_with_options?(node))
60
81
  return if valid_options_in_with_options_block?(node)
61
82
 
62
- add_offense(node, location: :selector)
83
+ add_offense(node.loc.selector)
63
84
  end
64
85
 
65
86
  private
66
87
 
88
+ def readonly_model?(node)
89
+ return false unless (parent = node.parent)
90
+
91
+ parent.each_descendant(:def).any? { |def_node| readonly?(def_node) }
92
+ end
93
+
67
94
  def valid_options_in_with_options_block?(node)
68
95
  return true unless node.parent
69
96
 
70
- n = node.parent.begin_type? ? node.parent.parent : node.parent
97
+ n = node.parent.begin_type? || association_extension_block?(node.parent) ? node.parent.parent : node.parent
71
98
 
72
99
  contain_valid_options_in_with_options_block?(n)
73
100
  end
74
101
 
75
102
  def contain_valid_options_in_with_options_block?(node)
76
- if with_options_block(node)
77
- return true if valid_options?(with_options_block(node))
103
+ if (options = with_options_block(node))
104
+ return true if valid_options?(options)
78
105
 
79
106
  return false unless node.parent
80
107
 
81
- return true if contain_valid_options_in_with_options_block?(
82
- node.parent.parent
83
- )
108
+ return true if contain_valid_options_in_with_options_block?(node.parent.parent)
84
109
  end
85
110
 
86
111
  false
87
112
  end
88
113
 
89
114
  def valid_options?(options)
115
+ return false if options.nil?
116
+
117
+ options = extract_option_if_kwsplat(options)
118
+
90
119
  return true unless options
91
120
  return true if options.any? do |o|
92
121
  dependent_option?(o) || present_option?(o)
@@ -95,6 +124,14 @@ module RuboCop
95
124
  false
96
125
  end
97
126
 
127
+ def extract_option_if_kwsplat(options)
128
+ if options.first.kwsplat_type? && options.first.children.first.hash_type?
129
+ return options.first.children.first.pairs
130
+ end
131
+
132
+ options
133
+ end
134
+
98
135
  def active_resource?(node)
99
136
  return false if node.nil?
100
137
 
@@ -3,16 +3,19 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop checks for use of the helper methods which reference
6
+ # Checks for use of the helper methods which reference
7
7
  # instance variables.
8
8
  #
9
9
  # Relying on instance variables makes it difficult to re-use helper
10
10
  # methods.
11
11
  #
12
12
  # If it seems awkward to explicitly pass in each dependent
13
- # variable, consider moving the behaviour elsewhere, for
13
+ # variable, consider moving the behavior elsewhere, for
14
14
  # example to a model, decorator or presenter.
15
15
  #
16
+ # Provided that a class inherits `ActionView::Helpers::FormBuilder`,
17
+ # an offense will not be registered.
18
+ #
16
19
  # @example
17
20
  # # bad
18
21
  # def welcome_message
@@ -23,15 +26,40 @@ module RuboCop
23
26
  # def welcome_message(user)
24
27
  # "Hello #{user.name}"
25
28
  # end
26
- class HelperInstanceVariable < Cop
29
+ #
30
+ # # good
31
+ # class MyFormBuilder < ActionView::Helpers::FormBuilder
32
+ # @template.do_something
33
+ # end
34
+ class HelperInstanceVariable < Base
27
35
  MSG = 'Do not use instance variables in helpers.'
28
36
 
37
+ def_node_matcher :form_builder_class?, <<~PATTERN
38
+ (const
39
+ (const
40
+ (const {nil? cbase} :ActionView) :Helpers) :FormBuilder)
41
+ PATTERN
42
+
29
43
  def on_ivar(node)
44
+ return if inherit_form_builder?(node)
45
+
30
46
  add_offense(node)
31
47
  end
32
48
 
33
49
  def on_ivasgn(node)
34
- add_offense(node, location: :name)
50
+ return if node.parent.or_asgn_type? || inherit_form_builder?(node)
51
+
52
+ add_offense(node.loc.name)
53
+ end
54
+
55
+ private
56
+
57
+ def inherit_form_builder?(node)
58
+ node.each_ancestor(:class) do |class_node|
59
+ return true if form_builder_class?(class_node.parent_class)
60
+ end
61
+
62
+ false
35
63
  end
36
64
  end
37
65
  end