Skip to content

ruby-grape/grape-roar

Repository files navigation

Grape::Roar

Gem Version test test-activerecord test-mongodb

Use Roar with Grape.

Table of Contents

Demo

See grape-with-roar.

Installation

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.

Usage

Tell your API to use Grape::Formatter::Roar

class API < Grape::API format :json formatter :json, Grape::Formatter::Roar end

Use Grape's Present

Include 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 end
get 'product/:id' do present Product.find(params[:id]), with: ProductRepresenter end

Presenting 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 end
get 'products' do present Product.all, with: ProductsRepresenter end

Accessing the Request Inside a Representer

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 end

Decorators

If 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 end
get 'products' do present Product.all, with: ProductsRepresenter end

Relation Extensions

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.

Designing Representers

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.

Example Models
class Item < ActiveRecord::Base belongs_to :cart end class Cart < ActiveRecord::Base has_many :items end
Example Representers
class 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 end

Although 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.

Example Item
{ "_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" } } }
Example Cart
{ "_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" }] } }

Errors

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!

Change how URLs are presented

The opts hash below is the same one as shown in prior examples.

Override base URI mappings
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 end
Override resource URI mappings
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_resource_path do |_opts, object, relation_name| "#{relation_name}/#{object.id}" end relation :has_many, :bars, embedded: false end

Designing Adapters

If 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.

Example: 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 end
Validations

Errors 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 end

After 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.

Contributing

See CONTRIBUTING.

Copyright and License

MIT License, see LICENSE for details.

(c) 2012-2025 Daniel Doubrovkine & Contributors, Artsy

About

Use Roar with Grape.

Resources

License

Contributing

Stars

Watchers

Forks

Sponsor this project

  •  

Packages

No packages published

Contributors 9

Languages