Ruby on Rails is a great choice for an API specifically because rendering JSON is as easy as render :json . However, out of the box, Rails serves up some pretty ugly data. Enter Serializer, the gem that allows us to format our JSON without having to lift a finger on the front end. We can select only the information we want, as well as get access to our relationships with a single request. Just take a look at this simple example and compare the results:

Standard on the left, Serialized on the right

Installation

Right after you finish setting up your API Rails project, you only have to add the serializer gem to your Gemfile, gem 'active_model_serializers' , and then run bundle install . Thanks to Rails being magic, that’s all it takes.

Setting up our standard files

For this example, we’ll keep it simple and just have people and their cats, in this case, a “belongs to” “has many” relationship. So, here’s all the things you should copy to play along:

After copying, don’t forget to create, migrate, and seed your database.

Setting up the Serializer files

To start off with, lets get all our cats, and just their name, id, and favorite food. To do this, either use rails g serializer cat , or manually make an /app/serializers/cat_serializer.rb file with this in it:

class CatSerializer < ActiveModel::Serializer

attributes :id, :name, :favorite_food

end

Because you installed Serializer, Rails will now look to this folder before rendering a resource. Each model needs a corresponding serializer file in this folder. Thats because when you pass an object to a render :json line in your controller, it’s now up to Serializer which attributes actually show. Test this out by visiting your /cats route and see what JSON gets rendered, then remove the favorite_food attribute. Now, try building the serializer for Person.

Bringing in relationships

You could do something like this to see a cat’s owner:

class CatSerializer < ActiveModel::Serializer

attributes :id, :name, :person

end

This is fine, Rails knows that each Cat belongs to a Person , so it will render out that full object…but this is sort of the whole point of Serializer. We don’t want the full object, just the attributes we need. To this end, we can use custom functions that narrow down our search:

class CatSerializer < ActiveModel::Serializer

attributes :id, :name, :owner

def owner

{owner_id: self.object.person.id,

owner_name: self.object.person.name}

end

end

In our attributes, we tell Serializer to use the owner method, which we then define as returning a structured hash. Another plus side to this approach is that we can redefine our vague “person” title as “owner” to our front end.Our Person class can also display each of their cats in a similar way:

class PersonSerializer < ActiveModel::Serializer

attributes :id, :cats

def cats

self.object.cats.map do |cat|

{name: cat.name,

age: cat.age,

favoriteFood: cat.favorite_food}

end

end

end

Here we get real fancy and overwrite the default cats method to better fit our needs. Since our front end is JS, we’re also giving our hash key a valid JS variable name, favoriteFood, so that it can be easily destructured once it gets there.

And relationships don’t have to be just “belongs to” “has many” ones, “many to many” also work, along with “has one” relationships.

What is that object though?

You’ll notice that we’re doing all this by going through self.object . self here refers to the <CatSerializer> (or <PersonSerializer> ) instance, and it has an attribute called object , which is whatever JSON object it’s going through at the moment. I say at the moment because Serializer essentially iterates through whatever it’s given. If it’s on an index route, it will loop through each object inside and format it, but it will do the same for a single object from a show route. Also, I like being explicit, but you don’t have to be, that self is implicit if you don’t put it.

These are just the basics

You could use an ApplicationSerializer file or some show methods to give an html template, create multiple, custom serializers for different routes, or hop over to the docs for more information, but what I’ve outlined here should be more than enough to get your API off the ground in the mean time.

happy coding everyone,

mike