FlexiView Serializer
FlexiView is a flexible Ruby serialization library that eliminates the need for multiple serializer classes by providing dynamic views and namespaces within a single serializer definition.
Installation
Install the gem and add to the application's Gemfile by executing:
bundle add flexi_view
If bundler is not being used to manage dependencies, install the gem by executing:
gem install flexi_view
Quick Example
class ProductSerializer
include FlexiView::Serializer
attributes :id, :name, :price, :retailer_price
one :offer, "OfferSerializer"
view(:index).fields(:id, :name).association(:offer)
namespace(:admin) do
view(:index).fields(:id, :name, :price, :retailer_price).association(:offer)
end
end
# Usage
ProductSerializer.new(product).serializable_hash(view: :index)
ProductSerializer.new(product).serializable_hash(namespace: :admin, view: :index)
Key Features
- Single Serializer, Multiple Views: Define different output formats in one class
- Namespace Support: Create context-specific serialization (admin, user, mobile, web)
- Flexible Attributes: Simple, conditional, and custom attribute definitions
- Association Handling: Support for both
oneandmanyrelationships - Custom Formatters: Global formatters for consistent data formatting
- Meta Information: Add pagination and other metadata with root key support
Core Benefits
- Reduced Code Duplication: No need for separate serializer classes for different views
- BFF Pattern Ready: Perfect for Backend for Frontend architecture with namespace support
- Clean & Maintainable: Centralized serialization logic for better organization
Perfect For
- API development requiring multiple response formats
- Backend for Frontend (BFF) implementations
- Applications with different user roles requiring different data views
Quick Help
- Configuration
- Basic Usage
- Defining Attributes
- Custom Formatters
- Associations
- Views
- Namespaces
- Meta Information
- Serialization
- Benefits
- Examples
Configuration
- How to use different JSON enconder gem.
Default JSON encoder gem is "json" gem..
require "oj"
FlexiView.json_encoder = Oj
Basic Usage
To create a serializer, include FlexiView::Serializer in your class:
class ProductSerializer
include FlexiView::Serializer
attributes :id, :name, :price
end
Defining Attributes
Simple Attributes
Define basic attributes that will be serialized:
attributes :id, :name, :discount
Conditional Attributes
Add conditions to attributes using the if option:
attributes :price, :retail_price, format: :currency, if: -> { object.available? }
Custom Attribute Blocks
Define custom attribute logic using blocks:
attribute :profit_margin do |object|
"#{object.calculate_profit_margin}%"
end
Formatted Attributes
Apply formatters to attributes:
attributes :price, :retail_price, format: :currency
Custom Formatters
Define custom formatters globally:
FlexiView.formatter :currency do |value, _serializer|
"$ #{value.to_f}"
end
Associations
Has One Association
one :offer, "OfferSerializer"
Has Many Association
many :assets, "AssetSerializer"
Association with Custom Logic
many :specifications, "SpecificationSerializer" do |product|
product.specifications.not_archived
end
Views
Views allow you to define different sets of fields and associations for different contexts.
Basic View Definition
view(:index).fields(:id, :name, :price).association(:offer).(:pagination)
Multiple Views
view(:index).fields(:id, :name, :price).association(:offer)
view(:show).fields(:id, :name, :price, :retail_price)
View with Associations
view(:index).fields(:id, :name).association(:assets, view: :show)
Namespaces
Namespaces allow you to group views by context (e.g., admin, user, api):
namespace(:admin) do
view(:index)
.fields(:id, :name, :price, :available_count, :profit_margin)
.associations(:specifications, :offer, view: :index)
end
Multiple Namespaces
namespace(:admin) do
view(:index).fields(:id, :name, :price, :available_count)
view(:show).fields(:id, :name, :price, :retail_price, :profit_margin)
end
namespace(:user) do
view(:index).fields(:id, :name, :price)
view(:show).fields(:id, :name, :price, :retail_price)
end
Meta Information
Add meta information to your serialized output:
(:pagination, root_key: "products") do |objects|
{ count: objects.length, page: [:page] }
end
The root_key parameter specifies which key in the serialized output should contain the meta information.
Serialization
Single Object Serialization
# Default serialization (all attributes)
ProductSerializer.new(Product.objects(1)).serializable_hash
# Using a specific view
ProductSerializer.new(Product.objects(1)).serializable_hash(view: :index)
# Using namespace and view
ProductSerializer.new(Product.objects(1)).serializable_hash(namespace: :admin, view: :index)
Array Serialization
FlexiView can serialize both single objects and arrays of objects:
# Serialize array of products
ProductSerializer.new(Product.all).serializable_hash(view: :index)
Benefits
1. Single Serializer, Multiple Views
No need to create separate serializer classes for different views. Define all variations in one place:
class ProductSerializer
include FlexiView::Serializer
attributes :id, :name, :price, :description, :stock
view(:list).fields(:id, :name, :price)
view(:detail).fields(:id, :name, :price, :description, :stock)
view(:minimal).fields(:id, :name)
end
2. Easy BFF (Backend for Frontend) Pattern Implementation
Different namespaces for different frontends:
namespace(:mobile) do
view(:list).fields(:id, :name, :price)
end
namespace(:web) do
view(:list).fields(:id, :name, :price, :description)
end
namespace(:admin) do
view(:list).fields(:id, :name, :price, :stock, :created_at)
end
3. Clean and Maintainable Code
All serialization logic is centralized, making it easier to maintain and understand the different output formats.
Examples
Complete Example
# Define formatters
FlexiView.formatter :currency do |value, _serializer|
"$ #{value.to_f}"
end
class ProductSerializer
include FlexiView::Serializer
attributes :id, :name, :available_count
attributes :price, :retail_price, format: :currency, if: -> { object.id == 1 }
attribute :profit_margin do |object|
"#{object.calculate_profile_margin}%"
end
many :specifications, "SpecificationSerializer" do |product|
product.specifications.not_archived
end
one :offer, "OfferSerializer"
(:pagination, root_key: "products") do |objects|
{ count: objects.length, page: [:page] }
end
# Public views
view(:index).fields(:id, :name, :price).association(:offer).(:pagination)
view(:show).fields(:id, :name, :price, :retail_price)
# Admin namespace
namespace(:admin) do
view(:index)
.fields(:id, :name, :price, :available_count, :profit_margin)
.associations(:specifications, :offer, view: :index)
end
end
class AssetSerializer
include FlexiView::Serializer
attributes :id, :name, :image, :type
view(:show).fields(:id, :name, :image)
end
class OfferSerializer
include FlexiView::Serializer
attributes :id, :name, :discount
many :assets, "AssetSerializer"
view(:index).fields(:id, :name).association(:assets, view: :show)
end
# Usage examples
product = Product.find(1)
# Default serialization
ProductSerializer.new(product).serializable_hash # HASH
ProductSerializer.new(product).serializable # JSON
# Index view
ProductSerializer.new(product).serializable_hash(view: :index)
ProductSerializer.new(product).serializable(view: :index)
# Admin namespace with index view
ProductSerializer.new(product).serializable_hash(namespace: :admin, view: :index)
ProductSerializer.new(product).serializable(namespace: :admin, view: :index)
Serializer Usage Examples
1. All Attributes and Associations Serialization
ProductSerializer.new(Product.objects(1)).serializable_hash
Output:
{:id=>1,
:name=>"Name - 1",
:available_count=>499,
:price=>"$ 53.0",
:retail_price=>"$ 86.0",
:profit_margin=>"40%",
:specifications=>[{:id=>1, :name=>"SpecName - 1", :image=>"http://test.com/test.png"}, {:id=>2, :name=>"SpecName - 2", :image=>"http://test.com/test.png"}],
:offer=>
{:id=>1,
:name=>"Offer - 1",
:discount=>18,
:assets=>
[{:id=>1, :name=>"Offer - 1", :image=>"http://test.com/test.png", :type=>"image"},
{:id=>2, :name=>"Offer - 2", :image=>"http://test.com/test.png", :type=>"image"}]}}
2. View Attributes and Associations Serialization
ProductSerializer.new(Product.objects).serializable_hash(view: :index)
Output:
{:pagination=>{:count=>2, :page=>nil},
"products"=>
[{:id=>1,
:name=>"Name - 1",
:price=>"$ 87.0",
:offer=>
{:id=>1,
:name=>"Offer - 1",
:discount=>45,
:assets=>
[{:id=>1, :name=>"Offer - 1", :image=>"http://test.com/test.png", :type=>"image"},
{:id=>2, :name=>"Offer - 2", :image=>"http://test.com/test.png", :type=>"image"}]}},
{:id=>2,
:name=>"Name - 2",
:offer=>
{:id=>1,
:name=>"Offer - 1",
:discount=>25,
:assets=>
[{:id=>1, :name=>"Offer - 1", :image=>"http://test.com/test.png", :type=>"image"},
{:id=>2, :name=>"Offer - 2", :image=>"http://test.com/test.png", :type=>"image"}]}}]}
3. Namespace View Attributes and Associations Serialization
ProductSerializer.new(Product.objects).serializable_hash(namespace: :admin, view: :index)
Output:
[{:id=>1,
:name=>"Name - 1",
:price=>"$ 75.0",
:available_count=>680,
:profit_margin=>"1%",
:specifications=>[{:id=>1, :name=>"SpecName - 1"}, {:id=>2, :name=>"SpecName - 2"}],
:offer=>
{:id=>1,
:name=>"Offer - 1",
:assets=>[{:id=>1, :name=>"Offer - 1", :image=>"http://test.com/test.png"}, {:id=>2, :name=>"Offer - 2", :image=>"http://test.com/test.png"}]}},
{:id=>2,
:name=>"Name - 2",
:available_count=>274,
:profit_margin=>"45%",
:specifications=>[{:id=>1, :name=>"SpecName - 1"}, {:id=>2, :name=>"SpecName - 2"}],
:offer=>
{:id=>1,
:name=>"Offer - 1",
:assets=>[{:id=>1, :name=>"Offer - 1", :image=>"http://test.com/test.png"}, {:id=>2, :name=>"Offer - 2", :image=>"http://test.com/test.png"}]}}]
Key Differences in Output:
- Default Serialization: Returns all defined attributes and associations for a single object
- View Serialization: Returns only the fields specified in the view definition, includes meta information with root key structure
- Namespace View Serialization: Returns fields specific to the namespace view, focuses on admin-specific data like
available_countandprofit_margin
Notice how the view serialization includes pagination meta information at the root level with the actual data nested under the "products" key, while the namespace view returns a clean array structure focused on admin requirements.
Development
After checking out the repo, run bin/setup to install dependencies. Then, run rake test 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 the created tag, and push the .gem file to rubygems.org.
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/jiren/flexi_view. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the code of conduct.
License
The gem is available as open source under the terms of the MIT License.
Code of Conduct
Everyone interacting in the FlexiView project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.