Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to validate an attribute with "validates :attribute" referring to the value of a parent object attribute?

In this exercise from O'Reilly's "Head first Rails" (ed. 2009) there are 2 related objects.

The "Flight" object [I used annotate gem to show every attribute]:

# == Schema Information
#
# Table name: flights
#
#  id                :integer         not null, primary key
#  departure         :datetime
#  arrival           :datetime
#  destination       :string(255)
#  baggage_allowance :decimal(, )
#  capacity          :integer
#  created_at        :datetime
#  updated_at        :datetime
#

class Flight < ActiveRecord::Base
  has_many :seats
end

Ant the "Seat" object:

# == Schema Information
#
# Table name: seats
#
#  id         :integer         not null, primary key
#  flight_id  :integer
#  name       :string(255)
#  baggage    :decimal(, )
#  created_at :datetime
#  updated_at :datetime
#

class Seat < ActiveRecord::Base
  belongs_to :flight
end

As you may guess the seat.baggage value should always be less than or equal to seat.flight.baggage_allowance.

So I wrote this validator, which works well:

class Seat < ActiveRecord::Base

  belongs_to :flight

  def validate
      if baggage > flight.baggage_allowance
      errors.add_to_base("Your have to much baggage for this flight!")
    end 
  end  
end

Then I tried to refactor it with this prettier one:

      validates :baggage, :numericality => { :less_than_or_equal_to => flight.baggage_allowance }, :presence => true 

But it causes a NameError in SeatsController:

undefined local variable or method `flight' for #<Class:0x68ac3d8>"

Than I also tried with "self.flight.baggage_allowance":

validates :baggage, :numericality => { :less_than_or_equal_to => self.flight.baggage_allowance }, :presence => true

But it throws a NoMethodError Exception:

undefined method `flight' for #<Class:0x67e9b40>

Is there a way to make the prettier validator work?

Which is the best practice to do this kind of validation?

Thank you.

---EDIT---

As Maurício Linhares kindly suggested hereafter, the problem is solvable defining a :bagging_allowance symbol. I'd like to understand better where custom validation is really needed. What about this one, is it possible to convert even this to a "validates" method or not? and why?

class Seat < ActiveRecord::Base
.
.
.
def validate
  if flight.capacity <= flight.seats.size
    errors.add_to_base("The flight is fully booked, no more seats available!")
  end
end

Thank you again.

like image 386
Darme Avatar asked Jul 29 '11 15:07

Darme


1 Answers

You can do it like this:

class Seat < ActiveRecord::Base

  validates :baggage, :numericality => { :less_than_or_equal_to => :baggage_allowance }, :presence => true 

  belongs_to :flight

  def baggage_allowance
    flight.baggage_allowance
  end  
end

EDIT

You can't do this:

class Seat < ActiveRecord::Base
        validates :baggage, :numericality => { :less_than_or_equal_to => flight.baggage_allowance }, :presence => true 
end

Because the method validates is being called at the class level, so there is no flight variable available, as it is an instance variable. When you configure it with :baggage_allowance you tell Rails to call the :baggage_allowance method on an instance of Seat to be able to access the value.

like image 161
Maurício Linhares Avatar answered Oct 18 '22 22:10

Maurício Linhares