Skip to content

OverridingSerialization

Josh Schairbaum edited this page May 21, 2013 · 15 revisions

Situation

The default serialization methods on a Ruby object do not include the proper attributes/methods. These objects are normally returned as resource representations in an API. ActiveModel-derived classes have "built-in" functionality to be easily overridden. Sometimes this functionality needs to be added from scratch.

OverridingSerialization is a technique that uses EmulateEstablishedInterfaces to provide to_format type behavior for Ruby objects.

NOTE: For the purpose of this entry to_format represents the call to which desired format is required, to_json or to_xml, for example. Also, I'm preferring to show examples as JSON, but the code should be the same for any format.

Adding Simple Format Serialization

Some objects, like DataObjects will not have complex serialization requirements. The simplest thing is to collect the necessary attributes in a hash and then call to_format on that hash. For example:

class Song
  attr_accessor :album, :artist, :title

  def initialize(album, artist, title)
    self.album  = album
    self.artist = artist
    self.title  = title
  end
end

To add a JSON format for this object, collect the necessary attributes in an attributes hash and then call to_json on that hash.

class Song
  def attributes
    { "album" => album, "artist" => artist, "title" => title }
  end

  def to_json
    attributes.to_json
  end
end

The attributes keys MUST be strings for consistency sake.

This is a pretty reliable but isn't a good choice where there needs to be different attributes based upon the context which the to_format is called. Additionally, adding new formats requires a new method for each object that serializes itself, although you could metaprogram the additional formats. If you're going to add a new format, ensure that the to_format method gets written on Hash.

ActiveModel::Serialization

If serialization requires additional configuration or requires differences based upon context, there is an existing library that can accomplish this: ActiveModel::Serialization. A standalone version has been extracted to rails-api/active_model_serializers.

How It Works

Your class essentially looks like this:

class Song
  def as_json(options = nil)
    serializable_hash(options)
  end

  def serializable_hash(options = nil)
    attributes # see note below
  end

  def to_json(options = nil)
    as_json(options)
  end
end

NOTE: I am simplifying the actual implementation for easier explanation, please refer to the documentation of serializable_hash and as_json for further understanding.

Implementing It

For JSON representations, also include ActiveModel::Serializers::JSON. This adds the methods #as_json, #from_json, #serializable_hash, and #to_json to the class. Include ActiveModel::Serializers::Xml for XML.

class Song
  include ActiveModel::Serializers::JSON
end

By default, #serializable_hash looks for a method called #attributes that returns a hash it can manipulate for it's representation. Create this method all fields you think are "valid" attributes or might want to display.

class Song
  include ActiveModel::Serializers::JSON

  def attributes
    { "album" => album, "artist" => artist, "title" => title }
  end
end

The attributes keys MUST be strings for consistency sake.

You now have a fully serializable class that accepts all the options that #serializable_hash accepts, such as :except, :methods, :only. Unless you have an ActiveRecord/Mongoid-derived class, it's unlikely the :include option will work.

Overriding It

If you want to change the behavior for all representations, you can manipulate the options sent to #serializable_hash by redefining it and pass them to super.

class Song
  def serializable_hash(options = nil)
    options         ||= {}
    default_options = { root: false }
    super(default_options.merge(options))
  end
end

This approach gives you the ability to always override your changes, if necessary. If you were only interested in modifying the JSON representation, you can do the same thing to #as_json. NOTE: The default value for the options argument must be nil as that is the value that is passed down from the stack.

Adding Another Serializer

That's a topic for another day, perhaps. Abusing ActiveModel Serializers for HAL

An Alternate Approach

If the logic for generating a representation is sufficiently complex, you might want to consider using an additional library such as JBuilder or RABL.

Further Reading

Clone this wiki locally