Home > Software engineering >  Rails - Validate presence of one of two belongs_to columns
Rails - Validate presence of one of two belongs_to columns

Time:10-26

I have a model called Log that has relations to two models ButtonPress and LinkClick. I need to validate the presence of either (for analytics reasons, I'm required to do this without using polymorphic relations)

My Spec file is as follows:

RSpec.describe RedirectLog, type: :model do
  it { is_expected.to belong_to(:button_press).optional }
  it { is_expected.to belong_to(:link_click).optional }

  it "has either a button_press or a link_click" do
    log = build(:log, button_press: nil, link_click: nil)
    expect(log).not_to be_valid
  end

end

To achieve this I created the model as follows:

class Log < ApplicationRecord
  belongs_to :button_press, optional: true
  belongs_to :link_click, optional: true

  validates :button_press, presence: true, unless: :link_click
end

When I run this, I get the following error:

Failure/Error: it { is_expected.to belong_to(:button_press).optional }
       Expected Log to have a belongs_to association called button_press (and for the record not to fail validation if :button_press is unset; i.e., either the association should have been defined with `optional: true`, or there should not be a presence validation on :button_press)

So in short, I cant have the belongs_to association if I want to validate its presence, even conditionally. My question is, what is the Rails way to have a validation on one of two belongs_to columns without using polymorphism?

CodePudding user response:

One way to do it is with a custom validator. I'd make it completely formal with its own file and such, but for a proof-of-concept you could do this:

class Log < ApplicationRecord

  ###################################################################
  #
  # Associations
  #
  ###################################################################
  belongs_to :button_press, optional: true
  belongs_to :link_click,   optional: true



  ###################################################################
  #
  # Validations
  #
  ###################################################################
  validate :button_press_or_link_click_presence



  ###################################################################
  #
  # In-Model Custom Validators (Private)
  #
  ###################################################################
  def button_press_or_link_click_presence
    self.errors.add(:base, 'Either a Button Press or a Link Click is required, but both are missing.') if button_press.blank? && link_click.blank?
  end
  private :button_press_or_link_click_presence
end
  • Related