Class: RuboCop::Cop::Rails::RedundantPresenceValidationOnBelongsTo

Inherits:
Base
  • Object
show all
Extended by:
AutoCorrector, TargetRailsVersion
Includes:
RangeHelp
Defined in:
lib/rubocop/cop/rails/redundant_presence_validation_on_belongs_to.rb

Overview

Since Rails 5.0 the default for ‘belongs_to` is `optional: false` unless `config.active_record.belongs_to_required_by_default` is explicitly set to `false`. The presence validator is added automatically, and explicit presence validation is redundant.

Examples:

# bad
belongs_to :user
validates :user, presence: true

# bad
belongs_to :user
validates :user_id, presence: true

# bad
belongs_to :author, foreign_key: :user_id
validates :user_id, presence: true

# good
belongs_to :user

# good
belongs_to :author, foreign_key: :user_id

Constant Summary collapse

MSG =
'Remove explicit presence validation for %<association>s.'
RESTRICT_ON_SEND =
%i[validates].freeze
NON_VALIDATION_OPTIONS =
%i[if unless on allow_blank allow_nil strict].freeze

Constants included from TargetRailsVersion

TargetRailsVersion::TARGET_GEM_NAME, TargetRailsVersion::USES_REQUIRES_GEM_API

Instance Method Summary collapse

Methods included from TargetRailsVersion

minimum_target_rails_version, support_target_rails_version?

Instance Method Details

#any_belongs_to?(node, association: ) ⇒ Array<RuboCop::AST::Node>?

Match a class with ‘belongs_to` with no regard to `foreign_key` option

Examples:

source that matches

belongs_to :user

source that matches - regardless of ‘foreign_key`

belongs_to :author, foreign_key: :user_id

Parameters:

  • node (RuboCop::AST::Node)
  • association (Symbol) (defaults to: )

Returns:

  • (Array<RuboCop::AST::Node>, nil)

    matching node



109
110
111
112
113
114
115
116
# File 'lib/rubocop/cop/rails/redundant_presence_validation_on_belongs_to.rb', line 109

def_node_matcher :any_belongs_to?, <<~PATTERN
  (begin
    <
      $(send nil? :belongs_to (sym %association) ...)
      ...
    >
  )
PATTERN

#belongs_to?(node, key: , fk: ) ⇒ Array<RuboCop::AST::Node>

Match a class with a matching association, either by name or an explicit ‘foreign_key` option

Examples:

source that matches - fk matches ‘foreign_key` option

belongs_to :author, foreign_key: :user_id

source that matches - key matches association name

belongs_to :user

source that does not match - explicit ‘foreign_key` does not match

belongs_to :user, foreign_key: :account_id

Parameters:

  • node (RuboCop::AST::Node)
  • key (Symbol) (defaults to: )

    e.g. ‘:user`

  • fk (Symbol) (defaults to: )

    e.g. ‘:user_id`

Returns:

  • (Array<RuboCop::AST::Node>)

    matching nodes



135
136
137
138
139
140
141
142
143
144
145
# File 'lib/rubocop/cop/rails/redundant_presence_validation_on_belongs_to.rb', line 135

def_node_matcher :belongs_to?, <<~PATTERN
  (begin
    <
      ${
        #belongs_to_without_fk?(%key)         # belongs_to :user
        #belongs_to_with_a_matching_fk?(%fk)  # belongs_to :author, foreign_key: :user_id
      }
      ...
    >
  )
PATTERN

#belongs_to_with_a_matching_fk?(node, fk) ⇒ Array<RuboCop::AST::Node>

Match a matching ‘belongs_to` association with a matching explicit `foreign_key` option

Examples:

source that matches

belongs_to :author, foreign_key: :user_id

Parameters:

  • node (RuboCop::AST::Node)
  • fk (Symbol)

    e.g. ‘:user_id`

Returns:

  • (Array<RuboCop::AST::Node>)

    matching nodes



