Merge pull request #1535 from bf4/domitian-move-namespace-of-adapter-to-active-model-serializers

Moved the adapter and adapter folder to active_model_serializers folder and changed the module namespace
This commit is contained in:
Yohan Robert 2016-02-28 12:58:36 +01:00
commit 8a040052af
67 changed files with 3032 additions and 3114 deletions

View File

@ -222,7 +222,6 @@ Style/TrailingBlankLines:
- 'test/adapter/null_test.rb'
- 'test/serializers/cache_test.rb'
- 'test/serializers/fieldset_test.rb'
- 'test/support/stream_capture.rb'
# Offense count: 5
# Cop supports --auto-correct.

View File

@ -12,7 +12,9 @@
- [![API Docs](http://img.shields.io/badge/yard-docs-blue.svg)](http://www.rubydoc.info/github/rails-api/active_model_serializers/v0.10.0.rc4)
- [Guides](docs)
- [0.9 (0-9-stable) Documentation](https://github.com/rails-api/active_model_serializers/tree/0-9-stable)
- [![API Docs](http://img.shields.io/badge/yard-docs-blue.svg)](http://www.rubydoc.info/github/rails-api/active_model_serializers/0-9-stable)
- [0.8 (0-8-stable) Documentation](https://github.com/rails-api/active_model_serializers/tree/0-8-stable)
- [![API Docs](http://img.shields.io/badge/yard-docs-blue.svg)](http://www.rubydoc.info/github/rails-api/active_model_serializers/0-8-stable)
## About

View File

@ -11,7 +11,7 @@ It should be set only once, preferably at initialization.
For example:
```ruby
ActiveModelSerializers.config.adapter = ActiveModel::Serializer::Adapter::JsonApi
ActiveModelSerializers.config.adapter = ActiveModelSerializers::Adapter::JsonApi
```
or
@ -117,46 +117,46 @@ The default adapter can be configured, as above, to use any class given to it.
An adapter may also be specified, e.g. when rendering, as a class or as a symbol.
If a symbol, then the adapter must be, e.g. `:great_example`,
`ActiveModel::Serializer::Adapter::GreatExample`, or registered.
`ActiveModelSerializers::Adapter::GreatExample`, or registered.
There are two ways to register an adapter:
1) The simplest, is to subclass `ActiveModel::Serializer::Adapter::Base`, e.g. the below will
1) The simplest, is to subclass `ActiveModelSerializers::Adapter::Base`, e.g. the below will
register the `Example::UsefulAdapter` as `"example/useful_adapter"`.
```ruby
module Example
class UsefulAdapter < ActiveModel::Serializer::Adapter::Base
class UsefulAdapter < ActiveModelSerializers::Adapter::Base
end
end
```
You'll notice that the name it registers is the underscored namespace and class.
Under the covers, when the `ActiveModel::Serializer::Adapter::Base` is subclassed, it registers
Under the covers, when the `ActiveModelSerializers::Adapter::Base` is subclassed, it registers
the subclass as `register("example/useful_adapter", Example::UsefulAdapter)`
2) Any class can be registered as an adapter by calling `register` directly on the
`ActiveModel::Serializer::Adapter` class. e.g., the below registers `MyAdapter` as
`ActiveModelSerializers::Adapter` class. e.g., the below registers `MyAdapter` as
`:special_adapter`.
```ruby
class MyAdapter; end
ActiveModel::Serializer::Adapter.register(:special_adapter, MyAdapter)
ActiveModelSerializers::Adapter.register(:special_adapter, MyAdapter)
```
### Looking up an adapter
| Method | Return value |
| :------------ |:---------------|
| `ActiveModel::Serializer::Adapter.adapter_map` | A Hash of all known adapters `{ adapter_name => adapter_class }` |
| `ActiveModel::Serializer::Adapter.adapters` | A (sorted) Array of all known `adapter_names` |
| `ActiveModel::Serializer::Adapter.lookup(name_or_klass)` | The `adapter_class`, else raises an `ActiveModel::Serializer::Adapter::UnknownAdapter` error |
| `ActiveModel::Serializer::Adapter.adapter_class(adapter)` | Delegates to `ActiveModel::Serializer::Adapter.lookup(adapter)` |
| `ActiveModel::Serializer.adapter` | A convenience method for `ActiveModel::Serializer::Adapter.lookup(config.adapter)` |
| `ActiveModelSerializers::Adapter.adapter_map` | A Hash of all known adapters `{ adapter_name => adapter_class }` |
| `ActiveModelSerializers::Adapter.adapters` | A (sorted) Array of all known `adapter_names` |
| `ActiveModelSerializers::Adapter.lookup(name_or_klass)` | The `adapter_class`, else raises an `ActiveModelSerializers::Adapter::UnknownAdapter` error |
| `ActiveModelSerializers::Adapter.adapter_class(adapter)` | Delegates to `ActiveModelSerializers::Adapter.lookup(adapter)` |
| `ActiveModelSerializers::Adapter.configured_adapter` | A convenience method for `ActiveModelSerializers::Adapter.lookup(config.adapter)` |
The registered adapter name is always a String, but may be looked up as a Symbol or String.
Helpfully, the Symbol or String is underscored, so that `get(:my_adapter)` and `get("MyAdapter")`
may both be used.
For more information, see [the Adapter class on GitHub](https://github.com/rails-api/active_model_serializers/blob/master/lib/active_model/serializer/adapter.rb)
For more information, see [the Adapter class on GitHub](https://github.com/rails-api/active_model_serializers/blob/master/lib/active_model_serializers/adapter.rb)

View File

@ -17,7 +17,7 @@ Payload (example):
```ruby
{
serializer: PostSerializer,
adapter: ActiveModel::Serializer::Adapter::Attributes
adapter: ActiveModelSerializers::Adapter::Attributes
}
```

View File

@ -70,7 +70,7 @@ at the first moment.
## Renaming of class and modules
When moving some content to the new namespace we can find some names that does
not make much sense like `ActiveModelSerializers::Serializer::Adapter::JsonApi`.
not make much sense like `ActiveModel::Serializer::Adapter::JsonApi`.
Discussion of renaming existing classes / modules and JsonApi objects will
happen in separate pull requests, and issues, and in the google doc
https://docs.google.com/document/d/1rcrJr0sVcazY2Opd_6Kmv1iIwuHbI84s1P_NzFn-05c/edit?usp=sharing

View File

@ -1,4 +1,5 @@
require 'set'
require 'active_model_serializers/adapter'
module ActiveModel
class SerializableResource
ADAPTER_OPTION_KEYS = Set.new([:include, :fields, :adapter, :meta, :meta_key, :links])
@ -30,7 +31,7 @@ module ActiveModel
end
def adapter
@adapter ||= ActiveModel::Serializer::Adapter.create(serializer_instance, adapter_opts)
@adapter ||= ActiveModelSerializers::Adapter.create(serializer_instance, adapter_opts)
end
alias_method :adapter_instance, :adapter

View File

@ -23,7 +23,6 @@ module ActiveModel
include Links
include Meta
include Type
require 'active_model/serializer/adapter'
# @param resource [ActiveRecord::Base, ActiveModelSerializers::Model]
# @return [ActiveModel::Serializer]
@ -42,9 +41,11 @@ module ActiveModel
end
end
# @see ActiveModel::Serializer::Adapter.lookup
# @see ActiveModelSerializers::Adapter.lookup
# Deprecated
def self.adapter
ActiveModel::Serializer::Adapter.lookup(config.adapter)
warn 'Calling adapter method in Serializer, please use the ActiveModelSerializers::configured_adapter'
ActiveModelSerializers::Adapter.lookup(config.adapter)
end
# @api private

View File

@ -1,91 +0,0 @@
module ActiveModel
class Serializer
module Adapter
UnknownAdapterError = Class.new(ArgumentError)
ADAPTER_MAP = {}
private_constant :ADAPTER_MAP if defined?(private_constant)
require 'active_model/serializer/adapter/fragment_cache'
require 'active_model/serializer/adapter/cached_serializer'
class << self # All methods are class functions
def new(*args)
fail ArgumentError, 'Adapters inherit from Adapter::Base.' \
"Adapter.new called with args: '#{args.inspect}', from" \
"'caller[0]'."
end
def create(resource, options = {})
override = options.delete(:adapter)
klass = override ? adapter_class(override) : ActiveModel::Serializer.adapter
klass.new(resource, options)
end
# @see ActiveModel::Serializer::Adapter.lookup
def adapter_class(adapter)
ActiveModel::Serializer::Adapter.lookup(adapter)
end
# @return Hash<adapter_name, adapter_class>
def adapter_map
ADAPTER_MAP
end
# @return [Array<Symbol>] list of adapter names
def adapters
adapter_map.keys.sort
end
# Adds an adapter 'klass' with 'name' to the 'adapter_map'
# Names are stringified and underscored
# @param name [Symbol, String, Class] name of the registered adapter
# @param klass [Class] adapter class itself, optional if name is the class
# @example
# AMS::Adapter.register(:my_adapter, MyAdapter)
# @note The registered name strips out 'ActiveModel::Serializer::Adapter::'
# so that registering 'ActiveModel::Serializer::Adapter::Json' and
# 'Json' will both register as 'json'.
def register(name, klass = name)
name = name.to_s.gsub(/\AActiveModel::Serializer::Adapter::/, ''.freeze)
adapter_map.update(name.underscore => klass)
self
end
# @param adapter [String, Symbol, Class] name to fetch adapter by
# @return [ActiveModel::Serializer::Adapter] subclass of Adapter
# @raise [UnknownAdapterError]
def lookup(adapter)
# 1. return if is a class
return adapter if adapter.is_a?(Class)
adapter_name = adapter.to_s.underscore
# 2. return if registered
adapter_map.fetch(adapter_name) do
# 3. try to find adapter class from environment
adapter_class = find_by_name(adapter_name)
register(adapter_name, adapter_class)
adapter_class
end
rescue NameError, ArgumentError => e
failure_message =
"NameError: #{e.message}. Unknown adapter: #{adapter.inspect}. Valid adapters are: #{adapters}"
raise UnknownAdapterError, failure_message, e.backtrace
end
# @api private
def find_by_name(adapter_name)
adapter_name = adapter_name.to_s.classify.tr('API', 'Api')
"ActiveModel::Serializer::Adapter::#{adapter_name}".safe_constantize ||
"ActiveModel::Serializer::Adapter::#{adapter_name.pluralize}".safe_constantize or # rubocop:disable Style/AndOr
fail UnknownAdapterError
end
private :find_by_name
end
# Gotta be at the bottom to use the code above it :(
require 'active_model/serializer/adapter/base'
require 'active_model/serializer/adapter/null'
require 'active_model/serializer/adapter/attributes'
require 'active_model/serializer/adapter/json'
require 'active_model/serializer/adapter/json_api'
end
end
end

View File

@ -1,100 +0,0 @@
module ActiveModel
class Serializer
module Adapter
class Attributes < Base
def initialize(serializer, options = {})
super
@include_tree = IncludeTree.from_include_args(options[:include] || '*')
@cached_attributes = options[:cache_attributes] || {}
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)
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)
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)
cached_serializer = CachedSerializer.new(serializer)
cached_attributes(cached_serializer) do
cached_serializer.cache_check(self) do
serializer.attributes(options[:fields])
end
end
end
end
end
end
end

View File

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

View File

@ -1,79 +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 && (@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]
@cache_key = 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
# 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
end

View File

@ -1,115 +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)
# 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)
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

View File

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

View File

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

View File

@ -1,188 +0,0 @@
module ActiveModel
class Serializer
module Adapter
class JsonApi < Base
extend ActiveSupport::Autoload
autoload :PaginationLinks
autoload :FragmentCache
autoload :Link
autoload :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.
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 ||= {}
is_collection = serializer.respond_to?(:each)
serializers = is_collection ? serializer : [serializer]
primary_data, included = resource_objects_for(serializers)
hash = {}
hash[:data] = is_collection ? primary_data : primary_data[0]
hash[:included] = included if included.any?
ApiObjects::JsonApi.add!(hash)
if instance_options[:links]
hash[:links] ||= {}
hash[:links].update(instance_options[:links])
end
if is_collection && serializer.paginated?
hash[:links] ||= {}
hash[:links].update(pagination_links_for(serializer, options))
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 resource_objects_for(serializers)
@primary = []
@included = []
@resource_identifiers = Set.new
serializers.each { |serializer| process_resource(serializer, true) }
serializers.each { |serializer| process_relationships(serializer, @include_tree) }
[@primary, @included]
end
def process_resource(serializer, primary)
resource_identifier = ApiObjects::ResourceIdentifier.new(serializer).as_json
return false unless @resource_identifiers.add?(resource_identifier)
resource_object = resource_object_for(serializer)
if primary
@primary << resource_object
else
@included << resource_object
end
true
end
def process_relationships(serializer, include_tree)
serializer.associations(include_tree).each do |association|
process_relationship(association.serializer, include_tree[association.key])
end
end
def process_relationship(serializer, include_tree)
if serializer.respond_to?(:each)
serializer.each { |s| process_relationship(s, include_tree) }
return
end
return unless serializer && serializer.object
return unless process_resource(serializer, false)
process_relationships(serializer, include_tree)
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 = ApiObjects::ResourceIdentifier.new(serializer).as_json
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
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 relationships_for(serializer, requested_associations)
include_tree = IncludeTree.from_include_args(requested_associations)
serializer.associations(include_tree).each_with_object({}) do |association, hash|
hash[association.key] = ApiObjects::Relationship.new(
serializer,
association.serializer,
association.options,
association.links,
association.meta
).as_json
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
def meta_for(serializer)
Meta.new(serializer).as_json
end
end
end
end
end

View File

@ -11,7 +11,7 @@ module ActiveModel
@options = options
@data = data_for(serializer, options)
@links = links.each_with_object({}) do |(key, value), hash|
hash[key] = Link.new(parent_serializer, value).as_json
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

View File

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

View File

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

View File

@ -1,45 +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 = {}
hash[:href] = @href if @href
hash[:meta] = @meta if @meta
hash
end
protected
attr_reader :object, :scope
end
end
end
end
end

View File

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

View File

@ -1,11 +0,0 @@
module ActiveModel
class Serializer
module Adapter
class Null < Base
def serializable_hash(options = nil)
{}
end
end
end
end
end

View File

@ -9,6 +9,7 @@ module ActiveModelSerializers
autoload :Deserialization
autoload :Logging
autoload :Test
autoload :Adapter
class << self; attr_accessor :logger; end
self.logger = ActiveSupport::TaggedLogging.new(ActiveSupport::Logger.new(STDOUT))

View File

@ -0,0 +1,93 @@
module ActiveModelSerializers
module Adapter
UnknownAdapterError = Class.new(ArgumentError)
ADAPTER_MAP = {}
private_constant :ADAPTER_MAP if defined?(private_constant)
require 'active_model_serializers/adapter/fragment_cache'
require 'active_model_serializers/adapter/cached_serializer'
class << self # All methods are class functions
def new(*args)
fail ArgumentError, 'Adapters inherit from Adapter::Base.' \
"Adapter.new called with args: '#{args.inspect}', from" \
"'caller[0]'."
end
def configured_adapter
lookup(ActiveModelSerializers.config.adapter)
end
def create(resource, options = {})
override = options.delete(:adapter)
klass = override ? adapter_class(override) : configured_adapter
klass.new(resource, options)
end
# @see ActiveModelSerializers::Adapter.lookup
def adapter_class(adapter)
ActiveModelSerializers::Adapter.lookup(adapter)
end
# @return Hash<adapter_name, adapter_class>
def adapter_map
ADAPTER_MAP
end
# @return [Array<Symbol>] list of adapter names
def adapters
adapter_map.keys.sort
end
# Adds an adapter 'klass' with 'name' to the 'adapter_map'
# Names are stringified and underscored
# @param name [Symbol, String, Class] name of the registered adapter
# @param klass [Class] adapter class itself, optional if name is the class
# @example
# AMS::Adapter.register(:my_adapter, MyAdapter)
# @note The registered name strips out 'ActiveModelSerializers::Adapter::'
# so that registering 'ActiveModelSerializers::Adapter::Json' and
# 'Json' will both register as 'json'.
def register(name, klass = name)
name = name.to_s.gsub(/\AActiveModelSerializers::Adapter::/, ''.freeze)
adapter_map.update(name.underscore => klass)
self
end
# @param adapter [String, Symbol, Class] name to fetch adapter by
# @return [ActiveModelSerializers::Adapter] subclass of Adapter
# @raise [UnknownAdapterError]
def lookup(adapter)
# 1. return if is a class
return adapter if adapter.is_a?(Class)
adapter_name = adapter.to_s.underscore
# 2. return if registered
adapter_map.fetch(adapter_name) do
# 3. try to find adapter class from environment
adapter_class = find_by_name(adapter_name)
register(adapter_name, adapter_class)
adapter_class
end
rescue NameError, ArgumentError => e
failure_message =
"NameError: #{e.message}. Unknown adapter: #{adapter.inspect}. Valid adapters are: #{adapters}"
raise UnknownAdapterError, failure_message, e.backtrace
end
# @api private
def find_by_name(adapter_name)
adapter_name = adapter_name.to_s.classify.tr('API', 'Api')
"ActiveModelSerializers::Adapter::#{adapter_name}".safe_constantize ||
"ActiveModelSerializers::Adapter::#{adapter_name.pluralize}".safe_constantize or # rubocop:disable Style/AndOr
fail UnknownAdapterError
end
private :find_by_name
end
# Gotta be at the bottom to use the code above it :(
require 'active_model_serializers/adapter/base'
require 'active_model_serializers/adapter/null'
require 'active_model_serializers/adapter/attributes'
require 'active_model_serializers/adapter/json'
require 'active_model_serializers/adapter/json_api'
end
end

View File

@ -0,0 +1,98 @@
module ActiveModelSerializers
module Adapter
class Attributes < Base
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)
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)
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)
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)
cached_serializer = CachedSerializer.new(serializer)
cached_attributes(cached_serializer) do
cached_serializer.cache_check(self) do
serializer.attributes(options[:fields])
end
end
end
end
end
end

