From e529466248b61168072a6b5b223fac50aea731f9 Mon Sep 17 00:00:00 2001 From: NullVoxPopuli Date: Mon, 30 May 2016 09:17:41 -0400 Subject: [PATCH 01/12] This adds namespace lookup to serializer_for --- .travis.yml | 3 +- lib/action_controller/serialization.rb | 7 ++ lib/active_model/serializer.rb | 10 ++- lib/active_model/serializer/reflection.rb | 4 + .../serializable_resource.rb | 2 +- .../serializer_lookup/in_controllers_test.rb | 0 .../serializer_for_with_namespace_test.rb | 87 +++++++++++++++++++ 7 files changed, 107 insertions(+), 6 deletions(-) create mode 100644 test/feature/configuration/serializer_lookup/in_controllers_test.rb create mode 100644 test/serializers/serializer_for_with_namespace_test.rb diff --git a/.travis.yml b/.travis.yml index 5fa7c225c..d18c084dd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,7 +20,8 @@ cache: script: - bundle exec rake ci - +after_success: + - codeclimate-test-reporter env: global: - "JRUBY_OPTS='--dev -J-Xmx1024M --debug'" diff --git a/lib/action_controller/serialization.rb b/lib/action_controller/serialization.rb index 527ed2b61..502453244 100644 --- a/lib/action_controller/serialization.rb +++ b/lib/action_controller/serialization.rb @@ -18,6 +18,10 @@ def serialization_scope(scope) self._serialization_scope = :current_user end + def namespace_for_serializer + @namespace_for_serializer ||= self.class.parent unless self.class.parent == Object + end + def serialization_scope return unless _serialization_scope && respond_to?(_serialization_scope, true) @@ -30,6 +34,9 @@ def get_serializer(resource, options = {}) "Please pass 'adapter: false' or see ActiveSupport::SerializableResource.new" options[:adapter] = false end + + options.fetch(:namespace) { options[:namespace] = namespace_for_serializer } + serializable_resource = ActiveModelSerializers::SerializableResource.new(resource, options) serializable_resource.serialization_scope ||= options.fetch(:scope) { serialization_scope } serializable_resource.serialization_scope_name = options.fetch(:scope_name) { _serialization_scope } diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index 7d499b909..590168605 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -44,7 +44,7 @@ def self.serializer_for(resource, options = {}) elsif resource.respond_to?(:to_ary) config.collection_serializer else - options.fetch(:serializer) { get_serializer_for(resource.class) } + options.fetch(:serializer) { get_serializer_for(resource.class, options[:namespace]) } end end @@ -59,13 +59,14 @@ class << self end # @api private - def self.serializer_lookup_chain_for(klass) + def self.serializer_lookup_chain_for(klass, namespace = nil) chain = [] resource_class_name = klass.name.demodulize resource_namespace = klass.name.deconstantize serializer_class_name = "#{resource_class_name}Serializer" + chain.push("#{namespace.name}::#{serializer_class_name}") if namespace chain.push("#{name}::#{serializer_class_name}") if self != ActiveModel::Serializer chain.push("#{resource_namespace}::#{serializer_class_name}") @@ -84,11 +85,12 @@ def self.serializers_cache # 1. class name appended with "Serializer" # 2. try again with superclass, if present # 3. nil - def self.get_serializer_for(klass) + def self.get_serializer_for(klass, namespace = nil) return nil unless config.serializer_lookup_enabled serializers_cache.fetch_or_store(klass) do # NOTE(beauby): When we drop 1.9.3 support we can lazify the map for perfs. - serializer_class = serializer_lookup_chain_for(klass).map(&:safe_constantize).find { |x| x && x < ActiveModel::Serializer } + lookup_chain = serializer_lookup_chain_for(klass, namespace) + serializer_class = lookup_chain.map(&:safe_constantize).find { |x| x && x < ActiveModel::Serializer } if serializer_class serializer_class diff --git a/lib/active_model/serializer/reflection.rb b/lib/active_model/serializer/reflection.rb index 5b4707058..96645bd73 100644 --- a/lib/active_model/serializer/reflection.rb +++ b/lib/active_model/serializer/reflection.rb @@ -106,6 +106,10 @@ def value(serializer, include_slice) # def build_association(parent_serializer, parent_serializer_options, include_slice = {}) reflection_options = options.dup + + # Pass the parent's namespace onto the child serializer + reflection_options[:namespace] ||= parent_serializer_options[:namespace] + association_value = value(parent_serializer, include_slice) serializer_class = parent_serializer.class.serializer_for(association_value, reflection_options) reflection_options[:include_data] = include_data?(include_slice) diff --git a/lib/active_model_serializers/serializable_resource.rb b/lib/active_model_serializers/serializable_resource.rb index 9e03c686d..f67cf2385 100644 --- a/lib/active_model_serializers/serializable_resource.rb +++ b/lib/active_model_serializers/serializable_resource.rb @@ -55,7 +55,7 @@ def serializer @serializer ||= begin @serializer = serializer_opts.delete(:serializer) - @serializer ||= ActiveModel::Serializer.serializer_for(resource) + @serializer ||= ActiveModel::Serializer.serializer_for(resource, serializer_opts) if serializer_opts.key?(:each_serializer) serializer_opts[:serializer] = serializer_opts.delete(:each_serializer) 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/serializers/serializer_for_with_namespace_test.rb b/test/serializers/serializer_for_with_namespace_test.rb new file mode 100644 index 000000000..5c5e37531 --- /dev/null +++ b/test/serializers/serializer_for_with_namespace_test.rb @@ -0,0 +1,87 @@ +require 'test_helper' + + + +module ActiveModel + class Serializer + class SerializerForWithNamespaceTest < ActiveSupport::TestCase + class Book < ::Model; end + class Page < ::Model; end + class Publisher < ::Model; end + + module Api + module V3 + class BookSerializer < ActiveModel::Serializer + attributes :title, :author_name + + has_many :pages + belongs_to :publisher + end + + class PageSerializer < ActiveModel::Serializer + attributes :number, :text + + belongs_to :book + end + + class PublisherSerializer < ActiveModel::Serializer + attributes :name + end + end + end + + # class ::BookSerializer < ActiveModel::Serializer + # attributes :title, :author_name + # end + # test 'resource without a namespace' do + # book = Book.new(title: 'A Post', author_name: 'hello') + # + # result = ActiveModelSerializers::SerializableResource.new(book).serializable_hash + # + # expected = { title: 'A Post', author_name: 'hello' } + # assert_equal expected, result + # end + + test 'resource with namespace' do + book = Book.new(title: 'A Post', author_name: 'hi') + + result = ActiveModelSerializers::SerializableResource.new(book, namespace: Api::V3).serializable_hash + + expected = { title: 'A Post', author_name: 'hi', pages: nil, publisher: nil } + assert_equal expected, result + end + + test 'has_many with nested serializer under the namespace' do + page = Page.new(number: 1, text: 'hello') + book = Book.new(title: 'A Post', author_name: 'hi', pages: [page]) + + result = ActiveModelSerializers::SerializableResource.new(book, namespace: Api::V3).serializable_hash + + expected = { + title: 'A Post', author_name: 'hi', + publisher: nil, + pages: [{ + number: 1, text: 'hello' + }] + } + assert_equal expected, result + end + + test 'belongs_to with nested serializer under the namespace' do + publisher = Publisher.new(name: 'Disney') + book = Book.new(title: 'A Post', author_name: 'hi', publisher: publisher) + + result = ActiveModelSerializers::SerializableResource.new(book, namespace: Api::V3).serializable_hash + + expected = { + title: 'A Post', author_name: 'hi', + pages: nil, + publisher: { + name: 'Disney' + } + } + assert_equal expected, result + end + end + end +end From 3c98d489cd19f87619c707d5e2ba6651a3d97989 Mon Sep 17 00:00:00 2001 From: "L. Preston Sego III" Date: Mon, 7 Nov 2016 23:07:51 -0500 Subject: [PATCH 02/12] address rubocop issue --- test/serializers/serializer_for_with_namespace_test.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/serializers/serializer_for_with_namespace_test.rb b/test/serializers/serializer_for_with_namespace_test.rb index 5c5e37531..989b38bbc 100644 --- a/test/serializers/serializer_for_with_namespace_test.rb +++ b/test/serializers/serializer_for_with_namespace_test.rb @@ -1,7 +1,5 @@ require 'test_helper' - - module ActiveModel class Serializer class SerializerForWithNamespaceTest < ActiveSupport::TestCase From 526738b4ff136c5279d7f916171203ba789b3a40 Mon Sep 17 00:00:00 2001 From: "L. Preston Sego III" Date: Tue, 8 Nov 2016 06:51:04 -0500 Subject: [PATCH 03/12] address @bf4's feedback --- lib/active_model/serializer.rb | 2 +- .../configuration/serializer_lookup/in_controllers_test.rb | 0 2 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 test/feature/configuration/serializer_lookup/in_controllers_test.rb diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index 590168605..688c644ce 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -66,7 +66,7 @@ def self.serializer_lookup_chain_for(klass, namespace = nil) resource_namespace = klass.name.deconstantize serializer_class_name = "#{resource_class_name}Serializer" - chain.push("#{namespace.name}::#{serializer_class_name}") if namespace + chain.push("#{namespace}::#{serializer_class_name}") if namespace chain.push("#{name}::#{serializer_class_name}") if self != ActiveModel::Serializer chain.push("#{resource_namespace}::#{serializer_class_name}") diff --git a/test/feature/configuration/serializer_lookup/in_controllers_test.rb b/test/feature/configuration/serializer_lookup/in_controllers_test.rb deleted file mode 100644 index e69de29bb..000000000 From 771af7367fc309c1120390eef7884ef9b00261c2 Mon Sep 17 00:00:00 2001 From: "L. Preston Sego III" Date: Tue, 8 Nov 2016 07:00:46 -0500 Subject: [PATCH 04/12] add docs --- docs/general/rendering.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/docs/general/rendering.md b/docs/general/rendering.md index 72392dd2b..a61ae745d 100644 --- a/docs/general/rendering.md +++ b/docs/general/rendering.md @@ -243,6 +243,28 @@ This will be rendered as: ``` Note: the `Attributes` adapter (default) does not include a resource root. You also will not be able to create a single top-level root if you are using the :json_api adapter. +#### namespace + +The namespace for serializer lookup is based on the controller, via `ActionController::Serialization#namespace_for_serializer`. + +To configure the implicit namespace, in your controller, define + +```ruby +def namespace_for_serializer + @namespace_for_serializer ||= self.class.parent unless self.class.parent == Object +end +``` + +`namespace` can also be passed in as a render option: + + +```ruby +render json: @post, namespace: Api::V2 +``` + +This tells the serializer lookup to check for the existence of `Api::V2::PostSerializer`, and if any relations are rendered with `@post`, they will also utilize the `Api::V2` namespace. + + #### serializer PR please :) From 81e743a39169add0eeffdb65c15165a0b7d008a1 Mon Sep 17 00:00:00 2001 From: "L. Preston Sego III" Date: Tue, 8 Nov 2016 13:35:42 -0500 Subject: [PATCH 05/12] update docs, add more tests --- docs/general/rendering.md | 18 ++- lib/action_controller/serialization.rb | 2 + .../namespace_lookup_test_isolated.rb | 145 ++++++++++++++++++ .../serializer_for_with_namespace_test.rb | 24 +-- 4 files changed, 173 insertions(+), 16 deletions(-) create mode 100644 test/action_controller/namespace_lookup_test_isolated.rb diff --git a/docs/general/rendering.md b/docs/general/rendering.md index a61ae745d..0cb246b2e 100644 --- a/docs/general/rendering.md +++ b/docs/general/rendering.md @@ -245,13 +245,14 @@ Note: the `Attributes` adapter (default) does not include a resource root. You a #### namespace -The namespace for serializer lookup is based on the controller, via `ActionController::Serialization#namespace_for_serializer`. +The namespace for serializer lookup is based on the controller. -To configure the implicit namespace, in your controller, define +To configure the implicit namespace, in your controller, create a before filter ```ruby -def namespace_for_serializer - @namespace_for_serializer ||= self.class.parent unless self.class.parent == Object +before_filter :use_my_namespace +def use_my_namespace + self.namespace_for_serializer = Api::V2 end ``` @@ -259,10 +260,17 @@ end ```ruby +@post = Post.first render json: @post, namespace: Api::V2 ``` -This tells the serializer lookup to check for the existence of `Api::V2::PostSerializer`, and if any relations are rendered with `@post`, they will also utilize the `Api::V2` namespace. +This tells the serializer lookup to check for the existence of `Api::V2::PostSerializer`, and if any relations are rendered with `@post`, they will also utilize the `Api::V2` namespace. + +The namespace can _only_ be in one of the following formats: + +- `Api::V2` +- `'Api::V2'` +- `:'Api::V2'` #### serializer diff --git a/lib/action_controller/serialization.rb b/lib/action_controller/serialization.rb index 502453244..ea84c6743 100644 --- a/lib/action_controller/serialization.rb +++ b/lib/action_controller/serialization.rb @@ -16,6 +16,8 @@ def serialization_scope(scope) included do class_attribute :_serialization_scope self._serialization_scope = :current_user + + attr_writer :namespace_for_serializer end def namespace_for_serializer diff --git a/test/action_controller/namespace_lookup_test_isolated.rb b/test/action_controller/namespace_lookup_test_isolated.rb new file mode 100644 index 000000000..ac2a3dd5c --- /dev/null +++ b/test/action_controller/namespace_lookup_test_isolated.rb @@ -0,0 +1,145 @@ +require 'test_helper' + +module ActionController + module Serialization + class NamespaceLookupTest < ActionController::TestCase + class Book < ::Model; end + class Page < ::Model; end + class Writer < ::Model; end + + module Api + module V2 + class BookSerializer < ActiveModel::Serializer + attributes :title + end + end + + module V3 + class BookSerializer < ActiveModel::Serializer + attributes :title, :body + + belongs_to :writer + end + + class WriterSerializer < ActiveModel::Serializer + attributes :name + end + + class LookupTestController < ActionController::Base + before_filter :set_namespace, only: [:namespace_set_in_before_filter] + + def implicit_namespaced_serializer + writer = Writer.new(name: 'Bob') + book = Book.new(title: 'New Post', body: 'Body', writer: writer) + + render json: book + end + + def explicit_namespace_as_module + book = Book.new(title: 'New Post', body: 'Body') + + render json: book, namespace: Api::V2 + end + + def explicit_namespace_as_string + book = Book.new(title: 'New Post', body: 'Body') + + render json: book, namespace: 'Api::V2' + end + + def explicit_namespace_as_symbol + book = Book.new(title: 'New Post', body: 'Body') + + render json: book, namespace: :'Api::V2' + end + + def invalid_namespace + book = Book.new(title: 'New Post', body: 'Body') + + render json: book, namespace: :api_v2 + end + + def namespace_set_in_before_filter + book = Book.new(title: 'New Post', body: 'Body') + render json: book + end + + private + + def set_namespace + self.namespace_for_serializer = Api::V2 + end + end + end + end + + tests Api::V3::LookupTestController + + test 'implicitly uses namespaced serializer' do + get :implicit_namespaced_serializer + + assert_serializer Api::V3::BookSerializer + + expected = { 'title' => 'New Post', 'body' => 'Body', 'writer' => { 'name' => 'Bob' } } + actual = JSON.parse(@response.body) + + assert_equal expected, actual + end + + test 'explicit namespace as module' do + get :explicit_namespace_as_module + + assert_serializer Api::V2::BookSerializer + + expected = { 'title' => 'New Post' } + actual = JSON.parse(@response.body) + + assert_equal expected, actual + end + + test 'explicit namespace as string' do + get :explicit_namespace_as_string + + assert_serializer Api::V2::BookSerializer + + expected = { 'title' => 'New Post' } + actual = JSON.parse(@response.body) + + assert_equal expected, actual + end + + test 'explicit namespace as symbol' do + get :explicit_namespace_as_symbol + + assert_serializer Api::V2::BookSerializer + + expected = { 'title' => 'New Post' } + actual = JSON.parse(@response.body) + + assert_equal expected, actual + end + + test 'invalid namespace' do + get :invalid_namespace + + assert_serializer ActiveModel::Serializer::Null + + expected = { 'title' => 'New Post' } + actual = JSON.parse(@response.body) + + assert_equal expected, actual + end + + test 'namespace set in before filter' do + get :namespace_set_in_before_filter + + assert_serializer Api::V2::BookSerializer + + expected = { 'title' => 'New Post' } + actual = JSON.parse(@response.body) + + assert_equal expected, actual + end + end + end +end diff --git a/test/serializers/serializer_for_with_namespace_test.rb b/test/serializers/serializer_for_with_namespace_test.rb index 989b38bbc..5a8a9ed54 100644 --- a/test/serializers/serializer_for_with_namespace_test.rb +++ b/test/serializers/serializer_for_with_namespace_test.rb @@ -28,17 +28,19 @@ class PublisherSerializer < ActiveModel::Serializer end end - # class ::BookSerializer < ActiveModel::Serializer - # attributes :title, :author_name - # end - # test 'resource without a namespace' do - # book = Book.new(title: 'A Post', author_name: 'hello') - # - # result = ActiveModelSerializers::SerializableResource.new(book).serializable_hash - # - # expected = { title: 'A Post', author_name: 'hello' } - # assert_equal expected, result - # end + class BookSerializer < ActiveModel::Serializer + attributes :title, :author_name + end + test 'resource without a namespace' do + book = Book.new(title: 'A Post', author_name: 'hello') + + # TODO: this should be able to pull up this serializer without explicitly specifying the serializer + # currently, with no options, it still uses the Api::V3 serializer + result = ActiveModelSerializers::SerializableResource.new(book, serializer: BookSerializer).serializable_hash + + expected = { title: 'A Post', author_name: 'hello' } + assert_equal expected, result + end test 'resource with namespace' do book = Book.new(title: 'A Post', author_name: 'hi') From 44b06a39cadeb38785bd1fbdc4666b5bb39e7482 Mon Sep 17 00:00:00 2001 From: "L. Preston Sego III" Date: Tue, 8 Nov 2016 15:30:13 -0500 Subject: [PATCH 06/12] apparently rails master doesn't have before filter --- test/action_controller/namespace_lookup_test_isolated.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/action_controller/namespace_lookup_test_isolated.rb b/test/action_controller/namespace_lookup_test_isolated.rb index ac2a3dd5c..23932f40f 100644 --- a/test/action_controller/namespace_lookup_test_isolated.rb +++ b/test/action_controller/namespace_lookup_test_isolated.rb @@ -26,7 +26,7 @@ class WriterSerializer < ActiveModel::Serializer end class LookupTestController < ActionController::Base - before_filter :set_namespace, only: [:namespace_set_in_before_filter] + # before_filter :set_namespace, only: [:namespace_set_in_before_filter] def implicit_namespaced_serializer writer = Writer.new(name: 'Bob') @@ -60,6 +60,9 @@ def invalid_namespace end def namespace_set_in_before_filter + # fake before_filter + set_namespace + book = Book.new(title: 'New Post', body: 'Body') render json: book end From aaa94afa1d402bc1b2077027f57b05a85ae00b5a Mon Sep 17 00:00:00 2001 From: "L. Preston Sego III" Date: Tue, 8 Nov 2016 15:39:20 -0500 Subject: [PATCH 07/12] try to address serializer cache issue between tests --- test/action_controller/namespace_lookup_test_isolated.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/action_controller/namespace_lookup_test_isolated.rb b/test/action_controller/namespace_lookup_test_isolated.rb index 23932f40f..52c519652 100644 --- a/test/action_controller/namespace_lookup_test_isolated.rb +++ b/test/action_controller/namespace_lookup_test_isolated.rb @@ -78,6 +78,10 @@ def set_namespace tests Api::V3::LookupTestController + before do + ActiveModel::Serializer.instance_variable_set('@serializers_cache', {}) + end + test 'implicitly uses namespaced serializer' do get :implicit_namespaced_serializer From c847e63cda19b5b6e9d1205a4518898d033c1d7c Mon Sep 17 00:00:00 2001 From: "L. Preston Sego III" Date: Tue, 8 Nov 2016 16:02:09 -0500 Subject: [PATCH 08/12] update cache for serializer lookup to include namespace in the key, and fix the tests for explicit namespace --- lib/active_model/serializer.rb | 2 +- ...p_test_isolated.rb => namespace_lookup_test.rb} | 14 +++++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) rename test/action_controller/{namespace_lookup_test_isolated.rb => namespace_lookup_test.rb} (85%) diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index 688c644ce..bf20b04d6 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -87,7 +87,7 @@ def self.serializers_cache # 3. nil def self.get_serializer_for(klass, namespace = nil) return nil unless config.serializer_lookup_enabled - serializers_cache.fetch_or_store(klass) do + serializers_cache.fetch_or_store("#{klass}-#{namespace}") do # NOTE(beauby): When we drop 1.9.3 support we can lazify the map for perfs. lookup_chain = serializer_lookup_chain_for(klass, namespace) serializer_class = lookup_chain.map(&:safe_constantize).find { |x| x && x < ActiveModel::Serializer } diff --git a/test/action_controller/namespace_lookup_test_isolated.rb b/test/action_controller/namespace_lookup_test.rb similarity index 85% rename from test/action_controller/namespace_lookup_test_isolated.rb rename to test/action_controller/namespace_lookup_test.rb index 52c519652..915e5fbf0 100644 --- a/test/action_controller/namespace_lookup_test_isolated.rb +++ b/test/action_controller/namespace_lookup_test.rb @@ -44,13 +44,17 @@ def explicit_namespace_as_module def explicit_namespace_as_string book = Book.new(title: 'New Post', body: 'Body') - render json: book, namespace: 'Api::V2' + # because this is a string, ruby can't auto-lookup the constant, so otherwise + # the looku things we mean ::Api::V2 + render json: book, namespace: 'ActionController::Serialization::NamespaceLookupTest::Api::V2' end def explicit_namespace_as_symbol book = Book.new(title: 'New Post', body: 'Body') - render json: book, namespace: :'Api::V2' + # because this is a string, ruby can't auto-lookup the constant, so otherwise + # the looku things we mean ::Api::V2 + render json: book, namespace: :'ActionController::Serialization::NamespaceLookupTest::Api::V2' end def invalid_namespace @@ -78,8 +82,8 @@ def set_namespace tests Api::V3::LookupTestController - before do - ActiveModel::Serializer.instance_variable_set('@serializers_cache', {}) + setup do + @test_namespace = self.class.parent end test 'implicitly uses namespaced serializer' do @@ -131,7 +135,7 @@ def set_namespace assert_serializer ActiveModel::Serializer::Null - expected = { 'title' => 'New Post' } + expected = { 'title' => 'New Post', 'body' => 'Body' } actual = JSON.parse(@response.body) assert_equal expected, actual From eb630e8443977747369dc3d7f29c63644b2265a1 Mon Sep 17 00:00:00 2001 From: "L. Preston Sego III" Date: Tue, 8 Nov 2016 16:24:48 -0500 Subject: [PATCH 09/12] update docs, and use better cache key creation method --- docs/general/rendering.md | 9 +++++---- lib/active_model/serializer.rb | 4 +++- test/action_controller/namespace_lookup_test.rb | 13 +++---------- 3 files changed, 11 insertions(+), 15 deletions(-) diff --git a/docs/general/rendering.md b/docs/general/rendering.md index 0cb246b2e..601ef743f 100644 --- a/docs/general/rendering.md +++ b/docs/general/rendering.md @@ -266,11 +266,12 @@ render json: @post, namespace: Api::V2 This tells the serializer lookup to check for the existence of `Api::V2::PostSerializer`, and if any relations are rendered with `@post`, they will also utilize the `Api::V2` namespace. -The namespace can _only_ be in one of the following formats: +The `namespace` can be any object whose namespace can be represented by string interpolation (i.e. by calling to_s) +- Module `Api::V2` +- String `'Api::V2'` +- Symbol `:'Api::V2'` -- `Api::V2` -- `'Api::V2'` -- `:'Api::V2'` +Note that by using a string and symbol, Ruby will assume the namespace is defined at the top level. #### serializer diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index bf20b04d6..81beffd7c 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -87,7 +87,9 @@ def self.serializers_cache # 3. nil def self.get_serializer_for(klass, namespace = nil) return nil unless config.serializer_lookup_enabled - serializers_cache.fetch_or_store("#{klass}-#{namespace}") do + + cache_key = ActiveSupport::Cache.expand_cache_key(klass, namespace) + serializers_cache.fetch_or_store(cache_key) do # NOTE(beauby): When we drop 1.9.3 support we can lazify the map for perfs. lookup_chain = serializer_lookup_chain_for(klass, namespace) serializer_class = lookup_chain.map(&:safe_constantize).find { |x| x && x < ActiveModel::Serializer } diff --git a/test/action_controller/namespace_lookup_test.rb b/test/action_controller/namespace_lookup_test.rb index 915e5fbf0..dfab02c48 100644 --- a/test/action_controller/namespace_lookup_test.rb +++ b/test/action_controller/namespace_lookup_test.rb @@ -26,7 +26,9 @@ class WriterSerializer < ActiveModel::Serializer end class LookupTestController < ActionController::Base - # before_filter :set_namespace, only: [:namespace_set_in_before_filter] + before_action only: [:namespace_set_in_before_filter] do + self.namespace_for_serializer = Api::V2 + end def implicit_namespaced_serializer writer = Writer.new(name: 'Bob') @@ -64,18 +66,9 @@ def invalid_namespace end def namespace_set_in_before_filter - # fake before_filter - set_namespace - book = Book.new(title: 'New Post', body: 'Body') render json: book end - - private - - def set_namespace - self.namespace_for_serializer = Api::V2 - end end end end From 0fa81960f3f38278d05518ac7fae8725ea10b12c Mon Sep 17 00:00:00 2001 From: "L. Preston Sego III" Date: Tue, 8 Nov 2016 16:45:38 -0500 Subject: [PATCH 10/12] update docs [ci skip] --- docs/general/rendering.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/general/rendering.md b/docs/general/rendering.md index 601ef743f..7d9b35066 100644 --- a/docs/general/rendering.md +++ b/docs/general/rendering.md @@ -250,8 +250,7 @@ The namespace for serializer lookup is based on the controller. To configure the implicit namespace, in your controller, create a before filter ```ruby -before_filter :use_my_namespace -def use_my_namespace +before_filter do self.namespace_for_serializer = Api::V2 end ``` From 29d14a87712a254a1875a0054e4b681690251666 Mon Sep 17 00:00:00 2001 From: "L. Preston Sego III" Date: Tue, 8 Nov 2016 16:45:56 -0500 Subject: [PATCH 11/12] update docs [ci skip] --- docs/general/rendering.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/general/rendering.md b/docs/general/rendering.md index 7d9b35066..b75c31938 100644 --- a/docs/general/rendering.md +++ b/docs/general/rendering.md @@ -250,7 +250,7 @@ The namespace for serializer lookup is based on the controller. To configure the implicit namespace, in your controller, create a before filter ```ruby -before_filter do +before_action do self.namespace_for_serializer = Api::V2 end ``` From 679ed725c7f4a63ae7547e9c65d35e44daa25748 Mon Sep 17 00:00:00 2001 From: "L. Preston Sego III" Date: Tue, 8 Nov 2016 17:11:40 -0500 Subject: [PATCH 12/12] add to changelog [ci skip] --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 48f235fc8..8c26ddf7f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,10 @@ Fixes: Features: +- [#1968](https://github.com/rails-api/active_model_serializers/pull/1968) (@NullVoxPopuli) + - Add controller namespace to default controller lookup + - Provide a `namespace` render option + - document how set the namespace in the controller for implicit lookup. - [#1791](https://github.com/rails-api/active_model_serializers/pull/1791) (@bf4, @youroff, @NullVoxPopuli) - Added `jsonapi_namespace_separator` config option. - [#1889](https://github.com/rails-api/active_model_serializers/pull/1889) Support key transformation for Attributes adapter (@iancanderson, @danbee)