diff --git a/lib/grape-active_model_serializers.rb b/lib/grape-active_model_serializers.rb index 0e93c5c..e395587 100644 --- a/lib/grape-active_model_serializers.rb +++ b/lib/grape-active_model_serializers.rb @@ -2,4 +2,6 @@ require 'active_model_serializers' require 'grape' require 'grape-active_model_serializers/endpoint_extension' require 'grape-active_model_serializers/formatter' +require 'grape-active_model_serializers/serializer_resolver' +require 'grape-active_model_serializers/options_builder' require 'grape-active_model_serializers/version' diff --git a/lib/grape-active_model_serializers/formatter.rb b/lib/grape-active_model_serializers/formatter.rb index bc97e1b..19b0183 100644 --- a/lib/grape-active_model_serializers/formatter.rb +++ b/lib/grape-active_model_serializers/formatter.rb @@ -11,80 +11,20 @@ module Grape serializer, options ).to_json else - Grape::Formatter::Json.call resource, env + Grape::Formatter::Json.call(resource, env) end end def build_options(resource, env) - endpoint = env['api.endpoint'] - options = build_options_from_endpoint(endpoint) - - options[:scope] = endpoint unless options.key?(:scope) - - # ensure we have a root to fallback on - if resource.respond_to?(:to_ary) && !options.key?(:root) - options[:root] = default_root(endpoint) - end - - options.merge(meta_options(env)) + Grape::ActiveModelSerializers::OptionsBuilder.new( + resource, env + ).options end def fetch_serializer(resource, options) - # use serializer specified by options - serializer = options[:serializer] - - if serializer.nil? - # fetch serializer leverage AMS lookup - serializer = ActiveModel::Serializer.serializer_for(resource) - # if grape version exists, attempt to apply version namespacing - serializer = namespace_serializer(serializer, options[:version]) - end - - return nil unless serializer - - serializer.new(resource, options) - end - - def namespace_serializer(serializer, namespace) - "#{namespace.try(:classify)}::#{serializer}".constantize - rescue NameError - serializer - end - - def meta_options(env) - options = {} - ams_meta = env['ams_meta'] || {} - meta = ams_meta.delete(:meta) - meta_key = ams_meta.delete(:meta_key) - options[:meta_key] = meta_key if meta && meta_key - options[:meta] = meta if meta - options - end - - def build_options_from_endpoint(endpoint) - [ - endpoint.default_serializer_options || {}, - endpoint.namespace_options, - endpoint.route_options, - endpoint.options, - endpoint.options.fetch(:route_options) - ].reduce(:merge) - end - - # array root is the innermost namespace name ('space') if there is one, - # otherwise the route name (e.g. get 'name') - def default_root(endpoint) - innermost_scope = if endpoint.respond_to?(:namespace_stackable) - endpoint.namespace_stackable(:namespace).last - else - endpoint.settings.peek[:namespace] - end - - if innermost_scope - innermost_scope.space - else - endpoint.options[:path][0].to_s.split('/')[-1] - end + Grape::ActiveModelSerializers::SerializerResolver.new( + resource, options + ).serializer end end end diff --git a/lib/grape-active_model_serializers/options_builder.rb b/lib/grape-active_model_serializers/options_builder.rb new file mode 100644 index 0000000..d7a19c8 --- /dev/null +++ b/lib/grape-active_model_serializers/options_builder.rb @@ -0,0 +1,68 @@ +module Grape + module ActiveModelSerializers + class OptionsBuilder + def initialize(resource, env) + self.resource = resource + self.env = env + end + + def options + @options ||= ( + options = endpoint_options + options.merge!(scope: endpoint) unless options.key?(:scope) + options.merge!(default_root_options) unless options.key?(:root) + options.merge!(meta_options) + options + ) + end + + private + + attr_accessor :resource, :env + + def endpoint_options + [ + endpoint.default_serializer_options || {}, + endpoint.namespace_options, + endpoint.route_options, + endpoint.options, + endpoint.options.fetch(:route_options) + ].reduce(:merge) + end + + def endpoint + @endpoint ||= env['api.endpoint'] + end + + # array root is the innermost namespace name ('space') if there is one, + # otherwise the route name (e.g. get 'name') + def default_root_options + return {} unless resource.respond_to?(:to_ary) + + if innermost_scope + { root: innermost_scope.space } + else + { root: endpoint.options[:path].first.to_s.split('/').last } + end + end + + def innermost_scope + if endpoint.respond_to?(:namespace_stackable) + endpoint.namespace_stackable(:namespace).last + else + endpoint.settings.peek[:namespace] + end + end + + def meta_options + options = {} + ams_meta = env['ams_meta'] || {} + meta = ams_meta[:meta] + meta_key = ams_meta[:meta_key] + options[:meta] = meta if meta + options[:meta_key] = meta_key if meta && meta_key + options + end + end + end +end diff --git a/lib/grape-active_model_serializers/serializer_resolver.rb b/lib/grape-active_model_serializers/serializer_resolver.rb new file mode 100644 index 0000000..e065fb6 --- /dev/null +++ b/lib/grape-active_model_serializers/serializer_resolver.rb @@ -0,0 +1,42 @@ +module Grape + module ActiveModelSerializers + class SerializerResolver + def initialize(resource, options) + self.resource = resource + self.options = options + end + + def serializer + @serializer ||= ( + serializer_klass.new(resource, options) if serializer_klass + ) + end + + private + + attr_accessor :resource, :options + + def serializer_klass + serializer_klass = options[:serializer] + serializer_klass ||= namespaced_resource_serializer_klass + serializer_klass + end + + def namespaced_resource_serializer_klass + "#{namespace}::#{resource_serializer_klass}".constantize + rescue NameError + resource_serializer_klass + end + + def namespace + options[:version].try(:classify) + end + + def resource_serializer_klass + @resource_serializer_klass ||= ActiveModel::Serializer.serializer_for( + resource + ) + end + end + end +end diff --git a/spec/grape-active_model_serializers/formatter_spec.rb b/spec/grape-active_model_serializers/formatter_spec.rb index 4c33296..18ab47b 100644 --- a/spec/grape-active_model_serializers/formatter_spec.rb +++ b/spec/grape-active_model_serializers/formatter_spec.rb @@ -18,10 +18,11 @@ describe Grape::Formatter::ActiveModelSerializers do end end + let(:env) { { 'api.endpoint' => app.endpoints.first } } + let(:options) { described_class.build_options(nil, env) } + it 'should read serializer options like "root"' do - expect( - described_class.build_options_from_endpoint(app.endpoints.first) - ).to include(:root) + expect(options).to include(:root) end end @@ -65,9 +66,7 @@ describe Grape::Formatter::ActiveModelSerializers do end it 'should read serializer options like "root"' do - expect( - described_class.build_options_from_endpoint(endpoint).keys - ).to include(:root) + expect(options).to include(:root) end end end diff --git a/spec/grape-active_model_serializers/versioned_api_formatter_spec.rb b/spec/grape-active_model_serializers/versioned_api_formatter_spec.rb index 657bb4d..e0bb0b9 100644 --- a/spec/grape-active_model_serializers/versioned_api_formatter_spec.rb +++ b/spec/grape-active_model_serializers/versioned_api_formatter_spec.rb @@ -26,10 +26,11 @@ describe Grape::Formatter::ActiveModelSerializers do end end + let(:env) { { 'api.endpoint' => app.endpoints.first } } + let(:options) { described_class.build_options(nil, env) } + it 'should read serializer options like "root"' do - expect( - described_class.build_options_from_endpoint(app.endpoints.first) - ).to include(:root) + expect(options).to include(:root) end end @@ -72,9 +73,7 @@ describe Grape::Formatter::ActiveModelSerializers do end it 'should read serializer options like "root"' do - expect( - described_class.build_options_from_endpoint(endpoint).keys - ).to include(:root) + expect(options).to include(:root) end end end