Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

This adds namespace lookup to serializer_for #1968

Merged

Conversation

NullVoxPopuli
Copy link
Contributor

Purpose

To Provide the ability to namespace serializers in the same fashion that controllers are namespaced -- as it is very common to add Api::V# to both controllers and serializers.

Changes

Pass the controllers namespace through serializer_options and serializer_for

Caveats

Namespaced lookup is first, given that namespace is passed in to the serializable resource options.

Related GitHub issues

Subset of the work in #1757

@mention-bot
Copy link

@NullVoxPopuli, thanks for your PR! By analyzing the history of the files in this pull request, we identified @bf4, @beauby and @groyoh to be potential reviewers.

@NullVoxPopuli
Copy link
Contributor Author

/cc @bf4 @remear

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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is namespace.name necessary? Seems it implies namespace is a class, whereas, if it is, when interpolating, Ruby will do the right thing whether it's a string, symbol, or class... i.e just namespace

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not necessary. removed.

@bf4
Copy link
Member

bf4 commented Nov 8, 2016

@NullVoxPopuli what is the test/feature/configuration/serializer_lookup/in_controllers_test.rb in the diff?

@@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you think we should make this a public interface? Once we add it, we need to support it. (I know I resurrected it, just asking now in context :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm sure people would appreciate it. And I don't think it would be a problem to support, as tons of people want namespaced-scoped serializer lookup -- but maybe that'd be better for the lookup customization PR?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once we add it, we need to support it.

:)

@bf4
Copy link
Member

bf4 commented Nov 8, 2016

Where do you think docs should go? Any sense of where people would look for this?

@NullVoxPopuli
Copy link
Contributor Author

@NullVoxPopuli what is the test/feature/configuration/serializer_lookup/in_controllers_test.rb in the diff?

that's a good question -- must have gotten carried over from the serializer-lookup branch -- which I also don't remember editing that file.

@NullVoxPopuli
Copy link
Contributor Author

Where do you think docs should go? Any sense of where people would look for this?

I'd say under rendering -- I'll add something real quick

@@ -20,7 +20,8 @@ cache:

script:
- bundle exec rake ci

after_success:
- codeclimate-test-reporter
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this working?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't error -- I'm not familiar enough with code climate to see if the results are published per-pull-request. But it works locally

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

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

replace the implementation of namespace_for_serializer
or pass in the render option :namespace

?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i.e. below is the current implementation. ooh,, this would be a good reason to add a setter

attr_writer :namespace_for_serializer

makes it cleaner and can do in a before filter ?



```ruby
render json: @post, namespace: Api::V2
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or namespace: 'Api::V2' or namespace: :'Api::V2' but not namespace: :api_v2'

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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this would be a better example with @post = Post.first or something to reference the class of the model.

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 }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice

@@ -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]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this not included, oy

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nah, without coping the namespace from the parent to the reflection_options, the namespace isn't passed to all the children

@@ -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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

class Page < ::Model; end
class Publisher < ::Model; end

module Api
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can save space by

module Api; end
module Api::V3; end
class Api::V3::BookSerializer < ApplicationSerializer
  # etc.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Decided to keep the expanded syntax for a V2 serializer

#
# expected = { title: 'A Post', author_name: 'hello' }
# assert_equal expected, result
# end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks about right except for the ::

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem I was having with this test is that I couldn't get a root level BookSerializer to be used if a namespace wasn't present.

@@ -0,0 +1,85 @@
require 'test_helper'

module ActiveModel
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks good. needs controller test for implicit lookup as well as option passing, which is really covered below, here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tests added

before_filter :use_my_namespace
def use_my_namespace
self.namespace_for_serializer = Api::V2
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

might as well just before_action { self.namespace_for_serializer = Api::V2 } (before_filter is such old news :( :))

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

excellent!


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:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any object whose namespace can be represented by string interpolation (i.e. by callingto_s)

  • Module: Api::V2
  • String: 'Api::V2'
  • Symbol: :'Api::V2'

end

def namespace_for_serializer
@namespace_for_serializer ||= self.class.parent unless self.class.parent == Object
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

return nil unless config.serializer_lookup_enabled
serializers_cache.fetch_or_store(klass) do
serializers_cache.fetch_or_store("#{klass}-#{namespace}") do
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this will look weird in some case.

ActiveSupport::Cache.expand_cache_key(klass, namespace)

also used to be in AMS...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cool, thanks

return nil unless config.serializer_lookup_enabled
serializers_cache.fetch_or_store(klass) do

cache_key = ActiveSupport::Cache.expand_cache_key(klass, namespace)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:-(

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cache updates and namespacing / configurable lookup chain sounds like a good set of updates for a release? ;-)

@NullVoxPopuli NullVoxPopuli merged commit b29395b into rails-api:master Nov 9, 2016
@NullVoxPopuli NullVoxPopuli deleted the controller-namespace-lookup branch November 9, 2016 12:57
GregPK pushed a commit to GregPK/active_model_serializers that referenced this pull request Apr 25, 2017
* This adds namespace lookup to serializer_for

* address rubocop issue

* address @bf4's feedback

* add docs

* update docs, add more tests

* apparently rails master doesn't have before filter

* try to address serializer cache issue between tests

* update cache for serializer lookup to include namespace in the key, and fix the tests for explicit namespace

* update docs, and use better cache key creation method

* update docs [ci skip]

* update docs [ci skip]

* add to changelog [ci skip]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants