APIs With Rails:
render :json => The_simple_way

Lately my work at Fancy Pixel has focused on the backend of a product we’re about to launch and for which we decided to build a JSON API-only server. These APIs can be consumed from third party clients/services but are also used by our frontend. In this short report I’d like to share with you the simple solution that we’re using for the JSON generation and that in my humble opinion can be a quick and easy alternative to most commonly used systems like Jbuilder or ActiveModel::Serializers.

Rails, one of my favourite work buddies that I happened to use several times on projects that included the development of APIs. Due to personal curiosity, during the years I had the opportunity to try several solutions for JSON generation and I have to say that sometimes I found some difficulties with certain tecnologies: great for the majority of their functionalities sometimes they may force you to take weird paths to achieve the desired result. To be honest, the main reason for this continuative experimentation is probably the constant search for the top balance between comfort/ease of use/development-speed and performances and after all this test and try I arrived to the current solution, that probably is not something new, but that in my opinion can give somebody an alternative idea combining together great flexibility and bare metal performances.

To cut a long story short…

I don’t want to dwell with epic tales or “disquisitions about the gender of the Angels” so I built a trivial demo app that you can find on our GitHub account, to show you what I’m talking about.

Let’s rapidly setup our application:

git clone "https://github.com/FancyPixel/serializers_demo"
cd serializers_demo
bundle
rake db:create && rake db:migrate && rake db:seed

For the purposes of this article I decided to use rails-api (if you don’t already know it I reccomend you to give it a try) instead of standard Rails, for the simple fact that this is what we’re using right now in the project I mentioned at the beginning of this post. Obviously the same concepts apply identically to vanilla Rails. Let’s open together the code and take a rapid look at it: as you can see these few lines of source do nothing but respond to three routes and if you take a look at config/routes.rb you’ll find something like:

# config/routes.rb

Rails.application.routes.draw do
  namespace :v1, defaults: { format: :json } do
    get :jbuilder, to: 'comparison#jbuilder'
    get :ams, to: 'comparison#ams'
    get :simple, to: 'comparison#simple'
  end
end 

As you can see I’ve set json as the default format and I defined a namespace in such a way to replicate a typical process of APIs versioning. Let’s jump to the only existing controller (comparison_controller) where we find the implementation of the actions called from the routes. Each of these actions does exactly the same: load a bunch of records from the DB and render it as JSON, but each one does the rendering in its own way i.e. using respectively jbuilder, ActiveModel::Serializers and “my solution” that I’m going to call “simple”… what a fancy name uh?

We’re not going to focus on the first two systems, because chances are that you master those tecnologies better than me already and, also, there is nothing out of standard in my implementations, but instead we’re jumping feet together to the simple action. Like the competitors, it does nothing more than render some JSON, but this time the serialize_awesome_stuffs helper method is called. This method is defined in the V1::SimpleAwesomeStuffSerializer module that is included by the controller. You can find the module under app/serializers/v1 and if you’re going to open the file you’ll notice that it’s just a plain Ruby module defining methods.

# app/serializers/v1/simple_awesome_stuff_serializer.rb

module V1
  module SimpleAwesomeStuffSerializer

    def serialize_awesome_stuff(awesome_stuff = @awesome_stuff)
      {
          name: awesome_stuff.name,
          some_attribute: awesome_stuff.some_attribute,
          a_counter: awesome_stuff.a_counter
      }
    end

    def serialize_awesome_stuffs(awesome_stuffs = @awesome_stuffs)
      {
          awesome_stuffs: awesome_stuffs.map do |awesome|
            serialize_awesome_stuff awesome
          end
      }
    end
  end
end

Both the methods do nothing more that returning an Hash defining key-values couples that we’re willing to return as JSON. In particular serialize_awesome_stuffs creates an Array of Hash and internally, just for DRYing things up a bit, calls serialize_awesome_stuff (singular). Maybe the overall naming is not the best in the world, uh? Bonus point: defining the method’s parameter awesome_stuffs = @awesome_stuffs allows us to make our code lighter and more readable, because if we remained adherent to conventional variable naming, probably our controller is defining something like @awesome_stuff (and as a matter of fact, we did) that is directly visible and usable by our module. If we’re going to have a bout of creativity and want to use our personal variables names, we won’t have any sort of problem.

Take this piece of code as example:

# some_controller.rb

def some_action
  @my_personal_awesome_stuffs = AwesomeStuff.all
  render json: serialize_awesome_stuffs @my_personal_awesome_stuffs
end

and everything will work as expected.

Step-by-step: let’s complicate things a bit

Let’s raise a bit the complexity of our example, adding a User model and defining a one-to-many relationship with our AwesomeStuff:

