Quần Cam

The Ruby world now has another serializer

This post is an opinionated comparison between ActiveModel::Serializer, Rabl, Grape::Entity, and Decoradar.


If you’re using Rails, ActiveModel::Serializer (AM::S) might have been your default choice when picking a serializer for your application. I really love the DSL that AM::S brings.

See the following example.

class PostSerializer < ::ActiveModel::Serializer
  attributes :id, :name

  has_many :comments


Anyway, AM::S only works if your object is an ActiveRecord aka ActiveModel . If you want to make AM::S works for your plain old ruby object, it’s another horrible long story. Look at this thread for more information.


Roar is a speedy Parse and render REST API documents using representers, it’s part of Trailblazer.

require 'roar/decorator'
require 'roar/json'

class SongRepresenter < Roar::Decorator
  include Roar::JSON

  property :title

Roar is not limited to ActiveRecord, it can be used with any PORO like Sequel::Model, or even a simple struct.

Grape Entity

Grape Entity describes itself as an API focused facade that sits on top of an object model. Entities in Grape Entity can be used to conditionally include fields, nest other entities, and build ever larger responses, using inheritance.

Grape Entity also provides an DSL to declare exposures (fields you want to expose), and supports options like :if, :unless, and :proc to determine which attribute to expose by conditions.

The only thing I personally don’t like about Grape Entity is that it has too troublesome DSL. Your class would end up in something like the following example if you’re trying to lump so many logics to your entity.

module API
  module Entities
    class Status < Grape::Entity
      format_with(:iso_timestamp) { |dt| dt.iso8601 }

      expose :user_name
      expose :text, documentation: { type: "String", desc: "Status update text." }
      expose :ip, if: { type: :full }
      expose :user_type, :user_id, if: lambda { |status, options| status.user.public? }
      expose :location, merge: true
      expose :contact_info do
        expose :phone
        expose :address, merge: true, using: API::Entities::Address
      expose :digest do |status, options|
        Digest::MD5.hexdigest status.txt
      expose :replies, using: API::Entities::Status, as: :responses
      expose :last_reply, using: API::Entities::Status do |status, options|

      with_options(format_with: :iso_timestamp) do
        expose :created_at
        expose :updated_at


I would love to have an serializer that:

  • Work for any Plain-Old Ruby Object.
  • Has the simple DSL like ActiveModel::Serializer does.
  • Conditional exposure like Grape::Entity.
  • Lastly, comes with the possibility of unit-testable.

That’s the reason why Decoradar was born.

Work for any Plain-Old Ruby Object and Simple DSL

Decoradar has a really simple DSL, attribute and collection.

  • attribute: for value.
  • collection: for collections.
class PostSerializer
  include Decoradar

  attribute :id, :name, :slug
  collection :comments, serializer: CommentSerializer

  def slug

post = Post.new(id: 1, name: "Ruby World needs another object serializer", comments: [])
# => {id: 1, name: "Ruby World needs another object serializer", slug: "ruby-world...-serializer", comments: []}

Since Decoradar is just Ruby (it has literally zero dependency) with zero magic stuff, so it works for any PORO-ish objects.

Conditional exposures

Decoradar allows conditional exposure by providing :include_if and :as option in attribute and collection declaration.

class UserSerializer
  include Decoradar

  attributes :id, :name
  attribute :admin, as: :is_admin, include_if: ->(user) { user.admin? }

bob = User.new(id: 1, name: "Bob", admin: true)
# => {id: 1, name: "Bob", is_admin: true}

alice = User.new(id: 2, name: "Alice", admin: false)
# => {id: 2, name: "Alice"}


Ease of test is very important and Decoradar was built with that thinking bearing in mind. Consider this example.

class PostSerializer
  include Decoradar

  attributes :id, :name

Wanna unit test it? As easy as 1 + 1 = 2.

mocked = mock(id: 1, name: 2)
expected = {id: 1, name: 2}

assert(expected, PostSerializer.new(mocked).as_json)


Benchmark was done based on benchmark_json_renderer on my Macbook Pro 2.7GHz core i5 8 GB 1867 MHz DDR3.


Calculating -------------------------------------
collection ultra simple 148.000  i/100ms
   collection simple    62.000  i/100ms
  collection complex    13.000  i/100ms
collection ultra simple 1.659k (± 5.6%) i/s -     16.576k
collection simple       532.225  (±15.8%) i/s -      5.208k
collection complex      123.775  (±10.5%) i/s -      1.235k


collection ultra simple
                        27.000  i/100ms
   collection simple    29.000  i/100ms
  collection complex    29.000  i/100ms
collection ultra simple
                        297.589  (± 8.4%) i/s -      2.970k
   collection simple    260.505  (±15.4%) i/s -      2.552k
  collection complex    285.935  (±14.7%) i/s -      2.755k


collection ultra simple
                        79.000  i/100ms
   collection simple    33.000  i/100ms
  collection complex     7.000  i/100ms
collection ultra simple
                        897.488  (± 4.8%) i/s -      9.006k
   collection simple    343.539  (± 4.1%) i/s -      3.432k
  collection complex     78.961  (± 5.1%) i/s -    791.000

So generally Decoradar was:

  • 2x faster than AM::S and 7x faster then Roar in ultra simple resources.
  • 1.5x faster than AM::S and 2x faster then Roar in simple resources.
  • ~2x faster than AM::S and 2x slower then Roar in complex resources.

on my computer.

That’s it, try Decoradar out and do give me some feedbacks on how to improve this.

Khuyến cáo giữ chặt bàn phím và lướt thật nhanh khi đi qua khu vực này.
Chức năng này hỗ trợ markdown và các thứ liên quan.

Bài viết cùng chủ đề

Tôi đã tuột quần với Bundler như thế nào?

Khi làm việc với Ruby và Bundler, ta rất hay thường dùng Bundle.setup và Bundle require để load các thư viện trong project. Vậy có bao giờ bạn bị lỗi tuột quần với nó chưa?

Euruko 2017 Notes

Vừa rồi mình đi Euruko 2017 ở Budapest, một số bài nói cũng khá thú vị nên mình sẽ note lại ở đây.

Six confusing features in Ruby

In this post I am trying to point out some Ruby features you might want to use with a lot of caution.