Sinatra: Accessing Data and Methods with {include:}
Tags: activerecord, sinatra, ruby
Published: 2022-02-10 22:36:52

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?  

<h1>My Project</h1>

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 `Model`s:

**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)`

<h1>Creating Our Method</h1>

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'
```

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.

<h1>How Can I Get That to My Front-End?</h1>

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

```

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.

<h1>Time to Access the Method</h1>

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](https://medium.com/@coywreid/using-include-in-your-sinatra-application-controller-methods-fc42f297b560) 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

```

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: {...} 
```

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
```

Photo by <a href="https://unsplash.com/@fabioha?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">fabio</a> on <a href="https://unsplash.com/s/photos/data?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a>