Modelling ActiveRecord associations with AirBnB

Kay Bennett
5 min readSep 19, 2018
Photo by Michael Ruiter from Pexels (https://www.pexels.com/photo/gray-fabric-padded-2-seat-sofa-beside-green-leafed-plants-1234462/)

Today we’re going to be practicing ActiveRecord associations by modelling the popular holiday rental marketplace AirBnB.

ActiveRecord allows us to build models and associate them to each other, and to their associated databases, without having to write a lot of code. By telling ActiveRecord that our models are related, using associations, it makes it much easier to carry out operations that affect multiple models. In this blog, I’m going to be using the belongs_to, has_many and has_many :through associations provided in ActiveRecord.

(Before we get started, if you need more information on the basics of ActiveRecord associations, check out the relevant section on RailsGuides.)

Now, let’s think about all of the different models, that are involved when a reservation is made on AirBnB. We have:

  • Listings — which contain information about each holiday rental, the city, the neighborhood, the host, the address, the price, a description, and more.
  • Guests — who browse listings in neighbourhoods and cities, make bookings, and leave reviews.
  • Hosts — who post listings, and leave reviews.
  • Cities where all of our neighbourhoods are located.
  • Neighbourhoods are located in a city, and have lots of listings of places to stay.
  • Reviewsare left by guests, and displayed on the listing

Phew! There’s a lot going on here. We’ll begin by thinking about users and listings.

User and Listings

AirBnB hosts can have many listings, and have many guests, through listings.

But wait — AirBnB guests are also users! Does this mean that we have to have a separate model for host users and guest users?

Actually, no we don’t! We can tell ActiveRecord which foreign key to look for on the listings table to find the listings associated with a particular host. We need to specify ‘host_id’, as ActiveRecord will default to ‘user_id’.

class Userhas_many :listings, :foreign_key => 'host_id'

We can tell our listing that it belongs_to a particular type of user, a host, using the below syntax.

class Listingbelongs_to :host, :class_name => "User"

Nice!

What other associations do we need for users? As guests, they have many trips, they leave many reviews, and they make many reservations, through listings. Let’s put it all together! (Note, I’m using Rails 4.2, so my classes inherit from ‘ActiveRecord::Base’ — things have changed for Rails 5, you must now inherit from ApplicationController).

class User < ActiveRecord::Base  has_many :listings, :foreign_key => 'host_id'
has_many :reservations, through: :listings
has_many :reviews, :foreign_key => 'guest_id'
has_many :trips, :foreign_key => 'guest_id', :class_name => "Reservation"
end

Let’s finish listings! We need to associate a listing with a neighbourhood. We also need to tell the listing that it has many reviews and guests through reservations.

Putting it all together, we get:

class Listing < ActiveRecord::Basebelongs_to :host, :class_name => "User"
belongs_to :neighborhood
has_many :reservations
has_many :reviews, through: :reservations
has_many :guests, through: :reservations
end

Now, let’s look at reservations and reviews.

Reservations and Reviews

We know that reservations are made by guests, so our reservation belongs_to a guest. It also belongs_to a listing, as without a listing, no one would be able to find our place to make a booking!

class Reservation < ActiveRecord::Base

belongs_to :listing
belongs_to :guest, :class_name => "User"
has_many :reviews
end

How about our reviews? Well, they belong to a guest, and they also belong to a reservation, as it would be quite unfair if you could leave a review without actually booking somewhere. They belong to a listing too, as each review can only review an individual listing. That gives us:

class Review < ActiveRecord::Base

belongs_to :guest, :class_name => "User"
belongs_to :reservation
belongs_to :listing
end

We’ve also got to cover:

Cities and Neighbourhoods

All our listings are geographically located somewhere, and our guests will usually pick where to stay based on the city they want to visit.

Each neighbourhood will have many listings, and will belong to a city. So building associations for our Neighbourhood class should be simple enough:

class Neighbourhood < ActiveRecord::Basehas_many :listings
belongs_to :city
end

In each city, there are several neighbourhoods, and each neighbourhood contains many listings. So our City class will have many listings, through neighbourhoods.

class City < ActiveRecord::Basehas_many :neighbourhoods
has_many :listings, through: :neighbourhoods
end

Alright! We’re now ready to build our migrations.

Migrations

We need to make the database aware of the schema of our tables. We want to make sure that our tables are created in the right order, as ActiveRecord will throw an error if we try to reference a foreign key that doesn’t exist yet.

It seems the safest place to start is with our User class. The database I’m using, SQLite3, will automatically generate a unique identifier for us — it is implicitly created. Therefore, we can get started with:

#001_create_user.rbclass CreateUser < ActiveRecord::Migration
def change
create_table :users do |t|
t.string :name
end
end
end

The filename needs to contain the name of the class in snake_case, otherwise ActiveRecord will throw an error. Our class definitions should be in ProperCase.

Now we’ve created our Users table, what should our second migration be? Cities are also unreliant on foreign keys, so we can define our second migration as below:

#002_create_city.rbclass CreateCity < ActiveRecord::Migration
def change
create_table :cities do |t|
t.string :name
end
end
end

Now we have cities, we can create our neighbourhoods table. We’ll tell our neighbourhoods which city they belong to through including a ‘city_id’ column.

#003_create_neighbourhood.rbclass CreateNeighbourhood < ActiveRecord::Migration
def change
create_table :neighbourhoods do |t|
t.string :name
t.integer :city_id
end
end
end

Now we have our users, our cities and our neighbourhoods, we can go ahead and create our listings table. We also include references to the id of both the host and the neighborhood.

#004_create_listing.rbclass CreateListing < ActiveRecord::Migration
def change
create_table :listings do |t|
t.string :title
t.string :description
t.string :address
t.string :listing_type
t.integer :price
t.integer :host_id
t.integer :neighborhood_id
t.timestamps
end
end
end

Now we’ve got our listings, it’s time to get away! Let’s define our tables for reservations and trips. Our reservation has a check-in time, a check-out time, belongs to a guest and belongs to a listing. Our trip table is our join table, pulling it all together — this is where we go to find all the ids from our cities, reservations, listings, guests and hosts. Let’s go!

#005_create_reservation.rbclass CreateReservation < ActiveRecord::Migration
def change
create_table :reservations do |t|
t.date :checkin
t.date :checkout
t.integer :listing_id
t.integer :guest_id
t.timestamps
end
end
end
#006_create_trip.rbclass CreateTrip < ActiveRecord::Migration
def change
create_table :trips do |t|
t.integer :city_id
t.integer :reservation_id
t.integer :listing_id
t.integer :guest_id
t.integer :host_id
t.timestamps
end
end
end

Are we done? Oh, we’ve still got to add reviews!

#007_create_review.rbclass CreateReview < ActiveRecord::Migration
def change
create_table :reviews do |t|
t.integer :rating
t.string :description
t.integer :reservation_id
t.integer :guest_id
t.integer :listing_id
t.integer :host_id
t.timestamps
end
end
end

It would be handy if our trip table also knew about the reviews. But — it’s bad practice to edit our previous migrations. Let’s create a new, 8th migration, to tell our trips table about reviews. We’ll use the add_column method, which asks us for details in the format of table_name, column_name and type, separated by commas.

#008_add_trips_to_reviews.rbclass AddTripsToReviews < ActiveRecord::Migration
def change
add_column :trips, :review_id, :integer
end
end

And we’re done!

(This blog is based on the “Flatiron Bnb Associations” lab on Learn.co. I am learning so please let me know about any suggested improvements to my code.)

--

--