diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index 0d94bfb5..597b34c8 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -4,13 +4,7 @@ require 'active_model/serializer/collection_serializer' 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 @@ module ActiveModel 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,200 @@ module ActiveModel @serialization_adapter_instance ||= ActiveModelSerializers::Adapter::Attributes end + # Configuration options may also be set in + # Serializers and Adapters + config.collection_serializer = ActiveModel::Serializer::CollectionSerializer + config.serializer_lookup_enabled = true + + # @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 = '*' + config.adapter = :attributes + config.key_transform = nil + config.jsonapi_pagination_links_enabled = true + config.jsonapi_resource_type = :plural + config.jsonapi_namespace_separator = '-'.freeze + 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 + + # 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' + + 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 + + # keys of attributes + # @see Serializer::attribute + def self._attributes + _attributes_data.keys + end + + # @example + # class AdminAuthorSerializer < ActiveModel::Serializer + # attributes :id, :name, :recent_edits + def self.attributes(*attrs) + 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 + + # @api private + # maps attribute value to explicit key name + # @see Serializer::attribute + # @see ActiveModel::Serializer::Caching#fragmented_attributes + def self._attributes_keys + _attributes_data + .each_with_object({}) do |(key, attr), hash| + next if key == attr.name + hash[attr.name] = { key: key } + end + end + + # @param [Symbol] name of the association + # @param [Hash any>] options for the reflection + # @return [void] + # + # @example + # has_many :comments, serializer: CommentSummarySerializer + # + def self.has_many(name, options = {}, &block) # rubocop:disable Style/PredicateName + associate(HasManyReflection.new(name, options, block)) + end + + # @param [Symbol] name of the association + # @param [Hash 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 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 + + # Define a link on a serializer. + # @example + # link(:self) { resource_url(object) } + # @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) + 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 + end + attr_accessor :object, :root, :scope # `scope_name` is set as :current_user by default in the controller. @@ -131,6 +322,36 @@ module ActiveModel 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] + # + def associations(include_directive = ActiveModelSerializers.default_include_directive, include_slice = nil) + include_slice ||= include_directive + return unless object + + Enumerator.new do |y| + self.class._reflections.values.each do |reflection| + next if reflection.excluded?(self) + key = reflection.options.fetch(:key, reflection.name) + 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. diff --git a/lib/active_model/serializer/concerns/associations.rb b/lib/active_model/serializer/concerns/associations.rb deleted file mode 100644 index ce0ea21f..00000000 --- a/lib/active_model/serializer/concerns/associations.rb +++ /dev/null @@ -1,102 +0,0 @@ -module ActiveModel - class Serializer - # Defines an association in the object should be rendered. - # - # The serializer object should implement the association name - # as a method which should return an array when invoked. If a method - # with the association name does not exist, the association name is - # dispatched to the serialized object. - # - module Associations - extend ActiveSupport::Concern - - included do - with_options instance_writer: false, instance_reader: true do |serializer| - serializer.class_attribute :_reflections - self._reflections ||= {} - end - - extend ActiveSupport::Autoload - autoload :Association - autoload :Reflection - autoload :SingularReflection - autoload :CollectionReflection - autoload :BelongsToReflection - autoload :HasOneReflection - autoload :HasManyReflection - end - - module ClassMethods - def inherited(base) - super - base._reflections = _reflections.dup - end - - # @param [Symbol] name of the association - # @param [Hash any>] options for the reflection - # @return [void] - # - # @example - # has_many :comments, serializer: CommentSummarySerializer - # - def has_many(name, options = {}, &block) # rubocop:disable Style/PredicateName - associate(HasManyReflection.new(name, options, block)) - end - - # @param [Symbol] name of the association - # @param [Hash any>] options for the reflection - # @return [void] - # - # @example - # belongs_to :author, serializer: AuthorSerializer - # - def belongs_to(name, options = {}, &block) - associate(BelongsToReflection.new(name, options, block)) - end - - # @param [Symbol] name of the association - # @param [Hash any>] options for the reflection - # @return [void] - # - # @example - # has_one :author, serializer: AuthorSerializer - # - def has_one(name, options = {}, &block) # rubocop:disable Style/PredicateName - associate(HasOneReflection.new(name, options, block)) - end - - private - - # Add reflection and define {name} accessor. - # @param [ActiveModel::Serializer::Reflection] reflection - # @return [void] - # - # @api private - # - def associate(reflection) - key = reflection.options[:key] || reflection.name - self._reflections[key] = reflection - end - end - - # @param [JSONAPI::IncludeDirective] include_directive (defaults to the - # +default_include_directive+ config value when not provided) - # @return [Enumerator] - # - def associations(include_directive = ActiveModelSerializers.default_include_directive, include_slice = nil) - include_slice ||= include_directive - return unless object - - Enumerator.new do |y| - self.class._reflections.values.each do |reflection| - next if reflection.excluded?(self) - key = reflection.options.fetch(:key, reflection.name) - next unless include_directive.key?(key) - - y.yield reflection.build_association(self, instance_options, include_slice) - end - end - end - end - end -end diff --git a/lib/active_model/serializer/concerns/attributes.rb b/lib/active_model/serializer/concerns/attributes.rb deleted file mode 100644 index 6ee2732f..00000000 --- a/lib/active_model/serializer/concerns/attributes.rb +++ /dev/null @@ -1,82 +0,0 @@ -module ActiveModel - class Serializer - module Attributes - extend ActiveSupport::Concern - - included do - with_options instance_writer: false, instance_reader: false do |serializer| - serializer.class_attribute :_attributes_data # @api private - self._attributes_data ||= {} - end - - extend ActiveSupport::Autoload - autoload :Attribute - - # 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 - end - - module ClassMethods - def inherited(base) - super - base._attributes_data = _attributes_data.dup - end - - # @example - # class AdminAuthorSerializer < ActiveModel::Serializer - # attributes :id, :name, :recent_edits - def attributes(*attrs) - 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 attribute(attr, options = {}, &block) - key = options.fetch(:key, attr) - _attributes_data[key] = Attribute.new(attr, options, block) - end - - # @api private - # keys of attributes - # @see Serializer::attribute - def _attributes - _attributes_data.keys - end - - # @api private - # maps attribute value to explicit key name - # @see Serializer::attribute - # @see FragmentCache#fragment_serializer - def _attributes_keys - _attributes_data - .each_with_object({}) do |(key, attr), hash| - next if key == attr.name - hash[attr.name] = { key: key } - end - end - end - end - end -end diff --git a/lib/active_model/serializer/concerns/caching.rb b/lib/active_model/serializer/concerns/caching.rb index 4809f4cb..c8340787 100644 --- a/lib/active_model/serializer/concerns/caching.rb +++ b/lib/active_model/serializer/concerns/caching.rb @@ -40,9 +40,9 @@ module ActiveModel module ClassMethods def inherited(base) - super caller_line = caller[1] base._cache_digest_file_path = caller_line + super end def _cache_digest diff --git a/lib/active_model/serializer/concerns/configuration.rb b/lib/active_model/serializer/concerns/configuration.rb deleted file mode 100644 index d6d3c610..00000000 --- a/lib/active_model/serializer/concerns/configuration.rb +++ /dev/null @@ -1,59 +0,0 @@ -module ActiveModel - class Serializer - module Configuration - include ActiveSupport::Configurable - extend ActiveSupport::Concern - - # Configuration options may also be set in - # Serializers and Adapters - included do |base| - config = base.config - config.collection_serializer = ActiveModel::Serializer::CollectionSerializer - config.serializer_lookup_enabled = true - - def config.array_serializer=(collection_serializer) - self.collection_serializer = collection_serializer - end - - def config.array_serializer - collection_serializer - end - - config.default_includes = '*' - config.adapter = :attributes - config.key_transform = nil - config.jsonapi_pagination_links_enabled = true - config.jsonapi_resource_type = :plural - config.jsonapi_namespace_separator = '-'.freeze - 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 - - # 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 - end - end -end diff --git a/lib/active_model/serializer/concerns/links.rb b/lib/active_model/serializer/concerns/links.rb deleted file mode 100644 index 1322adb0..00000000 --- a/lib/active_model/serializer/concerns/links.rb +++ /dev/null @@ -1,35 +0,0 @@ -module ActiveModel - class Serializer - module Links - extend ActiveSupport::Concern - - included do - with_options instance_writer: false, instance_reader: true do |serializer| - serializer.class_attribute :_links # @api private - self._links ||= {} - end - - extend ActiveSupport::Autoload - end - - module ClassMethods - def inherited(base) - super - base._links = _links.dup - end - - # Define a link on a serializer. - # @example - # link(:self) { resource_url(object) } - # @example - # link(:self) { "http://example.com/resource/#{object.id}" } - # @example - # link :resource, "http://example.com/resource" - # - def link(name, value = nil, &block) - _links[name] = block || value - end - end - end - end -end diff --git a/lib/active_model/serializer/concerns/meta.rb b/lib/active_model/serializer/concerns/meta.rb deleted file mode 100644 index 5160585e..00000000 --- a/lib/active_model/serializer/concerns/meta.rb +++ /dev/null @@ -1,29 +0,0 @@ -module ActiveModel - class Serializer - module Meta - extend ActiveSupport::Concern - - included do - with_options instance_writer: false, instance_reader: true do |serializer| - serializer.class_attribute :_meta # @api private - end - - extend ActiveSupport::Autoload - end - - module ClassMethods - # 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 meta(value = nil, &block) - self._meta = block || value - end - end - end - end -end diff --git a/lib/active_model/serializer/concerns/type.rb b/lib/active_model/serializer/concerns/type.rb deleted file mode 100644 index c37c9af8..00000000 --- a/lib/active_model/serializer/concerns/type.rb +++ /dev/null @@ -1,25 +0,0 @@ -module ActiveModel - class Serializer - module Type - extend ActiveSupport::Concern - - included do - with_options instance_writer: false, instance_reader: true do |serializer| - serializer.class_attribute :_type # @api private - end - - extend ActiveSupport::Autoload - end - - module ClassMethods - # Set the JSON API type of a serializer. - # @example - # class AdminAuthorSerializer < ActiveModel::Serializer - # type 'authors' - def type(type) - self._type = type && type.to_s - end - end - end - end -end