TinySerializer
Simple DSL for converting objects to Hashes, which is mostly API-compatible with ActiveModel::Serializers. Not quite a drop-in replacement, but facilitates migrating away from it. The resulting Hashes can be rendered as JSON by Rails using the ActiveSupport::JSON module.
Unlike AMS, it does not integrate with Rails for automatic usage with the render function,
which has proven to be one of the biggest sources of confusion and complexity for me.
render json: @object will continue to work exactly as it does in a base Rails setup.
Use if you have heavily invested in active_model_serializers, but have started experiencing the same frustrations I had with it and can't transition to jsonapi-rb or fast_jsonapi.
Benefits:
- Extremely simple and deterministic behavior; no adapter classes and other complications. Just turns Objects into Hashes.
- Easy to understand; uses
#as_jsonto serialize attributes. - Simple and explicit invocation.
- ~200 lines of code, give or take.
- Seems pretty darn fast, at least as fast as
public_send,Hash#[]=, andHash#to_jsoncan be.
Downsides:
- Uses
#as_jsonto serialize attributes (maybe unintended consequences, especially with complex objects). - Requires ActiveSupport.
Usage
Serializer definition:
MyObject = Struct.new(:id, :first_name, :last_name, :date) do
def parent; nil; end
def sub_record; nil; end
def related_items; []; end
end
class MyObjectSerializer < TinySerializer
attributes :id,
:first_name,
:last_name,
:date
belongs_to :parent, serializer: ParentSerializer
has_one :sub_record # serializer will be inferred to be SubRecordSerializer
has_many :related_items, serializer: RelatedItemSerializer
attribute :full_name do |object|
"#{object.first_name} #{object.last_name}"
end
endNotes on blocks:
The object parameter for blocks is optional, as blocks are executed
in the context of the serializer instance. It just makes it easier
to use the fast_jsonapi gem later if you want.
Usage:
object = MyObject.new(...)
# Several ways to invoke the serializer are available:
MyObjectSerializer.new(object).serialize
MyObjectSerializer.serialize(object)Produces:
{
"id": 1,
"first_name": "Fred",
"last_name": "Flintstone",
"date": "2000-01-01",
"full_name": "Fred Flintstone",
"parent": null,
"sub_record": null,
"related_items": []
}Usage in Rails:
In Rails, calling .serialize is optional because TinySerializer implements all the 'magic' methods (as_json, to_json, and serializable_hash), although I'm not the biggest fan (it loses some explicitness):
@model = MyObject.new(1, "Fred", "Flintstone", Date.new(2000, 1, 1))
render json: MyObjectSerializer.new(@model)Under the Hood
Takes every attribute you define, and uses it to call #public_send on the object to serialize,
then uses #as_json to serialize the resulting value. If you define the attribute with a block, it will call the block to get the value instead.
Usage with Collections:
Collections are handled automatically:
class MyObjectSerializer < TinySerializer
attributes :id, :first_name
end
items = [
MyObject.new(1, "Fred"),
MyObject.new(2, "Wilma")
]
MyObjectSerializer.new(items).serializeProduces:
[
{"id": 1, "name": "Fred"},
{"id": 2, "name": "Wilma"}
]Defining Associations and Serializing Sub-Objects
The DSL methods belongs_to, has_one, and has_many are all synonyms for "use this serializer to serialize this property". They are basically syntax sugar for:
class MySerializer < TinySerializer
attribute :parent do |object|
ParentSerializer.new(object.parent).serialize
end
endThese methods all optionally take a block, which you can use to customize the
object or collection. For example, to avoid loading every single item in a
has_many relation:
class MySerializer < TinySerializer
has_many :items, serializer: ItemSerializer do |object|
object.items.limit(100)
end
endLinks and URL's
When used with Rails, serializer instances have the #url_helpers
method available.
class ItemSerializer < TinySerializer
attribute :link do
url_helpers.item_path(object)
end
endNotes on ID's.
If you don't want ID's to be serialized as numeric types, you can have them automatically detected and coerced to Strings.
class MyObjectSerializer < TinySerializer
self.coerce_ids_to_string = true
endTo enable globally, but opt out for a specific attribute:
class MyObjectSerializer < TinySerializer
self.coerce_ids_to_string = true
attribute :id, is_id: false
endPassing options and additional arguments to the serializer
If you pass in an options object as the second argument to the serializer, it will be available for use within the serializer. For example:
class MyObjectSerializer < TinySerializer
attribute :id do |object, options|
options[:prefix] + object.id
end
end
MyObjectSerializer.new(object, { prefix: "prefix_" }).serialize
# Produces:
{
id: "prefix_id"
}Attribute Inheritance
Attributes are inherited from parent classes, but can be extended.
This example works as you would expect from active_model_serializers
(the name attribute will only appear if you use MyObjectSerializer::WithName.serialize(my_object)):
class MyObjectSerializer < TinySerializer
attribute :id
class WithName < MyObjectSerializer
attribute :name
end
endInstallation
Add this line to your application's Gemfile:
gem 'tiny_serializer', '~> 2.0.0'And then execute:
$ bundle
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/wmakley/tiny_serializer.
License
The gem is available as open source under the terms of the MIT License.