Make serializer interface more obvious#2074
Conversation
|
@bf4, thanks for your PR! By analyzing the history of the files in this pull request, we identified @bolshakov, @beauby and @NullVoxPopuli to be potential reviewers. |
|
👍 |
| # @return [void] | ||
| # | ||
| # @api private | ||
| # |
| Enumerator.new do |y| | ||
| self.class._reflections.values.each do |reflection| | ||
| next if reflection.excluded?(self) | ||
| key = reflection.options.fetch(:key, reflection.name) |
There was a problem hiding this comment.
NOTE: key should always be set since
def self.associate(reflection)
key = reflection.options[:key] || reflection.name
self._reflections[key] = reflection
endwhich mean we could safely change .values.each do |reflection| to .each do |key, reflection|
| # | ||
| def associations(include_directive = ActiveModelSerializers.default_include_directive, include_slice = nil) | ||
| include_slice ||= include_directive | ||
| return unless object |
There was a problem hiding this comment.
❓ For another PR, should return Enumerator.new, right?
There was a problem hiding this comment.
yeah, it should be lazy, cause depending on the request / include args, maybe associations is never evaluated
| # type 'authors' | ||
| def self.type(type) | ||
| self._type = type && type.to_s | ||
| end |
| # @example | ||
| # class AdminAuthorSerializer < ActiveModel::Serializer | ||
| # attributes :id, :name, :recent_edits | ||
| def self.attributes(*attrs) |
| # maps attribute value to explicit key name | ||
| # @see Serializer::attribute | ||
| # @see ActiveModel::Serializer::Caching#fragmented_attributes | ||
| def self._attributes_keys |
There was a problem hiding this comment.
TODO in another PR:
- Comment out of date
- Should be in the 'caching' concern. That's the only place it is used.
- (Also, is not a 'MACRO')
| # @api private | ||
| # keys of attributes | ||
| # @see Serializer::attribute | ||
| def self._attributes |
There was a problem hiding this comment.
This should really be a public API, I think (Also, is not a 'MACRO')
|
|
||
| # Define a link on a serializer. | ||
| # @example | ||
| # link(:self) { resource_url(object) } |
There was a problem hiding this comment.
NOTE: (for future PR)
resource_url will only work in the JSONAPI adapter, else need https://github.com/rails-api/active_model_serializers/blob/2a6d373cb269bd704d201fc0ff8e6c4255865c2c/docs/howto/add_relationship_links.md#links-as-an-attribute-of-a-resource
| key = reflection.options[:key] || reflection.name | ||
| self._reflections[key] = reflection | ||
| end | ||
| private_class_method :associate |
There was a problem hiding this comment.
NOTE for future PR:
- doc how to configure default inclusion behavior: all associations, some associations, some fields in the associations, etc
| config.collection_serializer = ActiveModel::Serializer::CollectionSerializer | ||
| config.serializer_lookup_enabled = true | ||
|
|
||
| def config.array_serializer=(collection_serializer) |
There was a problem hiding this comment.
are you gonna add these deprecation tags?
| self.collection_serializer = collection_serializer | ||
| end | ||
|
|
||
| def config.array_serializer |
| # Make JSON API top-level jsonapi member opt-in | ||
| # ref: http://jsonapi.org/format/#document-top-level | ||
| config.jsonapi_include_toplevel_object = false | ||
| config.include_data_default = true |
There was a problem hiding this comment.
TODO: DOC, is the default for the include_data reflection option, ref https://github.com/rails-api/active_model_serializers/blob/ff27032720956f7c5cd9ef50618606cfebaa7ad9/docs/general/serializers.md#associations
true(default)-- always include association datafalse-- never include association data:if_sideloaded(usesinclude_sliceoption passed toassociationsmethod, tl;drinclude_slice.key?(reflection.name))
|
LGTM so far |
cb5abd0 to
36b4eac
Compare
|
Updated |
| end | ||
|
|
||
| # Configuration options may also be set in | ||
| # Serializers and Adapters |
There was a problem hiding this comment.
❗️ comment appears to be out of date.
In addition, This is a reminder that some of the configuration stuff in here is 'serializer'-specific, and some is global, which is why I moved the preferred interface to ActiveModelSerializers.config from ActiveModel::Serializer.config, but I think there's an argument that some aspects of inheritable config should remain in the serializer.
| # Configuration options may also be set in | ||
| # Serializers and Adapters | ||
| config.collection_serializer = ActiveModel::Serializer::CollectionSerializer | ||
| config.serializer_lookup_enabled = true |
There was a problem hiding this comment.
Noting open issues with this:
- Explicit
serializer_lookup_enabledin controller doesn't retain Adapter for entire render #1500 - [WIP] Allow users to opt-out from the ActionController extensions #1636
- Setting
serializeroption to nil still using default serializer. #2014 - Possible regression in 0.10.1 - Object instance variable in parent serializer set to serializer in 0.10.1 #1813 esp Possible regression in 0.10.1 - Object instance variable in parent serializer set to serializer in 0.10.1 #1813 (comment)
| collection_serializer | ||
| end | ||
|
|
||
| config.default_includes = '*' |
There was a problem hiding this comment.
| config.key_transform = nil | ||
| config.jsonapi_pagination_links_enabled = true | ||
| config.jsonapi_resource_type = :plural | ||
| config.jsonapi_namespace_separator = '-'.freeze |
There was a problem hiding this comment.
I was thinking... though we recommend -- now, why not .?
| end | ||
|
|
||
| config.default_includes = '*' | ||
| config.adapter = :attributes |
There was a problem hiding this comment.
See Adapter unification RFC and the difference between 'attributes' and 'json', in terms of serializing individual records, should be whether include_root_in_json or root is true. In terms of generating the response document, when root is true, then other options like meta could be processed; Otherwise, they should be ignored since there's no notion of a document (distinct from the serialized resource(s)) without a root, and hence no document-level meta.
| # @see Serializer::attribute | ||
| def self._attributes | ||
| _attributes_data.keys | ||
| end |
There was a problem hiding this comment.
I moved this above the 'MACROS' section and removed @api private since it's been a public interface across multiple versions. I think we tagged it as private just to be conservative and let us pick a different method name, if we like.
Too bad _attributes returns an array of attribute names and _reflections returns a hash of association names to reflections (is more like _attributes_data)
| # @example | ||
| # has_many :comments, serializer: CommentSummarySerializer | ||
| # | ||
| def self.has_many(name, options = {}, &block) # rubocop:disable Style/PredicateName |
There was a problem hiding this comment.
making blocks available to associations complicates the reflection since there's no way to know anything about what's in it until it's evaluated, and hence trying to just get the id/type for a belongs_to, and not loading the association, is harder. We should probably revisit what information we have when a block is passed.
| # meta do | ||
| # { comment_count: object.comments.count } | ||
| # end | ||
| def self.meta(value = nil, &block) |
There was a problem hiding this comment.
It should be noted that this is a serializer-level (resource object) meta, distinct from adapter-level (document)
| # class AdminAuthorSerializer < ActiveModel::Serializer | ||
| # type 'authors' | ||
| def self.type(type) | ||
| self._type = type && type.to_s |
There was a problem hiding this comment.
resource roots esp as render options need review C: JSON Roots
Some thoughts I wrote in our Slack last September:
unifying the root key logic would be a huge boon,
from 1843 #1756 (comment) #1794 (comment)
summary of the issues: there's four methods that determine the resource root. This is especially confusing in the collection serializer. stay away until you need to, but can be useful in seeing the touch point. small working pieces > everything. so, any progress can be a pr.
summary of the goal: the main difference between the attributes and json adapters is the root option. look through the issues and prs and you'll see the trouble handling this at the adapter causes. If root could be turned on or off in the attributes or json adapter the same way, they might even be able to be rejoined. (They were split by joaomdmoura , and I've since moved a lot of the logic into the serializer)
There's lots of little pieces of this that are individually easish to find and fix. The overall goal is to just make it simple.
Naming: serializer type macro should be used to determine the root name, when present. The name can be over-ridden at the instance level either by passing in root or by defining a method named root. json_key is a bad name and should be deprecated. root is also kind of a bad name... actual method should probably be _type or something instead of root so as to not conflict with other instance methods and also for better parity. There's a lot of decisions to be made here.
so, within this there's 1) whether to include a root 2) where do we determine whether to declare a root (option, serializer, config, adapter)? 3) what should the value of the root be
| # Preferred interface is ActiveModelSerializers.config | ||
| # BEGIN DEFAULT CONFIGURATION | ||
| config.collection_serializer = ActiveModel::Serializer::CollectionSerializer | ||
| config.serializer_lookup_enabled = true |
There was a problem hiding this comment.
Noting open issues with this:
- Explicit
serializer_lookup_enabledin controller doesn't retain Adapter for entire render #1500 - [WIP] Allow users to opt-out from the ActionController extensions #1636
- Setting
serializeroption to nil still using default serializer. #2014 - Possible regression in 0.10.1 - Object instance variable in parent serializer set to serializer in 0.10.1 #1813 esp Possible regression in 0.10.1 - Object instance variable in parent serializer set to serializer in 0.10.1 #1813 (comment)
| end | ||
|
|
||
| # Preferred interface is ActiveModelSerializers.config | ||
| # BEGIN DEFAULT CONFIGURATION |
There was a problem hiding this comment.
This is a reminder that some of the configuration stuff in here is 'serializer'-specific, and some is global, which is why I moved the preferred interface to ActiveModelSerializers.config from ActiveModel::Serializer.config, but I think there's an argument that some aspects of inheritable config should remain in the serializer.
| # @see Serializer::attribute | ||
| def self._attributes | ||
| _attributes_data.keys | ||
| end |
There was a problem hiding this comment.
I moved this above the 'MACROS' section and removed @api private since it's been a public interface across multiple versions. I think we tagged it as private just to be conservative and let us pick a different method name, if we like.
Too bad _attributes returns an array of attribute names and _reflections returns a hash of association names to reflections (is more like _attributes_data)
|
I think this is ready for merge |
Remove unnecessary concerns that make understanding the Serializer just that much harder
cc @kurko
Inspired by working with this stuff in #2026
I started some work to more completely separate out the caching code... but it's not ready.