diff --git a/CHANGELOG.md b/CHANGELOG.md index f2607e07b..625d4c1d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ Breaking changes: Features: +- [#1336](https://github.com/rails-api/active_model_serializers/pull/1336) Added support for Grape >= 0.13, < 1.0 - [#1291](https://github.com/rails-api/active_model_serializers/pull/1291) Add logging (@maurogeorge) - [#1225](https://github.com/rails-api/active_model_serializers/pull/1125) Better serializer lookup, use nested serializer when it exists (@beauby) - [#1172](https://github.com/rails-api/active_model_serializers/pull/1172) Better serializer registration, get more than just the first module (@bf4) diff --git a/active_model_serializers.gemspec b/active_model_serializers.gemspec index 3ccd9654e..ad5fd2c7c 100644 --- a/active_model_serializers.gemspec +++ b/active_model_serializers.gemspec @@ -53,4 +53,5 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'bundler', '~> 1.6' spec.add_development_dependency 'timecop', '~> 0.7' spec.add_development_dependency 'minitest-reporters' + spec.add_development_dependency 'grape', '~> 0.13' end diff --git a/docs/README.md b/docs/README.md index a05fdfb3d..870b0ef36 100644 --- a/docs/README.md +++ b/docs/README.md @@ -18,6 +18,7 @@ This is the documentation of AMS, it's focused on the **0.10.x version.** - [How to add pagination links](howto/add_pagination_links.md) - [Using AMS Outside Of Controllers](howto/outside_controller_use.md) - [How to use JSON API with Ember](howto/ember-and-json-api.md) +- [Grape](howto/grape.md) ## Getting Help diff --git a/docs/howto/grape.md b/docs/howto/grape.md new file mode 100644 index 000000000..7c855ebf1 --- /dev/null +++ b/docs/howto/grape.md @@ -0,0 +1,19 @@ +# Integration with Grape + +[Grape](https://github.com/ruby-grape/grape) is an opinionated micro-framework for creating REST-like APIs in ruby. + +ActiveModelSerializers currently supports Grape >= 0.13, < 1.0 + +To add [Grape](https://github.com/ruby-grape/grape) support, enable the formatter and helper functions by including `Grape::ActiveModelSerializers` in your base endpoint. For example: + +```ruby +module Example + class Dummy < Grape::API + require 'grape/active_model_serializers' + include Grape::ActiveModelSerializers + mount Example::V1::Base + end +end +``` + +Aside from this, [configuration](../general/configuration_options.md) of ActiveModelSerializers is exactly the same. diff --git a/lib/grape/active_model_serializers.rb b/lib/grape/active_model_serializers.rb new file mode 100644 index 000000000..6e7ec74e7 --- /dev/null +++ b/lib/grape/active_model_serializers.rb @@ -0,0 +1,14 @@ +# To add grape support, require 'grape/active_model_serializers' in the base of your grape endpoints +# Then add 'include Grape::ActiveModelSerializers' to enable the formatter and helpers +require 'active_model_serializers' +require 'grape/formatters/active_model_serializers' +require 'grape/helpers/active_model_serializers' + +module Grape::ActiveModelSerializers + extend ActiveSupport::Concern + + included do + formatter :json, Grape::Formatters::ActiveModelSerializers + helpers Grape::Helpers::ActiveModelSerializers + end +end diff --git a/lib/grape/formatters/active_model_serializers.rb b/lib/grape/formatters/active_model_serializers.rb new file mode 100644 index 000000000..a9383ae9f --- /dev/null +++ b/lib/grape/formatters/active_model_serializers.rb @@ -0,0 +1,15 @@ +# A grape response formatter that can be used as 'formatter :json, Grape::Formatters::ActiveModelSerializers' +# +# Serializer options can be passed as a hash from your grape endpoint using env[:active_model_serializer_options], +# or better yet user the render helper in Grape::Helpers::ActiveModelSerializers +module Grape + module Formatters + module ActiveModelSerializers + def self.call(resource, env) + serializer_options = {} + serializer_options.merge!(env[:active_model_serializer_options]) if env[:active_model_serializer_options] + ActiveModel::SerializableResource.new(resource, serializer_options).to_json + end + end + end +end diff --git a/lib/grape/helpers/active_model_serializers.rb b/lib/grape/helpers/active_model_serializers.rb new file mode 100644 index 000000000..ef646477d --- /dev/null +++ b/lib/grape/helpers/active_model_serializers.rb @@ -0,0 +1,16 @@ +# Helpers can be included in your grape endpoint as: helpers Grape::Helpers::ActiveModelSerializers +module Grape + module Helpers + module ActiveModelSerializers + # A convenience method for passing ActiveModelSerializers serializer options + # + # Example: To include relationships in the response: render(post, include: ['comments']) + # + # Example: To include pagination meta data: render(posts, meta: { page: posts.page, total_pages: posts.total_pages }) + def render(resource, active_model_serializer_options = {}) + env[:active_model_serializer_options] = active_model_serializer_options + resource + end + end + end +end diff --git a/test/grape_test.rb b/test/grape_test.rb new file mode 100644 index 000000000..624768096 --- /dev/null +++ b/test/grape_test.rb @@ -0,0 +1,85 @@ +require 'test_helper' +require 'grape' +require 'grape/active_model_serializers' + +class ActiveModelSerializers::GrapeTest < Minitest::Test + include Rack::Test::Methods + + class GrapeTest < Grape::API + format :json + include Grape::ActiveModelSerializers + + resources :grape do + get '/render' do + render ARModels::Post.new(title: 'Dummy Title', body: 'Lorem Ipsum') + end + + get '/render_with_json_api' do + post = ARModels::Post.new(title: 'Dummy Title', body: 'Lorem Ipsum') + render(post, meta: { page: 1, total_pages: 2 }, adapter: :json_api) + end + + get '/render_array_with_json_api' do + post = ARModels::Post.create(title: 'Dummy Title', body: 'Lorem Ipsum') + post.dup.save + render(ARModels::Post.all, adapter: :json_api) + end + end + end + + def app + GrapeTest.new + end + + def test_formatter_returns_json + get '/grape/render' + assert last_response.ok? + expected = { id: nil, title: 'Dummy Title', body: 'Lorem Ipsum', comments: [], author: nil } + assert_equal expected.to_json, last_response.body + end + + def test_render_helper_passes_through_options_correctly + get '/grape/render_with_json_api' + + post = ARModels::Post.new(title: 'Dummy Title', body: 'Lorem Ipsum') + serializable_resource = serializable(post, serializer: ARModels::PostSerializer, adapter: :json_api, meta: { page: 1, total_pages: 2 }) + + assert last_response.ok? + assert_equal serializable_resource.to_json, last_response.body + end + + def test_formatter_handles_arrays + get '/grape/render_array_with_json_api' + + expected = { + 'data' => [ + { + id: '1', + type: 'ar_models_posts', + attributes: { + title: 'Dummy Title', + body: 'Lorem Ipsum' + }, + relationships: { + comments: { data: [] }, + author: { data: nil } + } + }, + { + id: '2', + type: 'ar_models_posts', + attributes: { + title: 'Dummy Title', + body: 'Lorem Ipsum' + }, + relationships: { + comments: { data: [] }, + author: { data: nil } + } + } + ] + } + + assert_equal expected.to_json, last_response.body + end +end