- Demo
- Installation
- Usage
- Tell your API to use Grape::Formatter::Roar
- Use Grape’s Present
- Dynamic Rendering Options
- Native Array Representations
- Representer Lookup
- Hash-backed Representers
- Accessing the Request Inside a Representer
- Decorators
- Presenter Parameter Types
- Swagger Documentation
- CRUD Endpoints
- Nested Representers
- Caching Representations
- Relation Extensions
- Contributing
- Copyright and License
See grape-with-roar.
CI covers Grape 1.x through 4.x on compatible Ruby versions, including Ruby 4.0. Grape 1.x requires Rack 2.x; newer Grape versions are tested with Rack 3.x. See CONTRIBUTING for the core and ORM compatibility matrices and local test commands.
Add the grape, roar and grape-roar gems to Gemfile.
gem 'grape'
gem 'roar'
gem 'grape-roar'If you're upgrading from an older version of this gem, please see UPGRADING.
class API < Grape::API
format :json
formatter :json, Grape::Formatter::Roar
endInclude Grape::Roar::Representer into a representer module after any Roar mixins, then use Grape's present keyword.
module ProductRepresenter
include Roar::JSON
include Roar::Hypermedia
include Grape::Roar::Representer
property :title
property :id
endget 'product/:id' do
present Product.find(params[:id]), with: ProductRepresenter
endPresenting collections works the same way. The following example returns an embedded set of products in the HAL Hypermedia format.
module ProductsRepresenter
include Roar::JSON::HAL
include Roar::Hypermedia
include Grape::Roar::Representer
collection :entries, extend: ProductRepresenter, as: :products, embedded: true
endget 'products' do
present Product.all, with: ProductsRepresenter
endTo supply Roar rendering options, such as wrap, or request-specific callback data, render a native Hash with to_hash and return it from the endpoint. Put callback data in user_options; include env there if any links or properties use the request:
get 'product/:id' do
product = Product.find(params[:id])
ProductRepresenter.represent(product).to_hash(
wrap: :product,
user_options: { env: env, student: current_student }
)
endThis returns a JSON object wrapped under "product", not a JSON-encoded string. It works with both representer modules and decorators. Hypermedia callbacks receive your user options directly; for example, link(:student) { |opts| "/students/#{opts.fetch(:student).id}" } can use the current student.
present product, with: ProductRepresenter does not forward arbitrary options to Roar's later rendering step. Use the explicit to_hash call above instead, and do not return to_json output through the formatter: the formatter would encode that string again. Keep options local to each rendering call rather than modifying shared representer definitions.
To render a plain array using a single-item representer, use Roar's native collection factory explicitly. This works with both representer modules and decorators:
get 'products' do
ProductRepresenter.for_collection.prepare(Product.all.to_a).to_hash(user_options: { env: env })
endThe response is an array of individually represented products. Empty collections return [], and callbacks receive the request environment. Return the native Array from to_hash, not a JSON string, so the formatter encodes it exactly once.
present products, with: ProductsRepresenter still treats ProductsRepresenter as the representation of the whole collection. This preserves existing collection properties, envelopes, and HAL _embedded responses. Automatically treating every Array as a collection of individually represented resources would change those responses; use the explicit factory above when that is what you want.
Grape can infer a representer from a model's Entity constant. Set it to a representer module or decorator, then call present without with::
class Product
Entity = ProductRepresenter
end
get 'product/:id' do
present Product.find(params[:id])
endFor decorator classes, an API-local mapping avoids adding a constant to the model:
class API < Grape::API
format :json
formatter :json, Grape::Formatter::Roar
represent Product, with: ProductRepresenter
get 'product/:id' do
present Product.find(params[:id])
end
endAn explicit present object, with: OtherRepresenter overrides inference. These are Grape's existing lookup mechanisms; grape-roar does not guess global constants from model names, choose between singular and plural representers, or automatically represent arbitrary endpoint return values. Call present to request inference.
A plain property :some_property reads object.some_property; it does not read object[:some_property]. When presenting a Hash, specify a getter instead of relying on implicit conversion to an object with attribute methods:
module HashRepresenter
include Roar::JSON
include Grape::Roar::Representer
property :some_property, getter: ->(*) { fetch(:some_property) }
endget 'hash' do
present({ some_property: 'From a hash', internal: 'Not exposed' }, with: HashRepresenter)
endThe response is {"some_property":"From a hash"}. Only declared properties are serialized. For string-keyed hashes, such as those returned by JSON.parse, use fetch('some_property') in the getter instead. Hash key types are not converted automatically, and fetch raises KeyError if the configured key is absent.
The same getter works in a class inheriting from Grape::Roar::Decorator with Roar::JSON included. Getters execute against the represented Hash by default; a decorator wraps it rather than adding representer methods to the Hash itself.
The formatter invokes to_json on presented objects and provides access to the requesting environment via the env option. The following example renders a full request URL in a representer.
module ProductRepresenter
include Roar::JSON
include Roar::Hypermedia
include Grape::Roar::Representer
link :self do |opts|
request = Grape::Request.new(opts[:env])
"#{request.url}"
end
endIf you prefer to use a decorator class instead of modules.
class ProductRepresenter < Grape::Roar::Decorator
include Roar::JSON
include Roar::Hypermedia
link :self do |opts|
"#{request(opts).url}/#{represented.id}"
end
private
def request(opts)
Grape::Request.new(opts[:env])
end
endget 'products' do
present Product.all, with: ProductsRepresenter
endGrape supports type: Presenter when the class implements its custom-type .parse contract. Define this on an application-specific input decorator: validate the input, parse only declared writable properties, and return a decorator instance so Grape's type check succeeds.
class ProductInputRepresenter < Grape::Roar::Decorator
include Roar::JSON
property :title
def self.parse(value)
raise ArgumentError, 'Expected a product object' unless value.is_a?(::Hash)
attributes = value.transform_keys(&:to_s)
title = attributes['title']
raise ArgumentError, 'Expected a nonblank title' unless title.is_a?(::String) && !title.strip.empty?
decorator = new(Product.new)
decorator.from_hash(attributes)
decorator
end
end
class ProductsAPI < Grape::API
format :json
formatter :json, Grape::Formatter::Roar
params do
requires :product, type: ProductInputRepresenter
end
post 'products' do
params[:product]
end
endSend a JSON body such as {"product":{"title":"Lonestar"}} with Content-Type: application/json. Grape converts parse failures into HTTP 400 validation errors. The response is {"title":"Lonestar"}; undeclared properties are not assigned to the model. The coerced parameter is the decorator, and params[:product].represented gives the model for application-specific processing or persistence.
Roar properties and Swagger response metadata do not automatically define required fields, validation, or a model constructor. Keep input validation explicit, use a separate input representer when writable fields differ from output fields, and do not add a generic parser that guesses which model to construct. from_hash returns the represented model, so return the decorator explicitly from .parse when using the decorator as the parameter type.
The optional grape-swagger-representable parser documents Roar decorator classes. Add it and grape-swagger to your application's Gemfile; neither is required for rendering:
gem 'grape-swagger'
gem 'grape-swagger-representable'Require the parser before calling add_swagger_documentation. Declare response field metadata under each property's documentation: option:
require 'grape-swagger-representable'
class ProductRepresenter < Grape::Roar::Decorator
include Roar::JSON
property :id, documentation: { type: Integer, desc: 'Product ID' }
property :title, documentation: { type: String, desc: 'Product title' }
end
class ProductsAPI < Grape::API
format :json
formatter :json, Grape::Formatter::Roar
desc 'Get a product', success: ProductRepresenter
get 'product/:id' do
present Product.find(params[:id]), with: ProductRepresenter
end
add_swagger_documentation
end/swagger_doc/product describes an integer id and string title, including their descriptions, while /product/:id renders the same properties with Roar. The parser registers decorator classes, not representer modules; use a decorator for a documented response model. Response documentation does not validate incoming parameters, and custom links, getters, and envelopes still need schemas that match their actual output.
Keep persistence application-local: use a dedicated input representer for writable fields, validate parameters with Grape, and call your model's persistence methods explicitly. For example, with an ActiveRecord-style model exposing find, save!, and destroy!:
class ProductInputRepresenter < Grape::Roar::Decorator
include Roar::JSON
property :title
end
class ProductOutputRepresenter < Grape::Roar::Decorator
include Roar::JSON
property :id
property :title
end
class ProductsAPI < Grape::API
format :json
formatter :json, Grape::Formatter::Roar
helpers do
def parse_product(product)
attributes = declared(params, include_missing: false).fetch(:product).transform_keys(&:to_s)
ProductInputRepresenter.new(product).from_hash(attributes)
end
end
params do
requires :product, type: Hash do
requires :title, type: String, allow_blank: false
end
end
post 'products' do
product = parse_product(Product.new)
product.save!
present product, with: ProductOutputRepresenter
end
params do
requires :id, type: Integer
requires :product, type: Hash do
optional :title, type: String, allow_blank: false
end
end
patch 'products/:id' do
product = parse_product(Product.find(params[:id]))
product.save!
present product, with: ProductOutputRepresenter
end
params do
requires :id, type: Integer
end
delete 'products/:id' do
product = Product.find(params[:id])
product.destroy!
present product, with: ProductOutputRepresenter
end
endSend create/update input as {"product":{"title":"Lonestar"}} with Content-Type: application/json. declared filters request fields and the input representer only assigns title; output fields such as id are not writable. include_missing: false preserves existing values on partial updates. from_hash returns the model, while present applies the output representer after a successful write.
Scope record lookups to the authorized user and apply your application's authorization policy before mutations. Adapt persistence calls to your ORM. Configure application-specific rescue_from handlers for expected not-found, validation, or failed-destruction exceptions, mapping them to appropriate HTTP errors. The example deliberately uses raising persistence methods and does not swallow failures or return success when a write fails. Roar does not infer persistence, authorization, or validation rules from output properties.
Use property for a single nested object and collection for multiple objects, specifying the representer at each level with extend:. For example, notifications can contain comments, posts, and images:
class ImageRepresenter < Grape::Roar::Decorator
include Roar::JSON
property :id
property :url
end
class PostRepresenter < Grape::Roar::Decorator
include Roar::JSON
property :id
property :title
property :image, extend: ImageRepresenter
end
class CommentRepresenter < Grape::Roar::Decorator
include Roar::JSON
property :id
property :body
property :post, extend: PostRepresenter
end
class NotificationRepresenter < Grape::Roar::Decorator
include Roar::JSON
property :id
property :comment, extend: CommentRepresenter
end
class NotificationsRepresenter < Grape::Roar::Decorator
include Roar::JSON
collection :entries, as: :notifications, extend: NotificationRepresenter
endget 'notifications' do
present Notification.all, with: NotificationsRepresenter
endThe response has a top-level notifications array; each notification contains its comment, whose post contains an image. Each representer controls only its own object's attributes, so an image's id does not replace its post's id.
Decorators wrap objects instead of extending them with representer modules. This is useful when the same model instance is shared between notifications or rendered with different representations. Keep the nested objects separate and let Roar compose the response rather than merging child attributes into a parent's hash.
present object, with: Representer returns a represented object, not a plain data snapshot. Module representers extend the object and attach serialization state, which can include Procs that a cache backend cannot serialize. Do not cache the represented model itself or change represent to return JSON globally.
Instead, render a plain Hash with Roar's to_hash before storing it. For example, using your application's cache store as CACHE:
get 'products/:id' do
cache_key = ['product-representation', request.url]
CACHE.fetch(cache_key) do
product = Product.find(params[:id])
ProductRepresenter.represent(product).to_hash(user_options: { env: env })
end
endReturn the cached Hash directly; the formatter encodes it as a JSON object. Do not call present on it with the model representer again, or cache a to_json string and return that through the JSON formatter, which would encode it a second time.
Rendering with user_options: { env: env } preserves request-aware links when populating the cache. The example includes the full request URL in the key because its self link depends on that URL. Include every other input that affects the response, such as authenticated user, locale, or header-based API version, in your cache key and invalidate cached representations when their source data changes. Alternatively, cache raw data and build a fresh model to present outside the cache block so request-dependent rendering runs on each request.
If you use either ActiveRecord or Mongoid, you can use the Grape::Roar::Extensions::Relations DSL to expose the relationships in between your models as a HAL response. The DSL methods used are the same regardless of what your ORM/ODM is, as long as there exists an adapter for it.
Arguments passed to #relation are forwarded to roar. Single member relations (e.g. belongs_to) are represented using #property, collections are represented using #collection; arguments provided to #relation will be passed through these methods (i.e. additional arguments roar and representable accept).
A default base URI is constructed from a Grape::Request by concatenating the #base_url and #script_name properties. The resource path is extracted from the name of the relation.
Otherwise, the extensions attempt to look up the correct representer module/class for the objects (e.g. we infer the extend argument). You can always specify the correct representer to use on your own.
class Item < ActiveRecord::Base
belongs_to :cart
end
class Cart < ActiveRecord::Base
has_many :items
endclass ItemEntity < Grape::Roar::Decorator
include Roar::JSON
include Roar::JSON::HAL
include Roar::Hypermedia
include Grape::Roar::Extensions::Relations
# Cart will be presented under the _embedded key
relation :belongs_to, :cart, embedded: true
link_self
end
class CartEntity < Grape::Roar::Decorator
include Roar::JSON
include Roar::JSON::HAL
include Roar::Hypermedia
include Grape::Roar::Extensions::Relations
# Items will be presented under the _links key
relation :has_many, :items, embedded: false
link_self
endAlthough this example uses Grape::Roar::Decorator, you can also use a module as show in prior examples above. If doing so, you no longer have to mix in Grape::Roar::Representer.
{
"_embedded": {
"cart": {
"_links": {
"self": {
"href": "http://example.org/carts/1"
},
"items": [{
"href": "http://example.org/items/1"
}]
}
}
},
"_links": {
"self": {
"href": "http://example.org/items/1"
}
}
}{
"_links": {
"self": {
"href": "http://example.org/carts/1"
},
"items": [{
"href": "http://example.org/items/1"
}, {
"href": "http://example.org/items/2"
}, {
"href": "http://example.org/items/3"
}, {
"href": "http://example.org/items/4"
}, {
"href": "http://example.org/items/5"
}]
}
}Should you incorrectly describe a relationship (e.g. you specify has_one but your model specifies has_many), an exception will be raised to notify you of the mismatch:
Grape::Roar::Extensions::Relations::Exceptions::InvalidRelationError:
Expected Mongoid::Association::Referenced::HasOne, got Mongoid::Association::Referenced::HasMany!The opts hash below is the same one as shown in prior examples.
class BarEntity < Grape::Roar::Decorator
include Roar::JSON
include Roar::JSON::HAL
include Roar::Hypermedia
include Grape::Roar::Extensions::Relations
# This is our default implementation
map_base_url do |opts|
request = Grape::Request.new(opts[:env])
"#{request.base_url}#{request.script_name}"
end
relation :has_many, :bars, embedded: false
endclass BarEntity < Grape::Roar::Decorator
include Roar::JSON
include Roar::JSON::HAL
include Roar::Hypermedia
include Grape::Roar::Extensions::Relations
# This is our default implementation
map_resource_path do |_opts, object, relation_name|
"#{relation_name}/#{object.id}"
end
relation :has_many, :bars, embedded: false
endIf you have custom domain objects, you can create an adapter to make your models compatible with the DSL methods. Below is an example of the ActiveRecord adapter.
module Extensions
module RelationalModels
module Adapter
class ActiveRecord < Base
include Validations::ActiveRecord
# We map your domain object to the correct adapter
# at runtime.
valid_for { |klass| klass < ::ActiveRecord::Base }
def collection_methods
@collection_methods ||= %i(has_many has_and_belongs_to_many)
end
def name_for_represented(represented)
klass_name = case represented
when ::ActiveRecord::Relation
represented.klass.name
else
represented.class.name
end
klass_name.demodulize.pluralize.downcase
end
def single_entity_methods
@single_entity_methods ||= %i(has_one belongs_to)
end
end
end
end
endErrors are handled by creating methods corresponding to those in collection_methods and single_entity_methods. For example, this is the validator for belongs_to:
module ActiveRecord
include Validations::Misc
def belongs_to_valid?(relation)
relation = klass.reflections[relation]
return true if relation.is_a?(
::ActiveRecord::Reflection::BelongsToReflection
)
# Provided by Validations::Misc
invalid_relation(
::ActiveRecord::Reflection::BelongsToReflection,
relation.class
)
end
endAfter writing your validation methods, just mix them into your adapter. You can choose to not write validation methods; they are only invoked if your adapter responds to them.
See CONTRIBUTING.
MIT License, see LICENSE for details.
(c) 2012-2025 Daniel Doubrovkine & Contributors, Artsy