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.
Install the gem and add to the application's Gemfile by executing:
bundle add flexi_viewIf bundler is not being used to manage dependencies, install the gem by executing:
gem install flexi_viewclass 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)- 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
- 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
- API development requiring multiple response formats
- Backend for Frontend (BFF) implementations
- Applications with different user roles requiring different data views
- Configuration
- Basic Usage
- Defining Attributes
- Custom Formatters
- Associations
- Views
- Namespaces
- Meta Information
- Serialization
- Benefits
- Examples
- How to use different JSON enconder gem.
Default JSON encoder gem is "json" gem..
require "oj"
FlexiView.json_encoder = OjTo create a serializer, include FlexiView::Serializer in your class:
class ProductSerializer
include FlexiView::Serializer
attributes :id, :name, :price
endDefine basic attributes that will be serialized:
attributes :id, :name, :discountAdd conditions to attributes using the if option:
attributes :price, :retail_price, format: :currency, if: -> { object.available? }Define custom attribute logic using blocks:
attribute :profit_margin do |object|
"#{object.calculate_profit_margin}%"
endApply formatters to attributes:
attributes :price, :retail_price, format: :currencyDefine custom formatters globally:
FlexiView.formatter :currency do |value, _serializer|
"$ #{value.to_f}"
endone :offer, "OfferSerializer"many :assets, "AssetSerializer"many :specifications, "SpecificationSerializer" do |product|
product.specifications.not_archived
endViews allow you to define different sets of fields and associations for different contexts.
view(:index).fields(:id, :name, :price).association(:offer).meta(:pagination)view(:index).fields(:id, :name, :price).association(:offer)
view(:show).fields(:id, :name, :price, :retail_price)view(:index).fields(:id, :name).association(:assets, view: :show)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)
endnamespace(: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)
endAdd meta information to your serialized output:
meta(:pagination, root_key: "products") do |objects|
{ count: objects.length, page: options[:page] }
endThe root_key parameter specifies which key in the serialized output should contain the meta information.
# 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)FlexiView can serialize both single objects and arrays of objects:
# Serialize array of products
ProductSerializer.new(Product.all).serializable_hash(view: :index)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)
endDifferent 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)
endAll serialization logic is centralized, making it easier to maintain and understand the different output formats.
# 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"
meta(:pagination, root_key: "products") do |objects|
{ count: objects.length, page: options[:page] }
end
# Public views
view(:index).fields(:id, :name, :price).association(:offer).meta(: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)ProductSerializer.new(Product.objects(1)).serializable_hashOutput:
{: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"}]}}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"}]}}]}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"}]}}]- 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.
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.
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.
The gem is available as open source under the terms of the MIT License.
Everyone interacting in the FlexiView project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.