Skip to content

Make serializer interface more obvious#2074

Merged
bf4 merged 2 commits intorails-api:masterfrom
bf4:make_serializer_interface_more_obvious
Mar 19, 2017
Merged

Make serializer interface more obvious#2074
bf4 merged 2 commits intorails-api:masterfrom
bf4:make_serializer_interface_more_obvious

Conversation

@bf4
Copy link
Copy Markdown
Member

@bf4 bf4 commented Mar 13, 2017

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.

@mention-bot
Copy link
Copy Markdown

@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.

@NullVoxPopuli
Copy link
Copy Markdown
Contributor

👍

Comment thread lib/active_model/serializer.rb Outdated
# @return [void]
#
# @api private
#
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

✂️

Enumerator.new do |y|
self.class._reflections.values.each do |reflection|
next if reflection.excluded?(self)
key = reflection.options.fetch(:key, reflection.name)
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

NOTE: key should always be set since

    def self.associate(reflection)
      key = reflection.options[:key] || reflection.name
      self._reflections[key] = reflection
    end

which 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
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

❓ For another PR, should return Enumerator.new, right?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

Choose a reason for hiding this comment

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

# END SERIALIZER MACROS

# @example
# class AdminAuthorSerializer < ActiveModel::Serializer
# attributes :id, :name, :recent_edits
def self.attributes(*attrs)
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

# BEGIN SERIALIZER MACROS

Comment thread lib/active_model/serializer.rb Outdated
# maps attribute value to explicit key name
# @see Serializer::attribute
# @see ActiveModel::Serializer::Caching#fragmented_attributes
def self._attributes_keys
Copy link
Copy Markdown
Member Author

@bf4 bf4 Mar 13, 2017

Choose a reason for hiding this comment

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

TODO in another PR:

  1. Comment out of date
  2. Should be in the 'caching' concern. That's the only place it is used.
  3. (Also, is not a 'MACRO')

Comment thread lib/active_model/serializer.rb Outdated
# @api private
# keys of attributes
# @see Serializer::attribute
def self._attributes
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

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

Choose a reason for hiding this comment

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

key = reflection.options[:key] || reflection.name
self._reflections[key] = reflection
end
private_class_method :associate
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

NOTE for future PR:

config.collection_serializer = ActiveModel::Serializer::CollectionSerializer
config.serializer_lookup_enabled = true

def config.array_serializer=(collection_serializer)
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

# @api deprecated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

are you gonna add these deprecation tags?

self.collection_serializer = collection_serializer
end

def config.array_serializer
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

# @api deprecated

# 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
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

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

  1. true (default)-- always include association data
  2. false -- never include association data
  3. :if_sideloaded (uses include_slice option passed to associations method, tl;dr include_slice.key?(reflection.name))

@rafaelfranca
Copy link
Copy Markdown

LGTM so far

@bf4 bf4 force-pushed the make_serializer_interface_more_obvious branch from cb5abd0 to 36b4eac Compare March 15, 2017 01:55
@bf4
Copy link
Copy Markdown
Member Author

bf4 commented Mar 15, 2017

Updated

Comment thread lib/active_model/serializer.rb Outdated
end

# Configuration options may also be set in
# Serializers and Adapters
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

❗️ 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
Copy link
Copy Markdown
Member Author

@bf4 bf4 Mar 15, 2017

collection_serializer
end

config.default_includes = '*'
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

config.key_transform = nil
config.jsonapi_pagination_links_enabled = true
config.jsonapi_resource_type = :plural
config.jsonapi_namespace_separator = '-'.freeze
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I was thinking... though we recommend -- now, why not .?

end

config.default_includes = '*'
config.adapter = :attributes
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

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

@bf4 bf4 Mar 15, 2017

Choose a reason for hiding this comment

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

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

@bf4 bf4 Mar 15, 2017

Choose a reason for hiding this comment

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

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.

See #1857 (and #2026 )

# meta do
# { comment_count: object.comments.count }
# end
def self.meta(value = nil, &block)
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

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

@bf4 bf4 Mar 15, 2017

Choose a reason for hiding this comment

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

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

end

# Preferred interface is ActiveModelSerializers.config
# BEGIN DEFAULT CONFIGURATION
Copy link
Copy Markdown
Member Author

@bf4 bf4 Mar 16, 2017

Choose a reason for hiding this comment

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

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

Choose a reason for hiding this comment

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

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)

@bf4
Copy link
Copy Markdown
Member Author

bf4 commented Mar 16, 2017

I think this is ready for merge

@bf4 bf4 merged commit 80e470d into rails-api:master Mar 19, 2017
@bf4 bf4 deleted the make_serializer_interface_more_obvious branch March 19, 2017 03:41
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