-
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
(WIP) Fix Serializer Lookup Chain for Namespaces #1883
base: master
Are you sure you want to change the base?
(WIP) Fix Serializer Lookup Chain for Namespaces #1883
Conversation
@thewatts, thanks for your PR! By analyzing the annotation information on this pull request, we identified @bf4, @beauby and @steveklabnik to be potential reviewers |
-- The Problem: Currently, when using a relationship from within a namespace, ex: ```ruby module Api class LessonSerializer < ApplicationSerializer belongs_to :trait end end ``` The method to fetch a serializer builds a lookup chain of possible constant names that gets looped over with `safe_constantize`. When using relationships from within a namespace (like above), the lookup chain would return this: ```ruby [ "Api::LessonSerializer::TraitSerializer", "::TraitSerializer", ] ``` This works fine the first time, as calling `.safe_constantize` on `"Api::LessonSerializer::TraitSerializer"` returns the correct constant in this case: `Api::TraitSerializer`. However, the _second_ time that the lookup has to happen (from a different relationship), ex: ```ruby module Api class CourseSerializer < ApplicationSerializer belongs_to :trait end end ``` The lookup chain returns a similar response: ```ruby [ "Api::CourseSerializer::TraitSerializer", "::TraitSerializer", ] ``` But at this point, calling `.safe_constantize` on `"Api::CourseSerializer::TraitSerializer"` returns `nil`. Here's an example from the Rails console: ![safe_constantize example](https://thewatts.s3-us-west-1.amazonaws.com/screenshots/Screen-Shot-2016-08-16-10-45-05.png) It looks like the main problem [lies in this issue here](rails/rails#9286) (many thanks to @willcosgrove for finding it!) So this means that calling `.safe_constantize` on the same name string returns the constant the first time (based on `ActiveSupport::Dependencies`), but the _second_ time it gets called on the same string it returns `nil`. ![safe_constantize of the same string](https://thewatts.s3-us-west-1.amazonaws.com/screenshots/Screen-Shot-2016-08-16-10-53-15.png) -- The Fix: I get the namespace of the `name` of the parent serializer, and use that to also build and add a string to the lookup chain array. So in our first example above, it adds: `"Api::TraitSerializer"` to the lookup chain, allowing the serializer to be found every time.
Hey @thewatts thanks for your PR! There have actually been many approaches to this before: My favorite: #1757 (bias though) I think that rails issue you linked to comes down to how ruby does constant lookup in an auto-loading environment (rails) http://cirw.in/blog/constant-lookup.html That said, for tests, I'd would add to the main AM::Serializer tests and add tests for the look up chain method |
I suppose you'd also wasn't to add integration tests to action controller tests somewhere. Maybe a new file for lookup? |
@NullVoxPopuli thanks for the reply! And sorry for the late reply on my end - that's quite the chain of issues/PRs to follow. Honestly, dynamically defining namespace by controller (or through configuration as in your PR) hadn't crossed my mind. I had just noticed that the dynamic lookup of some I've looked at making things more dynamic, so that deeper nested items could be found correctly (ie: I can see the desire for configuration though, especially for relationships where you have a Ex: module Api
module V1
class TraitSerializer < ApplicationSerializer
end
end
end
module Api
module V2
class LessonSerializer < ApplicationSerializer
belongs_to :trait
end
end
end In that case - Though my additions create a quick fix (at least to my own problem) - it looks like there's already some solid direction going with those other PRs. Has there been a consensus on the final direction? If so - I won't be offended if this PR is closed ;) |
@NullVoxPopuli if this PR is helpful, I absolutely don't mind adding tests (and of course - fixing the one I just broke 😅 ) |
tmp_namespace = name.to_s | ||
name_namespaces = [tmp_namespace] | ||
|
||
until tmp_namespace.empty? |
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.
do you need to have two loops here? I imagine you could add to the chain array as you deconstantized the namespaces.
Could you also add some documentation (comments) in here explaining how the chain array looks after different steps of this process?
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.
@NullVoxPopuli Good call! Thanks for the feedback!
Adjustments added in most recent commit. Let me know your thoughts!
@NullVoxPopuli for the lookup chain and versioning... would we try to specifically look for versioned namespaces? ex: if |
|
||
chain | ||
if self != ActiveModel::Serializer | ||
until parent_namespace.empty? |
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 is getting there! Why not use parent namespace.each?
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.
Actually, I think using .each would turn this in to too much work. Haha
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.
We could def use .each
if we split the string on ::
- but we'd have to build the namespaces a little differently.
I'm game for either.
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.
I think the way you have it may be the cleanest.
Just trying to think of a way it could possibly throw an exception.. (I haven't thought of one so far)
Could you add a change log entry as well? |
chain.push("#{resource_namespace}::#{serializer_class_name}") | ||
# Loops over a namespaced Serializer's name to add multiple | ||
# namespaced serializer candidates for lookup in the `lookup_chain` from | ||
# the "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.
This is the kind of documentation I'd like to see all over AMS :-)
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.
Yeah it's a tough balance - solid documentation can be a bonus - but it's the first thing to become stale unless tightly monitored.
Typically I try to have the code be self documenting (so to speak), but at this point we're deep into the belly of the beast :)
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.
Yeah, everyone's goal is self-documenting code. But as long as churn is low, documentation is great to have. I don't see serializer lookup changing very often :-)
Just wanna let you all know I'm aware of this but haven't yet looked at it :) |
serializer_class_name = "#{resource_class_name}Serializer" | ||
|
||
chain.push("#{name}::#{serializer_class_name}") if self != ActiveModel::Serializer | ||
chain.push("#{resource_namespace}::#{serializer_class_name}") | ||
# Loops over a namespaced Serializer's name to add multiple |
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 kind of comment should be above the method definition, no?
is this still an issue on master? |
@NullVoxPopuli - I see that you just pushed an update here, #1968. This specific PR was for trying to auto-build the namespace lookup chain used during declarations in the ex: module Api
module V1
class AuthorSerializer < ApplicationSerializer
has_many :posts # this would fail to fine the correct serializer My apologies for not staying on top of it - shortly after the PR was submitted - I found out I was losing my job @ my employer (cutbacks). I've since been in the transition to other employment. It looks like your PR adds the ability to specify the namespace directly in a If you still want it in master - I can rebase off of master and make the necessary adjustments, add tests, and update the Changelog (will take me a week or so to get the available time). If not - no worries 😄 |
sorry for your loss -- company cut backs are never fun -- even for those that stay. Anywho, for this PR, I'd like to see some tests so that we know exactly what situation your PR addresses. but being able to specify a namespace hopefully will help a ton of people. |
Purpose
This fixes dynamic lookup of namespaced serializers through relationships.
Changes
This adjusts the internal API of the serializer method used for dynamic lookup by adding an additional constant string to the lookup chain used to find the related serializer.
Caveats
No tests. I'm a testing guy - so, if someone can point me to where I should be testing this in the project, I will 100% write them.
I'm guessing I'll need to start with
test/serializers/associations_test.rb
Also - this is currently only working for a single nested resource, I'll adjust in short order to provide a dynamic amount of namespaced items.
Related GitHub issues
Not sure
Additional helpful information
-- The Problem:
Currently, when using a relationship from within a namespace, ex:
The method to fetch a serializer builds a lookup chain of possible
constant names that gets looped over with
safe_constantize
. When usingrelationships from within a namespace (like above), the lookup chain
would return this:
This works fine the first time, as calling
.safe_constantize
on"Api::LessonSerializer::TraitSerializer"
returns the correct constantin this case:
Api::TraitSerializer
.However, the second time that the lookup has to happen (from a
different relationship), ex:
The lookup chain returns a similar response:
But at this point, calling
.safe_constantize
on"Api::CourseSerializer::TraitSerializer"
returnsnil
.Here's an example from the Rails console:
It looks like the main problem lies in this issue here
(many thanks to @willcosgrove for finding it!)
So this means that calling
.safe_constantize
on the same name stringreturns the constant the first time (based on
ActiveSupport::Dependencies
), but the second time it gets called onthe same string it returns
nil
.-- The Fix:
I get the namespace of the
name
of the parent serializer, and use thatto also build and add a string to the lookup chain array.
So in our first example above, it adds:
"Api::TraitSerializer"
to thelookup chain, allowing the serializer to be found every time.