Class: RuboCop::Cop::Gusto::PolymorphicTypeValidation

Inherits:
Base
  • Object
show all
Defined in:
lib/rubocop/cop/gusto/polymorphic_type_validation.rb

Overview

Require polymorphic relations to validate their ‘*_type` field with an inclusion validation (or `polymorphic_methods_for`). This is needed for Tapioca to generate correct Sorbet types.

Examples:

# bad
belongs_to :subscription_detail, polymorphic: true

# good
VALID_TYPES = T.let([Foo.polymorphic_name, Bar.polymorphic_name].freeze, T::Array[String])
belongs_to :subscription_detail, polymorphic: true
validates :subscription_detail_type, presence: true, inclusion: { in: VALID_TYPES }

# also good
include PolymorphicCallable
VALID_TYPES = T.let([Foo.polymorphic_name, Bar.polymorphic_name].freeze, T::Array[String])
belongs_to :subscription_detail, polymorphic: true
polymorphic_methods_for :subscription_detail, VALID_TYPES

Constant Summary collapse

RESTRICT_ON_SEND =
%i(belongs_to validates polymorphic_methods_for).freeze
MSG =
<<~MESSAGE
  Polymorphic relations must validate their corresponding type field with "validates .. inclusion: { in: .. }", or using polymorphic_methods_for

  Example:
    # bad
    belongs_to :subscription_detail, polymorphic: true

    # good
    VALID_TYPES = T.let([LidiSubscriptionDetail.polymorphic_name, LegacyActiveBenefits::VoluntaryLifeSubscriptionDetail.polymorphic_name].freeze, T::Array[String])
    belongs_to :subscription_detail, polymorphic: true
    validates :subscription_detail_type, presence: true, inclusion: { in: VALID_TYPES }

    # also good (in ZP/HI, at least)
    include PolymorphicCallable
    VALID_TYPES = T.let([LidiSubscriptionDetail.polymorphic_name, LegacyActiveBenefits::VoluntaryLifeSubscriptionDetail.polymorphic_name].freeze, T::Array[String])
    belongs_to :subscription_detail, polymorphic: true
    polymorphic_methods_for :subscription_detail, VALID_TYPES
MESSAGE
ALLOW_BLANK_MSG =
"Polymorphic type validations cannot use allow_blank: true"

Instance Method Summary collapse

Instance Method Details

#allow_blank?(node) ⇒ Object



69
70
71
# File 'lib/rubocop/cop/gusto/polymorphic_type_validation.rb', line 69

def_node_matcher :allow_blank?, <<~PATTERN
  (pair (sym :allow_blank) (true))
PATTERN

#inclusion_in?(node) ⇒ Object



59
60
61
# File 'lib/rubocop/cop/gusto/polymorphic_type_validation.rb', line 59

def_node_matcher :inclusion_in?, <<~PATTERN
  (pair (sym :inclusion) (hash <(pair (sym :in) _) ...>))
PATTERN

#on_send(node) ⇒ Object



73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/rubocop/cop/gusto/polymorphic_type_validation.rb', line 73

def on_send(node)
  return unless polymorphic_relation?(node)

  relation_name = node.first_argument.value
  type_field = :"#{relation_name}_type"

  # Look for either a validation of the type field or polymorphic_methods_for
  has_validation = false
  has_allow_blank = false

  node.parent.each_node(:send) do |validation_node|
    if type_validation?(validation_node) && validation_node.first_argument.value == type_field
      has_validation = true
      # Check for allow_blank in the validation options
      validation_node.last_argument.each_node(:pair) do |pair_node|
        has_allow_blank = true if allow_blank?(pair_node)
      end
    elsif polymorphic_methods_for?(validation_node) && validation_node.first_argument.value == relation_name
      has_validation = true
    end
  end

  if has_allow_blank
    add_offense(node, message: ALLOW_BLANK_MSG)
  elsif !has_validation
    add_offense(node)
  end
end

#polymorphic_methods_for?(node) ⇒ Object



64
65
66
# File 'lib/rubocop/cop/gusto/polymorphic_type_validation.rb', line 64

def_node_matcher :polymorphic_methods_for?, <<~PATTERN
  (send nil? :polymorphic_methods_for (sym _) _)
PATTERN

#polymorphic_relation?(node) ⇒ Object



49
50
51
# File 'lib/rubocop/cop/gusto/polymorphic_type_validation.rb', line 49

def_node_matcher :polymorphic_relation?, <<~PATTERN
  (send nil? :belongs_to _ (hash <(pair (sym :polymorphic) (true)) ...>))
PATTERN

#type_validation?(node) ⇒ Object



54
55
56
# File 'lib/rubocop/cop/gusto/polymorphic_type_validation.rb', line 54

def_node_matcher :type_validation?, <<~PATTERN
  (send nil? :validates (sym _) (hash <#inclusion_in? ...>))
PATTERN