This gem provides a highly optimized framework for serializing Ruby objects into hashes suitable for serialization to some other format (i.e. JSON). It provides many of the same features as other serialization frameworks like active_model_serializers, but it is designed to emphasize code efficiency over feature set and syntactic surgar.

Examples

For these examples we'll assume we have a simple Person class.

class Person
  attr_accessor :id, :first_name, :last_name, :parents, :children

  def intitialize(attributes = {})
    @id = attributes[:id]
    @first_name = attributes[:first_name]
    @last_name = attributes[:last_name]
    @gender = attributes(:gender)
    @parent = attributes[:parents]
    @children = attributes[:children] || {}
  end

  def ==(other)
    other.instance_of?(self.class) && other.id == id
  end
end

person = Person.new(:id => 1, :first_name => "John", :last_name => "Doe", :gender => "M")

Serializers are classes that include FastSerializer::Serializer. Call the serialize method to specify which fields to include in the serialized object. Field values are gotten by calling the corresponding method on the serializer. By default each serialized field will define a method that delegates to the wrapped object.

class PersonSerializer
  include FastSerializer::Serializer
  serialize :id, :name

  def name
    "#{object.first_name} #{object.last_name}"
  end
end

PersonSerializer.new(person).as_json # => {:id => 1, :name => "John Doe"}

You can alias fields so the serialized field name is different than the internal field name. You can also turn off creating the delegation method if it isn't needed for a field.

class PersonSerializer
  include FastSerializer::Serializer
  serialize :id, as: :person_id
  serialize :name, :delegate => false

  def name
    "#{object.first_name} #{object.last_name}"
  end
end

PersonSerializer.new(person).as_json # => {:person_id => 1, :name => "John Doe"}

You can specify a serializer to use on fields that return complex objects.

class PersonSerializer
  include FastSerializer::Serializer
  serialize :id
  serialize :name, :delegate => false
  serialize :parent, serializer: PersonSerializer
  serialize :children, serializer: PersonSerializer, enumerable: true

  def name
    "#{object.first_name} #{object.last_name}"
  end
end

person.parent = Person.new(:id => 2, :first_name => "Sally", :last_name => "Smith")
person.children << Person.new(:id => 3, :first_name => "Jane", :last_name => "Doe")
PersonSerializer.new(person).as_json # => {
                                     #      :id => 1,
                                     #      :name => "John Doe",
                                     #      :parent => {:id => 2, :name => "Sally Smith"},
                                     #      :children => [{:id => 3, :name => "Jane Doe"}]
                                     #    }

Subclasses of serializers inherit all attributes. You can add or remove additional attributes in a subclass.

class PersonSerializer
  include FastSerializer::Serializer
  serialize :id
  serialize :name
  serialize :phone
end

class EmployeeSerializer < PersonSerializer
  serialize :email
  remove :phone
end

PersonSerializer.new(person).as_json # => {:id => 1, :name => "John Doe", :phone => "222-555-1212"}
EmployeeSerializer.new(person).as_json # => {:id => 1, :name => "John Doe", :email => "[email protected]"}

Optional and excluding fields

Serializer can have optional fields. You can also specify fields to exclude.

class PersonSerializer
  include FastSerializer::Serializer
  serialize :id
  serialize :name, :delegate => false
  serialize :gender, optional: true

  def name
    "#{object.first_name} #{object.last_name}"
  end
end

PersonSerializer.new(person).as_json # => {:id => 1, :name => "John Doe"}
PersonSerializer.new(person, :include => [:gender]).as_json # => {:id => 1, :name => "John Doe", :gender => "M"}
PersonSerializer.new(person, :exclude => [:id]).as_json # => {:name => "John Doe"}

You can also pass the :include and :exclude options as hashes if you want to have them apply to associated records.

class PersonSerializer
  include FastSerializer::Serializer
  serialize :id
  serialize :name
  serialize :company, serializer: CompanySerializer
end

PersonSerializer.new(person, :exclude => {:company => :address}).as_json

You can also specify fields to be optional with an :if block in the definition with the name of a method from the serializer. It can also be a Proc that will be executed with the binding of an instance of the serializer. The field will only be included if the method returns a truthy value.

class PersonSerializer
  include FastSerializer::Serializer
  serialize :id
  serialize :name, if: -> { scope && scope.id == id }
  serialize :role, if: :staff?

  def staff?
    object.staff?
  end
end

Serializer options

You can specify custom options that control how the object is serialized.

class PersonSerializer
  include FastSerializer::Serializer
  serialize :id
  serialize :name, :delegate => false

  def name
    if option(:last_first)
      "#{object.last_name}, #{object.first_name}"
    else
      "#{object.first_name} #{object.last_name}"
    end
  end
end

PersonSerializer.new(person).as_json # => {:id => 1, :name => "John Doe"}
PersonSerializer.new(person, :last_first => true).as_json # => {:id => 1, :name => "Doe, John"}

The options hash is passed to all nested serializers. The special option name :scope is available as a method within the serializer and is used by convention to enforce various data restrictions.

class PersonSerializer
  include FastSerializer::Serializer
  serialize :id
  serialize :name
  serialize :email, if: -> { scope && scope.id == object.id }
end

Caching

You can make serializers cacheable so that the serialized value can be stored and fetched from a cache.

class PersonSerializer
  include FastSerializer::Serializer
  serialize :id
  serialize :name, :delegate => false

  cacheable true, ttl: 60

  def name
    if option(:last_first)
      "#{object.last_name}, #{object.first_name}"
    else
      "#{object.first_name} #{object.last_name}"
    end
  end
end

FastSerializer.cache = MyCache.new # Must be an implementation of FastSerializer::Cache

For Rails application, you can run this in an initializer to tell FastSerializer to use Rails.cache

FastSerializer.cache = :rails

You can also pass a cache to a serializer using the :cache option.

Collections

If you have a collection of objects to serialize, you can use the FastSerializer::ArraySerializer to serialize an enumeration of objects.

  FastSerializer::ArraySerializer.new([a, b, c, d], :serializer => MyObjectSerializer)

Performance

Your mileage may vary. In many cases the performance of the serialization code doesn't particularly matter and this gem performs just about as well as other solutions. However, if you do have high throughput API or can utilize the caching features or have heavily nested models in your JSON responses, then the performance increase may be noticeable.