Building a Rails API part 5. ActiveModelSerializers and much more!
ActiveModel Serializers. Let's fixup our API.
Once again, the order of this tutorial is:
This is the last section of this tutorial. Git clone this branch to start.
We will be using the active-model serializer gem to customize our API structure.
This is the gem:
Active Model Serializer Gem
This is a useful resource:
Blog Post on Active Model Serializer
Quote from the above link:
These days, there are so many different choices when it comes to serving data from an API. You can build it in Node with ExpressJS, in Go with Martini, Clojure with Compojure, and many more. But in many cases, you just want to bring something to market as fast as you can. For those times, I still reach for Ruby on Rails.
With Rails, you can spin up a function API server in a very short period of time. Rails is large. Perhaps you object that there's "too much magic". Have you ever checked out the rails-api gem? It lets you enjoy all the benefits of Rails without including unnecessary view-layer and asset-related code.
On the active-model-serializers:
Rails-api is maintained by Carlos Antonio Da Silva, Santiago Pastorino, Rails Core team members, and all-around great Rubyist Steve Klabnik. While not busy working on Rails or the Rails API Gem, they found the time to put together the activemodelserializers gem to make it easier to format JSON responses when using Rails as an API server... ActiveModel::Serializers (AMS) is a powerful alternative to jbuilder, rabl, and other Ruby templating solutions.
So, currently our API response is mad ugly (I believe this is the technical term?).
It would be nice if we could remove the timestamps and IDs in our API response.
This is where AMS comes in.
Add the active-model-serializers gem to the Gemfile.
In our Gemfile, type:
In terminal, type:
rails g serializer vendor
That should create a app/serializers/vendor_serializer.rb file. In it, type:
class VendorSerializer < ActiveModel::Serializer attributes :id, :name end
In our vendors_controller, in the index action, type:
def index render json: Vendor.all, each_serializer: VendorSerializer end
In our show action, type:
render json: Vendor.find(params[:id]), serialiezr: VendorSerializer
However, see this issue:
Incompatibility between AMS ~> 0.9.0 and rails-api gem
In order to fix this error with the AMS gem of ~> 0.9.0, we need to include this in our ApplicationController:
class ApplicationController < ActionController::API include ActionController::Serialization end
Now when we visit /vendors or /vendors/1, we get a slightly cleaner API output without the createdat or updatedat.
We can also do other cool things with AMS.
Imagine we obtained this suya and vendor data from the national association of suya vendors (abbreviated as NASV from here on out). Say the NASV has a public API interface where you can obtain all the data related to vendors and their respective suyas. Say this information was incomplete. Say our seed data file is a replica of that real data and that's how sparse the NASV's API is. So we see an opportunity. We want to create an API with even more data and we may want to perform some calculations on the original API data. So let's add some fields.
Let's say we want to give each vendor a score based on how many suyas they own.
First, let's add this score method to our vendor model:
def suyas_count suyas.count end
And in our vendors_serializer:
attributes :id, :name, :score def score object.suyas_count end
The above could have been "simplified" by simply typing this in the vendors_serializer:
attributes :id, :name, :score def score object.suyas.count end
But I wanted to show the connection between the model and the serializer, and the object method that is available inside the serializer.
Right now, our API response looks like this after these changes:
Say we wanted to include another tag at the top level with say with the average suyas owned per vendor. We can do that with AMS.
In the index action of the vendors_controller.rb:
def index render json: Vendor.all, each_serializer: VendorSerializer, meta: Vendor.average_suya_per_vendor, meta_key: "average_suya" end
In our vendor_model:
def self.average_suya_per_vendor total = 0 total_vendors = all.count all.each do |vendor| total += vendor.suyas.count end total/total_vendors.to_f end
Lastly, our Gemfile needs to change to use a different compatible version of AMS with rails-api so that you can add root nodes like meta.
gem 'active_model_serializers', '~> 0.8.0'
And now when you visit /vendors, you should see the extra root node which contains your own calculated data.
Hope that was clear! If you have any questions, feel free to reach out to Jeffrey Wan on Github.