DEV Community

Cover image for Basic Intro to Rails Serializers
krisperu
krisperu

Posted on

Basic Intro to Rails Serializers

When working with Rails, it is simple to create a JSON object. Using an index or show method, will provide a list of all the data available.

#The Rails index method
def index
    render json: Book.all
end

#Returns this JSON hash
[
    {
        "id": 1
        "title": "Pride and Prejudice",
        "author": "Jane Austen",
        "description": "Set in England in the early 19th century, Pride and Prejudice tells the story of Mr and Mrs Bennet's five unmarried daughters after the rich and eligible Mr Bingley and his status-conscious friend, Mr Darcy, have moved into their neighbourhood. While Bingley takes an immediate liking to the eldest Bennet daughter, Jane, Darcy has difficulty adapting to local society and repeatedly clashes with the second-eldest Bennet daughter, Elizabeth.", 
        "image": "https://d1w7fb2mkkr3kw.cloudfront.net/assets/images/book/lrg/9781/4351/9781435159631.jpg", 
        "year": 1813,
        "genre": "Romance",
        "isbn": 9780140430721,
        "publisher": "T. Egerton, Whitehall",
        "length": 259,  
        "reading_time": 1, 
        "rating": 5,
        "created_at": "2022-04-14T15:42:23.682Z",
        "updated_at": "2022-04-14T15:42:23.682Z"
    }
]
Enter fullscreen mode Exit fullscreen mode

That is a lot of information! What happens in the scenario where I only need the title, author, and rating? I can always pass all the information and only use what I need. That could work! While continuing to work another scenario arises where only the Title, Image, and Rating are needed. Again, I can pass the whole object and only use the data required. But that is a lot of unnecessary information. Luckily, there is a way in Rails to pass only the information you need. Using .to_json will allow you to pass only certain information.

#Adding to_json to the show method
def show
    books = Book.find(params[:id])
    render json: book.to_json(only:[:id, :title, :image, :rating])
end

#Will return the following:
[
    { 
        "id": 1,
        "title": "Pride and Prejudice",
        "image": "https://d1w7fb2mkkr3kw.cloudfront.net/assets/images/book/lrg/9781/4351/9781435159631.jpg",
        "rating": 5
    }
]
Enter fullscreen mode Exit fullscreen mode

That is much more efficient! But this method can quickly become overwhelming and crowded. If we want a method where we can observe good coding practice and have separation of concerns, we'll have to look at something different.

Serializers

Active Model Serializers (AMS) are a gem you can add to your project to customize how your JSON is rendered. It is a way to get the exact data you need while keeping separation of concerns.

To start, you need to install the gem.

gem 'active_model_serializers'

You can do this to on an active Rails project you are working on, just run bundle install after adding the gem, or add it during the setup portion, with all your gems.

After installing, you can generate the serializers using Rails generators.

rails g serializer book

This will generate a serializers folder in the app folder of your project. In the folder there will be a file called book_serializer.rb.

class BookSerializer < ActiveModel::Serializer
  attributes :id
end
Enter fullscreen mode Exit fullscreen mode

You can add as many attributes as needed. For the example above where we only needed the id, title, image, and rating, we could have the following:

class BookSerializer < ActiveModel::Serializer
  attributes :id, :title, :image, :rating
end

#Will return the following:
[
    { 
        "id": 1,
        "title": "Pride and Prejudice",
        "image": "https://d1w7fb2mkkr3kw.cloudfront.net/assets/images/book/lrg/9781/4351/9781435159631.jpg",
        "rating": 5
    }
]
Enter fullscreen mode Exit fullscreen mode

Fantastic! We got exactly what we needed, and good coding practices are observed with separation of concerns.

Once again, all the steps.

  1. Install Active Model Serializers gem
  2. Create a Serializer
  3. Add required attributes

Custom Methods

We can also create custom methods for our serializers.

class BookSummarySerializer < ActiveModel::Serializer
  attributes :id, :summary, :author

  def summary
    "#{self.object.title} - #{self.object.description[0..49]}..."
  end

end
Enter fullscreen mode Exit fullscreen mode

We create a method called summary and define what it does. In this case we are interpolating the title and the first 49 characters of the book description. After creating the method, we just add it to the list of attributes, and it will show up in the JSON.

#Will return the following:
[
    { 
        "id": 1,
        "summary": "Pride and Prejudice - Set in England in the early 19th century, Pride an...",
        "author": "Jane Austen"
    }
]
Enter fullscreen mode Exit fullscreen mode

Let's go over those steps again.

  1. Create a custom method in your serializer
  2. Add the new method to your attributes

You can add as many custom methods as needed, but after a certain point, we can run into the issue of separation of concerns again. To separate our concerns, we can create a custom serializer.

Custom Serializers

To create a custom serializer for the method shown above, we need to start by creating the serializer. We can create a new file physically in our serializers folder, or we can use a generator again.

If you add a file by hand, make sure to observe proper syntax. Serializers need to be singular and named using snake_case. You will also need to add the class using PascalCase, and shovel it to your ActiveModel.
class BookSummarySerializer < ActiveModel::Serializer

