How To Build A Booking System with Ruby On Rails: Part 1
Picture it… You’re a Ruby on Rails developer and your client is offering 50-minute lessons on how to hula hoop underwater. Their inbox is filling up with emails from hula hoop enthusiasts who are dying to add underwater hula hooping to their repertoire. You discover, your client has no way of keeping track of bookings. Oh the horror! Now, you begin to hallucinate about all the dollar bills doing the moonwalk out of your client’s bank account because of it. What are you going to do?
Are you going to hand in a resignation letter that reads, “So long and thanks for all the fish.”?
No!
Are you going to offer the latest SaaS booking solution complete with a hefty monthly fee?
Of course not!
You’re going to build them a fancy-schmancy booking system, that’s what.
So let’s get to work.
There are many ways to build a booking system and there are many different types of use cases. This tutorial only covers one use case and you will have to use your ninja (I love how we just misuse and abuse this word in the tech world) thinking skills to apply the concepts you learn here to your own unique use case.
I built this booking system to allow clients to book a lesson for a specific trainer. It needed to allow them to select the type of lesson they are interested in, pick a trainer, choose a date and time for the lesson, enter in their personal information and pay using a credit card. An adjusted version of this same system allows users to book and pay for lessons from their account dashboard.
So what in the world are we going to need for this thing? Below is a list of everything we will need to create for this system. Feel free to adjust it to your needs. However, the tutorial will be based on the presence of these items.
- Account (the keeper of all things)
- User (anyone who can login)
- Client (profile only)
- Trainer (profile only)
- Schedule (dates and times a trainer is available)
- Lesson (details only)
- Booking (the date, time and lesson being booked and with whom)
- Lesson Payment (a record of the payment for the booked lesson)
Ultimately, the diagram below is what we will be building. However, I won’t cover setting up the Account and User because they are unique to your application. However, I will include the model associations for the Account and User.
NOTE: The Account is only used to scope data to a particular user and to store minimal data such as an account number (not the same as the account ID) and the account type (client, trainer, etc.). Every item in our booking system except for the Lesson has an account ID associated with it in an account_id field.
In part 1 of this tutorial, I will go over creating the Lesson, Trainer, Client and Schedule objects. These all have to be in place before you deal with the actual booking process.
NOTE: The Lesson object can be whatever you are offering. The Trainer and Client can be adjusted to your needs as well.
LESSONS:
Lessons are created only by the admin and do not have an account associated with them. I’m going to assume you have already setup a back end admin area. The Lesson fields will vary depending on your needs. I have included the bare minimum below for the sake of this tutorial.
rails g scaffold Lesson image:string title:string duration:integer cost:integer category:string language:string level:string description:textrake db:migrate
Now we need to make some adjustments to the Lesson model and controller. These Lesson associations are optional but if you want to display data in various other parts of your application, these associations can make things easier.
app/models/lesson.rb
has_many :trainers, :through => :bookings
has_many :clients, :through => :bookings
has_many :bookings, :inverse_of => :lessons
accepts_nested_attributes_for :bookings
mount_uploader :image, LessonUploader
app/controllers/lessons_controller.rb
I have used only the show and index actions for the Lessons controller used by the front end & booking system.
class LessonsController < ApplicationController
before_action :set_lesson, only: [:show]def index
@lessons = Lesson.paginate(:page => params[:page], :per_page => 6).order('sort ASC')
enddef show
@others = Lesson.paginate(:page => params[:page], :per_page => 4).order('sort ASC')
endprivatedef set_lesson
@lesson = Lesson.find(params[:id])
end
end
I only have a small bit of code to add for the SHOW view. The index and main show view layout and features are up to you. I’m including this bit of code because I use it to pass the lesson ID as parameters in the URL which will be used to make a booking for that specific lesson.
app/views/lessons/show.html.erb
<%= link_to custom_url(:lesson_id => @lesson.id), class: "course-btn" do %><i class="fa fa-calendar-plus-o"></i> Book This Lesson<% end %>
The custom_url would be a custom name for your booking page set in config/routes.rb. You should change yours to whatever you like.
TRAINER & CLIENT:
The fields you need for the Trainer and Client will also vary but I will include some basics for the sake of this tutorial. Use the code below to create the Trainer and Client.
rails g scaffold Trainer photo:string first_name:string last_name:string phone:string bio:text experience:string user_id:integer:index account_id:integer:indexrails g scaffold Client photo:string first_name:string last_name:string phone:string bio:text user_id:integer:index account_id:integer:indexrake db:migrate
NOTE: Neither the Trainer nor the Client has public facing views. For the actual booking system, the views for these two features are not important. We won’t be using them. The controllers are also not important but I must advise you to namespace them because you are going to want to use them for admin activities independent of the booking system.
We will need to add some associations to both models as well as the Account Model. Be sure to read through this entire tutorial before you start coding.
app/models/account.rb
# Users
has_many :users, dependent: :destroy, :inverse_of => :account
accepts_nested_attributes_for :users
# Trainers
has_many :trainers, dependent: :destroy, :inverse_of => :account
accepts_nested_attributes_for :trainers
has_many :schedules, dependent: :destroy, :inverse_of => :account
accepts_nested_attributes_for :schedules
# Clients
has_many :clients, dependent: :destroy, :inverse_of => :account
accepts_nested_attributes_for :clients
# Lessons
has_many :bookings, dependent: :destroy, :inverse_of => :account
accepts_nested_attributes_for :bookings
has_many :lesson_payments, dependent: :destroy, :inverse_of => :account
accepts_nested_attributes_for :lesson_payments
app/models/trainer.rb
# Tenant Of
belongs_to :account, :inverse_of => :trainers
accepts_nested_attributes_for :account
belongs_to :user, :inverse_of => :trainers
accepts_nested_attributes_for :user
has_many :bookings, dependent: :destroy, :inverse_of => :trainer
accepts_nested_attributes_for :bookings
has_many :lessons, :through => :bookings
has_many :lesson_payments, :through => :bookings
has_many :schedules, dependent: :destroy, :inverse_of => :trainer
accepts_nested_attributes_for :schedules
mount_uploader :photo, TrainerUploader
def name
"#{first_name} #{last_name}"
end
def email
User.find_by_id(self.user_id)
end
app/models/client.rb
# Tenant Of
belongs_to :account, :inverse_of => :clients
accepts_nested_attributes_for :account
belongs_to :user, :inverse_of => :clients
accepts_nested_attributes_for :user
has_many :bookings, dependent: :destroy, :inverse_of => :client
accepts_nested_attributes_for :bookings
has_many :lessons, :through => :bookings
has_many :lesson_payments, :through => :bookings
mount_uploader :photo, ClientUploader
def name
"#{first_name} #{last_name}"
end
def email
User.find_by_id(self.user_id)
end
SCHEDULE:
I spent a lot of time trying to come up with the best way to handle availability for the trainers. In the end, I just went with something simple. Each hour of availability created needed only to be associated with a particular trainer and include the start and end times for a particular day. Each hour created would exist as an individual object to make it easier to manage bookings and what is happening with that particular hour of availability (i.e. marking the time slot available again if a booked lesson has been canceled in time, etc.).
rails g scaffold Schedule title:string start:datetime end:datetime trainer_id:integer:index account_id:integer:indexrake db:migrate
Now we will need to add some associations to the Schedule model. Make a note of the start time validation. You don’t want trainers to add duplicate start times for the same day.
app/models/schedule.rb
# Tenant Of
belongs_to :account, :inverse_of => :schedules
accepts_nested_attributes_for :account
belongs_to :trainer, :inverse_of => :schedules
accepts_nested_attributes_for :trainer
has_many :bookings, :inverse_of => :schedule
accepts_nested_attributes_for :bookings
validates :start, uniqueness: { scope: :trainer_id, message: "You have already made this time available" }
amoeba do
enable
exclude_associations :bookings
end
This is a very simple implementation of an availability schedule. The “title” field will be “Available” or “Booked”, etc. You can change it to “status” or something else if your prefer. I used “title” because of the calendar feature I’m using but won’t cover in this tutorial. Past, unbooked time slots are cleaned from the database every week via rake task and a cron job.
The controller and views for the Schedule are only accessible to trainers when they are logged in. I’m going to leave the views up to you. I didn’t do anything too fancy to the controller except namespace it and make it only accessible to a trainer if they are logged in. For my views, I added an Ajax enabled form to the INDEX view so trainers can easily add their time without being sent to a different view for each entry. I also used the amoeba gem to add some features to allow trainers to pick the date and start time and then how many hours they want to work and then just click a button to add the time for the entire day (see screenshot below).
That’s it for Part 1 of this tutorial. Before we get started with Part 2, which covers the actual booking system, you will need to create a new user and a new trainer profile for that user. Then you will need to add some available dates and times to the schedule table for your new trainer.
TIP: You need to keep in mind that you are building this as a booking system. So you are going to want to add a way to determine if a User is a client or a trainer (or any other user types you need). You will want to make sure you do this throughout the application, especially when you are displaying data.
A new client will be created during the booking process so there is no need to create one manually. We will cover creating a new client in Part 2 of this tutorial.
TIP: More than likely you will be using an authentication system that offers you a current_user helper method. This is great but I would also recommend that you create helper methods for current_trainer and current_client in app/controllers/application_controller.rb as well.
Rails version: 5.1.3
PART 2: How To Build A Booking System with Ruby On Rails: Part 2
Have you built a booking system with Rails? How? Let me know in the comments, chat with me on Twitter, LinkedIn or if you are looking to improve your Technical or Business English, subscribe to my newsletter for video mini-courses!