1

I'm trying to build a reservation system where a customer can reserve a minibus. I've been able to get the all the data so a booking can be made.

I'm trying to avoid another user to reserve the minibus for the same day. I'm not to sure how to go about it as in new to ruby on rails.

In my reservation.rb I've got

 belongs_to :vehicle
 belongs_to :user

In my user.rb and vehicle.rb I've got

has_many :reservation

In my reservation controller I've got

 def new
    @vehicle = Vehicle.find(params[:vehicle_id])
    @reservation = Reservation.new(user_id: User.find(session[:user_id]).id)
    @reservation.vehicle_id = @vehicle.id
  end

would I use validation to stop double reservations? would it be something like in my reservation.rb

validates :vehicle_id, :startDate, :uniqueness => { :message => " minibus already reserved"}

Although the above will only allow the vehicle to be reserved.

Any help will be much appreciated!

M Ahmed
  • 129
  • 10

2 Answers2

1

You're gonna want to use the uniqueness validator lie you're already doing but use the scope option.

The example they give on that page is pretty similar to your use case:

class Holiday < ApplicationRecord
  validates :name, uniqueness: { scope: :year,
    message: "should happen once per year" }
end

As to which column you should validate, it doesn't really matter. Since the uniqueness scope is going to be all three columns, it can be any of them:

validates :vehicle_id, uniqueness, { scope: [:startDate, user_id], message: "your message" }

You should also add indexes to the database as described here (this question is very similar to yours by the way).

max pleaner
  • 26,189
  • 9
  • 66
  • 118
  • Hi Thanks for the above i've used the following validation. `validates :vehicle_id, uniqueness: {scope: [:startDate, :endDate], :message => " already booked"}` its sort of working if both start and end date are the same. Lets say start date is 10th march until 12th march I can still book the same vehicle from 10th march march till 11th. How would I avoid this? – M Ahmed Mar 10 '18 at 11:23
  • You’re looking for a range overlap validation, the uniqueness validations won’t do everything you need. To the best of my knowledge, there isn’t one baked in to Rails. There’s an ancient “range_validator” gem but it probably isn’t compatible with modern Rails (worth a shot though), but could be a starting point for you coming up with your own custom validation. – Aaron Breckenridge Mar 10 '18 at 14:37
1

As you already figured out you cannot use Rails' built-in uniqueness validator to validate that two ranges do not overlap.

You will have to build a custom validation to check this. A condition that checks if two time or date ranges A and B overlap is quite simple. Have a look at this image.

A:      |-----|
B1:   |-----|
B2:   |---------|
B3:       |-----|
C1: |-|
C2:             |-|

A and B overlap if B.start < A.end && B.end > A.start

Add the following to your model:

# app/models/reservation.rb
validate :reservations_must_not_overlap

private

def reservations_must_not_overlap
  return if self
              .class
              .where.not(id: id)
              .where(vehicle_id: vehicle_id)
              .where('start_date < ? AND end_date > ?', end_date, start_date)
              .none?

  errors.add(:base, 'Overlapping reservation exists')
end

Some notes:

  • You might need to adjust the naming of the database columns and the attributes names because I wasn't sure if it was just a typo or if you use names not following Ruby conventions.
  • Furthermore, you might need <= and >= (instead of < and >), depending on your definition of start and end.
  • Moving the condition into a named scope is a good idea and will improve readability
spickermann
  • 100,941
  • 9
  • 101
  • 131
  • I keep getting "You need to supply at least one validation" for `validates :reservation_must_not_overlap` how do I overcome this? I've changed the fields according to how they should be. i.e. I've named end_date as endDate – M Ahmed Mar 11 '18 at 12:40
  • I've sorted the above issue out, it had to be validate not validates. as mentioned on https://stackoverflow.com/questions/25954023/you-need-to-supply-at-least-one-validation-but-rails-validation-exists i'm now getting a error for `.where(vehicle_id: reservation_id)` for the reservation_id. why would this be? – M Ahmed Mar 11 '18 at 12:52
  • I fixed the typo in `validate :reservations_must_not_overlap` and the condition must be changed to `.where(vehicle_id: vehicle_id)`. The `reservation_id` didn't make any sense in this context, sorry about that. – spickermann Mar 11 '18 at 16:11