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.