Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails: Appointment Scheduling functionality?

So I have a User model, and all I want to be able to do is have a list of available appointment time slots, and have Users select one of them during their registration. I'm envisioning in my head that I would just have some sort of TimeSlotList that would generate TimeSlots in xxx minute increments between two DateTime instances, and then have a User have a TimeSlot as an attribute? Perhaps each TimeSlot would have a taken boolean that indicates whether a User has already taken it?

Is there some sort of standard way to do something like this? I can't imagine that this is too uncommon of a functionality to implement.

like image 673
r123454321 Avatar asked Jan 17 '14 05:01

r123454321


2 Answers

Although I'm sure there'll be a gem for this somewhere, I'll see if I can help you with an answer:


Methods

The two methods for handling this are as follows:

  1. Parse the time your users want to book (have time_1 & time_2), creating an Application object directly from the input
  2. Check against TimeSlots (makes "time" irrelevant), and associating the Application object to a TimeSlot

Time.parse

The first way you could do it will be to manually parse the times. I'd do this:

#app/models/appointment.rb
Class Appointment < ActiveRecord::Base
    belongs_to :user
end

#app/models/user.rb
Class User < ActiveRecord::Base
    has_many :appointments

    def valid?  
        taken = where("start <= ? AND end >= ?", start, end)
        save unless taken
    end
end

appointments 
id | user_id | start | end | created_at | updated_at

users
id | etc | etc | created_at | updated_at

#app/controllers/appointments_controller.rb
def new
    @appointment = Appointment.new
end 

def create

    #Validate
    @appointment = Appointment.new(appointment_params).valid?        

end

private
def appointment_params
    params.require(:appointment).permit(:start, :end).merge(user_id: current_user.id)
end

TimeSlots

If you're going to use pre-defined TimeSlots, you could do it like this:

#app/models/time_slot.rb
Class TimeSlot < ActiveRecord::Base
    has_many :appointments
    has_many :users, through: :user_time_slots
end

#app/models/appointment.rb
Class Appointment < ActiveRecord::Base
    belongs_to :time_slot
    belongs_to :user

    def valid?
        taken = where(day: day, time_slot_id: time_slot_id) 
        save unless taken
    end
end

#app/models/user.rb
Class User < ActiveRecord::Base
    has_many :appointments
    has_many :time_slots, through: :appointments
end

time_slots
id | name | time | duration | etc | etc | created_at | updated_at

appointments
id | user_id | time_slot_id | day | created_at | updated_at

users
id | etc | etc | etc | created_at | updated_at

This will allow you to create a system whereby specific TimeSlots will be available to the user. The difference will be that you'll have to validate on the Appointment model, against the TimeSlot & day that's been taken:

#app/controllers/appointments_controller.rb
def new
    @appointment = Appointment.new
end

def create

    #Validate
    valid = Appointment.new(appointment_params).valid?

    #Response
    respond_to do |format|
        if valid
           format.html { redirect_to success_url }
           format.js 
        else
           format.html { redirect_to failure_url }
           format.js
        end
    end

end

private
def appointment_params
    params.require(:appointment).permit(:time_slot_id, :day).merge(user_id: current_user.id)
end

You could optimize this by using some indexes in your DB, to prevent the same day & time_slot being used

like image 69
Richard Peck Avatar answered Nov 05 '22 15:11

Richard Peck


You could have a TimeSlotManager class of sorts that you use to when showing all of the time slots, along with the existing appointments

class Appointment < ActiveRecord::Base
  # :to_time
  # :from_time
  # :on_date
end

class TimeSlotManager
  def initialize(date, appointments)
    @date = date
    @appointments = appointments
  end

  def slots
    # generate slots
    # pass appropriate appointment if time slot within appointment range
  end
end

def TimeSlot
  def initialize(from_time, to_time, appointment=nil)
    @from_time = from_time
    @to_time = to_time
    @appointment = appointment
  end
  attr_accessor :from_time, :to_time, :appointment
end

You at least then would only record the actual appointments.

like image 21
S.Spencer Avatar answered Nov 05 '22 16:11

S.Spencer