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:
Benjamin Fleischer
2016-02-23 23:21:49 -06:00
39 changed files with 1357 additions and 341 deletions

View File

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

View 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

View File

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

View File

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

View 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

View File

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

View 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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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