Giter Site home page Giter Site logo

js-rails-as-api-custom-json-rendering-using-rails-austin-web-102819's Introduction

Custom JSON Rendering Using Rails

Learning Goals

  • Render JSON from a Rails controller
  • Select specific model attributes to render in a Rails controller

Introduction

By using render json: in our Rails controller, we can take entire models or even collections of models, have Rails convert them to JSON, and send them out on request. We already have the makings of a basic API. In this lesson, we're going to look at shaping that data that gets converted to JSON and making it more useful to us from the frontend JavaScript perspective.

The way we structure our data matters - it can lead to better, simpler code in the future. By specifically defining what data is being sent via a Rails controller, we have full control over what data our frontend has access to.

To follow along, run rails db:migrate and rails db:seed to set up your database and example data. We will continue to use our bird watching example in this lesson.

Adding Additional Routes to Separate JSON Data

The simplest way to make data more useful to us is to provide more routes and actions that help to divide and organize our data. For instance, we could add a show action to allow us to send specific record/model instances. First, we'd add a route:

Rails.application.routes.draw do
  get '/birds' => 'birds#index'
  get '/birds/:id' => 'birds#show' # new
end

Then we could add an additional action:

class BirdsController < ApplicationController
  def index
    birds = Bird.all
    render json: birds
  end

  def show
    bird = Bird.find_by(id: params[:id])
    render json: bird
  end
end

Reminder: No need for instance variables anymore, since we're immediately rendering birds and bird to JSON and are not going to be using ERB.

Now, visiting http://localhost:3000/birds will produce an array of Bird objects, but http://localhost:3000/birds/2 will produce just one:

{
  "id": 2,
  "name": "Grackle",
  "species": "Quiscalus Quiscula",
  "created_at": "2019-05-09T21:51:41.543Z",
  "updated_at": "2019-05-09T21:51:41.543Z"
}

We can use multiple routes to differentiate between specific requests. In an API, these are typically referred to as endpoints. A user of the API uses endpoints to access specific pieces of data. Just like a normal Rails app, we can create full CRUD based controllers that only render JSON.

ASIDE: If you've ever tried using rails generate scaffold to create a resource, you'll find that this is the case. Rails has favored convention over configuration and will set up JSON rendering for you almost immediately out of the box.

In terms of communicating with JavaScript, even when sending POST requests, we do not need to change anything in our controller to handle a fetch() request compared to a normal user visiting a page. This means that you could go back to any existing Rails project and all you would need to do is change the rendering portion of the controller to make it render JSON. Bam! You have a rudimentary Rails API!

Even though we are no longer serving up views the same way, maintaining RESTful conventions is still a HUGE plus here for your API end user (mainly yourself at the moment).

Removing Content When Rendering

Sometimes, when sending JSON data, such as an entire model, we don't want or need to send the entire thing. Some data is sensitive, for instance. An API that sends user information might contain details of a user internally that it does not want to ever share externally. Sometimes, data is just extra clutter we don't need. Consider, for instance, the last piece of data:

{
  "id": 2,
  "name": "Grackle",
  "species": "Quiscalus Quiscula",
  "created_at": "2019-05-09T21:51:41.543Z",
  "updated_at": "2019-05-09T21:51:41.543Z"
}

For our bird watching purposes, we probably don't need bits of data like created_at and updated_at. Rather than send this unnecessary info when rendering, we could just pick and choose what we want to send:

def show
  bird = Bird.find_by(id: params[:id])
  render json: {id: bird.id, name: bird.name, species: bird.species } 
end

Here, we've created a new hash out of three keys, assigning the keys manually with the attributes of bird.

The result is that when we visit a specific bird's endpoint, like http://localhost:3000/birds/3, we'll see just the id, name and species:

{
  "id": "3",
  "name": "Common Starling",
  "species": "Sturnus Vulgaris"
}

Another option would be to use Ruby's built-in slice method. On the show action, that would look like this:

def show
  bird = Bird.find_by(id: params[:id])
  render json: bird.slice(:id, :name, :species)
end

This achieves the same result but in a slightly different way. Rather than having to spell out each key, the Hash slice method returns a new hash with only the keys that are passed into slice. In this case, :id, :name, and :species were passed in, so created_at and updated_at get left out, just like before.

