From 629afb98872cfada637846b7bec8cc1d3e55019f Mon Sep 17 00:00:00 2001 From: Darren Cheng Date: Thu, 21 Jul 2016 18:46:16 -0700 Subject: [PATCH] Namespace inferred serializer resolution. --- .rubocop.yml | 4 + .rubocop_todo.yml | 4 - grape-active_model_serializers.gemspec | 2 +- .../endpoint_extension.rb | 4 +- .../options_builder.rb | 2 +- .../serializer_resolver.rb | 57 +++++++--- lib/grape-active_model_serializers/version.rb | 2 +- .../serializer_resolver_spec.rb | 102 ++++++++++++++++++ spec/support/api/users_api.rb | 13 +++ spec/support/api/v3/users_api.rb | 15 +++ .../support/serializers/v2/user_serializer.rb | 5 + .../support/serializers/v3/user_serializer.rb | 5 + .../support/serializers/v4/user_serializer.rb | 5 + 13 files changed, 198 insertions(+), 22 deletions(-) create mode 100644 spec/grape/active_model_serializers/serializer_resolver_spec.rb create mode 100644 spec/support/api/users_api.rb create mode 100644 spec/support/api/v3/users_api.rb create mode 100644 spec/support/serializers/v2/user_serializer.rb create mode 100644 spec/support/serializers/v3/user_serializer.rb create mode 100644 spec/support/serializers/v4/user_serializer.rb diff --git a/.rubocop.yml b/.rubocop.yml index d6d44bb..a3abca1 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,5 +1,9 @@ inherit_from: .rubocop_todo.yml + AllCops: Exclude: - Guardfile - grape-active_model_serializers.gemspec + +Style/BlockDelimiters: + Enabled: false diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index d0d8442..d28dbba 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -18,7 +18,3 @@ Style/Documentation: # Configuration parameters: Exclude. Style/FileName: Enabled: false - -# Offense count: 4 -Style/RegexpLiteral: - MaxSlashes: 0 diff --git a/grape-active_model_serializers.gemspec b/grape-active_model_serializers.gemspec index 9ce51c8..f7e1a8b 100644 --- a/grape-active_model_serializers.gemspec +++ b/grape-active_model_serializers.gemspec @@ -24,5 +24,5 @@ Gem::Specification.new do |gem| gem.add_development_dependency 'rack-test' gem.add_development_dependency 'rake' gem.add_development_dependency 'guard-rspec' - gem.add_development_dependency 'rubocop', '0.28.0' + gem.add_development_dependency 'rubocop' end diff --git a/lib/grape-active_model_serializers/endpoint_extension.rb b/lib/grape-active_model_serializers/endpoint_extension.rb index 0da9d44..444fa6b 100644 --- a/lib/grape-active_model_serializers/endpoint_extension.rb +++ b/lib/grape-active_model_serializers/endpoint_extension.rb @@ -9,7 +9,7 @@ module Grape attr_accessor :controller_name def namespace_options - if self.respond_to?(:inheritable_setting) + if respond_to?(:inheritable_setting) inheritable_setting.namespace else settings[:namespace] ? settings[:namespace].options : {} @@ -17,7 +17,7 @@ module Grape end def route_options - if self.respond_to?(:inheritable_setting) + if respond_to?(:inheritable_setting) inheritable_setting.route else options[:route_options] diff --git a/lib/grape-active_model_serializers/options_builder.rb b/lib/grape-active_model_serializers/options_builder.rb index d7a19c8..040d784 100644 --- a/lib/grape-active_model_serializers/options_builder.rb +++ b/lib/grape-active_model_serializers/options_builder.rb @@ -9,7 +9,7 @@ module Grape def options @options ||= ( options = endpoint_options - options.merge!(scope: endpoint) unless options.key?(:scope) + options[:scope] = endpoint unless options.key?(:scope) options.merge!(default_root_options) unless options.key?(:root) options.merge!(meta_options) options diff --git a/lib/grape-active_model_serializers/serializer_resolver.rb b/lib/grape-active_model_serializers/serializer_resolver.rb index e065fb6..d4fb0e0 100644 --- a/lib/grape-active_model_serializers/serializer_resolver.rb +++ b/lib/grape-active_model_serializers/serializer_resolver.rb @@ -8,7 +8,7 @@ module Grape def serializer @serializer ||= ( - serializer_klass.new(resource, options) if serializer_klass + serializer_class.new(resource, options) if serializer_class ) end @@ -16,26 +16,57 @@ module Grape attr_accessor :resource, :options - def serializer_klass - serializer_klass = options[:serializer] - serializer_klass ||= namespaced_resource_serializer_klass - serializer_klass + def 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 + serializer_class end - def namespaced_resource_serializer_klass - "#{namespace}::#{resource_serializer_klass}".constantize - rescue NameError - resource_serializer_klass + def resource_defined_class + resource.serializer_class if resource.respond_to?(:serializer_class) end - def namespace + def collection_class + return nil unless resource.respond_to?(:to_ary) + ActiveModel::Serializer.config.collection_serializer + end + + def namespace_inferred_class + return nil unless options[:for] + namespace = options[:for].to_s.deconstantize + "#{namespace}::#{resource_serializer_klass}".safe_constantize + end + + def version_inferred_class + "#{version}::#{resource_serializer_klass}".safe_constantize + end + + def version options[:version].try(:classify) end def resource_serializer_klass - @resource_serializer_klass ||= ActiveModel::Serializer.serializer_for( - resource - ) + @resource_serializer_klass ||= [ + resource_namespace, + "#{resource_klass}Serializer" + ].compact.join('::') + end + + def resource_klass + resource.class.name.demodulize + end + + def resource_namespace + klass = resource.class.name.deconstantize + klass.empty? ? nil : klass + end + + def resource_serializer_class + ActiveModel::Serializer.serializer_for(resource) end end end diff --git a/lib/grape-active_model_serializers/version.rb b/lib/grape-active_model_serializers/version.rb index 803b464..0497521 100644 --- a/lib/grape-active_model_serializers/version.rb +++ b/lib/grape-active_model_serializers/version.rb @@ -1,5 +1,5 @@ module Grape module ActiveModelSerializers - VERSION = '1.4.0' + VERSION = '1.4.0'.freeze end end diff --git a/spec/grape/active_model_serializers/serializer_resolver_spec.rb b/spec/grape/active_model_serializers/serializer_resolver_spec.rb new file mode 100644 index 0000000..1f5b6ab --- /dev/null +++ b/spec/grape/active_model_serializers/serializer_resolver_spec.rb @@ -0,0 +1,102 @@ +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 +# 6. resource_serializer_class # UserSerializer +# 7. missing resource # nil + +describe Grape::ActiveModelSerializers::SerializerResolver do + let(:resolver) { described_class.new(resource, options) } + let(:resource) { User.new } + let(:options) { + { + serializer: options_serializer_class, # options defined + for: V3::UsersApi, # namespace inference + version: 'v4' # version inference + } + } + # resource defined + let(:resource_defined?) { true } + let(:defined_serializer_class) { V1::UserSerializer } + # options defined + let(:options_serializer_class) { V2::UserSerializer } + + let(:serializer) { resolver.serializer } + + before do + if resource_defined? + allow(resource).to receive(:respond_to?).and_call_original + allow(resource).to receive(:respond_to?).with(:to_ary) { true } + allow(resource).to receive(:serializer_class) { defined_serializer_class } + end + end + + context 'resource defined' do + it 'returns serializer' do + expect(serializer).to be_kind_of(defined_serializer_class) + end + end + + context 'not resource defined' do + let(:resource_defined?) { false } + + context 'resource collection' do + let(:resource) { [User.new] } + let(:serializer_class) { ActiveModel::Serializer::CollectionSerializer } + + it 'returns serializer' do + expect(serializer).to be_kind_of(serializer_class) + 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) + end + end + + context 'not specified by options' do + let(:options) { super().except(:serializer) } + + context 'namespace inferred' do + it 'returns inferred serializer' do + expect(serializer).to be_kind_of(V3::UserSerializer) + end + end + + context 'not namespace inferred' do + let(:options) { super().except(:for) } + + context 'version inferred' do + it 'returns inferred serializer' do + expect(serializer).to be_kind_of(V4::UserSerializer) + end + end + + context 'not version inferred' do + let(:options) { super().except(:version) } + + context 'ASM resolved' do + it 'returns serializer' do + expect(serializer).to be_kind_of(UserSerializer) + end + end + + context 'not ASM resolved' do + let(:resource) { nil } + + it 'returns nil' do + expect(serializer).to eq(nil) + end + end + end + end + end + end + end +end diff --git a/spec/support/api/users_api.rb b/spec/support/api/users_api.rb new file mode 100644 index 0000000..7e7197a --- /dev/null +++ b/spec/support/api/users_api.rb @@ -0,0 +1,13 @@ +class UsersApi < Grape::API + resource :users do + desc 'all users' + get do + [User.new] + end + + desc 'specified user' + get '/:id' do + User.new + end + end +end diff --git a/spec/support/api/v3/users_api.rb b/spec/support/api/v3/users_api.rb new file mode 100644 index 0000000..5ce254c --- /dev/null +++ b/spec/support/api/v3/users_api.rb @@ -0,0 +1,15 @@ +module V3 + class UsersApi < Grape::API + resource :users do + desc 'all users' + get do + [User.new] + end + + desc 'specified user' + get '/:id' do + User.new + end + end + end +end diff --git a/spec/support/serializers/v2/user_serializer.rb b/spec/support/serializers/v2/user_serializer.rb new file mode 100644 index 0000000..5607217 --- /dev/null +++ b/spec/support/serializers/v2/user_serializer.rb @@ -0,0 +1,5 @@ +module V2 + class UserSerializer < ActiveModel::Serializer + attributes :first_name, :last_name, :email + end +end diff --git a/spec/support/serializers/v3/user_serializer.rb b/spec/support/serializers/v3/user_serializer.rb new file mode 100644 index 0000000..8ab15f3 --- /dev/null +++ b/spec/support/serializers/v3/user_serializer.rb @@ -0,0 +1,5 @@ +module V3 + class UserSerializer < ActiveModel::Serializer + attributes :first_name, :last_name, :email + end +end diff --git a/spec/support/serializers/v4/user_serializer.rb b/spec/support/serializers/v4/user_serializer.rb new file mode 100644 index 0000000..138bbba --- /dev/null +++ b/spec/support/serializers/v4/user_serializer.rb @@ -0,0 +1,5 @@ +module V4 + class UserSerializer < ActiveModel::Serializer + attributes :first_name, :last_name, :email + end +end