EasySerializer
Semantic serializer for making easy serializing objects. EasySerializer is inspired in ActiveModel Serializer > 0.10 it's a simple solution for a day to day work with APIs. It tries to give you a serializer with flexibility, full of features and important capabilities for caching.
Features:
- Nice and simple serialization DSL.
- Cache helpers to use with your favorite adapter like rails cache.
Advantages:
- Separated responsibility from Model class and serialization allowing multiple serializers for the same Model class, very useful for API versioning.
- In contraposition with active model serializers with EasySerializer you can serialize any object responding to the methods you want to serialize.
- EasySerializer is an small library with few dependencies.
Installation
Add this line to your application's Gemfile:
gem 'easy_serializer'
And then execute:
$ bundle
Or install it yourself as:
$ gem install easy_serializer
Add the configuration file:
Only if you need caching.
If your are in a Rails environment place this file at config/initializers
EasySerializer.setup do |config|
# = perform_caching
#
# Enable o disable caching.
# default: false
#
# config.perform_caching = true
# = cache
#
# Set your caching tool for the serializer
# must respond to fetch(obj, opts, &block) like Rails Cache.
# default: nil
#
# config.cache = Rails.cache
end
Usage
Simple example:
user = OpenStruct.new(name: 'John', surname: 'Doe')
class UserSerializer < EasySerializer::Base
attributes :name, :surname
end
UserSerializer.call(user)
# =>
{
name: 'John',
surname: 'Doe'
}
Using blocks:
Object being serialized is pass in the block as a first argument.
class UserSerializer < EasySerializer::Base
attribute(:name) { |user| user.name.capitalize }
attribute(:surname) { |user| user.surname.capitalize }
end
Using helpers in blocks:
Blocks are executed in the serializer instance, this way you can build your helpers and use them inside the blocks.
class BlockExample < EasySerializer::Base
attribute :name do |object|
upcase object.name
end
def upcase(str)
str.upcase
end
end
Changing keys:
class UserSerializer < EasySerializer::Base
attribute :name, key: :first_name
attribute(:surname, key: :last_name) { |user| user.surname.capitalize }
end
Using defaults:
Default will only be triggered when value is nil
obj = OpenStruct.new(name: 'Jack', boolean: nil, missing: nil)
class DefaultLiteral < EasySerializer::Base
attribute :name
attribute :boolean, default: true
attribute(:missing, default: 'anything') { |obj| obj.missing }
end
output = DefaultLiteral.call(obj)
output.fetch(:name) #=> 'Jack'
output.fetch(:boolean) #=> true
output.fetch(:missing) #=> 'anything'
Using blocks:
obj = OpenStruct.new(name: 'Jack', boolean: nil, missing: nil)
class DefaultBlock < EasySerializer::Base
attribute :name
attribute :boolean, default: proc { |obj| obj.name == 'Jack' }
attribute :missing, default: proc { |obj| "#{obj.name}-missing" } do |obj|
obj.missing
end
end
output = DefaultBlock.call(obj)
output.fetch(:name) #=> 'Jack'
output.fetch(:boolean) #=> true
output.fetch(:missing) #=> 'Jack-missing'
Serializing nested objects
user = OpenStruct.new(
name: 'John',
surname: 'Doe',
address: OpenStruct.new(
street: 'Happy street',
country: 'Wonderland'
)
)
class AddressSerializer < EasySerializer::Base
attributes :street, :country
end
class UserSerializer < EasySerializer::Base
attributes :name, :surname
attribute :address, serializer: AddressSerializer
end
UserSerializer.call(user)
# =>
{
name: 'John',
surname: 'Doe',
address: {
street: 'Happy street',
country: 'Wonderland'
}
}
Removing keys from nested hashes:
class UserSerializer < EasySerializer::Base
attribute :name, :lastname
attribute :address,
key: false,
serializer: AddressSerializer
end
UserSerializer.call(user)
# =>
{
name: 'John',
surname: 'Doe',
street: 'Happy street',
country: 'Wonderland'
}
Serializer option accepts a Block:
The block will be executed in the Serializer instance.
class DynamicSerializer < EasySerializer::Base
attribute :thing, serializer: proc { serializer_for_object }
attribute :d_name
def serializer_for_object
"#{object.class.name}Serializer".classify
end
end
Inside the block is yielded the value of the method
thing = OpenStruct.new(name: 'rigoverto', serializer: 'ThingSerializer')
obj = OpenStruct.new(d_name: 'a name', thing: thing)
class DynamicWithContentSerializer < EasySerializer::Base
attribute :thing,
serializer: proc { |value| to_const value.serializer }
# => block will output ThingSerializer
attribute :d_name
def to_const(str)
Class.const_get str.classify
end
end
DynamicWithContentSerializer.call(obj)
Collection Example:
user = OpenStruct.new(
name: 'John',
surname: 'Doe',
emails: [
OpenStruct.new(address: '[email protected]', type: 'work')
]
)
class EmailSerializer < EasySerializer::Base
attributes :address, :type
end
class UserSerializer < EasySerializer::Base
attributes :name, :surname
collection :emails, serializer: EmailSerializer
end
UserSerializer.call(user)
# =>
{
name: 'John',
surname: 'Doe',
emails: [ { address: '[email protected]', type: 'work' } ]
}
Cache
Important cache will only work if is set in the configuration file.
Caching the serialized object:
Serialization will happen only once and the resulting hash will be stored in the cache.
class UserSerializer < EasySerializer::Base
cache true
attributes :name, :surname
end
Caching attributes:
Attributes can be cached independently.
class UserSerializer < EasySerializer::Base
attributes :name, :surname
attribute :costly_query, cache: true
end
Of course it works with blocks:
class UserSerializer < EasySerializer::Base
attributes :name, :surname
attribute(:costly_query, cache: true) do |user|
user.best_friends
end
end
Passing cache key:
class UserSerializer < EasySerializer::Base
attribute(:costly_query, cache: true, cache_key: 'hello') do |user|
user.best_friends
end
end
Passing cache key block:
class UserSerializer < EasySerializer::Base
attribute(
:costly_query,
cache: true,
cache_key: proc { |object| [object, 'costly_query'] }
) do |user|
user.best_friends
end
end
Passing options to the cache:
Any option passed in the cache method not specified for EasySerializer will be forwarded as options to the set Cache as options for the fetch method.
example:
class OptionForRootCache < EasySerializer::Base
cache true, expires_in: 10.minutes, another_option: true
attribute :name
end
Cache fetch will receive:
EasySerializer.cache.fetch(
key,# object or defined key
expires_in: 10.minutes,
another_option: true
)
Use cache_options in attributes
class OptionForAttributeCache < EasySerializer::Base
attribute :name, cache: true, cache_options: { expires_in: 10.minutes }
end
Caching Collections:
Cache will try to fetch the cached object in the collection one by one, the whole collection is not cached.
class UserSerializer < EasySerializer::Base
attributes :name, :surname
collection :address, serializer: AddressSerializer, cache: true
end
Complex example using all features
class PolymorphicSerializer < EasySerializer::Base
cache true
attribute :segment_type do |object|
object.subject.class.name.demodulize
end
attribute :segment_id do |object|
object.id
end
attributes :initial_date,
:end_date
attribute :subject,
key: false,
serializer: proc { |serializer| serializer.serializer_for_subject },
cache: true
collection :elements, serializer: ElementsSerializer, cache: true
def serializer_for_subject
object_name = object.subject_type.demodulize
"#{object_name}Serializer".constantize
end
end
PolymorphicSerializer.call(Polymorphic.last)
# => Hash with the object serialized
Development
After checking out the repo, run bin/setup
to install dependencies. Then, run rake rspec
to run the tests. You can also run bin/console
for an interactive prompt that will allow you to experiment.
To install this gem onto your local machine, run bundle exec rake install
. To release a new version, update the version number in version.rb
, and then run bundle exec rake release
, which will create a git tag for the version, push git commits and tags, and push the .gem
file to rubygems.org.
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/arturictus/easy_serializer. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the Contributor Covenant code of conduct.
License
The gem is available as open source under the terms of the MIT License.