mirror of
https://github.com/ditkrg/active_model_serializers.git
synced 2026-01-25 07:16:49 +00:00
Moved the adapter and adapter folder to active_model_serializers folder and changed the module namespace
Changed the namespace in adapters and folder to active_model_serializers from active_model::serializer Changed namespace of adapters in serializers and other folders Moved adapter_for_test file to active_model_serializers folder and changed namespace of adapter inside the test file Require ActiveSupport's string/inflections We depend on string/inflections to define String#underscore. Refactor JsonApi adapter to avoid redundant computations. Update readme.md to link to v0.10.0.rc4 changed namespace of adapter folder testcases Changed all namespaces of adapter under active_moder_serializers Namespaced IncludeTree which is from serializer module, so needed to namespace it properly Fixed wrong namsepacing of fieldset namespace change in deserializer json_api Fixed the namespace for collection serializer when used inside adapter, changed namespace for adapter to new namespace which I had forgotten previously Modified logging test and adapter test cases to make the testcases pass Changed the yardoc links,as old links are not taking to documentation pages,proper links for 0.10,0.9 and 0.8 in rubydoc Rubocop errors are fixed by underscore naming unused variables Moved the require of adapter to serializable resource Remoeved adapter dependency inside serializer and added warning to Serializer::adapter method Fixed frament cache test which is calling Serializer.adapter Changed the name of lookup_adapter_from_config to configured_adapter Changed the docs which will show the new namespace of adapters Rubocop fix
This commit is contained in:
committed by
Benjamin Fleischer
parent
f5ec8ed9d4
commit
252f9c4ae9
@@ -1,66 +0,0 @@
|
||||
module ActiveModel
|
||||
class Serializer
|
||||
module Adapter
|
||||
class Attributes < Base
|
||||
def initialize(serializer, options = {})
|
||||
super
|
||||
@include_tree = IncludeTree.from_include_args(options[:include] || '*')
|
||||
end
|
||||
|
||||
def serializable_hash(options = nil)
|
||||
options ||= {}
|
||||
|
||||
if serializer.respond_to?(:each)
|
||||
serializable_hash_for_collection(options)
|
||||
else
|
||||
serializable_hash_for_single_resource(options)
|
||||
end
|
||||
end
|
||||
|
||||
def fragment_cache(cached_hash, non_cached_hash)
|
||||
Json::FragmentCache.new.fragment_cache(cached_hash, non_cached_hash)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def serializable_hash_for_collection(options)
|
||||
serializer.map { |s| Attributes.new(s, instance_options).serializable_hash(options) }
|
||||
end
|
||||
|
||||
def serializable_hash_for_single_resource(options)
|
||||
resource = resource_object_for(options)
|
||||
relationships = resource_relationships(options)
|
||||
resource.merge!(relationships)
|
||||
end
|
||||
|
||||
def resource_relationships(options)
|
||||
relationships = {}
|
||||
serializer.associations(@include_tree).each do |association|
|
||||
relationships[association.key] = relationship_value_for(association, options)
|
||||
end
|
||||
|
||||
relationships
|
||||
end
|
||||
|
||||
def relationship_value_for(association, options)
|
||||
return association.options[:virtual_value] if association.options[:virtual_value]
|
||||
return unless association.serializer && association.serializer.object
|
||||
|
||||
opts = instance_options.merge(include: @include_tree[association.key])
|
||||
Attributes.new(association.serializer, opts).serializable_hash(options)
|
||||
end
|
||||
|
||||
# no-op: Attributes adapter does not include meta data, because it does not support root.
|
||||
def include_meta(json)
|
||||
json
|
||||
end
|
||||
|
||||
def resource_object_for(options)
|
||||
cache_check(serializer) do
|
||||
serializer.attributes(options[:fields])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,58 +0,0 @@
|
||||
module ActiveModel
|
||||
class Serializer
|
||||
module Adapter
|
||||
class Base
|
||||
# Automatically register adapters when subclassing
|
||||
def self.inherited(subclass)
|
||||
ActiveModel::Serializer::Adapter.register(subclass)
|
||||
end
|
||||
|
||||
attr_reader :serializer, :instance_options
|
||||
|
||||
def initialize(serializer, options = {})
|
||||
@serializer = serializer
|
||||
@instance_options = options
|
||||
end
|
||||
|
||||
def serializable_hash(_options = nil)
|
||||
fail NotImplementedError, 'This is an abstract method. Should be implemented at the concrete adapter.'
|
||||
end
|
||||
|
||||
def as_json(options = nil)
|
||||
hash = serializable_hash(options)
|
||||
include_meta(hash)
|
||||
hash
|
||||
end
|
||||
|
||||
def fragment_cache(*_args)
|
||||
fail NotImplementedError, 'This is an abstract method. Should be implemented at the concrete adapter.'
|
||||
end
|
||||
|
||||
def cache_check(serializer)
|
||||
CachedSerializer.new(serializer).cache_check(self) do
|
||||
yield
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def meta
|
||||
instance_options.fetch(:meta, nil)
|
||||
end
|
||||
|
||||
def meta_key
|
||||
instance_options.fetch(:meta_key, 'meta'.freeze)
|
||||
end
|
||||
|
||||
def root
|
||||
serializer.json_key.to_sym if serializer.json_key
|
||||
end
|
||||
|
||||
def include_meta(json)
|
||||
json[meta_key] = meta if meta
|
||||
json
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,45 +0,0 @@
|
||||
module ActiveModel
|
||||
class Serializer
|
||||
module Adapter
|
||||
class CachedSerializer
|
||||
def initialize(serializer)
|
||||
@cached_serializer = serializer
|
||||
@klass = @cached_serializer.class
|
||||
end
|
||||
|
||||
def cache_check(adapter_instance)
|
||||
if cached?
|
||||
@klass._cache.fetch(cache_key, @klass._cache_options) do
|
||||
yield
|
||||
end
|
||||
elsif fragment_cached?
|
||||
FragmentCache.new(adapter_instance, @cached_serializer, adapter_instance.instance_options).fetch
|
||||
else
|
||||
yield
|
||||
end
|
||||
end
|
||||
|
||||
def cached?
|
||||
@klass._cache && !@klass._cache_only && !@klass._cache_except
|
||||
end
|
||||
|
||||
def fragment_cached?
|
||||
@klass._cache_only && !@klass._cache_except || !@klass._cache_only && @klass._cache_except
|
||||
end
|
||||
|
||||
def cache_key
|
||||
parts = []
|
||||
parts << object_cache_key
|
||||
parts << @klass._cache_digest unless @klass._cache_options && @klass._cache_options[:skip_digest]
|
||||
parts.join('/')
|
||||
end
|
||||
|
||||
def object_cache_key
|
||||
object_time_safe = @cached_serializer.object.updated_at
|
||||
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
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,111 +0,0 @@
|
||||
module ActiveModel
|
||||
class Serializer
|
||||
module Adapter
|
||||
class FragmentCache
|
||||
attr_reader :serializer
|
||||
|
||||
def initialize(adapter, serializer, options)
|
||||
@instance_options = options
|
||||
@adapter = adapter
|
||||
@serializer = serializer
|
||||
end
|
||||
|
||||
# TODO: Use Serializable::Resource
|
||||
# TODO: call +constantize+ less
|
||||
# 1. Create a CachedSerializer and NonCachedSerializer from the serializer class
|
||||
# 2. Serialize the above two with the given adapter
|
||||
# 3. Pass their serializations to the adapter +::fragment_cache+
|
||||
def fetch
|
||||
klass = serializer.class
|
||||
# It will split the serializer into two, one that will be cached and one that will not
|
||||
serializers = fragment_serializer(serializer.object.class.name, klass)
|
||||
|
||||
# Instantiate both serializers
|
||||
cached_serializer = serializers[:cached].constantize.new(serializer.object)
|
||||
non_cached_serializer = serializers[:non_cached].constantize.new(serializer.object)
|
||||
|
||||
cached_adapter = adapter.class.new(cached_serializer, instance_options)
|
||||
non_cached_adapter = adapter.class.new(non_cached_serializer, instance_options)
|
||||
|
||||
# Get serializable hash from both
|
||||
cached_hash = cached_adapter.serializable_hash
|
||||
non_cached_hash = non_cached_adapter.serializable_hash
|
||||
|
||||
# Merge both results
|
||||
adapter.fragment_cache(cached_hash, non_cached_hash)
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
attr_reader :instance_options, :adapter
|
||||
|
||||
private
|
||||
|
||||
# Given a serializer class and a hash of its cached and non-cached serializers
|
||||
# 1. Determine cached attributes from serializer class options
|
||||
# 2. Add cached attributes to cached Serializer
|
||||
# 3. Add non-cached attributes to non-cached Serializer
|
||||
def cached_attributes(klass, serializers)
|
||||
attributes = serializer.class._attributes
|
||||
cached_attributes = (klass._cache_only) ? klass._cache_only : attributes.reject { |attr| klass._cache_except.include?(attr) }
|
||||
non_cached_attributes = attributes - cached_attributes
|
||||
|
||||
cached_attributes.each do |attribute|
|
||||
options = serializer.class._attributes_keys[attribute]
|
||||
options ||= {}
|
||||
# Add cached attributes to cached Serializer
|
||||
serializers[:cached].constantize.attribute(attribute, options)
|
||||
end
|
||||
|
||||
non_cached_attributes.each do |attribute|
|
||||
options = serializer.class._attributes_keys[attribute]
|
||||
options ||= {}
|
||||
# Add non-cached attributes to non-cached Serializer
|
||||
serializers[:non_cached].constantize.attribute(attribute, options)
|
||||
end
|
||||
end
|
||||
|
||||
# Given a resource name and its serializer's class
|
||||
# 1. Dyanmically creates a CachedSerializer and NonCachedSerializer
|
||||
# for a given class 'name'
|
||||
# 2. Call
|
||||
# CachedSerializer.cache(serializer._cache_options)
|
||||
# CachedSerializer.fragmented(serializer)
|
||||
# NontCachedSerializer.cache(serializer._cache_options)
|
||||
# 3. Build a hash keyed to the +cached+ and +non_cached+ serializers
|
||||
# 4. Call +cached_attributes+ on the serializer class and the above hash
|
||||
# 5. Return the hash
|
||||
#
|
||||
# @example
|
||||
# When +name+ is <tt>User::Admin</tt>
|
||||
# creates the Serializer classes (if they don't exist).
|
||||
# User_AdminCachedSerializer
|
||||
# User_AdminNOnCachedSerializer
|
||||
#
|
||||
def fragment_serializer(name, klass)
|
||||
cached = "#{to_valid_const_name(name)}CachedSerializer"
|
||||
non_cached = "#{to_valid_const_name(name)}NonCachedSerializer"
|
||||
|
||||
Object.const_set cached, Class.new(ActiveModel::Serializer) unless Object.const_defined?(cached)
|
||||
Object.const_set non_cached, Class.new(ActiveModel::Serializer) unless Object.const_defined?(non_cached)
|
||||
|
||||
klass._cache_options ||= {}
|
||||
klass._cache_options[:key] = klass._cache_key if klass._cache_key
|
||||
|
||||
cached.constantize.cache(klass._cache_options)
|
||||
|
||||
cached.constantize.fragmented(serializer)
|
||||
non_cached.constantize.fragmented(serializer)
|
||||
|
||||
serializers = { cached: cached, non_cached: non_cached }
|
||||
cached_attributes(klass, serializers)
|
||||
serializers
|
||||
end
|
||||
|
||||
def to_valid_const_name(name)
|
||||
name.gsub('::', '_')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,21 +0,0 @@
|
||||
module ActiveModel
|
||||
class Serializer
|
||||
module Adapter
|
||||
class Json < Base
|
||||
extend ActiveSupport::Autoload
|
||||
autoload :FragmentCache
|
||||
|
||||
def serializable_hash(options = nil)
|
||||
options ||= {}
|
||||
{ root => Attributes.new(serializer, instance_options).serializable_hash(options) }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def fragment_cache(cached_hash, non_cached_hash)
|
||||
ActiveModel::Serializer::Adapter::Json::FragmentCache.new.fragment_cache(cached_hash, non_cached_hash)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,13 +0,0 @@
|
||||
module ActiveModel
|
||||
class Serializer
|
||||
module Adapter
|
||||
class Json
|
||||
class FragmentCache
|
||||
def fragment_cache(cached_hash, non_cached_hash)
|
||||
non_cached_hash.merge cached_hash
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,223 +0,0 @@
|
||||
module ActiveModel
|
||||
class Serializer
|
||||
module Adapter
|
||||
class JsonApi < Base
|
||||
extend ActiveSupport::Autoload
|
||||
autoload :PaginationLinks
|
||||
autoload :FragmentCache
|
||||
autoload :Link
|
||||
autoload :Deserialization
|
||||
|
||||
# TODO: if we like this abstraction and other API objects to it,
|
||||
# then extract to its own file and require it.
|
||||
module ApiObjects
|
||||
module JsonApi
|
||||
ActiveModelSerializers.config.jsonapi_version = '1.0'
|
||||
ActiveModelSerializers.config.jsonapi_toplevel_meta = {}
|
||||
# Make JSON API top-level jsonapi member opt-in
|
||||
# ref: http://jsonapi.org/format/#document-top-level
|
||||
ActiveModelSerializers.config.jsonapi_include_toplevel_object = false
|
||||
|
||||
module_function
|
||||
|
||||
def add!(hash)
|
||||
hash.merge!(object) if include_object?
|
||||
end
|
||||
|
||||
def include_object?
|
||||
ActiveModelSerializers.config.jsonapi_include_toplevel_object
|
||||
end
|
||||
|
||||
# TODO: see if we can cache this
|
||||
def object
|
||||
object = {
|
||||
jsonapi: {
|
||||
version: ActiveModelSerializers.config.jsonapi_version,
|
||||
meta: ActiveModelSerializers.config.jsonapi_toplevel_meta
|
||||
}
|
||||
}
|
||||
object[:jsonapi].reject! { |_, v| v.blank? }
|
||||
|
||||
object
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(serializer, options = {})
|
||||
super
|
||||
@include_tree = IncludeTree.from_include_args(options[:include])
|
||||
@fieldset = options[:fieldset] || ActiveModel::Serializer::Fieldset.new(options.delete(:fields))
|
||||
end
|
||||
|
||||
def serializable_hash(options = nil)
|
||||
options ||= {}
|
||||
|
||||
hash =
|
||||
if serializer.respond_to?(:each)
|
||||
serializable_hash_for_collection(options)
|
||||
else
|
||||
serializable_hash_for_single_resource
|
||||
end
|
||||
|
||||
ApiObjects::JsonApi.add!(hash)
|
||||
|
||||
if instance_options[:links]
|
||||
hash[:links] ||= {}
|
||||
hash[:links].update(instance_options[:links])
|
||||
end
|
||||
|
||||
hash
|
||||
end
|
||||
|
||||
def fragment_cache(cached_hash, non_cached_hash)
|
||||
root = false if instance_options.include?(:include)
|
||||
ActiveModel::Serializer::Adapter::JsonApi::FragmentCache.new.fragment_cache(root, cached_hash, non_cached_hash)
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
attr_reader :fieldset
|
||||
|
||||
private
|
||||
|
||||
def serializable_hash_for_collection(options)
|
||||
hash = { data: [] }
|
||||
included = []
|
||||
serializer.each do |s|
|
||||
result = self.class.new(s, instance_options.merge(fieldset: fieldset)).serializable_hash(options)
|
||||
hash[:data] << result[:data]
|
||||
next unless result[:included]
|
||||
|
||||
included |= result[:included]
|
||||
end
|
||||
|
||||
included.delete_if { |resource| hash[:data].include?(resource) }
|
||||
hash[:included] = included if included.any?
|
||||
|
||||
if serializer.paginated?
|
||||
hash[:links] ||= {}
|
||||
hash[:links].update(pagination_links_for(serializer, options))
|
||||
end
|
||||
|
||||
hash
|
||||
end
|
||||
|
||||
def serializable_hash_for_single_resource
|
||||
primary_data = resource_object_for(serializer)
|
||||
|
||||
hash = { data: primary_data }
|
||||
|
||||
included = included_resources(@include_tree, [primary_data])
|
||||
hash[:included] = included if included.any?
|
||||
|
||||
hash
|
||||
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)
|
||||
|
||||
requested_fields = fieldset && fieldset.fields_for(resource_object[:type])
|
||||
attributes = attributes_for(serializer, requested_fields)
|
||||
resource_object[:attributes] = attributes if attributes.any?
|
||||
resource_object
|
||||
end
|
||||
|
||||
relationships = relationships_for(serializer)
|
||||
resource_object[:relationships] = relationships if relationships.any?
|
||||
|
||||
links = links_for(serializer)
|
||||
resource_object[:links] = links if links.any?
|
||||
|
||||
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) || '*'
|
||||
include_tree = 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) }
|
||||
end
|
||||
end
|
||||
|
||||
def included_resources(include_tree, primary_data)
|
||||
included = []
|
||||
|
||||
serializer.associations(include_tree).each do |association|
|
||||
add_included_resources_for(association.serializer, include_tree[association.key], primary_data, included)
|
||||
end
|
||||
|
||||
included
|
||||
end
|
||||
|
||||
def add_included_resources_for(serializer, include_tree, primary_data, included)
|
||||
if serializer.respond_to?(:each)
|
||||
serializer.each { |s| add_included_resources_for(s, include_tree, primary_data, included) }
|
||||
else
|
||||
return unless serializer && serializer.object
|
||||
|
||||
resource_object = resource_object_for(serializer)
|
||||
|
||||
return if included.include?(resource_object) || primary_data.include?(resource_object)
|
||||
included.push(resource_object)
|
||||
|
||||
serializer.associations(include_tree).each do |association|
|
||||
add_included_resources_for(association.serializer, include_tree[association.key], primary_data, included)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def links_for(serializer)
|
||||
serializer._links.each_with_object({}) do |(name, value), hash|
|
||||
hash[name] = Link.new(serializer, value).as_json
|
||||
end
|
||||
end
|
||||
|
||||
def pagination_links_for(serializer, options)
|
||||
JsonApi::PaginationLinks.new(serializer.object, options[:serialization_context]).serializable_hash(options)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,207 +0,0 @@
|
||||
module ActiveModel
|
||||
class Serializer
|
||||
module Adapter
|
||||
class JsonApi
|
||||
# NOTE(Experimental):
|
||||
# This is an experimental feature. Both the interface and internals could be subject
|
||||
# to changes.
|
||||
module Deserialization
|
||||
InvalidDocument = Class.new(ArgumentError)
|
||||
|
||||
module_function
|
||||
|
||||
# Transform a JSON API document, containing a single data object,
|
||||
# into a hash that is ready for ActiveRecord::Base.new() and such.
|
||||
# Raises InvalidDocument if the payload is not properly formatted.
|
||||
#
|
||||
# @param [Hash|ActionController::Parameters] document
|
||||
# @param [Hash] options
|
||||
# only: Array of symbols of whitelisted fields.
|
||||
# except: Array of symbols of blacklisted fields.
|
||||
# keys: Hash of translated keys (e.g. :author => :user).
|
||||
# polymorphic: Array of symbols of polymorphic fields.
|
||||
# @return [Hash]
|
||||
#
|
||||
# @example
|
||||
# document = {
|
||||
# data: {
|
||||
# id: 1,
|
||||
# type: 'post',
|
||||
# attributes: {
|
||||
# title: 'Title 1',
|
||||
# date: '2015-12-20'
|
||||
# },
|
||||
# associations: {
|
||||
# author: {
|
||||
# data: {
|
||||
# type: 'user',
|
||||
# id: 2
|
||||
# }
|
||||
# },
|
||||
# second_author: {
|
||||
# data: nil
|
||||
# },
|
||||
# comments: {
|
||||
# data: [{
|
||||
# type: 'comment',
|
||||
# id: 3
|
||||
# },{
|
||||
# type: 'comment',
|
||||
# id: 4
|
||||
# }]
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
#
|
||||
# parse(document) #=>
|
||||
# # {
|
||||
# # title: 'Title 1',
|
||||
# # date: '2015-12-20',
|
||||
# # author_id: 2,
|
||||
# # second_author_id: nil
|
||||
# # comment_ids: [3, 4]
|
||||
# # }
|
||||
#
|
||||
# parse(document, only: [:title, :date, :author],
|
||||
# keys: { date: :published_at },
|
||||
# polymorphic: [:author]) #=>
|
||||
# # {
|
||||
# # title: 'Title 1',
|
||||
# # published_at: '2015-12-20',
|
||||
# # author_id: '2',
|
||||
# # author_type: 'people'
|
||||
# # }
|
||||
#
|
||||
def parse!(document, options = {})
|
||||
parse(document, options) do |invalid_payload, reason|
|
||||
fail InvalidDocument, "Invalid payload (#{reason}): #{invalid_payload}"
|
||||
end
|
||||
end
|
||||
|
||||
# Same as parse!, but returns an empty hash instead of raising InvalidDocument
|
||||
# on invalid payloads.
|
||||
def parse(document, options = {})
|
||||
document = document.dup.permit!.to_h if document.is_a?(ActionController::Parameters)
|
||||
|
||||
validate_payload(document) do |invalid_document, reason|
|
||||
yield invalid_document, reason if block_given?
|
||||
return {}
|
||||
end
|
||||
|
||||
primary_data = document['data']
|
||||
attributes = primary_data['attributes'] || {}
|
||||
attributes['id'] = primary_data['id'] if primary_data['id']
|
||||
relationships = primary_data['relationships'] || {}
|
||||
|
||||
filter_fields(attributes, options)
|
||||
filter_fields(relationships, options)
|
||||
|
||||
hash = {}
|
||||
hash.merge!(parse_attributes(attributes, options))
|
||||
hash.merge!(parse_relationships(relationships, options))
|
||||
|
||||
hash
|
||||
end
|
||||
|
||||
# Checks whether a payload is compliant with the JSON API spec.
|
||||
#
|
||||
# @api private
|
||||
# rubocop:disable Metrics/CyclomaticComplexity
|
||||
def validate_payload(payload)
|
||||
unless payload.is_a?(Hash)
|
||||
yield payload, 'Expected hash'
|
||||
return
|
||||
end
|
||||
|
||||
primary_data = payload['data']
|
||||
unless primary_data.is_a?(Hash)
|
||||
yield payload, { data: 'Expected hash' }
|
||||
return
|
||||
end
|
||||
|
||||
attributes = primary_data['attributes'] || {}
|
||||
unless attributes.is_a?(Hash)
|
||||
yield payload, { data: { attributes: 'Expected hash or nil' } }
|
||||
return
|
||||
end
|
||||
|
||||
relationships = primary_data['relationships'] || {}
|
||||
unless relationships.is_a?(Hash)
|
||||
yield payload, { data: { relationships: 'Expected hash or nil' } }
|
||||
return
|
||||
end
|
||||
|
||||
relationships.each do |(key, value)|
|
||||
unless value.is_a?(Hash) && value.key?('data')
|
||||
yield payload, { data: { relationships: { key => 'Expected hash with :data key' } } }
|
||||
end
|
||||
end
|
||||
end
|
||||
# rubocop:enable Metrics/CyclomaticComplexity
|
||||
|
||||
# @api private
|
||||
def filter_fields(fields, options)
|
||||
if (only = options[:only])
|
||||
fields.slice!(*Array(only).map(&:to_s))
|
||||
elsif (except = options[:except])
|
||||
fields.except!(*Array(except).map(&:to_s))
|
||||
end
|
||||
end
|
||||
|
||||
# @api private
|
||||
def field_key(field, options)
|
||||
(options[:keys] || {}).fetch(field.to_sym, field).to_sym
|
||||
end
|
||||
|
||||
# @api private
|
||||
def parse_attributes(attributes, options)
|
||||
attributes
|
||||
.map { |(k, v)| { field_key(k, options) => v } }
|
||||
.reduce({}, :merge)
|
||||
end
|
||||
|
||||
# Given an association name, and a relationship data attribute, build a hash
|
||||
# mapping the corresponding ActiveRecord attribute to the corresponding value.
|
||||
#
|
||||
# @example
|
||||
# parse_relationship(:comments, [{ 'id' => '1', 'type' => 'comments' },
|
||||
# { 'id' => '2', 'type' => 'comments' }],
|
||||
# {})
|
||||
# # => { :comment_ids => ['1', '2'] }
|
||||
# parse_relationship(:author, { 'id' => '1', 'type' => 'users' }, {})
|
||||
# # => { :author_id => '1' }
|
||||
# parse_relationship(:author, nil, {})
|
||||
# # => { :author_id => nil }
|
||||
# @param [Symbol] assoc_name
|
||||
# @param [Hash] assoc_data
|
||||
# @param [Hash] options
|
||||
# @return [Hash{Symbol, Object}]
|
||||
#
|
||||
# @api private
|
||||
def parse_relationship(assoc_name, assoc_data, options)
|
||||
prefix_key = field_key(assoc_name, options).to_s.singularize
|
||||
hash =
|
||||
if assoc_data.is_a?(Array)
|
||||
{ "#{prefix_key}_ids".to_sym => assoc_data.map { |ri| ri['id'] } }
|
||||
else
|
||||
{ "#{prefix_key}_id".to_sym => assoc_data ? assoc_data['id'] : nil }
|
||||
end
|
||||
|
||||
polymorphic = (options[:polymorphic] || []).include?(assoc_name.to_sym)
|
||||
hash.merge!("#{prefix_key}_type".to_sym => assoc_data['type']) if polymorphic
|
||||
|
||||
hash
|
||||
end
|
||||
|
||||
# @api private
|
||||
def parse_relationships(relationships, options)
|
||||
relationships
|
||||
.map { |(k, v)| parse_relationship(k, v['data'], options) }
|
||||
.reduce({}, :merge)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,21 +0,0 @@
|
||||
module ActiveModel
|
||||
class Serializer
|
||||
module Adapter
|
||||
class JsonApi
|
||||
class FragmentCache
|
||||
def fragment_cache(root, cached_hash, non_cached_hash)
|
||||
hash = {}
|
||||
core_cached = cached_hash.first
|
||||
core_non_cached = non_cached_hash.first
|
||||
no_root_cache = cached_hash.delete_if { |key, value| key == core_cached[0] }
|
||||
no_root_non_cache = non_cached_hash.delete_if { |key, value| key == core_non_cached[0] }
|
||||
cached_resource = (core_cached[1]) ? core_cached[1].deep_merge(core_non_cached[1]) : core_non_cached[1]
|
||||
hash = (root) ? { root => cached_resource } : cached_resource
|
||||
|
||||
hash.deep_merge no_root_non_cache.deep_merge no_root_cache
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,44 +0,0 @@
|
||||
module ActiveModel
|
||||
class Serializer
|
||||
module Adapter
|
||||
class JsonApi
|
||||
class Link
|
||||
def initialize(serializer, value)
|
||||
@object = serializer.object
|
||||
@scope = serializer.scope
|
||||
|
||||
# Use the return value of the block unless it is nil.
|
||||
if value.respond_to?(:call)
|
||||
@value = instance_eval(&value)
|
||||
else
|
||||
@value = value
|
||||
end
|
||||
end
|
||||
|
||||
def href(value)
|
||||
@href = value
|
||||
nil
|
||||
end
|
||||
|
||||
def meta(value)
|
||||
@meta = value
|
||||
nil
|
||||
end
|
||||
|
||||
def as_json
|
||||
return @value if @value
|
||||
|
||||
hash = { href: @href }
|
||||
hash.merge!(meta: @meta) if @meta
|
||||
|
||||
hash
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
attr_reader :object, :scope
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,58 +0,0 @@
|
||||
module ActiveModel
|
||||
class Serializer
|
||||
module Adapter
|
||||
class JsonApi < Base
|
||||
class PaginationLinks
|
||||
FIRST_PAGE = 1
|
||||
|
||||
attr_reader :collection, :context
|
||||
|
||||
def initialize(collection, context)
|
||||
@collection = collection
|
||||
@context = context
|
||||
end
|
||||
|
||||
def serializable_hash(options = {})
|
||||
pages_from.each_with_object({}) do |(key, value), hash|
|
||||
params = query_parameters.merge(page: { number: value, size: collection.size }).to_query
|
||||
|
||||
hash[key] = "#{url(options)}?#{params}"
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def pages_from
|
||||
return {} if collection.total_pages == FIRST_PAGE
|
||||
|
||||
{}.tap do |pages|
|
||||
pages[:self] = collection.current_page
|
||||
|
||||
unless collection.current_page == FIRST_PAGE
|
||||
pages[:first] = FIRST_PAGE
|
||||
pages[:prev] = collection.current_page - FIRST_PAGE
|
||||
end
|
||||
|
||||
unless collection.current_page == collection.total_pages
|
||||
pages[:next] = collection.current_page + FIRST_PAGE
|
||||
pages[:last] = collection.total_pages
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def url(options)
|
||||
@url ||= options.fetch(:links, {}).fetch(:self, nil) || request_url
|
||||
end
|
||||
|
||||
def request_url
|
||||
@request_url ||= context.request_url
|
||||
end
|
||||
|
||||
def query_parameters
|
||||
@query_parameters ||= context.query_parameters
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,11 +0,0 @@
|
||||
module ActiveModel
|
||||
class Serializer
|
||||
module Adapter
|
||||
class Null < Base
|
||||
def serializable_hash(options = nil)
|
||||
{}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user