Use active_model_serializers with Grape!
Add the grape
and grape-active_model_serializers
gems to Gemfile and run bundle install
.
gem 'grape-active_model_serializers'
See UPGRADING if you're upgrading from a previous version.
- >= Ruby v2.2
- >= grape v0.8.0
- >= active_model_serializers v0.10.0
# config.ru
require 'grape-active_model_serializers'
class API < Grape::API
format :json
formatter :json, Grape::Formatter::ActiveModelSerializers
# Serializes errors with ActiveModel::Serializer::ErrorSerializer if an ActiveModel.
# Serializer conforms to the adapter, ex: json, jsonapi.
# So an error formatted with a jsonapi formatter would render as per:
# http://jsonapi.org/format/#error-objects
error_formatter :json, Grape::Formatter::ActiveModelSerializers
end
grape-active_model_serializers
will search for serializers for the objects returned by your grape API.
namespace :users do
get ":id" do
@user = User.find(params[:id])
end
end
In this case, as User objects are being returned, grape-active_model_serializers will look for a serializer named UserSerializer.
When serializing an array, the array root is set to the innermost namespace name if there is one, otherwise it is set to the route name.
In the following API the array root is users
.
namespace :users do
get ":id" do
@user = User.find(params[:id])
end
end
In the following example the array root is people
.
get "people" do
@user = User.all
end
If your Grape API is versioned you must namespace your serializers accordingly.
For example, given the following API.
module CandyBar
class Core < Grape::API
version 'candy_bar', using: :header, vendor: 'acme'
end
end
module Chocolate
class Core < Grape::API
version 'chocolate', using: :header, vendor: 'acme'
end
end
class API < Grape::API
format :json
formatter :json, Grape::Formatter::ActiveModelSerializers
mount CandyBar::Core
mount Chocolate::Core
end
Namespace your serializers according to each version.
module CandyBar
class UserSerializer < ActiveModel::Serializer
attributes :first_name, :last_name, :email
end
end
module Chocolate
class UserSerializer < ActiveModel::Serializer
attributes :first_name, :last_name
end
end
This keeps serializers organized.
app
└── api
├── chocolate
└── core.rb
└── candy_bar
└── core.rb
api.rb
└── serializers
├── chocolate
└── user_serializer.rb
└── candy_bar
└── user_serializer.rb
Or as follows.
└── serializers
├── chocolate_user_serializer.rb
└── candy_bar_user_serializer.rb
ActiveModelSerializer will fetch automatically the right serializer to render.
# Serializer and adapter options can be specified on routes or namespaces.
namespace 'foo', serializer: BarSerializer do
get "/" do
# will use "bar" serializer
end
# Options specified on a route or namespace override those of the containing namespace.
get "/home", serializer: HomeSerializer do
# will use "home" serializer
end
# All standard options for `ActiveModel::Serializers` are supported.
get "/fancy_homes", root: 'world', each_serializer: FancyHomesSerializer
...
end
end
# Serializer and adapter options can also be specified in the body of the route
resource :users do
get '/:id' do
if conditional
# uses UserSerializer and configured default adapter automatically
current_user
else
# uses specified serializer and adapter
render current_user, serializer: ErrorSerializer, adapter: :attributes
end
end
end
# Adhoc serializer options can be specified in the body of the route
resource :users do
get '/:id' do
render current_user, extra: { adhoc_name_option: 'value' }
end
end
class UserSerializer
def name
instance_options[:adhoc_name_option] # accessible in instance_options
end
end
# Control any additional metadata using meta and meta_key
get "/homes"
collection = Home.all
render collection, { meta: { page: 5, current_page: 3 }, meta_key: :pagination_info }
end
helpers do
def default_serializer_options
{ only: params[:only], except: params[:except] }
end
end
One of the nice features of ActiveModel::Serializers is that it provides access to the authorization context via the current_user
.
In Grape, you can get the same behavior by defining a current_user
helper method.
helpers do
def current_user
@current_user ||= User.where(access_token: params[:token]).first
end
def authenticate!
error!('401 Unauthenticated', 401) unless current_user
end
end
Then, in your serializer, you could show or hide some elements based on the current user's permissions.
class PostSerializer < ActiveModel::Serializer
def include_admin_comments?
current_user.roles.member? :admin
end
end
Note: in the 0.9.x stable version of active model serializers, you have to access current user on scope - so scope.current_user
.
class User < ActiveRecord::Base
attr_accessor :first_name, :last_name, :password, :email
end
class UserSerializer < ActiveModel::Serializer
attributes :first_name, :last_name
end
class API < Grape::API
get("/home") do
User.new({first_name: 'JR', last_name: 'HE', email: 'contact@jrhe.co.uk'})
end
end
API.new.get "/home" # => '{ user: { first_name: "JR", last_name: "HE" } }'
See CONTRIBUTING.
Structured and based upon grape-rabl.