Skip to content

Namespaced serializers #1338

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

Closed

Conversation

bdmac
Copy link
Contributor

@bdmac bdmac commented Nov 20, 2015

Adds support for finding serializers based on the "current" serializers
namespace to support things like serializer versioning. Without this you
have to be explicit in naming which serializer to use in all of your
serializer associations. That's OK usually (albeit annoying) but it
breaks polymorphism since you can't really specify the right serializer
ahead of time.

I came across a bunch of PRs such as #1225 that talk about different
versions of namespacing based on modules or controllers or whatever and
some of them even talk about supporting serializer namespaces but I have
been unable to find one that actually does this part of it. If there is
already work being done for this, feel free to ignore this PR but we
needed this for our current work.

This PR builds on top of the work done by @beauby in #1225 to add
support for namespaced serializer lookups. @beauby's work only allowed
for actual nesting of serializers withing a namespaced serializer (e.g.
for overrides) but did not actually walk up the tree to siblings or
serializers farther up the inheritance/namespace tree.

It simply modifies Serializer#serializer_lookup_chain_for to add all
of the possible levels of namespaces based on the current serializer.
With this patch for example if we have:

class Public::V1::PostSerializer
  belongs_to :author
end

class Public::V1::AuthorSerializer
end

we will wind up with the following items in the lookup chain for an
author when serializing a Post with Public::V1::PostSerializer:

['Public::V1::PostSerializer::AuthorSerializer',
'Public::V1::AuthorSerializer', 'Public::AuthorSerializer',
'::AuthorSerializer']

@bdmac bdmac force-pushed the feature/serializer-namespaces branch from d64adba to b44eee1 Compare November 20, 2015 00:51
@@ -207,6 +207,49 @@ def test_associations_namespaced_resources
end
end
end

class NamespaceNestedSerializersTest < Minitest::Test
Copy link
Member

Choose a reason for hiding this comment

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

This snippet from the PR description would also make a great test showing the old behavior and the behavior this PR introduces. It wasn't obvious to me from the examples why you'd want to 'step back' one level in the serializer lookup.

It simply modifies Serializer#serializer_lookup_chain_for to add all
of the possible levels of namespaces based on the current serializer.
With this patch for example if we have:

class Public::V1::PostSerializer
belongs_to :author
end

class Public::V1::AuthorSerializer
end
we will wind up with the following items in the lookup chain for an
author when serializing a Post with Public::V1::PostSerializer:

['Public::V1::PostSerializer::AuthorSerializer',
'Public::V1::AuthorSerializer', 'Public::AuthorSerializer',
'::AuthorSerializer']

@beauby
Copy link
Contributor

beauby commented Nov 22, 2015

@bdmac I actually did that in #1157, but it didn't get traction back then, so it was never merged. Now I'm not too convinced anymore it would be useful, but if people want it, I'm not opposed to this PR getting merged.

chain.push("#{name}::#{serializer_class_name}") if self != ActiveModel::Serializer
if self != ActiveModel::Serializer
chain.push("#{name}::#{serializer_class_name}")
chain.push(*name.deconstantize.split('::').each_with_object([]) { |element, array| array.push((array.last ? array.last + '::' : '') + element) }.reverse.map { |k| k + "::#{serializer_class_name}" })
Copy link
Contributor

Choose a reason for hiding this comment

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

A slightly shorter version that leads to the same result:

klass.to_s.split('::').reverse.reduce([]) { |a, e| a + [[e] + Array(a.last)] }.reverse

@beauby
Copy link
Contributor

beauby commented Nov 22, 2015

The reason why I'm not sure it's that useful is because the "magic" can get confusing in some cases (think of having nested serializers like PostSerializer::CommentSerializer, AuthorSerializer, CommentSerializer::AuthorSerializer: when you serialize a Post along with its comments, should the authors of the comments be serialized according to AuthorSerializer or CommentSerializer::AuthorSerializer? Is it obvious to users which will be used when adding the whole suffix chain to the lookup chain?).

@bdmac
Copy link
Contributor Author

bdmac commented Nov 23, 2015

