From 26c0847563371685539cb584558067ae9299f733 Mon Sep 17 00:00:00 2001 From: Theodore Konukhov Date: Sun, 24 Aug 2014 15:57:24 +0200 Subject: [PATCH] - If a controller is in a namespace, serializer will be looked up in this namespace first. - Serializers for associations will be looked up in a parent serializer's namespace first too. - "Prefix" option for different versions of associations serializers. Example: has_many :users, as: :short -> ShortUserSerializer TODO: tests, config option for enabling namespace lookup --- lib/action_controller/serialization.rb | 20 +++++++++++++++++-- lib/active_model/array_serializer.rb | 4 +++- lib/active_model/serializable.rb | 16 +++++++++++++++ lib/active_model/serializer.rb | 27 +++++++++++++++++++++----- 4 files changed, 59 insertions(+), 8 deletions(-) diff --git a/lib/action_controller/serialization.rb b/lib/action_controller/serialization.rb index 5b1ee4ee..d86b2ac0 100644 --- a/lib/action_controller/serialization.rb +++ b/lib/action_controller/serialization.rb @@ -57,6 +57,18 @@ module ActionController private + def namespace_for_serializer + @namespace_for_serializer ||= self.class.parent unless self.class.parent == Object + end + + def default_serializer(resource) + options = {}.tap do |o| + o[:namespace] = namespace_for_serializer if namespace_for_serializer + end + + ActiveModel::Serializer.serializer_for(resource, options) + end + def default_serializer_options {} end @@ -69,9 +81,13 @@ module ActionController def build_json_serializer(resource, options = {}) options = default_serializer_options.merge(options) - if serializer = options.fetch(:serializer, ActiveModel::Serializer.serializer_for(resource)) + if serializer = options.fetch(:serializer, default_serializer(resource)) options[:scope] = serialization_scope unless options.has_key?(:scope) - options[:resource_name] = controller_name if resource.respond_to?(:to_ary) + + if resource.respond_to?(:to_ary) + options[:resource_name] = controller_name + options[:namespace] = namespace_for_serializer if namespace_for_serializer + end serializer.new(resource, options) end diff --git a/lib/active_model/array_serializer.rb b/lib/active_model/array_serializer.rb index 8c050770..25093ace 100644 --- a/lib/active_model/array_serializer.rb +++ b/lib/active_model/array_serializer.rb @@ -21,6 +21,7 @@ module ActiveModel @resource_name = options[:resource_name] @only = options[:only] ? Array(options[:only]) : nil @except = options[:except] ? Array(options[:except]) : nil + @namespace = options[:namespace] @key_format = options[:key_format] || options[:each_serializer].try(:key_format) end attr_accessor :object, :scope, :root, :meta_key, :meta, :key_format @@ -32,7 +33,7 @@ module ActiveModel end def serializer_for(item) - serializer_class = @each_serializer || Serializer.serializer_for(item) || DefaultSerializer + serializer_class = @each_serializer || Serializer.serializer_for(item, namespace: @namespace) || DefaultSerializer serializer_class.new(item, scope: scope, key_format: key_format, only: @only, except: @except) end @@ -58,6 +59,7 @@ module ActiveModel end private + def instrumentation_keys [:object, :scope, :root, :meta_key, :meta, :each_serializer, :resource_name, :key_format] end diff --git a/lib/active_model/serializable.rb b/lib/active_model/serializable.rb index e70a9afc..1faf62f1 100644 --- a/lib/active_model/serializable.rb +++ b/lib/active_model/serializable.rb @@ -20,11 +20,27 @@ module ActiveModel end end + if RUBY_VERSION >= '2.0' + def namespace + get_namespace && Object.const_get(get_namespace) + end + else + def namespace + get_namespace && get_namespace.safe_constantize + end + end + def embedded_in_root_associations {} end private + + def get_namespace + modules = self.class.name.split('::') + modules[0..-2].join('::') if modules.size > 1 + end + def instrument(action, &block) payload = instrumentation_keys.inject({ serializer: self.class.name }) do |payload, key| payload[:payload] = self.instance_variable_get(:"@#{key}") diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index e9edf41a..3913a5c3 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -56,7 +56,7 @@ end attr_reader :key_format if RUBY_VERSION >= '2.0' - def serializer_for(resource) + def serializer_for(resource, options = {}) if resource.respond_to?(:to_ary) if Object.constants.include?(:ArraySerializer) ::ArraySerializer @@ -65,14 +65,14 @@ end end else begin - Object.const_get "#{resource.class.name}Serializer" + Object.const_get build_serializer_class(resource, options) rescue NameError nil end end end else - def serializer_for(resource) + def serializer_for(resource, options = {}) if resource.respond_to?(:to_ary) if Object.constants.include?(:ArraySerializer) ::ArraySerializer @@ -80,7 +80,7 @@ end ArraySerializer end else - "#{resource.class.name}Serializer".safe_constantize + build_serializer_class(resource, options).safe_constantize end end end @@ -113,6 +113,14 @@ end private + def build_serializer_class(resource, options) + "".tap do |klass_name| + klass_name << "#{options[:namespace]}::" if options[:namespace] + klass_name << prefix.to_s.classify if options[:prefix] + klass_name << "#{resource.class.name}Serializer" + end + end + def associate(klass, *attrs) options = attrs.extract_options! @@ -215,7 +223,16 @@ end def build_serializer(association) object = send(association.name) - association.build_serializer(object, scope: scope) + association.build_serializer(object, association_options_for_serializer(association)) + end + + def association_options_for_serializer(association) + prefix = association.options[:prefix] + + { scope: scope }.tap do |opts| + opts[:namespace] = namespace if namespace + opts[:prefix] = prefix if prefix + end end def serialize(association)