Coder Social home page Coder Social logo

js-rails-as-api-using-fast-json-api's Introduction

Using Fast JSON API

Learning Goals

  • Introduce the Fast JSON API gem
  • Create serializers using the Fast JSON API gem
  • Render related object attributes into JSON data

Introduction

We've seen that it is entirely possible to create our own service class serializers from scratch. This issue is common enough, though, that there are some popular standardized serializer options available for us to use. In this lesson, we are going to look at one popular option, the Fast JSON API gem and use it to create a close approximation to our JSON data from the previous lessons.

The files in this lesson were populated using the API-only Rails build. Run rails db:migrate and rails db:seed to follow along.

Note: the Fast JSON API gem is no longer being maintained, but it's still a good tool to learn the basics of serialization that can also be applied with other tools.

Introduce the Fast JSON API

The Fast JSON API is a JSON serializer for Rails APIs. It provides a way for us to generate serializer classes for each resource object in our API that is involved in customized JSON rendering. We can use these serializer classes to define the specific attributes we want objects to share or not share, along with things like related object attributes.

The result is that in our controller actions, rather than writing a custom render each time, we write out a serializer for each object once and use Fast JSON API to control the way our data is structured.

Initial Configuration

Before we can see the solution Fast JSON API provides, let's look back at the problem we've been dealing with. We will start at the same place we started when creating our own service class serializer. This code-along has three resources set up: birds, locations and sightings:

class Bird < ApplicationRecord
  has_many :sightings
  has_many :locations, through: :sightings
end
class Location < ApplicationRecord
  has_many :sightings
  has_many :birds, through: :sightings
end
class Sighting < ApplicationRecord
  belongs_to :bird
  belongs_to :location
end

We also have one customized controller action:

class SightingsController < ApplicationController
  def show
    sighting = Sighting.find_by(id: params[:id])
    render json: sighting.to_json(:include => {
      :bird => {:only => [:name, :species]},
      :location => {:only => [:latitude, :longitude]}
    }, :except => [:updated_at])
  end
end

This produces a specific set of data, with some but not all related attributes included:

{
  "id": 2,
  "bird_id": 2,
  "location_id": 2,
  "created_at": "2019-05-14T11:20:37.228Z",
  "bird": {
    "name": "Grackle",
    "species": "Quiscalus Quiscula"
  },
  "location": {
    "latitude": 30.26715,
    "longitude": -97.74306
  }
}

With just three objects and some minor customization, rendering has become complicated. With Fast JSON API, we can extract and separate this work into Serializer classes, keeping our controller cleaner.

Setting up Fast JSON API

To include Fast JSON API, add gem 'fast_jsonapi' to your Rails project's Gemfile and run bundle install.

Once installed, you will gain access to a new generator, serializer.

Implementing the Fast JSON API

With the new serializer generator, we can create serializer classes for all three of our models, which will be available to us in any controller actions later.

rails g serializer Bird
rails g serializer Location
rails g serializer Sighting

Running the above generators will create a serializers folder within /app, and inside, bird_serializer.rb, location_serializer.rb, and sighting_serializer.rb. With these serializers, we can start to define information about each model and their related models we want to share in our API.

Updating the Controller Action

To start using the new serializers, we can update our render json: statement so that it initializes the newly created SightingSerializer, passing in a variable, just as we did when creating our own service class:

class SightingsController < ApplicationController
  def show
    sighting = Sighting.find(params[:id])
    render json: SightingSerializer.new(sighting)
  end
end

ASIDE: Serializers generated by the Fast JSON API gem have two built-in methods called serializable_hash and serialized_json which return a serialized hash and a JSON string respectively. However, we don't actually need either of these in this example, as to_json will still be called on SightingSerializer.new(sighting) implicitly. As we will see, once our serializers are configured and initialized, we will not need
to do any additional work

The SightingSerializer.new(sighting) statement can be used on all SightingController actions we want to serialize, so if we were to add an index, for instance, we just pass in the array of all sightings as well:

def index
  sightings = Sighting.all
  render json: SightingSerializer.new(sightings)
end

But there is a problem still! If we fire up our Rails server and visit http://localhost:3000/sightings/2, all we see is the following:

{
  "id": "2",
  "type": "sighting"
}

The serializer is working, but it behaves a little differently than we're used to.

Adding Attributes

When rendering JSON directly, controllers will render all attributes available by default. These serializers work the other way around - we must always specify what attributes we want to include. In our example, birds have name and species attributes and locations have latitude and longitude attributes, so to include these we would update both serializers. For sightings, we could include the created_at attribute:

class BirdSerializer
  include FastJsonapi::ObjectSerializer
  attributes :name, :species
end
class LocationSerializer
  include FastJsonapi::ObjectSerializer
  attributes :latitude, :longitude
end
class SightingSerializer
  include FastJsonapi::ObjectSerializer
  attributes :created_at
end

If we go back and check http://localhost:3000/sightings/2 again, this time, we will see that the created_at attribute is present:

{
  "id": "2",
  "type": "sighting",
  "attributes": {
    "created_at": "2019-05-14T16:39:37.011Z"
  }
}

We can also use attributes to access related objects, adding them alongside normal object attributes:

class SightingSerializer
  include FastJsonapi::ObjectSerializer
  attributes :created_at, :bird, :location
end

This results in our rendered JSON including an "attributes" object with "created_at", "bird", and "location":

{
  "id": "2",
  "type": "sighting",
  "attributes": {
    "created_at": "2019-05-14T16:39:37.011Z",
    "bird": {
      "id": 2,
      "name": "Grackle",
      "species": "Quiscalus Quiscula",
      "created_at": "2019-05-14T16:39:36.917Z",
      "updated_at": "2019-05-14T16:39:36.917Z"
    },
    "location": {
      "id": 2,
      "latitude": 30.26715,
      "longitude": -97.74306,
      "created_at": "2019-05-14T16:39:36.942Z",
      "updated_at": "2019-05-14T16:39:36.942Z"
    }
  }
}

However, here, we have no control over what attributes are included in the related objects, and so we get all the attributes of "bird" and "location".

Adding Relationships

Object relationships can be included in serializers in two steps. The first step is that we include the relationships we want to reflect in our serializers. We can do this in the same way that we include them in the models themselves. A sighting, for instance, belongs to a bird and a location, so we can update the serializer to reflect this:

class SightingSerializer
  include FastJsonapi::ObjectSerializer
  attributes :created_at
  belongs_to :bird
  belongs_to :location
end

However, when visiting http://localhost:3000/sightings/2, Fast JSON API will display a new "relationships" object, but will only provide limited information, including the id of the related object:

{
  "id": "2",
  "type": "sighting",
  "attributes": {
    "created_at": "2019-05-14T16:39:37.011Z"
  },
  "relationships": {
    "bird": {
      "data": {
        "id": "2",
        "type": "bird"
      }
    },
    "location": {
      "data": {
        "id": "2",
        "type": "location"
      }
    }
  }
}

Setting these relationships up is necessary for the second step. Now that we have included relationships connecting the SightingSerializer to :bird and :location, to include attributes from those objects, the recommended method is to pass in a second options parameter to the serializer indicating that we want to include those objects:

def show
  sighting = Sighting.find_by(id: params[:id])
  options = {
    include: [:bird, :location]
  }
  render json: SightingSerializer.new(sighting, options)
end

The result:

{
  "data": {
    "id": "2",
    "type": "sighting",
    "attributes": {
      "created_at": "2019-05-14T16:39:37.011Z"
    },
    "relationships": {
      "bird": {
        "data": {
          "id": "2",
          "type": "bird"
        }
      },
      "location": {
        "data": {
          "id": "2",
          "type": "location"
        }
      }
    }
  },
  "included": [{
      "id": "2",
      "type": "bird",
      "attributes": {
        "name": "Grackle",
        "species": "Quiscalus Quiscula"
      }
    },
    {
      "id": "2",
      "type": "location",
      "attributes": {
        "latitude": 30.26715,
        "longitude": -97.74306
      }
    }
  ]
}

Because we have a BirdSerializer and a LocationSerializer, when including :bird and :location, Fast JSON API will automatically serialize their attributes as well.

Not Quite the Data Structure We Started With

At the beginning of this lesson, we had the following JSON, but with a messy controller:

{
  "id": 2,
  "bird_id": 2,
  "location_id": 2,
  "created_at": "2019-05-14T11:20:37.228Z",
  "bird": {
    "name": "Grackle",
    "species": "Quiscalus Quiscula"
  },
  "location": {
    "latitude": 30.26715,
    "longitude": -97.74306
  }
}

If you recall from when we created our own service class, since we just moved the to_json call from the controller action to our serializer, the JSON data structure looked the same.

Using Fast JSON API, with the use of relationships and passing a second parameter, we are able to get the same data, but in a much different structure. Fast JSON API is meant to be flexible and easy to implement, and it definitely is! From this point in the example bird watching application, we could fill out Bird and Location controllers and when we add in controller actions, we already have serializers created and ready to use!

In using Fast JSON API though, we lose the ability to design the structure of our JSON data.

Conclusion

There is a lot more you can do with the Fast JSON API gem, and it is worth reading through their documentation to become more familiar with it. It is possible, for instance, to create entirely custom attributes!

What we covered is enough to get us close to where we were creating our own customized serializers. We do not get to choose exactly how data gets serialized the way we do when writing our own serializer classes, but we gain a lot of flexibility by using the Fast JSON API.

The Fast JSON API gem provides a quick way to generate and customize JSON serializers with minimal configuration. Its conventions also allow it to work well even when dealing with a large number of related objects.

Overall, the goal of this section is to get you comfortable enough to get Rails APIs up and running. With practice, it is possible to build a multi-resource API, complete with many serialized JSON rendering endpoints within minutes.

Being able to quickly spin up an API to practice your fetch() skills is an excellent way to get familiar with asynchronous requests. As you move towards building larger frontend projects, you'll also quickly need a place to persist data and handle things like login security. Rails as an API will be a critical asset in your development through the remainder of this course.

Resources

js-rails-as-api-using-fast-json-api's People

Contributors

dependabot[bot] avatar ihollander avatar lizbur10 avatar maxwellbenton avatar rrcobb avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

js-rails-as-api-using-fast-json-api's Issues

bundle locked to mimemagic 0.3.3

Thanks for raising this issue! Future learners thank you for your diligence. In
order to help the curriculum team address the problem, please use this template
to submit your feedback. We'll work on addressing the issue as soon as we can.

Please fill out as much of the information below as you can (it's ok if you
don't fill out every section). The more context we have, the easier it will be
to fix your issue!

Note: you should only raise issues related to the contents of this lesson.
If you have questions about your code or need help troubleshooting, reach out to
an instructor/your peers.


Link to Canvas

https://github.com/learn-co-curriculum/js-rails-as-api-using-fast-json-api

What should be changed?

Been getting the following error on the last 4-5 labs or so.
Didn't know if it was an issue on my computer of what the labs are requiring is outdated.

Your bundle is locked to mimemagic (0.3.3) from rubygems repository
https://rubygems.org/ or installed locally, but that version can no longer be
found in that source. That means the author of mimemagic (0.3.3) has removed
it. You'll need to update your bundle to a version other than mimemagic (0.3.3)
that hasn't been removed in order to install.

Additional context

Add any other context about the problem here.

Spelling mistake

Screen Shot 2019-05-18 at 18 39 16

Hello,
Just a heads up that the @Sightings within the SightingSerailizer.new() produces data :null when loading the api on localhost:3000/sightings/2. It should be @sighting instead of @Sightings. It was written in the above code snippet too.
Many thanks,
Leah Boyd

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.