rails g model user name:string

add the User reference in AwesomeStuff:

rails g migration add_user_reference_to_awesome_stuff user:references

and migrate everything:

rake db:migrate

Define now the relationships between models:

# app/models/awesome_stuff.rb
class AwesomeStuff < ActiveRecord::Base
  belongs_to :user
end

# app/models/user.rb
class User < ActiveRecord::Base
  has_many :awesome_stuffs
end

launch the Rails console with rails c and insert some test data into the DB:

# Add five users
users = []
5.times {|n| users << User.create(name: "user_#{n}") }

# Randomly associate our awesome records™ to users
AwesomeStuff.all.each { |aww| aww.update user: users.sample }

# A rapid test confirms that...
User.first.awesome_stuffs

=> #<ActiveRecord::Associations::CollectionProxy [#<AwesomeStuff id: ... ... >]>

Now that everything is prepared, let’s follow some of the steps we’d usually do during an API creation. Let’s create a UserController through which return to the client also user’s associated awesome records™. Create users_controller.rb` underapp/controllers/v1/and addindex“` action:

# app/controllers/v1/users_controller.rb

module V1
  class UsersController < ApplicationController
    include V1::UsersSerializer

    def index
      @users = User.all.includes(:awesome_stuffs)
      render json: serialize_users
    end
  end
end

As you can see I already added some stuff that we’ll need in short, that is the V1::UsersSerializer module. If you haven’t already, notice the scoping (V1) of our serializers: in doing so we can follow the evolution of our API’s versions with no hassles, possibly going to redefine the behavior of only serializers that may change. Do not forget to add new routes:

# config/routes.rb

Rails.application.routes.draw do
  namespace :v1, defaults: { format: :json } do
    get :jbuilder, to: 'comparison#jbuilder'
    get :ams, to: 'comparison#ams'
    get :simple, to: 'comparison#simple'

    # Let's add the 'index' only
    resources :users, only: :index
  end
end

What are we going to add to our UsersSerializer? A first idea should be something like:

# app/serializers/v1/users_serializer.rb

module V1
  module UsersSerializer

    def serialize_users(users = @users)
      {
        users: users.map do |user|
          {
              id: user.id,
              name: user.name,
              awesome_stuffs: user.awesome_stuffs.map do |aww|
                {
                    name: aww.name,
                    some_attribute: aww.some_attribute,
                    a_counter: aww.a_counter
                }
              end
          }
        end
      }
    end
  end
end

Ok, but there’s a lot of code smell here right? We already saw some of this stuff, let’s try to reuse it:

# app/serializers/v1/users_serializer.rb

module V1
  module UsersSerializer
    include V1::SimpleAwesomeStuffSerializer

    def serialize_users(users = @users)
      {
        users: users.map do |user|
          {
              id: user.id,
              name: user.name,
              awesome_stuffs: user.awesome_stuffs.map do |aww|
                serialize_awesome_stuff aww
              end
          }
        end
      }
    end
  end
end

Very well, but we can do better. Our API will probably have a route for a user’s data i.e. something like /v1/users/1 so we can move in advance and simultaneously dry up our current code:

# app/serializers/v1/users_serializer.rb

module V1
  module UsersSerializer
    include V1::SimpleAwesomeStuffSerializer

    def serialize_user(user = @user)
      {
          id: user.id,
          name: user.name,
      }
    end

    def serialize_users(users = @users)
      {
        users: users.map do |user|
          serialize_user(user).merge(
            {
                awesome_stuffs: user.awesome_stuffs.map do |aww|
                  serialize_awesome_stuff aww
                end
            }
          )
        end
      }
    end
  end
end

Ok, we’ve just “killed two birds with one stone”. (the birds are doing well, don’t worry) As you’ll have noticed it’s possible to obtain a further improvement:

# app/serializers/v1/users_serializer.rb

module V1
  module UsersSerializer
    include V1::SimpleAwesomeStuffSerializer

    def serialize_user(user = @user)
      {
          id: user.id,
          name: user.name
      }
    end

    def serialize_users(users = @users)
      {
        users: users.map do |user|
          serialize_user(user).merge(serialize_awesome_stuffs(user.awesome_stuffs))
        end
      }
    end
  end
end

What you’ve seen so far is a simple example of what it’s possibile to do with the tools we already have at hand and mainly wants to be an idea for those wo are constantly looking for the best performances and simplicity.

We reached the end and I hope I have not bored you too much, but if you got to this point, I probably didn’t :) What you have seen today may or may not be liked, but I personally find it a surely performant system that offers pure modularity, extensibility and code reuse.

Feel free to leave a comment, we’d really love to hear your feedback.

See ya soon!

Alessandro - @Aleverla