170
171
172
# File 'lib/rubocop/cop/rails/redundant_presence_validation_on_belongs_to.rb', line 170

def_node_matcher :belongs_to_with_a_matching_fk?, <<~PATTERN
  (send nil? :belongs_to ... (hash <(pair (sym :foreign_key) (sym %1)) ...>))
PATTERN

#belongs_to_without_fk?(node, key) ⇒ Array<RuboCop::AST::Node>

Match a matching ‘belongs_to` association, without an explicit `foreign_key` option

Parameters:

  • node (RuboCop::AST::Node)
  • key (Symbol)

    e.g. ‘:user`

Returns:

  • (Array<RuboCop::AST::Node>)

    matching nodes



153
154
155
156
157
158
159
# File 'lib/rubocop/cop/rails/redundant_presence_validation_on_belongs_to.rb', line 153

def_node_matcher :belongs_to_without_fk?, <<~PATTERN
  {
    (send nil? :belongs_to (sym %1))        # belongs_to :user
    (send nil? :belongs_to (sym %1) !hash ...)  # belongs_to :user, -> { not_deleted }
    (send nil? :belongs_to (sym %1) !(hash <(pair (sym :foreign_key) _) ...>))
  }
PATTERN

#on_send(node) ⇒ Object



174
175
176
177
178
179
180
181
182
183
184
185
186
187
# File 'lib/rubocop/cop/rails/redundant_presence_validation_on_belongs_to.rb', line 174

def on_send(node)
  presence_validation?(node) do |all_keys, options, presence|
    # If presence is the only validation option and other non-validation options
    # are present, removing it will cause rails to error.
    used_option_keys = options.keys.select(&:sym_type?).map(&:value)
    remaining_validations = used_option_keys - NON_VALIDATION_OPTIONS - [:presence]
    return if remaining_validations.none? && options.keys.length > 1

    keys = non_optional_belongs_to(node.parent, all_keys)
    return if keys.none?

    add_offense_and_correct(node, all_keys, keys, options, presence)
  end
end

#optional?(node) ⇒ Object

Match a ‘belongs_to` association with an optional option in a hash



84
85
86
# File 'lib/rubocop/cop/rails/redundant_presence_validation_on_belongs_to.rb', line 84

def_node_matcher :optional?, <<~PATTERN
  (send nil? :belongs_to _ ... #optional_option?)
PATTERN

#optional_option?(node) ⇒ Object

Match an optional option in a hash



90
91
92
93
94
95
# File 'lib/rubocop/cop/rails/redundant_presence_validation_on_belongs_to.rb', line 90

def_node_matcher :optional_option?, <<~PATTERN
  {
    (hash <(pair (sym :optional) true) ...>)   # optional: true
    (hash <(pair (sym :required) false) ...>)  # required: false
  }
PATTERN

#presence_validation?(node) ⇒ Object

Match a ‘validates` statement with a presence check

Examples:

source that matches - by association

validates :user, presence: true

source that matches - by association

validates :name, :user, presence: true

source that matches - by a foreign key

validates :user_id, presence: true

source that DOES NOT match - if condition

validates :user_id, presence: true, if: condition

source that DOES NOT match - unless condition

validates :user_id, presence: true, unless: condition

source that DOES NOT match - strict validation

validates :user_id, presence: true, strict: true

source that DOES NOT match - custom strict validation

validates :user_id, presence: true, strict: MissingUserError


70
71
72
73
74
75
76
77
78
79
80
# File 'lib/rubocop/cop/rails/redundant_presence_validation_on_belongs_to.rb', line 70

def_node_matcher :presence_validation?, <<~PATTERN
  (
    send nil? :validates
    (sym $_)+
    $[
      (hash <$(pair (sym :presence) true) ...>)         # presence: true
      !(hash <$(pair (sym :strict) {true const}) ...>)  # strict: true
      !(hash <$(pair (sym {:if :unless}) _) ...>)       # if: some_condition or unless: some_condition
    ]
  )
PATTERN