Skip to content

bdurand/fast_serializer

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

31 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Continuous Integration Regression Test Ruby Style Guide Gem Version

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 => "john@example.com"}

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)

You can also use the array helper class method on a serializer to do the same thing:

PersonSerializer.array([a, b, c, d])

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.

Installation

Add this line to your application's Gemfile:

gem 'fast_serializer'

And then execute:

$ bundle

Or install it yourself as:

$ gem install fast_serializer

Contributing

Open a pull request on GitHub.

Please use the standardrb syntax and lint your code with standardrb --fix before submitting.

License

The gem is available as open source under the terms of the MIT License.

About

Serialization library for Ruby objects that emphasizes speed and efficient code.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages