Home > other >  Rails conditional validation: if: doesn't working
Rails conditional validation: if: doesn't working

Time:10-20

I'm new to rails, I have a trip class with three foreign key. Two of these associate it with the same class: Place.

This is my model:

class Trip < ApplicationRecord
    belongs_to :from, class_name: "Place", foreign_key: "from_id"
    belongs_to :to, class_name: "Place", foreign_key: "to_id"
    belongs_to :vehicle, class_name: "Vehicle", foreign_key: "vehicle_id"

    validates :price, presence: true
    validates :time, presence: true
    validates :from_id, presence: true
    validates :to_id, presence: true, if: :from_different_to?
    
    def from_different_to?
        to_id != from_id
    end
end

All model tests pass except for the last one:

class TripTest < ActiveSupport::TestCase

  def setup
    @place1 = Place.create(name:"NewYork",cap:"11111",lat:"1234",long:"1478")
    @place2 = Place.create(name:"Los Angeles", cap:"22222", lat:"1234",long:"1478")
    @vehicle = Vehicle.create(targa: "ab123cd",modello:"500",marca:"Fiat", posti:5,alimentazione:"benzina")
    @trip = Trip.new(price: 10, time: Time.new(2021, 10, 14, 12,03), from_id: @place1.id, to_id: @place2.id,vehicle_id: @vehicle.id)  
  end
...

test "Departure id and arrival id should be different" do
    @trip.to_id = @place1.id
    assert_not @trip.valid?
  end

that result in a failure:

Failure:
TripTest#test_Departure_id_and_arrival_id_should_be_different [/media/alessandro/DATA/Universita/Magistrale/1_anno/Programmazione_concorrente/hitchhiker/test/models/trip_test.rb:45]:
Expected true to be nil or false

I'm not able to understand why. Can someone help me?

CodePudding user response:

I'm not able to understand why. Can someone help me?

That if conditionally enables a validation. Your to_id is the same as from_id and so to_id is not validated at all. But even if it was, to_id has a value, so there wouldn't be an error from this field.


Overall, I'm not quite sure why are you expecting a validation error here or what that error should be. In my experience, assertions like assert_not @model.valid? are virtually useless. The record might not be valid because of unrelated reasons and you'll have no idea. Personally, I assert the exact error message I'm expecting. Something along these lines (rspec syntax)

it "requires first_name" do
  expected_messages = {
    first_name: [:blank],
  }

  @model.valid?
  expect(@model.errors.full_messages).to eq expected_messages
end

CodePudding user response:

It seems like you think validates ... if: works differently as it actually does. This line

validates :to_id, presence: true, if: :from_different_to?

translates to validate that the to_id is present if the from_different_to method returns true. When from_different_to evaluates to false then do not validate. See Rails Guides.

That means when you define

@trip.to_id = @place1.id
assert_not @trip.valid?

in your test then the first line disables the check for the presence of the to_id. No validation, no error...

I suppose what you really try to achieve is to validate that to to_id is present and from_id and to_id are not equal. This can be done with a custom validation like this:

validates :to_id, presence: true
validate :validates_places_are_different

private
def validates_places_are_different
  errors.add(:to_id, "must be different to from_id") if to_id == from_id
end

CodePudding user response:

An alternative to that of @spickermann is that:

class Trip < ApplicationRecord
    belongs_to :from, class_name: "Place", foreign_key: "from_id"
    belongs_to :to, class_name: "Place", foreign_key: "to_id"
    belongs_to :vehicle, class_name: "Vehicle", foreign_key: "vehicle_id"

    validates :price, presence: true
    validates :time, presence: true
    validates :from_id, presence: true
    validates :to_id, numericality: {other_than: :from_id}, if: :from_place_id?

    def from_place_id
        from_id
    end

    def from_place_id?
        !from_id.nil?
    end
end

Note that we have to put a control to execute the last validates only if from_id is not null, because if we doesn't do that, we vanificate the control validates :from_id, presence:true on the superior line.

  • Related