Search code examples
ruby-on-railsrubyrspecmodelrspec-rails

Validation on rails that permits first_name or last_name to be null but not both


class Profile < ApplicationRecord
  belongs_to :user
  validate :first_or_last_name_null
  

  def first_or_last_name_null
    if first_name.nil? && last_name.nil?
        errors.add(:base, "either first_name or last_name must be present!")
    end
  end

I don't know what is wrong in my lines of code to get the following error from rspec.. Assignment rq11 Validators: allows a Profile with a null first name when last name present

 Failure/Error: expect(Profile.new(:first_name=>nil, :last_name=>"Smith", :gender=>"male")).to be_valid
   expected `#<Profile id: nil, gender: "male", birth_year: nil, first_name: nil, last_name: "Smith", user_id: nil, created_at: nil, updated_at: nil>.valid?` to return true, got false

The spec file has the following ..

context "rq11" do
      context "Validators:" do
        it "does not allow a User without a username" do
          expect(User.new(:username=> "")).to_not be_valid
        end

        it "does not allow a Profile with a null first and last name" do
          expect(Profile.new(:first_name=>nil, :last_name=>nil, :gender=>"male")).to_not be_valid
        end
        it "allows a Profile with a null first name when last name present" do
          expect(Profile.new(:first_name=>nil, :last_name=>"Smith", :gender=>"male")).to be_valid
        end
        it "allows a Profile with a null last name when first name present" do
          expect(Profile.new(:first_name=>"Joe", :last_name=>nil, :gender=>"male")).to be_valid
        end

        it "does not allow a Profile with a gender other than male or female " do
          expect(Profile.new(:first_name=>"first", :last_name=>"last", :gender=>"neutral")).to_not be_valid
        end
        it "does not allow a boy named Sue" do
          expect(Profile.new(:first_name=>"Sue", :last_name=>"last", :gender=>"male")).to_not be_valid
        end
        it "allows a Profile with gender male" do
          expect(Profile.new(:first_name=>"first", :last_name=>"last", :gender=>"male")).to be_valid
        end
        it "allows a Profile with gender female" do
          expect(Profile.new(:first_name=>"first", :last_name=>"last", :gender=>"female")).to be_valid
        end
      end
    end

Solution

  • While Roman's answer is correct, I would like to add more details and more options to solve the issue.

    Your Profile belong_to :user. Per default belongs_to associations require the associated object to exist. In this case, there must be a user associated with a profile otherwise the profile will not be valid.

    You have three options to fix this issue, depending on your use-case:

    1. Make the association optional, read about optional belongs_to associations in the Rails Guides. This obviously is only an option if it makes sense in the context of your application that there is no need for an association to always exist.

      belongs_to :user, optional: true
      

      optional: true disables the built-in validation.

    2. You make sure that each profile in your spec has always a valid user assigned. Something like this might work:

      let(:user) { User.find_or_create_by(username: 'test_user') }
      
      it "does not allow a Profile with a null first and last name" do
        expect(Profile.new(user: user, first_name: nil, last_name: nil, gender: "male")).to_not be_valid
      end
      
    3. You do not only test if an instance is valid, but instead, if there is an expected, specific error

      it "does not allow a Profile with a null first and last name" do
        profile = Profile.new(:first_name=>nil, :last_name=>nil, :gender=>"male")
        profile.valid? # trigger validations
      
        # with all Rails versions
        expect(profile.errors[:base]).to include "either first_name or last_name must be present!"
      
        # or with Rails >= 6:
        expect(profile.errors).to be_of_kind(:base, "either first_name or last_name must be present!")
      end