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.
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.
精彩评论