-
Notifications
You must be signed in to change notification settings - Fork 1.4k
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
Namespaced serializer lookup for associations #1196
Conversation
@@ -108,20 +110,29 @@ def self.digest_caller_file(caller_line) | |||
Digest::MD5.hexdigest(serializer_file_contents) | |||
end | |||
|
|||
def self.get_serializer_for(klass) | |||
def self.get_serializer_for(klass, parent_serializer) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you add some yardoc to various parts of this method? explaining what each section does, and what scenarios will be applicable to each block of code?
I like this overall, but here is a scenario, which I'd like your opinion on: rather than Back when I was at my previous company, the lack of controller namespacing was the one thing I didn't like about AMS. ha :-) (there was a requirement to have a versioned API, which it turned out ended up not being all that important :-\ ) But anyway, just wanted your thoughts on controller namespacing as well - but maybe that should be in an RFC |
@NullVoxPopuli Just added the controller namespace lookup. |
serializer_class ||= namespaced_serializer_class_name.safe_constantize | ||
elsif controller | ||
controller_namespaced_serializer_class_name = "#{controller.class.name.deconstantize}::#{serializer_class_name}" | ||
serializer_class ||= controller_namespaced_serializer_class_name.safe_constantize |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what if this serializer isn't found? like, what if someone wants to use a straight up PostSerializer
in /blog/id/posts
?
tests for this would be great :-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's why I use ||=
. What this piece does is look for a RelatedSerializer
serializer in:
CurrentSerializer::RelatedSerializer
(which would beActiveModel::Serializer::RelatedSerializer
if it is not actually a related serializer, but that would fail so it's ok)- revert to
My::Name::Space::RelatedSerializer
if called fromMy::Name::Space::CurrentSerializer
- or revert to
My::Controller::Name::Space::RelatedSerializer
if called fromMy::Controller::Name::Space::SomeController
- finally, revert to
::RelatedSerializer
if none of the above exist
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This part could clearly be improved though. Suggestions?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh ok, I thought it was going the other way (if RelatedSerializer isn't found, try the namespaced version)
in any case, tests for this new logic would be stellar.
chain = [] | ||
|
||
# Look for a serializer nested inside the current serializer first, if inside a user-defined serializer | ||
chain.push("#{self}::#{serializer_class_name}") if self.class != ActiveModel::Serializer |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That kind of check makes me believe it might be time to make serializer_for
an instance method?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's been around for a while, so I'd rather deprecate it
thanks for the docs! |
Tests fail because of the lack of lazy enumerators in ruby 1.9.3. |
@@ -25,7 +25,8 @@ def get_serializer(resource, options = {}) | |||
"Please pass 'adapter: false' or see ActiveSupport::SerializableResource.new" | |||
options[:adapter] = false | |||
end | |||
serializable_resource = ActiveModel::SerializableResource.new(resource, options) | |||
serializable_options = options.merge(controller: self) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why not mutate?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
also why change options when not serializing
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
and do we need the whole controller? I'd rather not and looks like you just want the class name
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Currently we just need the class name but there's virtually 0 overhead to getting the instance, so why not?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you make it easy to use the controller directly, people will, and the scope will creep and and new ams will gain responsibilities it shouldn't gave and cause pain and bugs to users and maintainers
Failure of Interface segregation, not keepig it narrow
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In my opinion, the real issue lies in the fact that we miss a layer of separation between the serializer definition and the serializer instances.
+1 |
@@ -16,7 +16,9 @@ Features: | |||
- [#1172](https://github.com/rails-api/active_model_serializers/pull/1172) Better serializer registration, get more than just the first module (@bf4) | |||
- [#1158](https://github.com/rails-api/active_model_serializers/pull/1158) Add support for wildcards in `include` option (@beauby) | |||
- [#1127](https://github.com/rails-api/active_model_serializers/pull/1127) Add support for nested | |||
associations for JSON and Attributes adapters via the `include` option (@NullVoxPopuli, @beauby). | |||
associations for JSON and Attributes adapters via the `include` option (@NullVoxPopuli, @beauby) | |||
- [#1193](https://github.com/rails-api/active_model_serializers/pull/1193) Add support for inline nested serializers (@beauby) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
1193?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This PR is based on #1193, that's why.
Example test case module ActiveModel
class Serializer
class SerializerForTest < Minitest::Test
class Tweet < Model; end
class Share < Model; end
class TweetSerializer < ActiveModel::Serializer
attributes :id, :body, :date
has_many :shares
class ShareSerializer < ActiveModel::Serializer
attributes :id, :platform, :date
end
end
class ShareSerializer < ActiveModel::Serializer
attributes :id, :platform, :date
end
def test_serializer_for_namespaced_serializers
tweet = Tweet.new
tweet.shares << share = Share.new
# called from the has_many :shares on TweetSerializer
# when passed a tweet with shares, which returns an ArraySerializer
# which uses the TweetSerializer::ShareSerializer on each share
assert_equal(TweetSerializer.serializer_for(share), TweetSerializer::ShareSerializer)
assert_equal(Serializer.serializer_for(share), ShareSerializer)
# assert_equal(HasManyReflection.new(:shares, {}).build_association(tweet_serializer, tweet_serializer.instance_options), TweetSerializer::ShareSerializer)
end
class ActiveModel::Serializer
def self.serializer_for(resource)
serializers_cache.fetch_or_store(klass) do
serializer_class_name = "#{klass.name}Serializer"
tries = 0
serializer_base_class = self
serializer_class = nil
begin
serializer = serializer_base_class.constants.find {|constant| constant.to_s == serializer_class_name}
# serializer_class = serializer_class_name.safe_constantize # or whatever
if serializer
serializer_class = const_get(:"#{serializer}")
elsif tries < 1
tries = 1
serializer_base_class = Serializer # or some better logic to figure out the nesting, like the amazing `klass.to_s.split('::')[0..-2].join('::').constantize` or using nesting...
retry # just being lazy for now
end
end
if serializer_class
serializer_class
elsif klass.superclass
get_serializer_for(klass.superclass)
else
nil
end
end
class Reflection
def build_association(subject, parent_serializer_options)
association_value = subject.send(name)
# ? association_value = subject.public_send(name)
# ? association_value = subject.read_attribute_for_serialization(name)
reflection_options = options.dup
# serializer_class = ActiveModel::Serializer.serializer_for(association_value, reflection_options)
serializer_class = subject.class.serializer_for(association_value, reflection_options)
# ... etc
end
end
end
end
end
end
end drafted, not tested |
Closed by #1029 ? |
ref: #1436 |
Given the following situation:
AMS currently has troubles inferring the right serializer class for
MyRelatedModel
(it should beMyNamespace::MyRelatedModelSerializer
, but it only looks upMyRelatedModelSerializer
). This PR fixes that by keeping trace of the "root" serializer, that is the first non-ArraySerializer
serializer in the chain of nested serializers, and looking up for a serializer in that "root serializer"'s namespace. Especially cool nor polymorphic associations in the namespaced scenario.Solves stuff such as #886, #916, #879 but without having to manually specify the namespace.
Based on #1193 because it sort of paved the way.
[TODO] Add some tests.