Introducing custom validators!
Assuming you validate your dates with this "fresh" syntax:
validates :reservation_start, # Note the comma!
availability: true # ...and some other validations
validates :reservation_end,
availability: {name: "End date"} # same here
Define this validator separately, a common place is app/validators
, in this case that file would be named availability_validator.rb
class AvailabilityValidator < ActiveModel::EachValidator
# `each` stands for 'each attribute with a validation'
def validate_each(record, attribute, value)
# Args: a model instance, a symbol of attribute and a value it has
# You also get a hash in `options`
# If you specified validation as `availability: true`, you wouldn't get it
reservations = Reservation.where(["transport_id =?", record.transport_id])
date_ranges = reservations.map { |e| e.reservation_start..e.reservation_end }
date_ranges.each do |range|
if range.include? value
errors.add(attribute, "#{options[:name] || 'Date'} not available")
end
end
end
end
Sure this example needs some refinement, but it demonstrates most tools you have at hand.
- The first validation gets no
options
, because there's true
specified, we avoid errors by using a default name with ||
. Not the best practice (merging with a separate hash with defaults is), but when you don't have many parameters and places, that will do.
- Extraction of
transport_id
into options
could come in handy if validation like this is broadly used.
- "Availability" is a bit too common term, you might need to rename this class.
- Stuff like error messages and attribute names is better be put into a locale file and fetched using
I18n.t 'some.key.name'
.
You're doing too much on Ruby side. When armed with a relational database, you can do stuff like this:
Reservation.where(transport_id: transport_id). # period tells there are further calls
where("reservation_start <= :date AND reservation_end >= :date", date: value).
exists? # returns true if there's a reservation with our value in range
If a lower bound is smaller and the upper bound is bigger than our balue, then our value is in the range of that entry. SQL can handle it.
There is a possible error: the validation (even yours) will false-accept the range if the submitted range encloses one existing range completely (start and end are both outside bounds of any other range). I'm not sure if that's what you want, so be advised. This can be fixed by an extra validation, I'm leaving this up to you.