DEV Community

Douglas Berkley
Douglas Berkley

Posted on

Active Record Scopes Tutorial - Rails - Booking example

At Le Wagon, our students spend 1 week building a Ruby on Rails Airbnb clone. We normally end up with a schema relatively close to this:
DB Schema example

When we get to the bookings page, it can be tricky how to retrieve all of the relevant bookings for a particular user. An empty bookings page example (with tabs)
Bookings page example

If we use Booking.all, this will give us all of the bookings for every user in the entire application which is obviously not what we want. The simple version of this would just to get all of the bookings for the person logged in (if you're using Devise) current_user.bookings

However, you may have noticed that on our booking, we have a start_date, end_date, and status. So our bookings become a little bit more complicated. Do we want all of our pending bookings? Bookings that are in the future? Bookings that are currently happening? Well when we use current_user.bookings, it's giving us everything.

To be able to sort through all the different types of bookings that we have, we can use Active Record scopes. If you haven't dealt with scopes before, the syntax might feel a bit strange. Here is one example:

# booking.rb (model)
scope :past, -> { where('end_date < ?', Date.today) }
Enter fullscreen mode Exit fullscreen mode

This gives us access to method .past now. So in our controller, we could use Booking.past if we want past bookings from all users. But in this particular example, now we can access all the past bookings for a particular user: current_user.bookings.past

So given this one example of a scope, we could now go crazy with lot of different types of bookings (depending on how we're using them in the view). Our model might end up looks like this:

class Booking < ApplicationRecord
  belongs_to :user
  belongs_to :offer
  validates :start_date, presence: true
  validates :end_date, presence: true
  enum status: { pending: 0, accepted: 1, rejected: 2 }

  scope :past, -> { where('end_date < ?', Date.today) }
  scope :future, -> { where('start_date > ?', Date.today) }
  scope :active, -> { where('start_date < ? AND end_date > ?', Date.today, Date.today) }
  scope :today, -> { accepted.where(date: Date.today) }
  scope :upcoming, -> { accepted.future }
  scope :need_response, -> { pending.future }
  scope :expired, -> { pending.past }
  scope :completed, -> { accepted.past }
  scope :not_rejected, -> { where.not(status: :rejected) }

  def number_of_days
    (start_date - end_date).to_i 
  end

  def price
    offer.price * number_of_days
  end
end
Enter fullscreen mode Exit fullscreen mode

Now thinking about our how to use this, we can load all of the user's bookings in the bookings controller.

def index
  @bookings = current_user.bookings
end
Enter fullscreen mode Exit fullscreen mode

Then in our view we can display whatever type of bookings that we decided, using our newly made scopes!

<%= render 'cards', bookings: @bookings.today %>
<%= render 'cards', bookings: @bookings.future %>
<%= render 'cards', bookings: @bookings.active %>
<%= render 'cards', bookings: @bookings.need_response %>
<%= render 'cards', bookings: @bookings.past %>
<!-- etc. -->
Enter fullscreen mode Exit fullscreen mode

Top comments (0)