From d364c4f18831a763fee93a72300e85731c0085ef Mon Sep 17 00:00:00 2001 From: Benjamin Fleischer Date: Mon, 29 Feb 2016 16:10:41 -0600 Subject: [PATCH] Spike Jsonapi Renderer registration --- .../register_jsonapi_renderer.rb | 64 +++++++++++++++++++ .../action_controller/json_api/linked_test.rb | 19 +++--- 2 files changed, 74 insertions(+), 9 deletions(-) create mode 100644 lib/active_model_serializers/register_jsonapi_renderer.rb diff --git a/lib/active_model_serializers/register_jsonapi_renderer.rb b/lib/active_model_serializers/register_jsonapi_renderer.rb new file mode 100644 index 00000000..d17841fb --- /dev/null +++ b/lib/active_model_serializers/register_jsonapi_renderer.rb @@ -0,0 +1,64 @@ +# Based on discussion in https://github.com/rails/rails/pull/23712#issuecomment-184977238, +# the JSON API media type will have its own format/renderer. +# +# > We recommend the media type be registered on its own as jsonapi +# when a jsonapi Renderer and deserializer (Http::Parameters::DEFAULT_PARSERS) are added. +# +# Usage: +# +# ActiveSupport.on_load(:action_controller) do +# require 'active_model_serializers/register_jsonapi_renderer' +# end +# +# And then in controllers, use `render jsonapi: model` rather than `render json: model, adapter: :json_api`. +# +# For example, in a controller action, we can: +# respond_to do |format| +# format.jsonapi { render jsonapi: model } +# end +# +# or +# +# render jsonapi: model +# +# No wrapper format needed as it does not apply (i.e. no `wrap_parameters format: [jsonapi]`) + +module ActiveModelSerializers::Jsonapi + MEDIA_TYPE = 'application/vnd.api+json'.freeze + HEADERS = { + response: { 'CONTENT_TYPE'.freeze => MEDIA_TYPE }, + request: { 'ACCEPT'.freeze => MEDIA_TYPE } + }.freeze + module ControllerSupport + def serialize_jsonapi(json, options) + options[:adapter] = :json_api + options.fetch(:serialization_context) { options[:serialization_context] = ActiveModelSerializers::SerializationContext.new(request) } + get_serializer(json, options) + end + end +end + +# actionpack/lib/action_dispatch/http/mime_types.rb +Mime::Type.register ActiveModelSerializers::Jsonapi::MEDIA_TYPE, :jsonapi + +parsers = Rails::VERSION::MAJOR >= 5 ? ActionDispatch::Http::Parameters : ActionDispatch::ParamsParser +media_type = Mime::Type.lookup(ActiveModelSerializers::Jsonapi::MEDIA_TYPE) + +# Proposal: should actually deserialize the JSON API params +# to the hash format expected by `ActiveModel::Serializers::JSON` +# actionpack/lib/action_dispatch/http/parameters.rb +parsers::DEFAULT_PARSERS[media_type] = lambda do |body| + data = JSON.parse(body) + data = { :_json => data } unless data.is_a?(Hash) + data.with_indifferent_access +end + +# ref https://github.com/rails/rails/pull/21496 +ActionController::Renderers.add :jsonapi do |json, options| + json = serialize_jsonapi(json, options).to_json(options) unless json.is_a?(String) + self.content_type ||= media_type + headers.merge! ActiveModelSerializers::Jsonapi::HEADERS[:response] + self.response_body = json +end + +ActionController::Base.send :include, ActiveModelSerializers::Jsonapi::ControllerSupport diff --git a/test/action_controller/json_api/linked_test.rb b/test/action_controller/json_api/linked_test.rb index 8d541f5b..5a1e3bc3 100644 --- a/test/action_controller/json_api/linked_test.rb +++ b/test/action_controller/json_api/linked_test.rb @@ -5,6 +5,7 @@ module ActionController class JsonApi class LinkedTest < ActionController::TestCase class LinkedTestController < ActionController::Base + require 'active_model_serializers/register_jsonapi_renderer' def setup_post ActionController::Base.cache_store.clear @role1 = Role.new(id: 1, name: 'admin') @@ -38,49 +39,49 @@ module ActionController def render_resource_without_include setup_post - render json: @post, adapter: :json_api + render jsonapi: @post end def render_resource_with_include setup_post - render json: @post, include: [:author], adapter: :json_api + render jsonapi: @post, include: [:author] end def render_resource_with_include_of_custom_key_by_original setup_post - render json: @post, include: [:reviews], adapter: :json_api, serializer: PostWithCustomKeysSerializer + render jsonapi: @post, include: [:reviews], serializer: PostWithCustomKeysSerializer end def render_resource_with_nested_include setup_post - render json: @post, include: [comments: [:author]], adapter: :json_api + render jsonapi: @post, include: [comments: [:author]] end def render_resource_with_nested_has_many_include_wildcard setup_post - render json: @post, include: 'author.*', adapter: :json_api + render jsonapi: @post, include: 'author.*' end def render_resource_with_missing_nested_has_many_include setup_post @post.author = @author2 # author2 has no roles. - render json: @post, include: [author: [:roles]], adapter: :json_api + render jsonapi: @post, include: [author: [:roles]] end def render_collection_with_missing_nested_has_many_include setup_post @post.author = @author2 - render json: [@post, @post2], include: [author: [:roles]], adapter: :json_api + render jsonapi: [@post, @post2], include: [author: [:roles]] end def render_collection_without_include setup_post - render json: [@post], adapter: :json_api + render jsonapi: [@post] end def render_collection_with_include setup_post - render json: [@post], include: 'author, comments', adapter: :json_api + render jsonapi: [@post], include: 'author, comments' end end