diff --git a/CHANGELOG.md b/CHANGELOG.md index 7fe7df63d..0a4a9a79c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ Features: Fixes: - [#1754](https://github.com/rails-api/active_model_serializers/pull/1754) Fixes #1759, Grape integration, improves serialization_context missing error message on pagination. Document overriding CollectionSerializer#paginated?. (@bf4) +- [#1754](https://github.com/rails-api/active_model_serializers/pull/1754) Fixes #1759, Grape integration, moved serialization_context + creation to Grape formatter, so resource serialization works without explicit calls to the `render` helper method. Added Grape collection tests. (@onomated) - [#1710](https://github.com/rails-api/active_model_serializers/pull/1710) Prevent association loading when `include_data` option is set to `false`. (@groyoh) - [#1747](https://github.com/rails-api/active_model_serializers/pull/1747) Improve jsonapi mime type registration for Rails 5 (@remear) diff --git a/lib/grape/formatters/active_model_serializers.rb b/lib/grape/formatters/active_model_serializers.rb index 20537e74f..534c5babf 100644 --- a/lib/grape/formatters/active_model_serializers.rb +++ b/lib/grape/formatters/active_model_serializers.rb @@ -2,14 +2,31 @@ # # 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 + +require 'active_model_serializers/serialization_context' + 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] + serializer_options = build_serializer_options(env) ::ActiveModelSerializers::SerializableResource.new(resource, serializer_options).to_json end + + def self.build_serializer_options(env) + ams_options = env[:active_model_serializer_options] || {} + + # Add serialization context + ams_options.fetch(:serialization_context) do + request = env['grape.request'] + ams_options[:serialization_context] = ::ActiveModelSerializers::SerializationContext.new( + request_url: request.url[/\A[^?]+/], + query_parameters: request.params + ) + end + + ams_options + end end end end diff --git a/lib/grape/helpers/active_model_serializers.rb b/lib/grape/helpers/active_model_serializers.rb index baaa166d8..afbdab85a 100644 --- a/lib/grape/helpers/active_model_serializers.rb +++ b/lib/grape/helpers/active_model_serializers.rb @@ -1,7 +1,5 @@ # Helpers can be included in your Grape endpoint as: helpers Grape::Helpers::ActiveModelSerializers -require 'active_model_serializers/serialization_context' - module Grape module Helpers module ActiveModelSerializers @@ -11,12 +9,6 @@ module ActiveModelSerializers # # Example: To include pagination meta data: render(posts, meta: { page: posts.page, total_pages: posts.total_pages }) def render(resource, active_model_serializer_options = {}) - active_model_serializer_options.fetch(:serialization_context) do - active_model_serializer_options[:serialization_context] = ::ActiveModelSerializers::SerializationContext.new( - original_url: request.url[/\A[^?]+/], - query_parameters: request.params - ) - end env[:active_model_serializer_options] = active_model_serializer_options resource end diff --git a/test/grape_test.rb b/test/grape_test.rb index 8ba8d6abd..91753704c 100644 --- a/test/grape_test.rb +++ b/test/grape_test.rb @@ -1,6 +1,9 @@ require 'test_helper' require 'grape' require 'grape/active_model_serializers' +require 'kaminari' +require 'kaminari/hooks' +::Kaminari::Hooks.init class ActiveModelSerializers::GrapeTest < ActiveSupport::TestCase include Rack::Test::Methods @@ -21,6 +24,30 @@ def self.all ARModels::Post.all end end + + def self.reset_all + ARModels::Post.delete_all + @all = nil + end + + def self.collection_per + 2 + end + + def self.collection + @collection ||= + begin + Kaminari.paginate_array( + [ + Profile.new(id: 1, name: 'Name 1', description: 'Description 1', comments: 'Comments 1'), + Profile.new(id: 2, name: 'Name 2', description: 'Description 2', comments: 'Comments 2'), + Profile.new(id: 3, name: 'Name 3', description: 'Description 3', comments: 'Comments 3'), + Profile.new(id: 4, name: 'Name 4', description: 'Description 4', comments: 'Comments 4'), + Profile.new(id: 5, name: 'Name 5', description: 'Description 5', comments: 'Comments 5') + ] + ).page(1).per(collection_per) + end + end end class GrapeTest < Grape::API @@ -41,11 +68,28 @@ class GrapeTest < Grape::API posts = Models.all render posts, adapter: :json_api end + + get '/render_collection_with_json_api' do + posts = Models.collection + render posts, adapter: :json_api + end + + get '/render_with_implicit_formatter' do + Models.model1 + end + + get '/render_array_with_implicit_formatter' do + Models.all + end + + get '/render_collection_with_implicit_formatter' do + Models.collection + end end end def app - GrapeTest.new + Grape::Middleware::Globals.new(GrapeTest.new) end def test_formatter_returns_json @@ -77,6 +121,53 @@ def test_formatter_handles_arrays assert last_response.ok? assert_equal serializable_resource.to_json, last_response.body ensure - ARModels::Post.delete_all + Models.reset_all + end + + def test_formatter_handles_collections + get '/grape/render_collection_with_json_api' + assert last_response.ok? + + representation = JSON.parse(last_response.body) + assert representation.include?('data') + assert representation['data'].count == Models.collection_per + assert representation.include?('links') + assert representation['links'].count > 0 + end + + def test_implicit_formatter + ActiveModel::Serializer.config.adapter = :json_api + get '/grape/render_with_implicit_formatter' + + post = Models.model1 + serializable_resource = serializable(post, adapter: :json_api) + + assert last_response.ok? + assert_equal serializable_resource.to_json, last_response.body + end + + def test_implicit_formatter_handles_arrays + ActiveModel::Serializer.config.adapter = :json_api + get '/grape/render_array_with_implicit_formatter' + + posts = Models.all + serializable_resource = serializable(posts, adapter: :json_api) + + assert last_response.ok? + assert_equal serializable_resource.to_json, last_response.body + ensure + Models.reset_all + end + + def test_implicit_formatter_handles_collections + ActiveModel::Serializer.config.adapter = :json_api + get '/grape/render_collection_with_implicit_formatter' + assert last_response.ok? + + representation = JSON.parse(last_response.body) + assert representation.include?('data') + assert representation['data'].count == Models.collection_per + assert representation.include?('links') + assert representation['links'].count > 0 end end