After creating or generating the new serializer called BookSummarySerializer, we need to create the custom method, and add it to the attributes.

class BookSummarySerializer < ActiveModel::Serializer
  attributes :id, :summary, :author
end

def summary
  "#{self.object.title} - #{self.object.description[0..49]}..."
end

#Will return the following:
[
    { 
        "id": 1,
        "summary": "Pride and Prejudice - Set in England in the early 19th century, Pride an...",
        "author": "Jane Austen"
    }
]

Enter fullscreen mode Exit fullscreen mode

After completing the custom serializer, it needs to be added to your controller. Rails will automatically check for a serializer with the same name as your controller, so if you want your custom serializer to show, you need to add it to your controller after rendering to JSON.

class BooksController < ApplicationController
    def index
        render json: Book.all
    end

    def show
        book = Book.find(params[:id])
        render json: book, serializer: BookSummarySerializer
    end
end
Enter fullscreen mode Exit fullscreen mode

Steps to create a custom serializer:

  1. Create a new serializer file with the custom name
  2. Add all the needed information to your custom serializer
  3. Add the custom serializer to the proper controller

Associated Data

If your data has associations created through models, you can access that data in your serializers as well. Building on our Book example, we can have an association where a book can have many reviews.
Book-<Reviews
You can show associated reviews of your books show in your JSON through serializers by simply adding the association.

If you have a many through relationship, you only need to write has_many for the info to show up.
Ex: Author -< Books >- Genre
An author has many books and has many genres through books. If you wanted to have the associated genres to your author on your JSON, you add the has_many :genres macro without needing to specify the "through" relationship.

class BookSerializer < ActiveModel::Serializer
  attributes :id, :title, :author

  has_many :reviews
end

#Will return the following:
[
    { 
        "id": 1,
        "title": "Pride and Prejudice",
        "author": "Jane Austen"
        "reviews": [
            { 
                "name": "Bob"
                "review": "Awesome classic!"
            },
            { 
                "name": "Nancy"
                "review": "Best book ever!"
            },
            { 
                "name": "Lea"
                "review": "Meh.. Not my speed."
            }
        ]
    }
]
Enter fullscreen mode Exit fullscreen mode

Great! Now we have our book and all its related reviews!

Let's go over those steps again.

  1. Have associations created in your models
  2. Add required associations to your serializer

Deeply Nested Data

Let's look at a different example where we have a different relationship.
Author-< Book -< Review
The author has many books which have many reviews. If we wanted to have that information appear in our JSON, could we do that through serializers as well? Active Model Serializers only nest one level deep to protect from overcomplicating our data. If we still wanted the deeply nested data, we could overwrite AMS behavior.

We'll start by looking at our serializers.

#Author Serializer
class AuthourSerializer < ActiveModel::Serializer
  attributes :id, :first_name, :last_name

  has_many :books
end


#Book Serializer
class BookSerializer < ActiveModel::Serializer
  attributes :id, :title

  belongs_to :author
  has_many :reviews
end


#Review Serializer
class ReviewSerializer < ActiveModel::Serializer
    attributes :id, :name, :review
end
Enter fullscreen mode Exit fullscreen mode

As we can see above, the author serializer includes its association to books, and the book serializer includes its association to author and reviews. For our deeply nested models to work, we need to include the associations in the proper serializers.

After creating the proper associations, we need to add information to our author controller so we can override the AMS behavior.

class AuthorController < ApplicationController
    def index
        render json: Book.all
    end

    def show
        author = Author.find(params[:id])
        render json: author, include: ['books', 'books.reviews']
    end
end
Enter fullscreen mode Exit fullscreen mode

In the show method, after the render to JSON, there is code that tells Active Model Serializer that we want to render information for the author, and to also include information for the books associated with that author, and for the reviews associated with those books. Now our JSON will return the following:

[
    {
        "id": 1,
        "first_name": "Jane",
        "last_name": "Austen",
        "books": [
            {
                "id": 1,
                "title": "Pride and Prejudice",
                "reviews": [
                    { 
                        "name": "Bob"
                        "review": "Awesome classic!"
                    },
                    { 
                        "name": "Nancy"
                        "review": "Best book ever!"
                    },
                    { 
                        "name": "Lea"
                        "review": "Meh.. Not my speed."
                    }
                ]
            }
        ]
    }
]
Enter fullscreen mode Exit fullscreen mode

Mission accomplished!
Let's go over the steps one more time.

  1. Create serializers
  2. Add the associations to the proper serializers
  3. Include the information we want to render in the associated controller

Final Thoughts

Serializers are very powerful and useful. We can do so much with them and get our JSON exactly how we need it to be. One thing we should keep in mind, however, is that with each of the serializers, custom serializers, and custom methods we are adding complexity to our program. All the macros and methods start to add up quickly and can make things very complex.


Sources
Cover Image
Flatiron
Serializer Docs

Discussion (0)