Adds Jsonapi v1.0 sugar on Grape-Entity.
Add the grape, grape-entity and grape-jsonapi_entity gems to Gemfile.
gem 'grape'
gem 'grape-entity'
gem 'grape-jsonapi_entity'Run bundle install.
class API < Grape::API
format :jsonapi
formatter :jsonapi, Grape::Jsonapi::Formatter
endFollowing Json Api v 1.0 spec. Resource Objects may expose the following fields
| field | description |
|---|---|
id |
automatically exposed by Grape::Jsonapi::Entity::Resource |
type |
automatically exposed by Grape::Jsonapi::Entity::Resource, additionally it will make an attempt to automatically determine a type using either the plural word passed via the root method, or based on the class name of the resource entity. To specify a type, you can overload the json_api_type method in your resource as shown here. You can also use the json_api_type keyword as in this example |
attributes |
can be exposed using the attribute method, instead of expose. All expose options and block syntax should work with attribute |
relationships |
relationships of compound-documents can be represented using the nest method, and expects the :using option to be passed with another Resource Entity |
module API
module Entities
class Status < Grape::Jsonapi::Entity::Resource
root 'statuses'
format_with(:iso_timestamp) { |dt| dt.iso8601 }
attribute :user_name
attribute :text, documentation: { type: "String", desc: "Status update text." }
attribute :ip, if: { type: :full }
attribute :user_type, :user_id, if: lambda { |status, options| status.user.public? }
attribute :location, merge: true
attribute :contact_info do
expose :phone
expose :address, merge: true, using: API::Entities::Address
end
attribute :digest do |status, options|
Digest::MD5.hexdigest status.txt
end
nest :replies, using: API::Entities::Status, as: :responses
nest :last_reply, using: API::Entities::Status do |status, options|
status.replies.last
end
with_options(format_with: :iso_timestamp) do
attribute :created_at
attribute :updated_at
end
end
end
end
module API
module Entities
class StatusDetailed < Grape::Jsonapi::Entity::Resource
attribute :internal_id
end
end
endOverloading the json_api_type method is one way of specifying the type field in your Json Api output. The following code will output 'Weather' in the type field.
module API
module Entities
class Status < Grape::Jsonapi::Entity::Resource
attribute :user_name
attribute :ip
def json_api_type
'Weather'
end
end
end
end
This follows the grape-entity use of present.
but instead of passing your entity directly to :with, we wrap it in a factory call to nest the data inside Jsonapi's
top level document structure. It also makes the output of the type field the result of current_user.admin? ? :full : :default
class Statuses < Grape::API
version 'v1'
desc 'Statuses index' do
params: API::Entities::Status.documentation
end
get '/statuses' do
statuses = Status.all
type = current_user.admin? ? :full : :default
present({ data: statuses },
with: Grape::Jsonapi::Document.top(API::Entities::Status),
json_api_type: type
)
end
endThis fulfills server responsibilites for JSON API Content Negotiation v1.0
and makes one addition. JSON API specifies that the client must send Content-Type: application/vnd.api+json with all
request documents. This gem specifies that the client must send Content-Type: application/vnd.api+json with every request,
regardless of whether sending a document or not.
Two new exceptions have also been added.
- UnsupportedMediaTypeError is raised when the Content-Type header is invalid.
- NotAcceptableError is raised when the Accept header is invalid.
Usage:
- Run the service and pass it the Accept header and Content-Type header.
- It returns
trueor raises exception if not JSON API-compliant.
Grape::Jsonapi::Services::ContentNegotiator.run(accept_header, content_type)This follows the JSON APi specifications for filtering.
- Accepts a hash with the key as search field:
{ foo: value }- When value is scalar type, operation 'equals(==)' is implied:
{ foo: 'scalar', bar: 123 } => where(foo: 'scalar', bar: 123)- if value is an array, 'IN' operation is applied:
{ foo: [1,2,3] } => where(:foo.in([1,2,3]))- if value is a Hash, there must be one entry, of which the value_key is the operation, and the value must be scalar. Valid operations include
<,<=,>,>=,==:
{ foo: {'<': 100 } } => where(foo < 100)In your API, accept an optional filter parameter with with a custom type. It should call the Filter module's allow method and pass it your search term:
params do
optional :filter, type:
Grape::Jsonapi::Parameters::Filter.allow([:something_id])
endPass the params like this in your tests:
context 'with a something' do
let(:params) do
{ filter: JSON.unparse(something_id: [something.id.to_s]) }
end
end##Copyright and License
MIT License, see LICENSE for details.