mirror of
https://github.com/ditkrg/active_model_serializers.git
synced 2026-01-25 15:23:06 +00:00
Merge branch 'master' into domitian-move-namespace-of-adapter-to-active-model-serializers
Conflicts: CHANGELOG.md lib/active_model/serializer/adapter/attributes.rb lib/active_model/serializer/adapter/cached_serializer.rb lib/active_model/serializer/adapter/fragment_cache.rb lib/active_model/serializer/adapter/json_api.rb lib/active_model/serializer/adapter/json_api/link.rb test/adapter/fragment_cache_test.rb test/adapter/json_api/links_test.rb test/adapter/json_api/resource_type_config_test.rb
This commit is contained in:
@@ -9,6 +9,7 @@ require 'active_model/serializer/configuration'
|
||||
require 'active_model/serializer/fieldset'
|
||||
require 'active_model/serializer/lint'
|
||||
require 'active_model/serializer/links'
|
||||
require 'active_model/serializer/meta'
|
||||
require 'active_model/serializer/type'
|
||||
|
||||
# ActiveModel::Serializer is an abstract class that is
|
||||
@@ -20,6 +21,7 @@ module ActiveModel
|
||||
include Attributes
|
||||
include Caching
|
||||
include Links
|
||||
include Meta
|
||||
include Type
|
||||
# Deprecated
|
||||
require 'active_model_serializers/adapter'
|
||||
@@ -70,7 +72,7 @@ module ActiveModel
|
||||
|
||||
# @api private
|
||||
# Find a serializer from a class and caches the lookup.
|
||||
# Preferentially retuns:
|
||||
# Preferentially returns:
|
||||
# 1. class name appended with "Serializer"
|
||||
# 2. try again with superclass, if present
|
||||
# 3. nil
|
||||
|
||||
13
lib/active_model/serializer/adapter/json_api/api_objects.rb
Normal file
13
lib/active_model/serializer/adapter/json_api/api_objects.rb
Normal file
@@ -0,0 +1,13 @@
|
||||
module ActiveModel
|
||||
class Serializer
|
||||
module Adapter
|
||||
class JsonApi
|
||||
module ApiObjects
|
||||
extend ActiveSupport::Autoload
|
||||
autoload :Relationship
|
||||
autoload :ResourceIdentifier
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,52 @@
|
||||
module ActiveModel
|
||||
class Serializer
|
||||
module Adapter
|
||||
class JsonApi
|
||||
module ApiObjects
|
||||
class Relationship
|
||||
def initialize(parent_serializer, serializer, options = {}, links = {}, meta = nil)
|
||||
@object = parent_serializer.object
|
||||
@scope = parent_serializer.scope
|
||||
|
||||
@options = options
|
||||
@data = data_for(serializer, options)
|
||||
@links = links.each_with_object({}) do |(key, value), hash|
|
||||
hash[key] = ActiveModelSerializers::Adapter::JsonApi::Link.new(parent_serializer, value).as_json
|
||||
end
|
||||
@meta = meta.respond_to?(:call) ? parent_serializer.instance_eval(&meta) : meta
|
||||
end
|
||||
|
||||
def as_json
|
||||
hash = {}
|
||||
hash[:data] = data if options[:include_data]
|
||||
links = self.links
|
||||
hash[:links] = links if links.any?
|
||||
meta = self.meta
|
||||
hash[:meta] = meta if meta
|
||||
|
||||
hash
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
attr_reader :object, :scope, :data, :options, :links, :meta
|
||||
|
||||
private
|
||||
|
||||
def data_for(serializer, options)
|
||||
if serializer.respond_to?(:each)
|
||||
serializer.map { |s| ResourceIdentifier.new(s).as_json }
|
||||
else
|
||||
if options[:virtual_value]
|
||||
options[:virtual_value]
|
||||
elsif serializer && serializer.object
|
||||
ResourceIdentifier.new(serializer).as_json
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,39 @@
|
||||
module ActiveModel
|
||||
class Serializer
|
||||
module Adapter
|
||||
class JsonApi
|
||||
module ApiObjects
|
||||
class ResourceIdentifier
|
||||
def initialize(serializer)
|
||||
@id = id_for(serializer)
|
||||
@type = type_for(serializer)
|
||||
end
|
||||
|
||||
def as_json
|
||||
{ id: id, type: type }
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
attr_reader :id, :type
|
||||
|
||||
private
|
||||
|
||||
def type_for(serializer)
|
||||
return serializer._type if serializer._type
|
||||
if ActiveModelSerializers.config.jsonapi_resource_type == :singular
|
||||
serializer.object.class.model_name.singular
|
||||
else
|
||||
serializer.object.class.model_name.plural
|
||||
end
|
||||
end
|
||||
|
||||
def id_for(serializer)
|
||||
serializer.read_attribute_for_serialization(:id).to_s
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
29
lib/active_model/serializer/adapter/json_api/meta.rb
Normal file
29
lib/active_model/serializer/adapter/json_api/meta.rb
Normal file
@@ -0,0 +1,29 @@
|
||||
module ActiveModel
|
||||
class Serializer
|
||||
module Adapter
|
||||
class JsonApi
|
||||
class Meta
|
||||
def initialize(serializer)
|
||||
@object = serializer.object
|
||||
@scope = serializer.scope
|
||||
|
||||
# Use the return value of the block unless it is nil.
|
||||
if serializer._meta.respond_to?(:call)
|
||||
@value = instance_eval(&serializer._meta)
|
||||
else
|
||||
@value = serializer._meta
|
||||
end
|
||||
end
|
||||
|
||||
def as_json
|
||||
@value
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
attr_reader :object, :scope
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -9,7 +9,7 @@ module ActiveModel
|
||||
# @example
|
||||
# Association.new(:comments, CommentSummarySerializer)
|
||||
#
|
||||
Association = Struct.new(:name, :serializer, :options) do
|
||||
Association = Struct.new(:name, :serializer, :options, :links, :meta) do
|
||||
# @return [Symbol]
|
||||
#
|
||||
def key
|
||||
|
||||
29
lib/active_model/serializer/meta.rb
Normal file
29
lib/active_model/serializer/meta.rb
Normal file
@@ -0,0 +1,29 @@
|
||||
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
|
||||
@@ -34,6 +34,43 @@ module ActiveModel
|
||||
# So you can inspect reflections in your Adapters.
|
||||
#
|
||||
class Reflection < Field
|
||||
def initialize(*)
|
||||
super
|
||||
@_links = {}
|
||||
@_include_data = true
|
||||
end
|
||||
|
||||
def link(name, value = nil, &block)
|
||||
@_links[name] = block || value
|
||||
:nil
|
||||
end
|
||||
|
||||
def meta(value = nil, &block)
|
||||
@_meta = block || value
|
||||
:nil
|
||||
end
|
||||
|
||||
def include_data(value = true)
|
||||
@_include_data = value
|
||||
:nil
|
||||
end
|
||||
|
||||
def value(serializer)
|
||||
@object = serializer.object
|
||||
@scope = serializer.scope
|
||||
|
||||
if block
|
||||
block_value = instance_eval(&block)
|
||||
if block_value == :nil
|
||||
serializer.read_attribute_for_serialization(name)
|
||||
else
|
||||
block_value
|
||||
end
|
||||
else
|
||||
serializer.read_attribute_for_serialization(name)
|
||||
end
|
||||
end
|
||||
|
||||
# Build association. This method is used internally to
|
||||
# build serializer's association by its reflection.
|
||||
#
|
||||
@@ -59,6 +96,7 @@ module ActiveModel
|
||||
association_value = value(subject)
|
||||
reflection_options = options.dup
|
||||
serializer_class = subject.class.serializer_for(association_value, reflection_options)
|
||||
reflection_options[:include_data] = @_include_data
|
||||
|
||||
if serializer_class
|
||||
begin
|
||||
@@ -73,9 +111,13 @@ module ActiveModel
|
||||
reflection_options[:virtual_value] = association_value
|
||||
end
|
||||
|
||||
Association.new(name, serializer, reflection_options)
|
||||
Association.new(name, serializer, reflection_options, @_links, @_meta)
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
attr_accessor :object, :scope
|
||||
|
||||
private
|
||||
|
||||
def serializer_options(subject, parent_serializer_options, reflection_options)
|
||||
|
||||
@@ -17,7 +17,7 @@ module ActiveModel
|
||||
# class AdminAuthorSerializer < ActiveModel::Serializer
|
||||
# type 'authors'
|
||||
def type(type)
|
||||
self._type = type
|
||||
self._type = type && type.to_s
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,6 +4,7 @@ module ActiveModelSerializers
|
||||
def initialize(serializer, options = {})
|
||||
super
|
||||
@include_tree = ActiveModel::Serializer::IncludeTree.from_include_args(options[:include] || '*')
|
||||
@cached_attributes = options[:cache_attributes] || {}
|
||||
end
|
||||
|
||||
def serializable_hash(options = nil)
|
||||
@@ -23,9 +24,38 @@ module ActiveModelSerializers
|
||||
private
|
||||
|
||||
def serializable_hash_for_collection(options)
|
||||
cache_attributes
|
||||
|
||||
serializer.map { |s| Attributes.new(s, instance_options).serializable_hash(options) }
|
||||
end
|
||||
|
||||
# Read cache from cache_store
|
||||
# @return [Hash]
|
||||
def cache_read_multi
|
||||
return {} if ActiveModelSerializers.config.cache_store.blank?
|
||||
|
||||
keys = CachedSerializer.object_cache_keys(serializer, @include_tree)
|
||||
|
||||
return {} if keys.blank?
|
||||
|
||||
ActiveModelSerializers.config.cache_store.read_multi(*keys)
|
||||
end
|
||||
|
||||
# Set @cached_attributes
|
||||
def cache_attributes
|
||||
return if @cached_attributes.present?
|
||||
|
||||
@cached_attributes = cache_read_multi
|
||||
end
|
||||
|
||||
# Get attributes from @cached_attributes
|
||||
# @return [Hash] cached attributes
|
||||
def cached_attributes(cached_serializer)
|
||||
return yield unless cached_serializer.cached?
|
||||
|
||||
@cached_attributes.fetch(cached_serializer.cache_key) { yield }
|
||||
end
|
||||
|
||||
def serializable_hash_for_single_resource(options)
|
||||
resource = resource_object_for(options)
|
||||
relationships = resource_relationships(options)
|
||||
@@ -55,8 +85,12 @@ module ActiveModelSerializers
|
||||
end
|
||||
|
||||
def resource_object_for(options)
|
||||
cache_check(serializer) do
|
||||
serializer.attributes(options[:fields])
|
||||
cached_serializer = CachedSerializer.new(serializer)
|
||||
|
||||
cached_attributes(cached_serializer) do
|
||||
cached_serializer.cache_check(self) do
|
||||
serializer.attributes(options[:fields])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -23,14 +23,16 @@ module ActiveModelSerializers
|
||||
end
|
||||
|
||||
def fragment_cached?
|
||||
@klass._cache_only && !@klass._cache_except || !@klass._cache_only && @klass._cache_except
|
||||
@klass._cache && (@klass._cache_only && !@klass._cache_except || !@klass._cache_only && @klass._cache_except)
|
||||
end
|
||||
|
||||
def cache_key
|
||||
return @cache_key if defined?(@cache_key)
|
||||
|
||||
parts = []
|
||||
parts << object_cache_key
|
||||
parts << @klass._cache_digest unless @klass._cache_options && @klass._cache_options[:skip_digest]
|
||||
parts.join('/')
|
||||
@cache_key = parts.join('/')
|
||||
end
|
||||
|
||||
def object_cache_key
|
||||
@@ -38,6 +40,38 @@ module ActiveModelSerializers
|
||||
object_time_safe = object_time_safe.strftime('%Y%m%d%H%M%S%9N') if object_time_safe.respond_to?(:strftime)
|
||||
(@klass._cache_key) ? "#{@klass._cache_key}/#{@cached_serializer.object.id}-#{object_time_safe}" : @cached_serializer.object.cache_key
|
||||
end
|
||||
|
||||
# find all cache_key for the collection_serializer
|
||||
# @param collection_serializer
|
||||
# @param include_tree
|
||||
# @return [Array] all cache_key of collection_serializer
|
||||
def self.object_cache_keys(serializers, include_tree)
|
||||
cache_keys = []
|
||||
|
||||
serializers.each do |serializer|
|
||||
cache_keys << object_cache_key(serializer)
|
||||
|
||||
serializer.associations(include_tree).each do |association|
|
||||
if association.serializer.respond_to?(:each)
|
||||
association.serializer.each do |sub_serializer|
|
||||
cache_keys << object_cache_key(sub_serializer)
|
||||
end
|
||||
else
|
||||
cache_keys << object_cache_key(association.serializer)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
cache_keys.compact.uniq
|
||||
end
|
||||
|
||||
# @return [String, nil] the cache_key of the serializer or nil
|
||||
def self.object_cache_key(serializer)
|
||||
return unless serializer.present? && serializer.object.present?
|
||||
|
||||
cached_serializer = new(serializer)
|
||||
cached_serializer.cached? ? cached_serializer.cache_key : nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -93,6 +93,10 @@ module ActiveModelSerializers
|
||||
|
||||
cached.constantize.cache(klass._cache_options)
|
||||
|
||||
# Preserve the type setting in the cached/non-cached serializer classes
|
||||
cached.constantize.type(klass._type)
|
||||
non_cached.constantize.type(klass._type)
|
||||
|
||||
cached.constantize.fragmented(serializer)
|
||||
non_cached.constantize.fragmented(serializer)
|
||||
|
||||
|
||||
@@ -5,7 +5,9 @@ module ActiveModelSerializers
|
||||
autoload :PaginationLinks
|
||||
autoload :FragmentCache
|
||||
autoload :Link
|
||||
require 'active_model/serializer/adapter/json_api/meta'
|
||||
autoload :Deserialization
|
||||
require 'active_model/serializer/adapter/json_api/api_objects'
|
||||
|
||||
# TODO: if we like this abstraction and other API objects to it,
|
||||
# then extract to its own file and require it.
|
||||
@@ -96,7 +98,7 @@ module ActiveModelSerializers
|
||||
end
|
||||
|
||||
def process_resource(serializer, primary)
|
||||
resource_identifier = resource_identifier_for(serializer)
|
||||
resource_identifier = ActiveModel::Serializer::Adapter::JsonApi::ApiObjects::ResourceIdentifier.new(serializer).as_json
|
||||
return false unless @resource_identifiers.add?(resource_identifier)
|
||||
|
||||
resource_object = resource_object_for(serializer)
|
||||
@@ -126,37 +128,13 @@ module ActiveModelSerializers
|
||||
process_relationships(serializer, include_tree)
|
||||
end
|
||||
|
||||
def resource_identifier_type_for(serializer)
|
||||
return serializer._type if serializer._type
|
||||
if ActiveModelSerializers.config.jsonapi_resource_type == :singular
|
||||
serializer.object.class.model_name.singular
|
||||
else
|
||||
serializer.object.class.model_name.plural
|
||||
end
|
||||
end
|
||||
|
||||
def resource_identifier_id_for(serializer)
|
||||
if serializer.respond_to?(:id)
|
||||
serializer.id
|
||||
else
|
||||
serializer.object.id
|
||||
end
|
||||
end
|
||||
|
||||
def resource_identifier_for(serializer)
|
||||
type = resource_identifier_type_for(serializer)
|
||||
id = resource_identifier_id_for(serializer)
|
||||
|
||||
{ id: id.to_s, type: type }
|
||||
end
|
||||
|
||||
def attributes_for(serializer, fields)
|
||||
serializer.attributes(fields).except(:id)
|
||||
end
|
||||
|
||||
def resource_object_for(serializer)
|
||||
resource_object = cache_check(serializer) do
|
||||
resource_object = resource_identifier_for(serializer)
|
||||
resource_object = ActiveModel::Serializer::Adapter::JsonApi::ApiObjects::ResourceIdentifier.new(serializer).as_json
|
||||
|
||||
requested_fields = fieldset && fieldset.fields_for(resource_object[:type])
|
||||
attributes = attributes_for(serializer, requested_fields)
|
||||
@@ -164,33 +142,29 @@ module ActiveModelSerializers
|
||||
resource_object
|
||||
end
|
||||
|
||||
relationships = relationships_for(serializer)
|
||||
requested_associations = fieldset.fields_for(resource_object[:type]) || '*'
|
||||
relationships = relationships_for(serializer, requested_associations)
|
||||
resource_object[:relationships] = relationships if relationships.any?
|
||||
|
||||
links = links_for(serializer)
|
||||
resource_object[:links] = links if links.any?
|
||||
|
||||
meta = meta_for(serializer)
|
||||
resource_object[:meta] = meta unless meta.nil?
|
||||
|
||||
resource_object
|
||||
end
|
||||
|
||||
def relationship_value_for(serializer, options = {})
|
||||
if serializer.respond_to?(:each)
|
||||
serializer.map { |s| resource_identifier_for(s) }
|
||||
else
|
||||
if options[:virtual_value]
|
||||
options[:virtual_value]
|
||||
elsif serializer && serializer.object
|
||||
resource_identifier_for(serializer)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def relationships_for(serializer)
|
||||
resource_type = resource_identifier_type_for(serializer)
|
||||
requested_associations = fieldset.fields_for(resource_type) || '*'
|
||||
def relationships_for(serializer, requested_associations)
|
||||
include_tree = ActiveModel::Serializer::IncludeTree.from_include_args(requested_associations)
|
||||
serializer.associations(include_tree).each_with_object({}) do |association, hash|
|
||||
hash[association.key] = { data: relationship_value_for(association.serializer, association.options) }
|
||||
hash[association.key] = ActiveModel::Serializer::Adapter::JsonApi::ApiObjects::Relationship.new(
|
||||
serializer,
|
||||
association.serializer,
|
||||
association.options,
|
||||
association.links,
|
||||
association.meta
|
||||
).as_json
|
||||
end
|
||||
end
|
||||
|
||||
@@ -203,6 +177,10 @@ module ActiveModelSerializers
|
||||
def pagination_links_for(serializer, options)
|
||||
JsonApi::PaginationLinks.new(serializer.object, options[:serialization_context]).serializable_hash(options)
|
||||
end
|
||||
|
||||
def meta_for(serializer)
|
||||
ActiveModel::Serializer::Adapter::JsonApi::Meta.new(serializer).as_json
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -27,8 +27,9 @@ module ActiveModelSerializers
|
||||
def as_json
|
||||
return @value if @value
|
||||
|
||||
hash = { href: @href }
|
||||
hash.merge!(meta: @meta) if @meta
|
||||
hash = {}
|
||||
hash[:href] = @href if @href
|
||||
hash[:meta] = @meta if @meta
|
||||
|
||||
hash
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user