-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Make serializer interface more obvious #2074
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,13 +4,7 @@ | |
| require 'active_model/serializer/array_serializer' | ||
| require 'active_model/serializer/error_serializer' | ||
| require 'active_model/serializer/errors_serializer' | ||
| require 'active_model/serializer/concerns/associations' | ||
| require 'active_model/serializer/concerns/attributes' | ||
| require 'active_model/serializer/concerns/caching' | ||
| require 'active_model/serializer/concerns/configuration' | ||
| require 'active_model/serializer/concerns/links' | ||
| require 'active_model/serializer/concerns/meta' | ||
| require 'active_model/serializer/concerns/type' | ||
| require 'active_model/serializer/fieldset' | ||
| require 'active_model/serializer/lint' | ||
|
|
||
|
|
@@ -23,13 +17,16 @@ class Serializer | |
| extend ActiveSupport::Autoload | ||
| autoload :Adapter | ||
| autoload :Null | ||
| include Configuration | ||
| include Associations | ||
| include Attributes | ||
| autoload :Attribute | ||
| autoload :Association | ||
| autoload :Reflection | ||
| autoload :SingularReflection | ||
| autoload :CollectionReflection | ||
| autoload :BelongsToReflection | ||
| autoload :HasOneReflection | ||
| autoload :HasManyReflection | ||
| include ActiveSupport::Configurable | ||
| include Caching | ||
| include Links | ||
| include Meta | ||
| include Type | ||
|
|
||
| # @param resource [ActiveRecord::Base, ActiveModelSerializers::Model] | ||
| # @return [ActiveModel::Serializer] | ||
|
|
@@ -111,6 +108,193 @@ def self.serialization_adapter_instance | |
| @serialization_adapter_instance ||= ActiveModelSerializers::Adapter::Attributes | ||
| end | ||
|
|
||
| # Preferred interface is ActiveModelSerializers.config | ||
| # BEGIN DEFAULT CONFIGURATION | ||
| config.collection_serializer = ActiveModel::Serializer::CollectionSerializer | ||
| config.serializer_lookup_enabled = true | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Noting open issues with this:
|
||
|
|
||
| # @deprecated Use {#config.collection_serializer=} instead of this. Is | ||
| # compatibilty layer for ArraySerializer. | ||
| def config.array_serializer=(collection_serializer) | ||
| self.collection_serializer = collection_serializer | ||
| end | ||
|
|
||
| # @deprecated Use {#config.collection_serializer} instead of this. Is | ||
| # compatibilty layer for ArraySerializer. | ||
| def config.array_serializer | ||
| collection_serializer | ||
| end | ||
|
|
||
| config.default_includes = '*' | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| config.adapter = :attributes | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
| config.key_transform = nil | ||
| config.jsonapi_pagination_links_enabled = true | ||
| config.jsonapi_resource_type = :plural | ||
| config.jsonapi_namespace_separator = '-'.freeze | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was thinking... though we recommend |
||
| config.jsonapi_version = '1.0' | ||
| config.jsonapi_toplevel_meta = {} | ||
| # 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 | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. TODO: DOC, is the default for the
|
||
|
|
||
| # For configuring how serializers are found. | ||
| # This should be an array of procs. | ||
| # | ||
| # The priority of the output is that the first item | ||
| # in the evaluated result array will take precedence | ||
| # over other possible serializer paths. | ||
| # | ||
| # i.e.: First match wins. | ||
| # | ||
| # @example output | ||
| # => [ | ||
| # "CustomNamespace::ResourceSerializer", | ||
| # "ParentSerializer::ResourceSerializer", | ||
| # "ResourceNamespace::ResourceSerializer" , | ||
| # "ResourceSerializer"] | ||
| # | ||
| # If CustomNamespace::ResourceSerializer exists, it will be used | ||
| # for serialization | ||
| config.serializer_lookup_chain = ActiveModelSerializers::LookupChain::DEFAULT.dup | ||
|
|
||
| config.schema_path = 'test/support/schemas' | ||
| # END DEFAULT CONFIGURATION | ||
|
|
||
| with_options instance_writer: false, instance_reader: false do |serializer| | ||
| serializer.class_attribute :_attributes_data # @api private | ||
| self._attributes_data ||= {} | ||
| end | ||
| with_options instance_writer: false, instance_reader: true do |serializer| | ||
| serializer.class_attribute :_reflections | ||
| self._reflections ||= {} | ||
| serializer.class_attribute :_links # @api private | ||
| self._links ||= {} | ||
| serializer.class_attribute :_meta # @api private | ||
| serializer.class_attribute :_type # @api private | ||
| end | ||
|
|
||
| def self.inherited(base) | ||
| super | ||
| base._attributes_data = _attributes_data.dup | ||
| base._reflections = _reflections.dup | ||
| base._links = _links.dup | ||
| end | ||
|
|
||
| # @return [Array<Symbol>] Key names of declared attributes | ||
| # @see Serializer::attribute | ||
| def self._attributes | ||
| _attributes_data.keys | ||
| end | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I moved this above the 'MACROS' section and removed Too bad |
||
|
|
||
| # BEGIN SERIALIZER MACROS | ||
|
|
||
| # @example | ||
| # class AdminAuthorSerializer < ActiveModel::Serializer | ||
| # attributes :id, :name, :recent_edits | ||
| def self.attributes(*attrs) | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| attrs = attrs.first if attrs.first.class == Array | ||
|
|
||
| attrs.each do |attr| | ||
| attribute(attr) | ||
| end | ||
| end | ||
|
|
||
| # @example | ||
| # class AdminAuthorSerializer < ActiveModel::Serializer | ||
| # attributes :id, :recent_edits | ||
| # attribute :name, key: :title | ||
| # | ||
| # attribute :full_name do | ||
| # "#{object.first_name} #{object.last_name}" | ||
| # end | ||
| # | ||
| # def recent_edits | ||
| # object.edits.last(5) | ||
| # end | ||
| def self.attribute(attr, options = {}, &block) | ||
| key = options.fetch(:key, attr) | ||
| _attributes_data[key] = Attribute.new(attr, options, block) | ||
| end | ||
|
|
||
| # @param [Symbol] name of the association | ||
| # @param [Hash<Symbol => any>] options for the reflection | ||
| # @return [void] | ||
| # | ||
| # @example | ||
| # has_many :comments, serializer: CommentSummarySerializer | ||
| # | ||
| def self.has_many(name, options = {}, &block) # rubocop:disable Style/PredicateName | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||
| associate(HasManyReflection.new(name, options, block)) | ||
| end | ||
|
|
||
| # @param [Symbol] name of the association | ||
| # @param [Hash<Symbol => any>] options for the reflection | ||
| # @return [void] | ||
| # | ||
| # @example | ||
| # belongs_to :author, serializer: AuthorSerializer | ||
| # | ||
| def self.belongs_to(name, options = {}, &block) | ||
| associate(BelongsToReflection.new(name, options, block)) | ||
| end | ||
|
|
||
| # @param [Symbol] name of the association | ||
| # @param [Hash<Symbol => any>] options for the reflection | ||
| # @return [void] | ||
| # | ||
| # @example | ||
| # has_one :author, serializer: AuthorSerializer | ||
| # | ||
| def self.has_one(name, options = {}, &block) # rubocop:disable Style/PredicateName | ||
| associate(HasOneReflection.new(name, options, block)) | ||
| end | ||
|
|
||
| # Add reflection and define {name} accessor. | ||
| # @param [ActiveModel::Serializer::Reflection] reflection | ||
| # @return [void] | ||
| # | ||
| # @api private | ||
| def self.associate(reflection) | ||
| key = reflection.options[:key] || reflection.name | ||
| self._reflections[key] = reflection | ||
| end | ||
| private_class_method :associate | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. NOTE for future PR:
|
||
|
|
||
| # Define a link on a serializer. | ||
| # @example | ||
| # link(:self) { resource_url(object) } | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 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 |
||
| # @example | ||
| # link(:self) { "http://example.com/resource/#{object.id}" } | ||
| # @example | ||
| # link :resource, "http://example.com/resource" | ||
| # | ||
| def self.link(name, value = nil, &block) | ||
| _links[name] = block || value | ||
| end | ||
|
|
||
| # Set the JSON API meta attribute of a serializer. | ||
| # @example | ||
| # class AdminAuthorSerializer < ActiveModel::Serializer | ||
| # meta { stuff: 'value' } | ||
| # @example | ||
| # meta do | ||
| # { comment_count: object.comments.count } | ||
| # end | ||
| def self.meta(value = nil, &block) | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) |
||
| self._meta = block || value | ||
| end | ||
|
|
||
| # Set the JSON API type of a serializer. | ||
| # @example | ||
| # class AdminAuthorSerializer < ActiveModel::Serializer | ||
| # type 'authors' | ||
| def self.type(type) | ||
| self._type = type && type.to_s | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 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 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 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 |
||
| end | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
|
||
| # END SERIALIZER MACROS | ||
|
|
||
| attr_accessor :object, :root, :scope | ||
|
|
||
| # `scope_name` is set as :current_user by default in the controller. | ||
|
|
@@ -131,6 +315,36 @@ def success? | |
| true | ||
| end | ||
|
|
||
| # Return the +attributes+ of +object+ as presented | ||
| # by the serializer. | ||
| def attributes(requested_attrs = nil, reload = false) | ||
| @attributes = nil if reload | ||
| @attributes ||= self.class._attributes_data.each_with_object({}) do |(key, attr), hash| | ||
| next if attr.excluded?(self) | ||
| next unless requested_attrs.nil? || requested_attrs.include?(key) | ||
| hash[key] = attr.value(self) | ||
| end | ||
| end | ||
|
|
||
| # @param [JSONAPI::IncludeDirective] include_directive (defaults to the | ||
| # +default_include_directive+ config value when not provided) | ||
| # @return [Enumerator<Association>] | ||
| # | ||
| def associations(include_directive = ActiveModelSerializers.default_include_directive, include_slice = nil) | ||
| include_slice ||= include_directive | ||
| return unless object | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ❓ For another PR, should return
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
|
|
||
| Enumerator.new do |y| | ||
| self.class._reflections.values.each do |reflection| | ||
| next if reflection.excluded?(self) | ||
| key = reflection.options.fetch(:key, reflection.name) | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. NOTE: def self.associate(reflection)
key = reflection.options[:key] || reflection.name
self._reflections[key] = reflection
endwhich mean we could safely change |
||
| next unless include_directive.key?(key) | ||
|
|
||
| y.yield reflection.build_association(self, instance_options, include_slice) | ||
| end | ||
| end | ||
| end | ||
|
|
||
| # @return [Hash] containing the attributes and first level | ||
| # associations, similar to how ActiveModel::Serializers::JSON is used | ||
| # in ActiveRecord::Base. | ||
|
|
||
This file was deleted.
Uh oh!
There was an error while loading. Please reload this page.
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 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.