-
Notifications
You must be signed in to change notification settings - Fork 1
OverridingSerialization
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.
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
.
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
.
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.
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.
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.
That's a topic for another day, perhaps. Abusing ActiveModel Serializers for HAL
If the logic for generating a representation is sufficiently complex, you might want to consider using an additional library such as JBuilder
or RABL
.
Concepts
Elements
Guidelines
Miscellaneous
Techniques