From c84430cdadeefe7382668f596c230c0599ba6b3e Mon Sep 17 00:00:00 2001 From: Gary Gordon Date: Mon, 27 Oct 2014 10:44:21 -0400 Subject: [PATCH 1/2] Support serializer and each_serializer in render json --- README.md | 16 ++++++ lib/action_controller/serialization.rb | 21 +++++--- .../serializer/array_serializer.rb | 5 +- .../explicit_serializer_test.rb | 53 +++++++++++++++++++ test/fixtures/poro.rb | 12 +++++ 5 files changed, 100 insertions(+), 7 deletions(-) create mode 100644 test/action_controller/explicit_serializer_test.rb diff --git a/README.md b/README.md index 4121dfe2c..bb976b28a 100644 --- a/README.md +++ b/README.md @@ -94,6 +94,22 @@ member when the resource names are included in the `include` option. render @posts, include: 'authors,comments' ``` +### Specify a serializer + +If you wish to use a serializer other than the default, you can explicitly pass it to the renderer. + +#### 1. For a resource: + +```ruby + render json: @post, serializer: PostPreviewSerializer +``` + +#### 2. For an array resource: + +```ruby + render json: @posts, serializer: PaginatedSerializer, each_serializer: PostPreviewSerializer +``` + ## Installation Add this line to your application's Gemfile: diff --git a/lib/action_controller/serialization.rb b/lib/action_controller/serialization.rb index 2b460cb4f..150b9aa2a 100644 --- a/lib/action_controller/serialization.rb +++ b/lib/action_controller/serialization.rb @@ -8,16 +8,25 @@ module Serialization ADAPTER_OPTION_KEYS = [:include, :root] + def get_serializer(resource, options) + @_serializer ||= if (serializer = options.delete :serializer) + options[:serializer] = options.delete :each_serializer + serializer + else + ActiveModel::Serializer.serializer_for(resource) + end + end + [:_render_option_json, :_render_with_renderer_json].each do |renderer_method| define_method renderer_method do |resource, options| - serializer = ActiveModel::Serializer.serializer_for(resource) - if serializer - adapter_opts, serializer_opts = - options.partition { |k, _| ADAPTER_OPTION_KEYS.include? k } + adapter_opts, serializer_opts = + options.partition { |k, _| ADAPTER_OPTION_KEYS.include? k }.map { |h| Hash[h] } + + if (serializer = get_serializer(resource, serializer_opts)) # omg hax - object = serializer.new(resource, Hash[serializer_opts]) - adapter = ActiveModel::Serializer.adapter.new(object, Hash[adapter_opts]) + object = serializer.new(resource, serializer_opts) + adapter = ActiveModel::Serializer.adapter.new(object, adapter_opts) super(adapter, options) else super(resource, options) diff --git a/lib/active_model/serializer/array_serializer.rb b/lib/active_model/serializer/array_serializer.rb index b52a4f51d..83693c978 100644 --- a/lib/active_model/serializer/array_serializer.rb +++ b/lib/active_model/serializer/array_serializer.rb @@ -6,7 +6,10 @@ class ArraySerializer def initialize(objects, options = {}) @objects = objects.map do |object| - serializer_class = ActiveModel::Serializer.serializer_for(object) + serializer_class = options.fetch( + :serializer, + ActiveModel::Serializer.serializer_for(object) + ) serializer_class.new(object) end end diff --git a/test/action_controller/explicit_serializer_test.rb b/test/action_controller/explicit_serializer_test.rb new file mode 100644 index 000000000..e4bddd04c --- /dev/null +++ b/test/action_controller/explicit_serializer_test.rb @@ -0,0 +1,53 @@ +require 'test_helper' + +module ActionController + module Serialization + class ExplicitSerializerTest < ActionController::TestCase + class MyController < ActionController::Base + def render_using_explicit_serializer + @profile = Profile.new(name: 'Name 1', + description: 'Description 1', + comments: 'Comments 1') + render json: @profile, serializer: ProfilePreviewSerializer + end + + def render_array_using_explicit_serializer + array = [ + Profile.new(name: 'Name 1', + description: 'Description 1', + comments: 'Comments 1'), + Profile.new(name: 'Name 2', + description: 'Description 2', + comments: 'Comments 2') + ] + render json: array, + serializer: PaginatedSerializer, + each_serializer: ProfilePreviewSerializer + end + end + + tests MyController + + def test_render_using_explicit_serializer + get :render_using_explicit_serializer + + assert_equal 'application/json', @response.content_type + assert_equal '{"name":"Name 1"}', @response.body + end + + def test_render_array_using_explicit_serializer + get :render_array_using_explicit_serializer + assert_equal 'application/json', @response.content_type + + expected = { + 'paginated' => [ + { 'name' => 'Name 1' }, + { 'name' => 'Name 2' } + ] + } + + assert_equal expected.to_json, @response.body + end + end + end +end diff --git a/test/fixtures/poro.rb b/test/fixtures/poro.rb index 7d8d57b42..9b52c8707 100644 --- a/test/fixtures/poro.rb +++ b/test/fixtures/poro.rb @@ -35,6 +35,12 @@ class ProfileSerializer < ActiveModel::Serializer urls :posts, :comments end +class ProfilePreviewSerializer < ActiveModel::Serializer + attributes :name + + urls :posts, :comments +end + Post = Class.new(Model) Comment = Class.new(Model) Author = Class.new(Model) @@ -67,3 +73,9 @@ class ProfileSerializer < ActiveModel::Serializer belongs_to :writer has_many :articles end + +PaginatedSerializer = Class.new(ActiveModel::Serializer::ArraySerializer) do + def json_key + 'paginated' + end +end From 58b6c4a6b7e04764db55dcbffb92b66f8642463f Mon Sep 17 00:00:00 2001 From: "Mike A. Owens" Date: Fri, 7 Nov 2014 09:39:36 -0500 Subject: [PATCH 2/2] Allow for the implicit use of ArraySerializer when :each_serializer is specified. --- README.md | 7 +++++- lib/action_controller/serialization.rb | 12 +++++---- .../explicit_serializer_test.rb | 25 +++++++++++++++++++ 3 files changed, 38 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index bb976b28a..1d9db323e 100644 --- a/README.md +++ b/README.md @@ -107,7 +107,12 @@ If you wish to use a serializer other than the default, you can explicitly pass #### 2. For an array resource: ```ruby - render json: @posts, serializer: PaginatedSerializer, each_serializer: PostPreviewSerializer +# Use the default `ArraySerializer`, which will use `each_serializer` to +# serialize each element +render json: @posts, each_serializer: PostPreviewSerializer + +# Or, you can explicitly provide the collection serializer as well +render json: @posts, serializer: PaginatedSerializer, each_serializer: PostPreviewSerializer ``` ## Installation diff --git a/lib/action_controller/serialization.rb b/lib/action_controller/serialization.rb index 150b9aa2a..d157e2f9d 100644 --- a/lib/action_controller/serialization.rb +++ b/lib/action_controller/serialization.rb @@ -9,12 +9,14 @@ module Serialization ADAPTER_OPTION_KEYS = [:include, :root] def get_serializer(resource, options) - @_serializer ||= if (serializer = options.delete :serializer) - options[:serializer] = options.delete :each_serializer - serializer - else - ActiveModel::Serializer.serializer_for(resource) + @_serializer ||= options.delete(:serializer) + @_serializer ||= ActiveModel::Serializer.serializer_for(resource) + + if options.key?(:each_serializer) + options[:serializer] = options.delete(:each_serializer) end + + @_serializer end [:_render_option_json, :_render_with_renderer_json].each do |renderer_method| diff --git a/test/action_controller/explicit_serializer_test.rb b/test/action_controller/explicit_serializer_test.rb index e4bddd04c..cb8b2bd7b 100644 --- a/test/action_controller/explicit_serializer_test.rb +++ b/test/action_controller/explicit_serializer_test.rb @@ -24,6 +24,19 @@ def render_array_using_explicit_serializer serializer: PaginatedSerializer, each_serializer: ProfilePreviewSerializer end + + def render_array_using_implicit_serializer + array = [ + Profile.new(name: 'Name 1', + description: 'Description 1', + comments: 'Comments 1'), + Profile.new(name: 'Name 2', + description: 'Description 2', + comments: 'Comments 2') + ] + render json: array, + each_serializer: ProfilePreviewSerializer + end end tests MyController @@ -48,6 +61,18 @@ def test_render_array_using_explicit_serializer assert_equal expected.to_json, @response.body end + + def test_render_array_using_explicit_serializer + get :render_array_using_implicit_serializer + assert_equal 'application/json', @response.content_type + + expected = [ + { 'name' => 'Name 1' }, + { 'name' => 'Name 2' } + ] + assert_equal expected.to_json, @response.body + end + end end end