View File

@ -0,0 +1,56 @@
module ActiveModelSerializers
module Adapter
class Base
# Automatically register adapters when subclassing
def self.inherited(subclass)
ActiveModelSerializers::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

View File

@ -0,0 +1,77 @@
module ActiveModelSerializers
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 && (@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]
@cache_key = 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
# 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

@ -0,0 +1,113 @@
module ActiveModelSerializers
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)
# 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)
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

View File

@ -0,0 +1,19 @@
module ActiveModelSerializers
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)
ActiveModelSerializers::Adapter::Json::FragmentCache.new.fragment_cache(cached_hash, non_cached_hash)
end
end
end
end

View File

@ -0,0 +1,11 @@
module ActiveModelSerializers
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

View File

@ -0,0 +1,186 @@
module ActiveModelSerializers
module Adapter
class JsonApi < Base
extend ActiveSupport::Autoload
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.
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 = ActiveModel::Serializer::IncludeTree.from_include_args(options[:include])
@fieldset = options[:fieldset] || ActiveModel::Serializer::Fieldset.new(options.delete(:fields))
end
def serializable_hash(options = nil)
options ||= {}
is_collection = serializer.respond_to?(:each)
serializers = is_collection ? serializer : [serializer]
primary_data, included = resource_objects_for(serializers)
hash = {}
hash[:data] = is_collection ? primary_data : primary_data[0]
hash[:included] = included if included.any?
ApiObjects::JsonApi.add!(hash)
if instance_options[:links]
hash[:links] ||= {}
hash[:links].update(instance_options[:links])
end
if is_collection && serializer.paginated?
hash[:links] ||= {}
hash[:links].update(pagination_links_for(serializer, options))
end
hash
end
def fragment_cache(cached_hash, non_cached_hash)
root = false if instance_options.include?(:include)
ActiveModelSerializers::Adapter::JsonApi::FragmentCache.new.fragment_cache(root, cached_hash, non_cached_hash)
end
protected
attr_reader :fieldset
private
def resource_objects_for(serializers)
@primary = []
@included = []
@resource_identifiers = Set.new
serializers.each { |serializer| process_resource(serializer, true) }
serializers.each { |serializer| process_relationships(serializer, @include_tree) }
[@primary, @included]
end
def process_resource(serializer, primary)
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)
if primary
@primary << resource_object
else
@included << resource_object
end
true
end
def process_relationships(serializer, include_tree)
serializer.associations(include_tree).each do |association|
process_relationship(association.serializer, include_tree[association.key])
end
end
def process_relationship(serializer, include_tree)
if serializer.respond_to?(:each)
serializer.each { |s| process_relationship(s, include_tree) }
return
end
return unless serializer && serializer.object
return unless process_resource(serializer, false)
process_relationships(serializer, include_tree)
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 = 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)
resource_object[:attributes] = attributes if attributes.any?
resource_object
end
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 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] = ActiveModel::Serializer::Adapter::JsonApi::ApiObjects::Relationship.new(
serializer,
association.serializer,
association.options,
association.links,
association.meta
).as_json
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
def meta_for(serializer)
ActiveModel::Serializer::Adapter::JsonApi::Meta.new(serializer).as_json
end
end
end
end

