Restful Renders

When to not be so uptight with your Restful API

Jason Tseng
Undefined Methods
Published in
3 min readAug 21, 2017

--

For the past few days I’ve been working on a simple React application that makes fetch requests to an API I’ve built using Ruby on Rails. The application, DistressCall, lets users who are dealing with addiction and other mental health issues, particularly those in 12 step programs, to define a circle of supporters that can be texted all at once at the click of a button. Supporters are sent a text instructing them to reach out to the user in need to help them combat their mental health challenge.

Here’s a simple diagram of some of the models and their relationships in my API:

As you can see, Users have many Supporters but only through Circles and Supporter Circles. When I was trying to mount a component which displayed a list of all the supporters of a specific user’s Circle, my first thought was that my UsersController actions should look something like:

class Api::V1::UsersController < ApplicationController
skip_before_action :authorized, only: [:create]
def index
@users = User.all
render json: @users
end
def show
@user = User.find(params[:id])
render json: @user
end

As you can see, the show method is pretty basic and will return an object containing only the user’s attributes of id, first_name, last_initial, and phone. It, however, is not rending a list of the user's supporters.

Originally, I was doing multiple fetch requests to get the user’s id, then all the circles in the database, followed by fetching all the supporters in the database. I would then save all of these into my state and try to filter and map until I got down to just the list of supporters in my user’s circle. That looked something like this:

constructor(){
super()
this.state = {loggedin: {}, user: {}, circles: [], supporterCircles: [], supporters: []}
}
componentDidMount(){
fetch(`http://localhost:3000/v1/api/users/${this.state.loggedIn.id}`)
.then(response => response.json())
.then(json => this.setState({user: json})
.then(fetch(http://localhost:3000/v1/api/circles)
.then(response => response.json())
.then(jason => this.setState({circles: json})
.then(fetch(http://localhost:3000/v1/api/supporter_circles)
.then(response => response.json())
.then(jason => this.setState({supporter_circles: json})
.then(fetch(http://localhost:3000/v1/api/supporters)
.then(response => response.json())
.then(jason => this.setState({supporters: json})
)))
}
const user = this.state.currentUserconst circles = this.state.circles.filter(circle => {return circle.user_id === user.id})const supporterCircles = this.state.supportercircles.filter(supporterCircle => {return circles.map(circle => {return circle.id}).includes(supporterCircle.circle_id)})const supporters = this.state.supporters.filter(supporter => {return supporterCircles.map(supporterCircle => {return supporterCircle.supporter_id}).includes(supporter.id)})

That. was. crazy.

I was essentially exporting my entire database and saving it to my parent component’s state and then trying to coerce various data points to replicate what would be a simple command on my server end, like supporters = User.find(1).supporters .

I realized that instead of being so rigid with my RESTful API, I could just have my API’s user#show action render out, not just the user record, but also grab it’s circles and supporters, which would be relatively simple:

class Api::V1::UsersController < ApplicationController
skip_before_action :authorized, only: [:create]
def index
@users = User.all
render json: @users
end
def show
@user = User.find(params[:id])
circles = @user.circles
circles_json = circles.map { |circle| {circle: circle, supporters: circle.supporters} }
render json: {user: @user, circles: circles_json}
end

which will render out a JSON response like:

{
user: {id: 1, first_name: 'John', last_initial: 'D', phone: '555-555-5555'},
circles: [
{circle: {id: 1, user_id: 1, name: 'AA-NYC'},
supporters: [{id: 1, first_name: 'Jane', last_initial: 'D', phone: '123-456-7890'}]
]
}

Wow. so. much. easier.

It’s easy to want to follow convention to a T, but sometimes it’s okay to bend convention slightly in order to get out a reasonably useful response from your API call.

--

--