diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index 7d499b909..ef8e55deb 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -60,16 +60,10 @@ class << self # @api private def self.serializer_lookup_chain_for(klass) - chain = [] - - resource_class_name = klass.name.demodulize - resource_namespace = klass.name.deconstantize - serializer_class_name = "#{resource_class_name}Serializer" - - chain.push("#{name}::#{serializer_class_name}") if self != ActiveModel::Serializer - chain.push("#{resource_namespace}::#{serializer_class_name}") - - chain + lookups = ActiveModelSerializers.config.serializer_lookup_chain + Array[*lookups].map do |lookup| + lookup.call(klass, self) + end.flatten.compact end # Used to cache serializer name => serializer class diff --git a/lib/active_model/serializer/concerns/configuration.rb b/lib/active_model/serializer/concerns/configuration.rb index 2604033c4..f6a452cb2 100644 --- a/lib/active_model/serializer/concerns/configuration.rb +++ b/lib/active_model/serializer/concerns/configuration.rb @@ -31,6 +31,31 @@ def config.array_serializer # ref: http://jsonapi.org/format/#document-top-level config.jsonapi_include_toplevel_object = false + # example output + # => [ + # "::Api::V1::ResNamespace::ResourceSerializer", + # "::Api::ResNamespace::ResourceSerializer", + # "::ResNamespace::ResourceSerializer" , + # "::ResourceSerializer"] + # + # NOTE: lookup_namespace is often the ControllerClass.name + config.serializer_lookup_chain = ActiveModelSerializers::LookupChain::DEFAULT.dup + # config.serializer_lookup_chain = proc { |lookup_namespace, _, _| + # lookups = [] + # current_resource = lookup_namespace.name.remove(/Controller\z/) + # current_resource += 'Serializer' + # namespaces = current_resource.split('::') + # resource_serializer = namespaces.slice!(-1) + # + # lookups << '::' + resource_serializer + # namespaces.length.times do |i| + # current_namespace = namespaces[i - namespaces.length..-1].join('::') + # lookups << '::' + current_namespace + '::' + resource_serializer + # end + # + # lookups + # } + config.schema_path = 'test/support/schemas' end end diff --git a/lib/active_model_serializers.rb b/lib/active_model_serializers.rb index 76a32c497..b55dae35a 100644 --- a/lib/active_model_serializers.rb +++ b/lib/active_model_serializers.rb @@ -14,6 +14,7 @@ module ActiveModelSerializers autoload :Adapter autoload :JsonPointer autoload :Deprecate + autoload :LookupChain class << self; attr_accessor :logger; end self.logger = ActiveSupport::TaggedLogging.new(ActiveSupport::Logger.new(STDOUT)) diff --git a/lib/active_model_serializers/lookup_chain.rb b/lib/active_model_serializers/lookup_chain.rb new file mode 100644 index 000000000..d34e1b4dd --- /dev/null +++ b/lib/active_model_serializers/lookup_chain.rb @@ -0,0 +1,70 @@ +module ActiveModelSerializers + module LookupChain + # Standard appending of Serializer to the resource name. + # + # Example: + # Author => AuthorSerializer + BY_RESOURCE = lambda do |resource_class, _serializer_class| + serializer_from(resource_class) + end + + # Uses the namespace of the resource to find the serializer= + # + # Example: + # British::Author => British::AuthorSerializer + BY_NAMESPACE = lambda do |resource_class, _serializer_class| + resource_namespace = namespace_for(resource_class) + serializer_name = serializer_from(resource_class) + + "#{resource_namespace}::#{serializer_name}" + end + + # Allows for serializers to be defined in parent serializers + # - useful if a relationship only needs a different set of attributes + # than if it were rendered independently. + # + # Example: + # class BlogSerializer < ActiveModel::Serializer + # class AuthorSerialier < ActiveModel::Serializer + # ... + # end + # + # belongs_to :author + # ... + # end + # + # The belongs_to relationship would be rendered with + # BlogSerializer::AuthorSerialier + BY_PARENT_SERIALIZER = lambda do |resource_class, serializer_class| + return if serializer_class == ActiveModel::Serializer + + serializer_name = serializer_from(resource_class) + "#{serializer_class.name}::#{serializer_name}" + end + + DEFAULT = [ + BY_PARENT_SERIALIZER, + BY_NAMESPACE, + BY_RESOURCE + ].freeze + + module_function + + def namespace_for(klass) + klass.name.deconstantize + end + + def resource_class_name(klass) + klass.name.demodulize + end + + def serializer_from_resource_name(name) + "#{name}Serializer" + end + + def serializer_from(klass) + name = resource_class_name(klass) + serializer_from_resource_name(name) + end + end +end diff --git a/test/feature/configuration/serializer_lookup/in_controllers_test.rb b/test/feature/configuration/serializer_lookup/in_controllers_test.rb new file mode 100644 index 000000000..e69de29bb diff --git a/test/feature/configuration/serializer_lookup/using_serializable_resource_test.rb b/test/feature/configuration/serializer_lookup/using_serializable_resource_test.rb new file mode 100644 index 000000000..f5ef5b08d --- /dev/null +++ b/test/feature/configuration/serializer_lookup/using_serializable_resource_test.rb @@ -0,0 +1,42 @@ +require 'test_helper' + +describe 'feature' do + describe 'configuration' do + describe 'serializer lookup' do + describe 'using SerializableResource' do + it 'uses the child serializer' do + class Parent < ::Model; end + class Child < ::Model; end + + class ParentSerializer < ActiveModel::Serializer + class ChildSerializer < ActiveModel::Serializer + attributes :name, :child_attr + def child_attr + true + end + end + + attributes :name + belongs_to :child + end + + parent = Parent.new(name: 'parent', child: Child.new(name: 'child')) + resource = ActiveModelSerializers::SerializableResource.new( + parent, + adapter: :attributes + ) + + expected = { + name: 'parent', + child: { + name: 'child', + child_attr: true + } + } + + assert_equal expected, resource.serializable_hash + end + end + end + end +end