{
  "id": "3",
  "name": "Common Starling",
  "species": "Sturnus Vulgaris"
}

Cool, but once again, Rails has one better. While slice works fine for a single hash, as with bird, it won't work for an array of hashes like the one we have in our index action:

def index
  birds = Bird.all
  render json: birds
end

In this case, we can add in the only: option directly after listing an object we want to render to JSON:

def index
  birds = Bird.all
  render json: birds, only: [:id, :name, :species]
end

Visiting or fetching http://localhost:3000/birds will now produce our array of bird objects and each object will only have the id, name and species values, leaving out everything else:

[
  {
    "id": 1,
    "name": "Black-Capped Chickadee",
    "species": "Poecile Atricapillus"
  },
  {
    "id": 2,
    "name": "Grackle",
    "species": "Quiscalus Quiscula"
  },
  {
    "id": 3,
    "name": "Common Starling",
    "species": "Sturnus Vulgaris"
  },
  {
    "id": 4,
    "name": "Mourning Dove",
    "species": "Zenaida Macroura"
  }
]

Alternatively, rather than specifically listing every key we want to include, we could also exclude particular content using the except: option, like so:

def index
  birds = Bird.all
  render json: birds, except: [:created_at, :updated_at]
end

The above code would achieve the same result, producing only id, name, and species for each bird. All the keys except created_at and updated_at.

Drawing Back the Curtain on Rendering JSON Data

As we touched upon briefly in the previous lesson, the controller actions we have seen so far have a bit of Rails 'magic' in them that obscures what is actually happening in the render statements. The only and except keywords are actually parameters of the to_json method, obscured by that magic. The last code snippet can be rewritten in full to show what is actually happening:

def index
  birds = Bird.all
  render json: birds.to_json(except: [:created_at, :updated_at])
end

As customization becomes more complicated, writing in sometimes help to clarify what is happening.

Basic Error Messaging When Rendering JSON Data

With the power to create our own APIs, we also have the power to define what to do when things go wrong. In our show action, we are currently using Bird.find_by, passing in id: params[:id]:

def show
  bird = Bird.find_by(id: params[:id])
  render json: {id: bird.id, name: bird.name, species: bird.species } 
end

When using find_by, if the record is not found, nil is returned. As we have it set up, if params[:id] does not match a valid id, nil will be assigned to the bird variable.

As nil is a false-y value in Ruby, this gives us the ability to write our own error messaging in the event that a request is made for a record that doesn't exist:

def show
  bird = Bird.find_by(id: params[:id])
  if bird
    render json: { id: bird.id, name: bird.name, species: bird.species }
  else
    render json: { message: 'Bird not found' }
  end
end

Now, if we were to send a request to an invalid endpoint like http://localhost:3000/birds/hello_birds, rather than receiving a general HTTP error, we would still receive a response from the API:

{
  "message": "Bird not found"
}

From here, we could build a more complex response, including additional details about what might have occurred.

Conclusion

We can now take a single model or all the instances of that model and render it to JSON, extracting out any specific content we do or do not want to send!

Whether you are building a professional API for a company or for your own personal site, having the ability to fine tune how your data look is a critical skill that we're only just beginning to scratch the surface on.

In the next lesson, we're going to continue to look at options for customizing rendered JSON content. Particularly, we'll be looking more at what we can add.

js-rails-as-api-custom-json-rendering-using-rails-austin-web-102819's People

Contributors

maxwellbenton avatar graciemcguire avatar dependabot[bot] avatar ihollander avatar

Watchers

James Cloos avatar  avatar Mohawk Greene avatar Victoria Thevenot avatar Bernard Mordan avatar Otha avatar raza jafri avatar  avatar Joe Cardarelli avatar The Learn Team avatar Sophie DeBenedetto avatar  avatar  avatar Matt avatar Antoin avatar  avatar Alex Griffith avatar  avatar Amanda D'Avria avatar  avatar Ahmed avatar Nicole Kroese  avatar Kaeland Chatman avatar Lisa Jiang avatar Vicki Aubin avatar  avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.