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
endclass 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
endrender json: widgets, each_serializer: WidgetSerializer
After:
class WidgetsSerializer < ApplicationSerializer
collection :data, exec_context: :decorator,
decorator: WidgetSerializer, wrap: false def data
represented
end
endrender 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
endrender 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
endrender 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!