Migrating from ActiveModelSerializers to Representable

At Innocode, we use active_model_serializers gem for one of our products and one day it became no longer supported. Therefore we made a decision to look for another gem to replace it.

There are many great articles on the Internet what choices do you have if the project uses JSON API format for API responses. But what alternatives are there if you use a default ActiveModel::Serializer config adapter :attributes instead of :json_api?

I decided to start with alternatives mentioned at the ActiveModelSerializers gem page. The first three (jsonapi-rb, fast_jsonapi and jsonapi-resources) don’t support changing the format (they offer only JSON API), so they were not suitable for our needs. The last one, blueprinter, does not have the possibility to wrap the response (for example, in data), also not good.

And then I recalled, that the other Innocode project I work with uses Trailblazer and the part of it, Representable gem, felt like just what I was looking for.

Moving from ActiveModelSerializers to Representable took only a few steps that are described below.

Inheritance

First of all, you need to inherit from Representable::Decorator instead of ActiveModel::Serializer and include Representable::JSON module. It’s very easy to do so by creating the base decorator class and inheriting all the existing serializers from it.

Before:

class WidgetSerializer < ActiveModel::Serializer
end

After:

class ApplicationSerializer < Representable::Decorator
include Representable::JSON
end
class WidgetSerializer < ApplicationSerializer
end

Rendering JSON

Representable doesn’t automatically detect which serializer to use. You have to explicitly define it.

Before:

class WidgetsController < ApplicationController
def show
widget = Widget.find(params[:id])
    render json: widget
end
end

After:

class WidgetsController < ApplicationController
def show
widget = Widget.find(params[:id])
    render json: WidgetSerializer.new(widget)
end
end

Attributes

The next step is to update the attributes declaration of the object. With ActiveModelSerializers we listed all of them with the attributes option. Now we need to declare one at a line with a property option.

Before:

class WidgetSerializer < ActiveModel::Serializer
attributes :id, :name
end

After:

class WidgetSerializer < ApplicationSerializer
property :id
property :name
end

The object inside this serializer can be accessed by represented and all the properties are usually called on it. But what should you do if you want to call some property on the decorator instead, the helper method? Use :exec_context.

Before:

class WidgetSerializer < ApplicationSerializer
attributes :id, :name
  def name
object.title
end
end

After:


class WidgetSerializer < ApplicationSerializer
property :id
property :name, exec_context: :decorator
  def name
represented.title
end
end

Wrapping

root becomes representation_wrap.

Before:

class WidgetSerializer < ApplicationSerializer
root :data
end

After:

class WidgetSerializer < ApplicationSerializer
self.representation_wrap = :data
end

There’s also an ability to pass the wrap dynamically, as representation_wrap is a dynamic function option.

class WidgetSerializer < ApplicationSerializer
self.representation_wrap = ->(user_options:) do
user_options[:my_wrap] || :data
end
end
# wrap object with 'widget'
WidgetSerializer.new(widget).to_json(
user_options: { my_wrap: 'widget' }
)
# do not wrap the object
WidgetSerializer.new(widget).to_json(
user_options: { my_wrap: false }
)
# wrap the object with default 'data'
WidgetSerializer.new(widget).to_json

Associations

With Representable, has_one association is declared with the same property, has_many— with collection option. Note that you can turn the wrap off.

Before:

class WidgetSerializer < ApplicationSerializer
has_one :category, serializer: CategorySerializer
has_many :sources, each_serializer: SourceSerializer
end

After:

class WidgetSerializer < ApplicationSerializer
property :category, decorator: CategorySerializer, wrap: false
collection :sources, decorator: SourceSerializer
end

ActiveModelSerializers magic

ActiveModelSerializers gem is able to create a serialized collection given a serializer for a singular instance. Representable doesn’t know how to do it automatically, separate serializer for collection should be created.

Before:

class WidgetSerializer < ApplicationSerializer
end
render json: widgets, each_serializer: WidgetSerializer

After:

class WidgetsSerializer < ApplicationSerializer
collection :data, exec_context: :decorator,
decorator: WidgetSerializer, wrap: false
  def data
represented
end
end
render json: WidgetsSerializer.new(widgets)

External parameter

Sometimes object attribute depends on the additional options, that can be passed alongside.

Before:

class WidgetSerializer < ApplicationSerializer
attributes :id, :meta
attribute :name, if: :include_name?
  def meta
options[:meta]
end
  def include_name?
options[:include_name]
end
end
render json: widgets, meta: meta, include_name: true

After:

class WidgetsSerializer < ApplicationSerializer
property :name, if: ->(user_options:, **) do
user_options[:include_name]
end
  property :meta, getter: ->(user_options:, **) do
user_options[:meta]
end
end
render json: WidgetsSerializer.new(widgets).to_json(
user_options: { meta: meta, include_name: true }
)

These are the only changes needed to switch from ActiveModelSerializers to Representable in our project.

Representable is a great choice to select among the other options, as it has very similar API to ActiveModelSerializers and it’s not supposed to be challenging to start using it instead.

Check out the full documentation on Representable if you got interested!