From 6bb205731a6af0ae42f17eb89fb08f4917211878 Mon Sep 17 00:00:00 2001 From: Darren Cheng Date: Sun, 24 Jul 2016 15:49:32 -0700 Subject: [PATCH] Implement support for collection serializers. --- CHANGELOG.md | 6 +- .../serializer_resolver.rb | 48 ++++++++++---- .../serializer_resolver_spec.rb | 64 ++++++++++++++++--- spec/support/api/{v3 => v4}/users_api.rb | 2 +- .../support/serializers/v5/user_serializer.rb | 5 ++ 5 files changed, 102 insertions(+), 23 deletions(-) rename spec/support/api/{v3 => v4}/users_api.rb (95%) create mode 100644 spec/support/serializers/v5/user_serializer.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index d4a3bda..56bb058 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ### 1.5.0 (Next) +* [#61](https://github.com/ruby-grape/grape-active_model_serializers/pull/61): Adds support for collection serializers - [@drn](https://github.com/drn). +* [#60](https://github.com/ruby-grape/grape-active_model_serializers/pull/60): Namespace serializer inference - [@drn](https://github.com/drn). +* [#59](https://github.com/ruby-grape/grape-active_model_serializers/pull/59): Refactor option and serializer resolution - [@drn](https://github.com/drn). +* [#57](https://github.com/ruby-grape/grape-active_model_serializers/pull/57): Solve line length linter issues - [@drn](https://github.com/drn). * [#54](https://github.com/ruby-grape/grape-active_model_serializers/pull/54): Adding support for ASM v0.10. Drops support for ASM v0.9 - [@drn](https://github.com/drn). ### 1.4.0 (July 14, 2016) @@ -11,8 +15,8 @@ ### v1.3.2 (February 27, 2015) -* [#39](https://github.com/ruby-grape/grape-active_model_serializers/pull/39): Look for namespace and other options to configure serializers - [@jwkoelewijn](https://github.com/jwkoelewijn). * [#40](https://github.com/ruby-grape/grape-active_model_serializers/pull/40): Use env to pass AMS meta around - [@dblock](https://github.com/dblock). +* [#39](https://github.com/ruby-grape/grape-active_model_serializers/pull/39): Look for namespace and other options to configure serializers - [@jwkoelewijn](https://github.com/jwkoelewijn). ### v1.3.1 (November 20, 2014) diff --git a/lib/grape-active_model_serializers/serializer_resolver.rb b/lib/grape-active_model_serializers/serializer_resolver.rb index d4fb0e0..b9949e3 100644 --- a/lib/grape-active_model_serializers/serializer_resolver.rb +++ b/lib/grape-active_model_serializers/serializer_resolver.rb @@ -7,9 +7,7 @@ def initialize(resource, options) end def serializer - @serializer ||= ( - serializer_class.new(resource, options) if serializer_class - ) + serializer_class.new(resource, serializer_options) if serializer_class end private @@ -17,13 +15,32 @@ def serializer attr_accessor :resource, :options def serializer_class - serializer_class = resource_defined_class - serializer_class ||= collection_class - serializer_class ||= options[:serializer] + return @serializer_class if defined?(@serializer_class) + @serializer_class = resource_defined_class + @serializer_class ||= collection_class + @serializer_class ||= options[:serializer] + @serializer_class ||= namespace_inferred_class + @serializer_class ||= version_inferred_class + @serializer_class ||= resource_serializer_class + end + + def serializer_options + if collection_serializer? && !options.key?(:serializer) + options.merge(each_serializer_option) + else + options + end + end + + def collection_serializer? + serializer_class == ActiveModel::Serializer.config.collection_serializer + end + + def each_serializer_option + serializer_class = options[:each_serializer] serializer_class ||= namespace_inferred_class serializer_class ||= version_inferred_class - serializer_class ||= resource_serializer_class - serializer_class + serializer_class ? { serializer: serializer_class } : {} end def resource_defined_class @@ -36,12 +53,13 @@ def collection_class end def namespace_inferred_class - return nil unless options[:for] + return nil unless options.key?(:for) namespace = options[:for].to_s.deconstantize "#{namespace}::#{resource_serializer_klass}".safe_constantize end def version_inferred_class + return nil unless options.key?(:version) "#{version}::#{resource_serializer_klass}".safe_constantize end @@ -57,14 +75,22 @@ def resource_serializer_klass end def resource_klass - resource.class.name.demodulize + resource_class.name.demodulize end def resource_namespace - klass = resource.class.name.deconstantize + klass = resource_class.name.deconstantize klass.empty? ? nil : klass end + def resource_class + if resource.respond_to?(:to_ary) + resource.try(:klass) || resource.compact.first.class + else + resource.class + end + end + def resource_serializer_class ActiveModel::Serializer.serializer_for(resource) end diff --git a/spec/grape/active_model_serializers/serializer_resolver_spec.rb b/spec/grape/active_model_serializers/serializer_resolver_spec.rb index 1f5b6ab..4972551 100644 --- a/spec/grape/active_model_serializers/serializer_resolver_spec.rb +++ b/spec/grape/active_model_serializers/serializer_resolver_spec.rb @@ -1,11 +1,12 @@ +require 'pry' require 'spec_helper' # asserts serializer resolution order: # 1. resource_defined_class # V1::UserSerializer -# 2. collection_class # CollectionSerializer -# 3. options[:serializer] # V2::UserSerializer -# 4. namespace_inferred_class # V3::UserSerializer -# 5. version_inferred_class # V4::UserSerializer +# 2. collection_class # CollectionSerializer, V2::UserSerializer +# 3. options[:serializer] # V3::UserSerializer +# 4. namespace_inferred_class # V4::UserSerializer +# 5. version_inferred_class # V5::UserSerializer # 6. resource_serializer_class # UserSerializer # 7. missing resource # nil @@ -15,15 +16,15 @@ let(:options) { { serializer: options_serializer_class, # options defined - for: V3::UsersApi, # namespace inference - version: 'v4' # version inference + for: V4::UsersApi, # namespace inference + version: 'v5' # version inference } } # resource defined let(:resource_defined?) { true } let(:defined_serializer_class) { V1::UserSerializer } # options defined - let(:options_serializer_class) { V2::UserSerializer } + let(:options_serializer_class) { V3::UserSerializer } let(:serializer) { resolver.serializer } @@ -51,12 +52,55 @@ it 'returns serializer' do expect(serializer).to be_kind_of(serializer_class) end + + context 'each serializer' do + let(:options) { + super().except(:serializer).merge( + each_serializer: V2::UserSerializer + ) + } + let(:each_serializer) { serializer.send(:options)[:serializer] } + + context 'each_serializer option' do + it 'returns expected serializer' do + expect(each_serializer).to eq(V2::UserSerializer) + end + end + + context 'no each_serializer option' do + let(:options) { super().except(:each_serializer) } + + context 'namespace inferred' do + it 'returns expected serializer' do + expect(each_serializer).to eq(V4::UserSerializer) + end + end + + context 'not namespace inferred' do + let(:options) { super().except(:for) } + + context 'version inferred' do + it 'returns expected serializer' do + expect(each_serializer).to eq(V5::UserSerializer) + end + end + + context 'not version inferred' do + let(:options) { super().except(:version) } + + it 'returns nil' do + expect(each_serializer).to eq(nil) + end + end + end + end + end end context 'not resource collection' do context 'specified by options' do it 'returns specified serializer' do - expect(serializer).to be_kind_of(V2::UserSerializer) + expect(serializer).to be_kind_of(V3::UserSerializer) end end @@ -65,7 +109,7 @@ context 'namespace inferred' do it 'returns inferred serializer' do - expect(serializer).to be_kind_of(V3::UserSerializer) + expect(serializer).to be_kind_of(V4::UserSerializer) end end @@ -74,7 +118,7 @@ context 'version inferred' do it 'returns inferred serializer' do - expect(serializer).to be_kind_of(V4::UserSerializer) + expect(serializer).to be_kind_of(V5::UserSerializer) end end diff --git a/spec/support/api/v3/users_api.rb b/spec/support/api/v4/users_api.rb similarity index 95% rename from spec/support/api/v3/users_api.rb rename to spec/support/api/v4/users_api.rb index 5ce254c..7f61e50 100644 --- a/spec/support/api/v3/users_api.rb +++ b/spec/support/api/v4/users_api.rb @@ -1,4 +1,4 @@ -module V3 +module V4 class UsersApi < Grape::API resource :users do desc 'all users' diff --git a/spec/support/serializers/v5/user_serializer.rb b/spec/support/serializers/v5/user_serializer.rb new file mode 100644 index 0000000..afd9058 --- /dev/null +++ b/spec/support/serializers/v5/user_serializer.rb @@ -0,0 +1,5 @@ +module V5 + class UserSerializer < ActiveModel::Serializer + attributes :first_name, :last_name, :email + end +end