Totally get that the magic can get confusing but there is not currently a valid solution supporting all uses with namespaced versions. Specifically polymorphic associations. I wouldn't be opposed to something more explicit but it would need to work with controller and non rendering paths.

@beauby
Copy link
Contributor

beauby commented Nov 23, 2015

Two possible solutions (for which I issued PRs at some point but they ended up in limbo):

  1. Hit the controller's namespace (will be really easy to do with the whole context thing)
  2. Hit the "root serializer"'s namespace, that is the one that was specified/inferred for the actual render call

@bdmac
Copy link
Contributor Author

bdmac commented Nov 23, 2015

Would 1 work outside of a controller render? I can't find much docs on the whole context thing you mentioned anywhere.

The root serializer doesn't allow for any nesting. For example we have Public::V1 with most of our stuff but then a Public::V1::ConversationElement namespace with some polymorphic resources that reference things in the parent namespace that aren't polymorphic

Maybe the root serializer specifies just the highest level in which to look. I was thinking it could be an actual class level setting that you could set on a parent serializer to opt-in to this feature. Something like:

class Public::V1::Serializer < ActiveModel::Serializer
  root_namespace 'Public::V1'
end

class Public::V1::CustomerSerializer < Public::V1::Serializer
end

@beauby
Copy link
Contributor

beauby commented Nov 24, 2015

  1. could be made to work outside of a controller render, although one would have to manually specify the root namespace, a bit like what you suggested in the second part of your post, except that it would be an option to SerializableResource's constructor, rather than a class method on ActiveModel::Serializer. I'll try to put something together tonight or tomorrow if I find time. In the meantime, I'd like for other people to chip in to the conversation so that we make sure we're not missing out on an easier solution to your problem.

@bf4
Copy link
Member

bf4 commented Dec 23, 2015

Are we still working on this?

@bf4
Copy link
Member

bf4 commented Jan 15, 2016

ref: #1436

Adds support for finding serializers based on the "current" serializers
namespace to support things like serializer versioning. Without this you
have to be explicit in naming which serializer to use in all of your
serializer associations. That's OK usually (albeit annoying) but it
breaks polymorphism since you can't really specify the right serializer
ahead of time.

I came across a bunch of PRs such as rails-api#1225 that talk about different
versions of namespacing based on modules or controllers or whatever and
some of them even talk about supporting serializer namespaces but I have
been unable to find one that actually does this part of it. If there is
already work being done for this, feel free to ignore this PR but we
needed this for our current work.

This PR builds on top of the work done by @beauby in rails-api#1225 to add
support for namespaced serializer lookups. @beauby's work only allowed
for actual nesting of serializers withing a namespaced serializer (e.g.
for overrides) but did not actually walk up the tree to siblings or
serializers farther up the inheritance/namespace tree.

It simply modifies `Serializer#serializer_lookup_chain_for` to add all
of the possible levels of namespaces based on the current serializer.
With this patch for example if we have:
```ruby
class Public::V1::PostSerializer
  belongs_to :author
end

class Public::V1::AuthorSerializer
end
```
we will wind up with the following items in the lookup chain for an
author when serializing a Post with Public::V1::PostSerializer:
```ruby
['Public::V1::PostSerializer::AuthorSerializer',
'Public::V1::AuthorSerializer', 'Public::AuthorSerializer',
'::AuthorSerializer']
```
@bdmac
Copy link
Contributor Author

bdmac commented Jan 22, 2016

I'm certainly still hopeful to get this in. Would rather not have to keep rebasing my fork/branch. :)

Also open to other working solutions to the original problem if there has been something added that I have missed.

@beauby
Copy link
Contributor

beauby commented Jan 22, 2016

@bdmac Totally understandable ;) Would you mind chipping in to the #1442? I think we finally got most people on board with the idea of a stronger automatic lookup, we just have to decide how exactly it should work.

@bdmac bdmac force-pushed the feature/serializer-namespaces branch from b44eee1 to 2553d68 Compare January 22, 2016 17:09
@bdmac
Copy link
Contributor Author

bdmac commented Jan 22, 2016

Rebased.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants