Sinatra: Accessing Data and Methods with {include:}

alternate_robot

Christopher Ninman

Posted on February 10, 2022

Sinatra: Accessing Data and Methods with {include:}

So you've been using ActiveRecord/Sinatra for your backend and have come up with a clever method to filter your database and achieve world peace. Or maybe you just created a Class method to calculate the average rating of a particular location in your app, like me. Good for you!!! But now what? How can you access that brilliant/less brilliant but useful information?

My Project

I like to have something tangible as an example when trying to learn a new concept. So here's a brief scenario (part of a project that I created during bootcamp) to be our spirit guide:

The backend contains these Models:

City has_many :users, has_many: locations
User belongs_to :city, has_many :reviews, has_many :locations, through: :reviews
Location belongs_to :city, has_many :reviews, has_many :users, through: :reviews
Review belongs_to :location, belongs_to :user

Each user can write a review for a location. This review includes a rating, which is an integer between 1 and 5.

Several Users have written Reviews for the Location, so now our location has several Reviews. Luckily, ActiveRecord has given us a very non-original, but ridiculously simple, way to calculate a Location's rating.

Class.average(:attribute)

Creating Our Method

Time to put the Class.average method to use in our current scenario

class Location < ActiveRecord::Base
  belongs_to :city
  has_many :reviews
  has_many :users, through: :reviews

  def average_rating
    self.reviews.average(:rating).to_f
  end
end

// Assuming you'll want to use that average as a decimal,
// we're calling `to_f` on our 'average_rating'
Enter fullscreen mode Exit fullscreen mode

Previously we had access to instance methods on instances of locations, such as Location.first.reviews, Location.first.users, Location.first.reviews.first, etc. Now we have an additional method we can call on our Location instances: Location.first.average_rating. This will return us the average of all the ratings that have been given in the Reviews of that instance.

How Can I Get That to My Front-End?

Let's begin by taking a look at our Locations Controller, and what we can access when we make a fetch request to our backend.

class LocationsController < ApplicationController

  def serialize_locations(objects)
    objects.to_json(include: [:reviews, :users, :city])
  end

  get '/locations' do
    serialize_locations(Location.all)
  end
end

// Rather than calling 
// Location.all.to_json(include: [:.......]),
// we are using the serialize_locations function to do 
// that work, as we may want to reuse that logic in other
// fetch requests we make, such as '/locations/:id', or 
// post, patch, and delete requests

Enter fullscreen mode Exit fullscreen mode

Since we may want access to the Location instance's reviews, users, and city information, we're using include: and an array of it's connected models. We could now call something like Location.first.city.name and have access to the name of the city where the Location belongs.

The problem is, we still can't access the average rating of a Location instance.

Time to Access the Method

We can dig further and further into our data and create nested routes using include:, example: calling include inside of the Location's Users and including the Location's User's Review. Check out this great post for more info.

But there is something else that we can include: methods

class LocationsController < ApplicationController

  def serialize_locations(objects)
    objects.to_json(include: [:reviews, :users, :city], methods: :average_rating)
  end

  get '/locations' do
    serialize_locations(Location.all)
  end
end

Enter fullscreen mode Exit fullscreen mode

We have included an array of related tables, along with the named method (average_rating) inside our Locations class.

id: 1,
location_name: "Prospect Park",
city_id: 2,
neighborhood: "Paradise Creek",
times_visited: 8,
average_rating: 3.25,
+reviews: [...],
+users: [...],
+city: {...} 
Enter fullscreen mode Exit fullscreen mode

Oh, you actually have several methods that you created in your Location class that you want? Let's include an array of methods!

  def serialize_locations(objects)
    objects.to_json(include: [:reviews, :users, :city], methods: [:average_rating, :sum_times_visited])
  end
Enter fullscreen mode Exit fullscreen mode

Photo by fabio on Unsplash

💖 💪 🙅 🚩
alternate_robot
Christopher Ninman

Posted on February 10, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related