Rails API, beyond the basics
Tags: ruby, api, tutorial
Published: 2020-02-19 20:18:26

For my final project at Flatiron, I developed a web app for volunteers to signup and help keep the streets of San Francisco clean. The idea was inspired by my roommate. She expressed frustration in the way San Francisco Public Works operates. There was even one time where she contacted public works multiple times, contacted our district supervisor, and contacted the mayor as well. It took all of that just to deal with the issue outside of our building. After dealing with this, She came up with the idea to leverage community to address some of San Francisco's issues. And so, I built "SF Heroes," a React web application to do just that. 

While the front end design took a while to build, it is actually the backend that I am most proud of. During this project I leveraged the power of a Rails API and the API is doing all of the heavy lifting. In previous projects I used a Rails backend as well. However, for those projects the API was built to do less lifting and I relied on the logic of the front end. This may have worked for previous projects, but not SF Heroes. 

SF Heroes has a map page which displays the SF 311 Public Works cases I gathered. Users are given the ability to sort through these cases by status, district, and/or service subtype. Users can apply as many filters as they's like. On my initial draft I had originally made a fetch request to get all of the 311 cases. Each filter would be set in state. On submission, the display map would be set by filtering through all of the cases using state. However, this method created many bugs on the display map. 

On my first draft, my backend controller looked something like this...
``` ruby 
class CommunityEventController < ApplicationController

    def index
     community_events = CommunityEvent.all
     render json: community_events
    end 

end 
```

You can see I am calling on the ActiveRecord relations method "all" to return all instances of the cases. However, given the bugs I was dealing with, I decided to remove some logic from the front end and leverage the power of the back end.

Upon revision, I decided to use the ActiveRecord relations method "where" instead. 
Let's take a look at the revision...

``` ruby 

class CommunityEventController < ApplicationController

    def index

           community_events = [ ]
           if params[:title] && params[:district] && params[:status]
                community_events.push(CommunityEvent.where(title: 
                params[:title], district: params[:district], status: 
                params[:status]))
          
           
           elsif params[:district] && params[:title]
                    community_events.push(CommunityEvent.where(district: 
                    params[:district], title: params[:title]))
              

           elsif params[:status] && params[:district]
                        community_events.push(CommunityEvent.where(status: 
                        params[:status], district: params[:district]))
            
           elsif params[:title] && params[:status]
                    community_events.push(CommunityEvent.where(status: 
                     params[:status], title: params[:title]))

           elsif params[:district]
                    community_events.push(CommunityEvent.where(district: 
                    params[:district])) 
           
           elsif params[:title]
                    community_events.push(CommunityEvent.where(title: 
                    params[:title]))

           elsif params[:status]
                    community_events.push(CommunityEvent.where(status: 
                    params[:status]))
           else 
                    community_events.push(CommunityEvent.all)
           end 
                    render json: community_events
         
          
    end 
```

The ActiveRecord relations method "where" takes in two arguments the key of the query params and the value of the query params. It then uses SQL to query the database for all of the instances that match those parameters. My fetch request on the front end was now pinging the database for instances that matched only the query parameters set by the user. 

The front end code looked something like this...
``` javascript
let url = 'http://localhost:3000/community-events?'
        let params = filterData
        if(params.district !== " "){
        url += `district=${params.district}&`}

        for(let i = 0; i < params.title.length; i++){
             url +=`title[]=${params.title[i]}&`
        }
        
        for(let i = 0; i < params.status.length; i++){
            url += `status[]=${params.status[i]}&`
        }

        return fetch(url, {
            headers: {
                "Content-type": "application/json",
                'Accept': 'application/json',
                'Authorization': `Bearer ${localStorage.getItem("jwt")}`
            }
        }).then(response => response.json())
        .then(json => { 
            
            dispatch({ type: "SET_DISPLAY_MAP", displayMap: json})
        })
```
Every filter applied updates the fetch URL accordingly.
 
Using the relations method "where" gave me one other positive outcome. I was now able to pass in an array of values for one key. Users could now select multiple values to chain on to their fetch URL. 

One thing to note: 
If you want rails to understand one param having multiple values, you need to use an array in the URL. Let's say I want all of the cases where the tittle of those cases is either "Graffiti" or "Noise Reports," I would need to pass those query params as such: 
```
http://localhost:3000/community-events?title[]=Graffiti&title[]=Noise%20Report 
```

After leveraging the power of my backend and ActiveRecord relations, my display map had less bugs and rendered content faster.