View File

@ -0,0 +1,205 @@
module ActiveModelSerializers
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

View File

@ -0,0 +1,18 @@
module ActiveModelSerializers
module Adapter
class JsonApi
class FragmentCache
def fragment_cache(root, cached_hash, non_cached_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

View File

@ -0,0 +1,43 @@
module ActiveModelSerializers
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 = {}
hash[:href] = @href if @href
hash[:meta] = @meta if @meta
hash
end
protected
attr_reader :object, :scope
end
end
end
end

View File

@ -0,0 +1,56 @@
module ActiveModelSerializers
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

View File

@ -0,0 +1,10 @@
module ActiveModelSerializers
module Adapter
class Null < Base
# Since options param is not being used, underscored naming of the param
def serializable_hash(_options = nil)
{}
end
end
end
end

View File

@ -3,11 +3,11 @@ module ActiveModelSerializers
module_function
def jsonapi_parse(*args)
ActiveModel::Serializer::Adapter::JsonApi::Deserialization.parse(*args)
ActiveModelSerializers::Adapter::JsonApi::Deserialization.parse(*args)
end
def jsonapi_parse!(*args)
ActiveModel::Serializer::Adapter::JsonApi::Deserialization.parse!(*args)
ActiveModelSerializers::Adapter::JsonApi::Deserialization.parse!(*args)
end
end
end

View File

@ -3,7 +3,6 @@ require 'test_helper'
module ActionController
module Serialization
class ImplicitSerializerTest < ActionController::TestCase
include ActiveSupport::Testing::Stream
class ImplicitSerializationTestController < ActionController::Base
include SerializationTesting
def render_using_implicit_serializer
@ -46,7 +45,7 @@ module ActionController
end
def render_array_using_implicit_serializer_and_links
with_adapter ActiveModel::Serializer::Adapter::JsonApi do
with_adapter ActiveModelSerializers::Adapter::JsonApi do
@profiles = [
Profile.new(name: 'Name 1', description: 'Description 1', comments: 'Comments 1')
]
@ -438,9 +437,9 @@ module ActionController
false
end
end.new
assert_match(/adapter: false/, (capture(:stderr) do
assert_output(nil, /adapter: false/) do
controller.get_serializer(Profile.new)
end))
end
end
def test_dont_warn_overridding_use_adapter_as_truthy_on_controller_instance
@ -449,9 +448,9 @@ module ActionController
true
end
end.new
assert_equal '', (capture(:stderr) do
assert_output(nil, '') do
controller.get_serializer(Profile.new)
end)
end
end
def test_render_event_is_emmited

View File

@ -0,0 +1,203 @@
module ActiveModelSerializers
class AdapterForTest < ActiveSupport::TestCase
UnknownAdapterError = ::ActiveModelSerializers::Adapter::UnknownAdapterError
def setup
@previous_adapter = ActiveModelSerializers.config.adapter
end
def teardown
ActiveModelSerializers.config.adapter = @previous_adapter
end
def test_serializer_adapter_returns_configured__adapter
assert_output(nil, /ActiveModelSerializers::configured_adapter/) do
assert_equal ActiveModelSerializers::Adapter.configured_adapter, ActiveModel::Serializer.adapter
end
end
def test_returns_default_adapter
adapter = ActiveModelSerializers::Adapter.configured_adapter
assert_equal ActiveModelSerializers::Adapter::Attributes, adapter
end
def test_overwrite_adapter_with_symbol
ActiveModelSerializers.config.adapter = :null
adapter = ActiveModelSerializers::Adapter.configured_adapter
assert_equal ActiveModelSerializers::Adapter::Null, adapter
ensure
ActiveModelSerializers.config.adapter = @previous_adapter
end
def test_overwrite_adapter_with_camelcased_symbol
ActiveModelSerializers.config.adapter = :JsonApi
adapter = ActiveModelSerializers::Adapter.configured_adapter
assert_equal ActiveModelSerializers::Adapter::JsonApi, adapter
ensure
ActiveModelSerializers.config.adapter = @previous_adapter
end
def test_overwrite_adapter_with_string
ActiveModelSerializers.config.adapter = 'json_api'
adapter = ActiveModelSerializers::Adapter.configured_adapter
assert_equal ActiveModelSerializers::Adapter::JsonApi, adapter
ensure
ActiveModelSerializers.config.adapter = @previous_adapter
end
def test_overwrite_adapter_with_a_camelcased_string
ActiveModelSerializers.config.adapter = 'JsonApi'
adapter = ActiveModelSerializers::Adapter.configured_adapter
assert_equal ActiveModelSerializers::Adapter::JsonApi, adapter
ensure
ActiveModelSerializers.config.adapter = @previous_adapter
end
def test_overwrite_adapter_with_class
ActiveModelSerializers.config.adapter = ActiveModelSerializers::Adapter::Null
adapter = ActiveModelSerializers::Adapter.configured_adapter
assert_equal ActiveModelSerializers::Adapter::Null, adapter
ensure
ActiveModelSerializers.config.adapter = @previous_adapter
end
def test_raises_exception_if_invalid_symbol_given
ActiveModelSerializers.config.adapter = :unknown
assert_raises UnknownAdapterError do
ActiveModelSerializers::Adapter.configured_adapter
end
ensure
ActiveModelSerializers.config.adapter = @previous_adapter
end
def test_raises_exception_if_it_does_not_know_hot_to_infer_adapter
ActiveModelSerializers.config.adapter = 42
assert_raises UnknownAdapterError do
ActiveModelSerializers::Adapter.configured_adapter
end
ensure
ActiveModelSerializers.config.adapter = @previous_adapter
end
def test_adapter_class_for_known_adapter
klass = ActiveModelSerializers::Adapter.adapter_class(:json_api)
assert_equal ActiveModelSerializers::Adapter::JsonApi, klass
end
def test_adapter_class_for_unknown_adapter
assert_raises UnknownAdapterError do
ActiveModelSerializers::Adapter.adapter_class(:json_simple)
end
end
def test_adapter_map
expected_adapter_map = {
'null'.freeze => ActiveModelSerializers::Adapter::Null,
'json'.freeze => ActiveModelSerializers::Adapter::Json,
'attributes'.freeze => ActiveModelSerializers::Adapter::Attributes,
'json_api'.freeze => ActiveModelSerializers::Adapter::JsonApi
}
actual = ActiveModelSerializers::Adapter.adapter_map
assert_equal actual, expected_adapter_map
end
def test_adapters
assert_equal ActiveModelSerializers::Adapter.adapters.sort, [
'attributes'.freeze,
'json'.freeze,
'json_api'.freeze,
'null'.freeze
]
end
def test_lookup_adapter_by_string_name
assert_equal ActiveModelSerializers::Adapter.lookup('json'.freeze), ActiveModelSerializers::Adapter::Json
end
def test_lookup_adapter_by_symbol_name
assert_equal ActiveModelSerializers::Adapter.lookup(:json), ActiveModelSerializers::Adapter::Json
end
def test_lookup_adapter_by_class
klass = ActiveModelSerializers::Adapter::Json
assert_equal ActiveModelSerializers::Adapter.lookup(klass), klass
end
def test_lookup_adapter_from_environment_registers_adapter
ActiveModelSerializers::Adapter.const_set(:AdapterFromEnvironment, Class.new)
klass = ::ActiveModelSerializers::Adapter::AdapterFromEnvironment
name = 'adapter_from_environment'.freeze
assert_equal ActiveModelSerializers::Adapter.lookup(name), klass
assert ActiveModelSerializers::Adapter.adapters.include?(name)
ensure
ActiveModelSerializers::Adapter.adapter_map.delete(name)
ActiveModelSerializers::Adapter.send(:remove_const, :AdapterFromEnvironment)
end
def test_lookup_adapter_for_unknown_name
assert_raises UnknownAdapterError do
ActiveModelSerializers::Adapter.lookup(:json_simple)
end
end
def test_adapter
assert_equal ActiveModelSerializers.config.adapter, :attributes
assert_equal ActiveModelSerializers::Adapter.configured_adapter, ActiveModelSerializers::Adapter::Attributes
end
def test_register_adapter
new_adapter_name = :foo
new_adapter_klass = Class.new
ActiveModelSerializers::Adapter.register(new_adapter_name, new_adapter_klass)
assert ActiveModelSerializers::Adapter.adapters.include?('foo'.freeze)
assert ActiveModelSerializers::Adapter.lookup(:foo), new_adapter_klass
ensure
ActiveModelSerializers::Adapter.adapter_map.delete(new_adapter_name.to_s)
end
def test_inherited_adapter_hooks_register_adapter
Object.const_set(:MyAdapter, Class.new)
my_adapter = MyAdapter
ActiveModelSerializers::Adapter::Base.inherited(my_adapter)
assert_equal ActiveModelSerializers::Adapter.lookup(:my_adapter), my_adapter
ensure
ActiveModelSerializers::Adapter.adapter_map.delete('my_adapter'.freeze)
Object.send(:remove_const, :MyAdapter)
end
def test_inherited_adapter_hooks_register_namespaced_adapter
Object.const_set(:MyNamespace, Module.new)
MyNamespace.const_set(:MyAdapter, Class.new)
my_adapter = MyNamespace::MyAdapter
ActiveModelSerializers::Adapter::Base.inherited(my_adapter)
assert_equal ActiveModelSerializers::Adapter.lookup(:'my_namespace/my_adapter'), my_adapter
ensure
ActiveModelSerializers::Adapter.adapter_map.delete('my_namespace/my_adapter'.freeze)
MyNamespace.send(:remove_const, :MyAdapter)
Object.send(:remove_const, :MyNamespace)
end
def test_inherited_adapter_hooks_register_subclass_of_registered_adapter
Object.const_set(:MyAdapter, Class.new)
my_adapter = MyAdapter
Object.const_set(:MySubclassedAdapter, Class.new(MyAdapter))
my_subclassed_adapter = MySubclassedAdapter
ActiveModelSerializers::Adapter::Base.inherited(my_adapter)
ActiveModelSerializers::Adapter::Base.inherited(my_subclassed_adapter)
assert_equal ActiveModelSerializers::Adapter.lookup(:my_adapter), my_adapter
assert_equal ActiveModelSerializers::Adapter.lookup(:my_subclassed_adapter), my_subclassed_adapter
ensure
ActiveModelSerializers::Adapter.adapter_map.delete('my_adapter'.freeze)
ActiveModelSerializers::Adapter.adapter_map.delete('my_subclassed_adapter'.freeze)
Object.send(:remove_const, :MyAdapter)
Object.send(:remove_const, :MySubclassedAdapter)
end
end
end

View File

@ -65,7 +65,7 @@ module ActiveModel
def test_logs_correct_adapter
ActiveModel::SerializableResource.new(@post).serializable_hash
assert_match(/ActiveModel::Serializer::Adapter::Attributes/, @logger.messages)
assert_match(/ActiveModelSerializers::Adapter::Attributes/, @logger.messages)
end
def test_logs_the_duration

View File

@ -1,6 +1,5 @@
require 'test_helper'
module ActiveModel
class Serializer
module ActiveModelSerializers
module Adapter
class FragmentCacheTest < ActiveSupport::TestCase
TypedRoleSerializer = Class.new(ActiveModel::Serializer) do
@ -19,8 +18,8 @@ module ActiveModel
@role.author = [@author]
@role_serializer = RoleSerializer.new(@role)
@spam_serializer = Spam::UnrelatedLinkSerializer.new(@spam)
@role_hash = FragmentCache.new(RoleSerializer.adapter.new(@role_serializer), @role_serializer, {})
@spam_hash = FragmentCache.new(Spam::UnrelatedLinkSerializer.adapter.new(@spam_serializer), @spam_serializer, {})
@role_hash = FragmentCache.new(::ActiveModelSerializers::Adapter.configured_adapter.new(@role_serializer), @role_serializer, {})
@spam_hash = FragmentCache.new(::ActiveModelSerializers::Adapter.configured_adapter.new(@spam_serializer), @spam_serializer, {})
end
def test_fragment_fetch_with_virtual_attributes
@ -46,5 +45,4 @@ module ActiveModel
end
end
end
end
end

View File

@ -1,7 +1,6 @@
require 'test_helper'
module ActiveModel
class Serializer
module ActiveModelSerializers
module Adapter
class Json
class BelongsToTest < ActiveSupport::TestCase
@ -19,7 +18,7 @@ module ActiveModel
@anonymous_post.blog = nil
@serializer = CommentSerializer.new(@comment)
@adapter = ActiveModel::Serializer::Adapter::Json.new(@serializer)
@adapter = ActiveModelSerializers::Adapter::Json.new(@serializer)
ActionController::Base.cache_store.clear
end
@ -29,19 +28,18 @@ module ActiveModel
def test_include_nil_author
serializer = PostSerializer.new(@anonymous_post)
adapter = ActiveModel::Serializer::Adapter::Json.new(serializer)
adapter = ActiveModelSerializers::Adapter::Json.new(serializer)
assert_equal({ post: { title: 'Hello!!', body: 'Hello, world!!', id: 43, comments: [], blog: { id: 999, name: 'Custom blog' }, author: nil } }, adapter.serializable_hash)
end
def test_include_nil_author_with_specified_serializer
serializer = PostPreviewSerializer.new(@anonymous_post)
adapter = ActiveModel::Serializer::Adapter::Json.new(serializer)
adapter = ActiveModelSerializers::Adapter::Json.new(serializer)
assert_equal({ post: { title: 'Hello!!', body: 'Hello, world!!', id: 43, comments: [], author: nil } }, adapter.serializable_hash)
end
end
end
end
end
end

View File

@ -1,7 +1,6 @@
require 'test_helper'
module ActiveModel
class Serializer
module ActiveModelSerializers
module Adapter
class Json
class Collection < ActiveSupport::TestCase
@ -23,8 +22,8 @@ module ActiveModel
def test_with_serializer_option
@blog.special_attribute = 'Special'
@blog.articles = [@first_post, @second_post]
serializer = CollectionSerializer.new([@blog], serializer: CustomBlogSerializer)
adapter = ActiveModel::Serializer::Adapter::Json.new(serializer)
serializer = ActiveModel::Serializer::CollectionSerializer.new([@blog], serializer: CustomBlogSerializer)
adapter = ActiveModelSerializers::Adapter::Json.new(serializer)
expected = { blogs: [{
id: 1,
@ -35,8 +34,8 @@ module ActiveModel
end
def test_include_multiple_posts
serializer = CollectionSerializer.new([@first_post, @second_post])
adapter = ActiveModel::Serializer::Adapter::Json.new(serializer)
serializer = ActiveModel::Serializer::CollectionSerializer.new([@first_post, @second_post])
adapter = ActiveModelSerializers::Adapter::Json.new(serializer)
expected = { posts: [{
title: 'Hello!!',
@ -70,15 +69,15 @@ module ActiveModel
def test_root_is_underscored
virtual_value = VirtualValue.new(id: 1)
serializer = CollectionSerializer.new([virtual_value])
adapter = ActiveModel::Serializer::Adapter::Json.new(serializer)
serializer = ActiveModel::Serializer::CollectionSerializer.new([virtual_value])
adapter = ActiveModelSerializers::Adapter::Json.new(serializer)
assert_equal 1, adapter.serializable_hash[:virtual_values].length
end
def test_include_option
serializer = CollectionSerializer.new([@first_post, @second_post])
adapter = ActiveModel::Serializer::Adapter::Json.new(serializer, include: '')
serializer = ActiveModel::Serializer::CollectionSerializer.new([@first_post, @second_post])
adapter = ActiveModelSerializers::Adapter::Json.new(serializer, include: '')
actual = adapter.serializable_hash
expected = { posts: [{ id: 1, title: 'Hello!!', body: 'Hello, world!!' },
{ id: 2, title: 'New Post', body: 'Body' }] }
@ -88,5 +87,4 @@ module ActiveModel
end
end
end
end
end

View File

@ -1,7 +1,6 @@
require 'test_helper'
module ActiveModel
class Serializer
module ActiveModelSerializers
module Adapter
class Json
class HasManyTestTest < ActiveSupport::TestCase
@ -23,7 +22,7 @@ module ActiveModel
def test_has_many
serializer = PostSerializer.new(@post)
adapter = ActiveModel::Serializer::Adapter::Json.new(serializer)
adapter = ActiveModelSerializers::Adapter::Json.new(serializer)
assert_equal([
{ id: 1, body: 'ZOMG A COMMENT' },
{ id: 2, body: 'ZOMG ANOTHER COMMENT' }
@ -32,7 +31,7 @@ module ActiveModel
def test_has_many_with_no_serializer
serializer = PostWithTagsSerializer.new(@post)
adapter = ActiveModel::Serializer::Adapter::Json.new(serializer)
adapter = ActiveModelSerializers::Adapter::Json.new(serializer)
assert_equal({
id: 42,
tags: [
@ -43,5 +42,4 @@ module ActiveModel
end
end
end
end
end

View File

@ -1,7 +1,6 @@
require 'test_helper'
module ActiveModel
class Serializer
module ActiveModelSerializers
module Adapter
class JsonApi
class BelongsToTest < ActiveSupport::TestCase
@ -27,7 +26,7 @@ module ActiveModel
@author.posts = []
@serializer = CommentSerializer.new(@comment)
@adapter = ActiveModel::Serializer::Adapter::JsonApi.new(@serializer)
@adapter = ActiveModelSerializers::Adapter::JsonApi.new(@serializer)
ActionController::Base.cache_store.clear
end
@ -38,7 +37,7 @@ module ActiveModel
end
def test_includes_linked_post
@adapter = ActiveModel::Serializer::Adapter::JsonApi.new(@serializer, include: [:post])
@adapter = ActiveModelSerializers::Adapter::JsonApi.new(@serializer, include: [:post])
expected = [{
id: '42',
type: 'posts',
@ -56,7 +55,7 @@ module ActiveModel
end
def test_limiting_linked_post_fields
@adapter = ActiveModel::Serializer::Adapter::JsonApi.new(@serializer, include: [:post], fields: { post: [:title, :comments, :blog, :author] })
@adapter = ActiveModelSerializers::Adapter::JsonApi.new(@serializer, include: [:post], fields: { post: [:title, :comments, :blog, :author] })
expected = [{
id: '42',
type: 'posts',
@ -74,14 +73,14 @@ module ActiveModel
def test_include_nil_author
serializer = PostSerializer.new(@anonymous_post)
adapter = ActiveModel::Serializer::Adapter::JsonApi.new(serializer)
adapter = ActiveModelSerializers::Adapter::JsonApi.new(serializer)
assert_equal({ comments: { data: [] }, blog: { data: { type: 'blogs', id: '999' } }, author: { data: nil } }, adapter.serializable_hash[:data][:relationships])
end
def test_include_type_for_association_when_different_than_name
serializer = BlogSerializer.new(@blog)
adapter = ActiveModel::Serializer::Adapter::JsonApi.new(serializer)
adapter = ActiveModelSerializers::Adapter::JsonApi.new(serializer)
relationships = adapter.serializable_hash[:data][:relationships]
expected = {
writer: {
@ -108,7 +107,7 @@ module ActiveModel
def test_include_linked_resources_with_type_name
serializer = BlogSerializer.new(@blog)
adapter = ActiveModel::Serializer::Adapter::JsonApi.new(serializer, include: [:writer, :articles])
adapter = ActiveModelSerializers::Adapter::JsonApi.new(serializer, include: [:writer, :articles])
linked = adapter.serializable_hash[:included]
expected = [
{
@ -153,5 +152,4 @@ module ActiveModel
end
end
end
end
end

View File

@ -1,7 +1,6 @@
require 'test_helper'
module ActiveModel
class Serializer
module ActiveModelSerializers
module Adapter
class JsonApi
class CollectionTest < ActiveSupport::TestCase
@ -19,8 +18,8 @@ module ActiveModel
@second_post.author = @author
@author.posts = [@first_post, @second_post]
@serializer = CollectionSerializer.new([@first_post, @second_post])
@adapter = ActiveModel::Serializer::Adapter::JsonApi.new(@serializer)
@serializer = ActiveModel::Serializer::CollectionSerializer.new([@first_post, @second_post])
@adapter = ActiveModelSerializers::Adapter::JsonApi.new(@serializer)
ActionController::Base.cache_store.clear
end
@ -93,5 +92,4 @@ module ActiveModel
end
end
end
end
end

View File

@ -1,7 +1,6 @@
require 'test_helper'
module ActiveModel
class Serializer
module ActiveModelSerializers
module Adapter
class JsonApi
class FieldsTest < ActiveSupport::TestCase
@ -85,5 +84,4 @@ module ActiveModel
end
end
end
end
end

View File

@ -1,7 +1,6 @@
require 'test_helper'
module ActiveModel
class Serializer
module ActiveModelSerializers
module Adapter
class JsonApi
class HasManyEmbedIdsTest < ActiveSupport::TestCase
@ -21,7 +20,7 @@ module ActiveModel
@second_post.blog = nil
@serializer = AuthorSerializer.new(@author)
@adapter = ActiveModel::Serializer::Adapter::JsonApi.new(@serializer)
@adapter = ActiveModelSerializers::Adapter::JsonApi.new(@serializer)
end
def test_includes_comment_ids
@ -41,5 +40,4 @@ module ActiveModel
end
end
end
end
end

View File

@ -1,7 +1,6 @@
require 'test_helper'
module ActiveModel
class Serializer
module ActiveModelSerializers
module Adapter
class JsonApi
# Test 'has_many :assocs, serializer: AssocXSerializer'
@ -22,7 +21,7 @@ module ActiveModel
@post.blog = @blog
@serializer = PostPreviewSerializer.new(@post)
@adapter = ActiveModel::Serializer::Adapter::JsonApi.new(
@adapter = ActiveModelSerializers::Adapter::JsonApi.new(
@serializer,
include: [:comments, :author]
)
@ -94,5 +93,4 @@ module ActiveModel
end
end
end
end
end

View File

@ -1,7 +1,6 @@
require 'test_helper'
module ActiveModel
class Serializer
module ActiveModelSerializers
module Adapter
class JsonApi
class HasManyTest < ActiveSupport::TestCase
@ -30,7 +29,7 @@ module ActiveModel
@tag = Tag.new(id: 1, name: '#hash_tag')
@post.tags = [@tag]
@serializer = PostSerializer.new(@post)
@adapter = ActiveModel::Serializer::Adapter::JsonApi.new(@serializer)
@adapter = ActiveModelSerializers::Adapter::JsonApi.new(@serializer)
@virtual_value = VirtualValue.new(id: 1)
end
@ -42,7 +41,7 @@ module ActiveModel
end
def test_includes_linked_comments
@adapter = ActiveModel::Serializer::Adapter::JsonApi.new(@serializer, include: [:comments])
@adapter = ActiveModelSerializers::Adapter::JsonApi.new(@serializer, include: [:comments])
expected = [{
id: '1',
type: 'comments',
@ -68,7 +67,7 @@ module ActiveModel
end
def test_limit_fields_of_linked_comments
@adapter = ActiveModel::Serializer::Adapter::JsonApi.new(@serializer, include: [:comments], fields: { comment: [:id, :post, :author] })
@adapter = ActiveModelSerializers::Adapter::JsonApi.new(@serializer, include: [:comments], fields: { comment: [:id, :post, :author] })
expected = [{
id: '1',
type: 'comments',
@ -89,14 +88,14 @@ module ActiveModel
def test_no_include_linked_if_comments_is_empty
serializer = PostSerializer.new(@post_without_comments)
adapter = ActiveModel::Serializer::Adapter::JsonApi.new(serializer)
adapter = ActiveModelSerializers::Adapter::JsonApi.new(serializer)
assert_nil adapter.serializable_hash[:linked]
end
def test_include_type_for_association_when_different_than_name
serializer = BlogSerializer.new(@blog)
adapter = ActiveModel::Serializer::Adapter::JsonApi.new(serializer)
adapter = ActiveModelSerializers::Adapter::JsonApi.new(serializer)
actual = adapter.serializable_hash[:data][:relationships][:articles]
expected = {
@ -110,7 +109,7 @@ module ActiveModel
def test_has_many_with_no_serializer
serializer = PostWithTagsSerializer.new(@post)
adapter = ActiveModel::Serializer::Adapter::JsonApi.new(serializer)
adapter = ActiveModelSerializers::Adapter::JsonApi.new(serializer)
assert_equal({
data: {
@ -125,7 +124,7 @@ module ActiveModel
def test_has_many_with_virtual_value
serializer = VirtualValueSerializer.new(@virtual_value)
adapter = ActiveModel::Serializer::Adapter::JsonApi.new(serializer)
adapter = ActiveModelSerializers::Adapter::JsonApi.new(serializer)
assert_equal({
data: {
@ -141,5 +140,4 @@ module ActiveModel
end
end
end
end
end

View File

@ -1,7 +1,6 @@
require 'test_helper'
module ActiveModel
class Serializer
module ActiveModelSerializers
module Adapter
class JsonApi
class HasOneTest < ActiveSupport::TestCase
@ -28,7 +27,7 @@ module ActiveModel
@virtual_value = VirtualValue.new(id: 1)
@serializer = AuthorSerializer.new(@author)
@adapter = ActiveModel::Serializer::Adapter::JsonApi.new(@serializer, include: [:bio, :posts])
@adapter = ActiveModelSerializers::Adapter::JsonApi.new(@serializer, include: [:bio, :posts])
end
def test_includes_bio_id
@ -38,7 +37,7 @@ module ActiveModel
end
def test_includes_linked_bio
@adapter = ActiveModel::Serializer::Adapter::JsonApi.new(@serializer, include: [:bio])
@adapter = ActiveModelSerializers::Adapter::JsonApi.new(@serializer, include: [:bio])
expected = [
{
@ -59,7 +58,7 @@ module ActiveModel
def test_has_one_with_virtual_value
serializer = VirtualValueSerializer.new(@virtual_value)
adapter = ActiveModel::Serializer::Adapter::JsonApi.new(serializer)
adapter = ActiveModelSerializers::Adapter::JsonApi.new(serializer)
expected = {
data: {
@ -77,5 +76,4 @@ module ActiveModel
end
end
end
end
end

View File

@ -1,7 +1,6 @@
require 'test_helper'
module ActiveModel
class Serializer
module ActiveModelSerializers
module Adapter
class JsonApiTest < ActiveSupport::TestCase
def setup
@ -20,7 +19,7 @@ module ActiveModel
def test_custom_keys
serializer = PostWithCustomKeysSerializer.new(@post)
adapter = ActiveModel::Serializer::Adapter::JsonApi.new(serializer)
adapter = ActiveModelSerializers::Adapter::JsonApi.new(serializer)
assert_equal({
reviews: { data: [
@ -33,5 +32,4 @@ module ActiveModel
end
end
end
end
end

View File

@ -5,8 +5,7 @@ class NestedPostSerializer < ActiveModel::Serializer
has_many :nested_posts
end
module ActiveModel
class Serializer
module ActiveModelSerializers
module Adapter
class JsonApi
class LinkedTest < ActiveSupport::TestCase
@ -45,12 +44,12 @@ module ActiveModel
end
def test_include_multiple_posts_and_linked_array
serializer = CollectionSerializer.new([@first_post, @second_post])
adapter = ActiveModel::Serializer::Adapter::JsonApi.new(
serializer = ActiveModel::Serializer::CollectionSerializer.new([@first_post, @second_post])
adapter = ActiveModelSerializers::Adapter::JsonApi.new(
serializer,
include: [:comments, author: [:bio]]
)
alt_adapter = ActiveModel::Serializer::Adapter::JsonApi.new(
alt_adapter = ActiveModelSerializers::Adapter::JsonApi.new(
serializer,
include: [:comments, author: [:bio]]
)
@ -156,11 +155,11 @@ module ActiveModel
def test_include_multiple_posts_and_linked
serializer = BioSerializer.new @bio1
adapter = ActiveModel::Serializer::Adapter::JsonApi.new(
adapter = ActiveModelSerializers::Adapter::JsonApi.new(
serializer,
include: [author: [:posts]]
)
alt_adapter = ActiveModel::Serializer::Adapter::JsonApi.new(
alt_adapter = ActiveModelSerializers::Adapter::JsonApi.new(
serializer,
include: [author: [:posts]]
)
@ -212,7 +211,7 @@ module ActiveModel
spammy_post = Post.new(id: 123)
spammy_post.related = [Spam::UnrelatedLink.new(id: 456)]
serializer = SpammyPostSerializer.new(spammy_post)
adapter = ActiveModel::Serializer::Adapter::JsonApi.new(serializer)
adapter = ActiveModelSerializers::Adapter::JsonApi.new(serializer)
relationships = adapter.serializable_hash[:data][:relationships]
expected = {
related: {
@ -226,8 +225,8 @@ module ActiveModel
end
def test_multiple_references_to_same_resource
serializer = CollectionSerializer.new([@first_comment, @second_comment])
adapter = ActiveModel::Serializer::Adapter::JsonApi.new(
serializer = ActiveModel::Serializer::CollectionSerializer.new([@first_comment, @second_comment])
adapter = ActiveModelSerializers::Adapter::JsonApi.new(
serializer,
include: [:post]
)
@ -260,7 +259,7 @@ module ActiveModel
def test_nil_link_with_specified_serializer
@first_post.author = nil
serializer = PostPreviewSerializer.new(@first_post)
adapter = ActiveModel::Serializer::Adapter::JsonApi.new(
adapter = ActiveModelSerializers::Adapter::JsonApi.new(
serializer,
include: [:author]
)
@ -390,5 +389,4 @@ module ActiveModel
end
end
end
end
end

View File

@ -1,7 +1,6 @@
require 'test_helper'
module ActiveModel
class Serializer
module ActiveModelSerializers
module Adapter
class JsonApi
class LinksTest < ActiveSupport::TestCase
@ -82,5 +81,4 @@ module ActiveModel
end
end
end
end
end

View File

@ -4,8 +4,7 @@ require 'kaminari'
require 'kaminari/hooks'
::Kaminari::Hooks.init
module ActiveModel
class Serializer
module ActiveModelSerializers
module Adapter
class JsonApi
class PaginationLinksTest < ActiveSupport::TestCase
@ -111,5 +110,4 @@ module ActiveModel
end
end
end
end
end

View File

@ -1,6 +1,5 @@
require 'test_helper'
module ActiveModel
class Serializer
module ActiveModelSerializers
module Adapter
class JsonApi
module Deserialization
@ -60,19 +59,19 @@ module ActiveModel
end
def test_hash
parsed_hash = ActiveModel::Serializer::Adapter::JsonApi::Deserialization.parse!(@hash)
parsed_hash = ActiveModelSerializers::Adapter::JsonApi::Deserialization.parse!(@hash)
assert_equal(@expected, parsed_hash)
end
def test_actioncontroller_parameters
assert_equal(false, @params.permitted?)
parsed_hash = ActiveModel::Serializer::Adapter::JsonApi::Deserialization.parse!(@params)
parsed_hash = ActiveModelSerializers::Adapter::JsonApi::Deserialization.parse!(@params)
assert_equal(@expected, parsed_hash)
end
def test_illformed_payloads_safe
@illformed_payloads.each do |p|
parsed_hash = ActiveModel::Serializer::Adapter::JsonApi::Deserialization.parse(p)
parsed_hash = ActiveModelSerializers::Adapter::JsonApi::Deserialization.parse(p)
assert_equal({}, parsed_hash)
end
end
@ -80,13 +79,13 @@ module ActiveModel
def test_illformed_payloads_unsafe
@illformed_payloads.each do |p|
assert_raises(InvalidDocument) do
ActiveModel::Serializer::Adapter::JsonApi::Deserialization.parse!(p)
ActiveModelSerializers::Adapter::JsonApi::Deserialization.parse!(p)
end
end
end
def test_filter_fields_only
parsed_hash = ActiveModel::Serializer::Adapter::JsonApi::Deserialization.parse!(@hash, only: [:id, :title, :author])
parsed_hash = ActiveModelSerializers::Adapter::JsonApi::Deserialization.parse!(@hash, only: [:id, :title, :author])
expected = {
id: 'zorglub',
title: 'Ember Hamster',
@ -96,7 +95,7 @@ module ActiveModel
end
def test_filter_fields_except
parsed_hash = ActiveModel::Serializer::Adapter::JsonApi::Deserialization.parse!(@hash, except: [:id, :title, :author])
parsed_hash = ActiveModelSerializers::Adapter::JsonApi::Deserialization.parse!(@hash, except: [:id, :title, :author])
expected = {
src: 'http://example.com/images/productivity.png',
photographer_id: '9',
@ -106,7 +105,7 @@ module ActiveModel
end
def test_keys
parsed_hash = ActiveModel::Serializer::Adapter::JsonApi::Deserialization.parse!(@hash, keys: { author: :user, title: :post_title })
parsed_hash = ActiveModelSerializers::Adapter::JsonApi::Deserialization.parse!(@hash, keys: { author: :user, title: :post_title })
expected = {
id: 'zorglub',
post_title: 'Ember Hamster',
@ -119,7 +118,7 @@ module ActiveModel
end
def test_polymorphic
parsed_hash = ActiveModel::Serializer::Adapter::JsonApi::Deserialization.parse!(@hash, polymorphic: [:photographer])
parsed_hash = ActiveModelSerializers::Adapter::JsonApi::Deserialization.parse!(@hash, polymorphic: [:photographer])
expected = {
id: 'zorglub',
title: 'Ember Hamster',
@ -135,5 +134,4 @@ module ActiveModel
end
end
end
end
end

View File

@ -1,7 +1,6 @@
require 'test_helper'
module ActiveModel
class Serializer
module ActiveModelSerializers
module Adapter
class JsonApi
class TopLevelJsonApiTest < ActiveSupport::TestCase
@ -80,5 +79,4 @@ module ActiveModel
end
end
end
end
end

View File

@ -1,7 +1,6 @@
require 'test_helper'
module ActiveModel
class Serializer
module ActiveModelSerializers
module Adapter
class JsonTest < ActiveSupport::TestCase
def setup
@ -18,7 +17,7 @@ module ActiveModel
@post.blog = @blog
@serializer = PostSerializer.new(@post)
@adapter = ActiveModel::Serializer::Adapter::Json.new(@serializer)
@adapter = ActiveModelSerializers::Adapter::Json.new(@serializer)
end
def test_has_many
@ -30,7 +29,7 @@ module ActiveModel
def test_custom_keys
serializer = PostWithCustomKeysSerializer.new(@post)
adapter = ActiveModel::Serializer::Adapter::Json.new(serializer)
adapter = ActiveModelSerializers::Adapter::Json.new(serializer)
assert_equal({
id: 1,
@ -43,5 +42,4 @@ module ActiveModel
end
end
end
end
end

View File

@ -1,7 +1,6 @@
require 'test_helper'
module ActiveModel
class Serializer
module ActiveModelSerializers
module Adapter
class NullTest < ActiveSupport::TestCase
def setup
@ -20,6 +19,5 @@ module ActiveModel
end
end
end
end
end

View File

@ -1,12 +1,11 @@
require 'test_helper'
module ActiveModel
class Serializer
module ActiveModelSerializers
class AdapterTest < ActiveSupport::TestCase
def setup
profile = Profile.new
@serializer = ProfileSerializer.new(profile)
@adapter = ActiveModel::Serializer::Adapter::Base.new(@serializer)
@adapter = ActiveModelSerializers::Adapter::Base.new(@serializer)
end
def test_serializable_hash_is_abstract_method
@ -20,23 +19,22 @@ module ActiveModel
end
def test_create_adapter
adapter = ActiveModel::Serializer::Adapter.create(@serializer)
assert_equal ActiveModel::Serializer::Adapter::Attributes, adapter.class
adapter = ActiveModelSerializers::Adapter.create(@serializer)
assert_equal ActiveModelSerializers::Adapter::Attributes, adapter.class
end
def test_create_adapter_with_override
adapter = ActiveModel::Serializer::Adapter.create(@serializer, { adapter: :json_api })
assert_equal ActiveModel::Serializer::Adapter::JsonApi, adapter.class
adapter = ActiveModelSerializers::Adapter.create(@serializer, { adapter: :json_api })
assert_equal ActiveModelSerializers::Adapter::JsonApi, adapter.class
end
def test_inflected_adapter_class_for_known_adapter
ActiveSupport::Inflector.inflections(:en) { |inflect| inflect.acronym 'API' }
klass = ActiveModel::Serializer::Adapter.adapter_class(:json_api)
klass = ActiveModelSerializers::Adapter.adapter_class(:json_api)
ActiveSupport::Inflector.inflections.acronyms.clear
assert_equal ActiveModel::Serializer::Adapter::JsonApi, klass
end
assert_equal ActiveModelSerializers::Adapter::JsonApi, klass
end
end
end

View File

@ -6,11 +6,11 @@ module ActiveModel
# Minitest.run_one_method isn't present in minitest 4
if $minitest_version > 4 # rubocop:disable Style/GlobalVars
class ArraySerializerTest < CollectionSerializerTest
extend ActiveSupport::Testing::Stream
extend Minitest::Assertions
def self.run_one_method(*)
stderr = (capture(:stderr) do
_, stderr = capture_io do
super
end)
end
if stderr !~ /Calling deprecated ArraySerializer/
fail Minitest::Assertion, stderr
end
@ -22,14 +22,13 @@ module ActiveModel
end
else
class ArraySerializerTest < ActiveSupport::TestCase
extend ActiveSupport::Testing::Stream
def test_json_key_with_root_warns_when_using_array_serializer
stderr = (capture(:stderr) do
_, stderr = capture_io do
comment = Comment.new
post = Post.new
serializer = ArraySerializer.new([comment, post])
assert_equal 'comments', serializer.json_key
end)
end
assert_match(/Calling deprecated ArraySerializer/, stderr)
end
end

View File

@ -5,7 +5,7 @@ module ActiveModel
def setup
@resource = Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' })
@serializer = ProfileSerializer.new(@resource)
@adapter = ActiveModel::Serializer::Adapter.create(@serializer)
@adapter = ActiveModelSerializers::Adapter.create(@serializer)
@serializable_resource = ActiveModel::SerializableResource.new(@resource)
end

View File

@ -1,166 +0,0 @@
module ActiveModel
class Serializer
class AdapterForTest < ActiveSupport::TestCase
UnknownAdapterError = ::ActiveModel::Serializer::Adapter::UnknownAdapterError
def setup
@previous_adapter = ActiveModelSerializers.config.adapter
end
def teardown
ActiveModelSerializers.config.adapter = @previous_adapter
end
def test_returns_default_adapter
adapter = ActiveModel::Serializer.adapter
assert_equal ActiveModel::Serializer::Adapter::Attributes, adapter
end
def test_overwrite_adapter_with_symbol
ActiveModelSerializers.config.adapter = :null
adapter = ActiveModel::Serializer.adapter
assert_equal ActiveModel::Serializer::Adapter::Null, adapter
ensure
ActiveModelSerializers.config.adapter = @previous_adapter
end
def test_overwrite_adapter_with_class
ActiveModelSerializers.config.adapter = ActiveModel::Serializer::Adapter::Null
adapter = ActiveModel::Serializer.adapter
assert_equal ActiveModel::Serializer::Adapter::Null, adapter
end
def test_raises_exception_if_invalid_symbol_given
ActiveModelSerializers.config.adapter = :unknown
assert_raises UnknownAdapterError do
ActiveModel::Serializer.adapter
end
end
def test_raises_exception_if_it_does_not_know_hot_to_infer_adapter
ActiveModelSerializers.config.adapter = 42
assert_raises UnknownAdapterError do
ActiveModel::Serializer.adapter
end
end
def test_adapter_class_for_known_adapter
klass = ActiveModel::Serializer::Adapter.adapter_class(:json_api)
assert_equal ActiveModel::Serializer::Adapter::JsonApi, klass
end
def test_adapter_class_for_unknown_adapter
assert_raises UnknownAdapterError do
ActiveModel::Serializer::Adapter.adapter_class(:json_simple)
end
end
def test_adapter_map
expected_adapter_map = {
'null'.freeze => ActiveModel::Serializer::Adapter::Null,
'json'.freeze => ActiveModel::Serializer::Adapter::Json,
'attributes'.freeze => ActiveModel::Serializer::Adapter::Attributes,
'json_api'.freeze => ActiveModel::Serializer::Adapter::JsonApi
}
actual = ActiveModel::Serializer::Adapter.adapter_map
assert_equal actual, expected_adapter_map
end
def test_adapters
assert_equal ActiveModel::Serializer::Adapter.adapters.sort, [
'attributes'.freeze,
'json'.freeze,
'json_api'.freeze,
'null'.freeze
]
end
def test_lookup_adapter_by_string_name
assert_equal ActiveModel::Serializer::Adapter.lookup('json'.freeze), ActiveModel::Serializer::Adapter::Json
end
def test_lookup_adapter_by_symbol_name
assert_equal ActiveModel::Serializer::Adapter.lookup(:json), ActiveModel::Serializer::Adapter::Json
end
def test_lookup_adapter_by_class
klass = ActiveModel::Serializer::Adapter::Json
assert_equal ActiveModel::Serializer::Adapter.lookup(klass), klass
end
def test_lookup_adapter_from_environment_registers_adapter
ActiveModel::Serializer::Adapter.const_set(:AdapterFromEnvironment, Class.new)
klass = ::ActiveModel::Serializer::Adapter::AdapterFromEnvironment
name = 'adapter_from_environment'.freeze
assert_equal ActiveModel::Serializer::Adapter.lookup(name), klass
assert ActiveModel::Serializer::Adapter.adapters.include?(name)
ensure
ActiveModel::Serializer::Adapter.adapter_map.delete(name)
ActiveModel::Serializer::Adapter.send(:remove_const, :AdapterFromEnvironment)
end
def test_lookup_adapter_for_unknown_name
assert_raises UnknownAdapterError do
ActiveModel::Serializer::Adapter.lookup(:json_simple)
end
end
def test_adapter
assert_equal ActiveModelSerializers.config.adapter, :attributes
assert_equal ActiveModel::Serializer.adapter, ActiveModel::Serializer::Adapter::Attributes
end
def test_register_adapter
new_adapter_name = :foo
new_adapter_klass = Class.new
ActiveModel::Serializer::Adapter.register(new_adapter_name, new_adapter_klass)
assert ActiveModel::Serializer::Adapter.adapters.include?('foo'.freeze)
assert ActiveModel::Serializer::Adapter.lookup(:foo), new_adapter_klass
ensure
ActiveModel::Serializer::Adapter.adapter_map.delete(new_adapter_name.to_s)
end
def test_inherited_adapter_hooks_register_adapter
Object.const_set(:MyAdapter, Class.new)
my_adapter = MyAdapter
ActiveModel::Serializer::Adapter::Base.inherited(my_adapter)
assert_equal ActiveModel::Serializer::Adapter.lookup(:my_adapter), my_adapter
ensure
ActiveModel::Serializer::Adapter.adapter_map.delete('my_adapter'.freeze)
Object.send(:remove_const, :MyAdapter)
end
def test_inherited_adapter_hooks_register_namespaced_adapter
Object.const_set(:MyNamespace, Module.new)
MyNamespace.const_set(:MyAdapter, Class.new)
my_adapter = MyNamespace::MyAdapter
ActiveModel::Serializer::Adapter::Base.inherited(my_adapter)
assert_equal ActiveModel::Serializer::Adapter.lookup(:'my_namespace/my_adapter'), my_adapter
ensure
ActiveModel::Serializer::Adapter.adapter_map.delete('my_namespace/my_adapter'.freeze)
MyNamespace.send(:remove_const, :MyAdapter)
Object.send(:remove_const, :MyNamespace)
end
def test_inherited_adapter_hooks_register_subclass_of_registered_adapter
Object.const_set(:MyAdapter, Class.new)
my_adapter = MyAdapter
Object.const_set(:MySubclassedAdapter, Class.new(MyAdapter))
my_subclassed_adapter = MySubclassedAdapter
ActiveModel::Serializer::Adapter::Base.inherited(my_adapter)
ActiveModel::Serializer::Adapter::Base.inherited(my_subclassed_adapter)
assert_equal ActiveModel::Serializer::Adapter.lookup(:my_adapter), my_adapter
assert_equal ActiveModel::Serializer::Adapter.lookup(:my_subclassed_adapter), my_subclassed_adapter
ensure
ActiveModel::Serializer::Adapter.adapter_map.delete('my_adapter'.freeze)
ActiveModel::Serializer::Adapter.adapter_map.delete('my_subclassed_adapter'.freeze)
Object.send(:remove_const, :MyAdapter)
Object.send(:remove_const, :MySubclassedAdapter)
end
end
end
end

View File

@ -14,14 +14,14 @@ module ActiveModel
end
def test_json_serializable_hash
adapter = ActiveModel::Serializer::Adapter::Json.new(@blog_serializer)
adapter = ActiveModelSerializers::Adapter::Json.new(@blog_serializer)
assert_equal({ blog: { id: 1, title: 'AMS Hints' } }, adapter.serializable_hash)
end
def test_attribute_inheritance_with_key
inherited_klass = Class.new(AlternateBlogSerializer)
blog_serializer = inherited_klass.new(@blog)
adapter = ActiveModel::Serializer::Adapter::Attributes.new(blog_serializer)
adapter = ActiveModelSerializers::Adapter::Attributes.new(blog_serializer)
assert_equal({ :id => 1, :title => 'AMS Hints' }, adapter.serializable_hash)
end
@ -39,7 +39,7 @@ module ActiveModel
attribute :name, key: :id
end
adapter = ActiveModel::Serializer::Adapter::Json.new(serializer.new(@blog))
adapter = ActiveModelSerializers::Adapter::Json.new(serializer.new(@blog))
assert_equal({ blog: { id: 'AMS Hints' } }, adapter.serializable_hash)
end
@ -48,7 +48,7 @@ module ActiveModel
attribute :name, key: :object
end
adapter = ActiveModel::Serializer::Adapter::Json.new(serializer.new(@blog))
adapter = ActiveModelSerializers::Adapter::Json.new(serializer.new(@blog))
assert_equal({ blog: { object: 'AMS Hints' } }, adapter.serializable_hash)
end
@ -60,10 +60,10 @@ module ActiveModel
attributes :type
end
adapter = ActiveModel::Serializer::Adapter::Json.new(attribute_serializer.new(@blog))
adapter = ActiveModelSerializers::Adapter::Json.new(attribute_serializer.new(@blog))
assert_equal({ blog: { type: 1 } }, adapter.serializable_hash)
adapter = ActiveModel::Serializer::Adapter::Json.new(attributes_serializer.new(@blog))
adapter = ActiveModelSerializers::Adapter::Json.new(attributes_serializer.new(@blog))
assert_equal({ blog: { type: 'stuff' } }, adapter.serializable_hash)
end

View File

@ -1,11 +1,8 @@
require 'test_helper'
require 'tmpdir'
require 'tempfile'
module ActiveModel
class Serializer
module ActiveModelSerializers
class CacheTest < ActiveSupport::TestCase
include ActiveSupport::Testing::Stream
def setup
ActionController::Base.cache_store.clear
@comment = Comment.new(id: 1, body: 'ZOMG A COMMENT')
@ -150,10 +147,10 @@ module ActiveModel
end
def test_object_cache_keys
serializer = CollectionSerializer.new([@comment, @comment])
include_tree = IncludeTree.from_include_args('*')
serializer = ActiveModel::Serializer::CollectionSerializer.new([@comment, @comment])
include_tree = ActiveModel::Serializer::IncludeTree.from_include_args('*')
actual = Serializer::Adapter::CachedSerializer.object_cache_keys(serializer, include_tree)
actual = Adapter::CachedSerializer.object_cache_keys(serializer, include_tree)
assert_equal actual.size, 3
assert actual.any? { |key| key == 'comment/1' }
@ -162,12 +159,12 @@ module ActiveModel
end
def test_cached_attributes
serializer = CollectionSerializer.new([@comment, @comment])
serializer = ActiveModel::Serializer::CollectionSerializer.new([@comment, @comment])
Timecop.freeze(Time.now) do
render_object_with_cache(@comment)
attributes = ActiveModel::Serializer::Adapter::Attributes.new(serializer)
attributes = Adapter::Attributes.new(serializer)
attributes.send(:cache_attributes)
cached_attributes = attributes.instance_variable_get(:@cached_attributes)
@ -223,10 +220,10 @@ module ActiveModel
def test_warn_on_serializer_not_defined_in_file
called = false
serializer = Class.new(ActiveModel::Serializer)
assert_match(/_cache_digest/, (capture(:stderr) do
assert_output(nil, /_cache_digest/) do
serializer.digest_caller_file('')
called = true
end))
end
assert called
end
@ -236,6 +233,4 @@ module ActiveModel
ActiveModel::SerializableResource.new(obj).serializable_hash
end
end
end
end

View File

@ -1,6 +1,5 @@
require 'test_helper'
module ActiveModel
class Serializer
module ActiveModelSerializers
module Adapter
class CachedSerializerTest < ActiveSupport::TestCase
def test_cached_false_without_cache_store
@ -74,8 +73,7 @@ module ActiveModel
serializer._cache_options = nil
yield serializer if block_given?
serializer_instance = serializer.new(Object)
ActiveModel::Serializer::Adapter::CachedSerializer.new(serializer_instance)
end
CachedSerializer.new(serializer_instance)
end
end
end

View File

@ -1,50 +0,0 @@
# Use cleaner stream testing interface from Rails 5 if available
# see https://github.com/rails/rails/blob/29959eb59d/activesupport/lib/active_support/testing/stream.rb
begin
require 'active_support/testing/stream'
rescue LoadError
require 'tempfile'
module ActiveSupport
module Testing
module Stream #:nodoc:
private
def silence_stream(stream)
old_stream = stream.dup
stream.reopen(IO::NULL)
stream.sync = true
yield
ensure
stream.reopen(old_stream)
old_stream.close
end
def quietly
silence_stream(STDOUT) do
silence_stream(STDERR) do
yield
end
end
end
def capture(stream)
stream = stream.to_s
captured_stream = Tempfile.new(stream)
stream_io = eval("$#{stream}") # rubocop:disable Lint/Eval
origin_stream = stream_io.dup
stream_io.reopen(captured_stream)
yield
stream_io.rewind
return captured_stream.read
ensure
captured_stream.close
captured_stream.unlink
stream_io.reopen(origin_stream)
end
end
end
end
end

View File

@ -43,8 +43,6 @@ end
require 'minitest/reporters'
Minitest::Reporters.use!
require 'support/stream_capture'
require 'support/rails_app'
require 'support/test_case'