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/adapter/null_test.rb'
- 'test/serializers/cache_test.rb' - 'test/serializers/cache_test.rb'
- 'test/serializers/fieldset_test.rb' - 'test/serializers/fieldset_test.rb'
- 'test/support/stream_capture.rb'
# Offense count: 5 # Offense count: 5
# Cop supports --auto-correct. # 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) - [![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) - [Guides](docs)
- [0.9 (0-9-stable) Documentation](https://github.com/rails-api/active_model_serializers/tree/0-9-stable) - [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) - [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 ## About

View File

@ -11,7 +11,7 @@ It should be set only once, preferably at initialization.
For example: For example:
```ruby ```ruby
ActiveModelSerializers.config.adapter = ActiveModel::Serializer::Adapter::JsonApi ActiveModelSerializers.config.adapter = ActiveModelSerializers::Adapter::JsonApi
``` ```
or 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. 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`, 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: 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"`. register the `Example::UsefulAdapter` as `"example/useful_adapter"`.
```ruby ```ruby
module Example module Example
class UsefulAdapter < ActiveModel::Serializer::Adapter::Base class UsefulAdapter < ActiveModelSerializers::Adapter::Base
end end
end end
``` ```
You'll notice that the name it registers is the underscored namespace and class. 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)` the subclass as `register("example/useful_adapter", Example::UsefulAdapter)`
2) Any class can be registered as an adapter by calling `register` directly on the 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`. `:special_adapter`.
```ruby ```ruby
class MyAdapter; end class MyAdapter; end
ActiveModel::Serializer::Adapter.register(:special_adapter, MyAdapter) ActiveModelSerializers::Adapter.register(:special_adapter, MyAdapter)
``` ```
### Looking up an adapter ### Looking up an adapter
| Method | Return value | | Method | Return value |
| :------------ |:---------------| | :------------ |:---------------|
| `ActiveModel::Serializer::Adapter.adapter_map` | A Hash of all known adapters `{ adapter_name => adapter_class }` | | `ActiveModelSerializers::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` | | `ActiveModelSerializers::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 | | `ActiveModelSerializers::Adapter.lookup(name_or_klass)` | The `adapter_class`, else raises an `ActiveModelSerializers::Adapter::UnknownAdapter` error |
| `ActiveModel::Serializer::Adapter.adapter_class(adapter)` | Delegates to `ActiveModel::Serializer::Adapter.lookup(adapter)` | | `ActiveModelSerializers::Adapter.adapter_class(adapter)` | Delegates to `ActiveModelSerializers::Adapter.lookup(adapter)` |
| `ActiveModel::Serializer.adapter` | A convenience method for `ActiveModel::Serializer::Adapter.lookup(config.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. 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")` Helpfully, the Symbol or String is underscored, so that `get(:my_adapter)` and `get("MyAdapter")`
may both be used. 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 ```ruby
{ {
serializer: PostSerializer, 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 ## Renaming of class and modules
When moving some content to the new namespace we can find some names that does 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 Discussion of renaming existing classes / modules and JsonApi objects will
happen in separate pull requests, and issues, and in the google doc 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 https://docs.google.com/document/d/1rcrJr0sVcazY2Opd_6Kmv1iIwuHbI84s1P_NzFn-05c/edit?usp=sharing

View File

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

View File

@ -23,7 +23,6 @@ module ActiveModel
include Links include Links
include Meta include Meta
include Type include Type
require 'active_model/serializer/adapter'
# @param resource [ActiveRecord::Base, ActiveModelSerializers::Model] # @param resource [ActiveRecord::Base, ActiveModelSerializers::Model]
# @return [ActiveModel::Serializer] # @return [ActiveModel::Serializer]
@ -42,9 +41,11 @@ module ActiveModel
end end
end end
# @see ActiveModel::Serializer::Adapter.lookup # @see ActiveModelSerializers::Adapter.lookup
# Deprecated
def self.adapter 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 end
# @api private # @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 @options = options
@data = data_for(serializer, options) @data = data_for(serializer, options)
@links = links.each_with_object({}) do |(key, value), hash| @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 end
@meta = meta.respond_to?(:call) ? parent_serializer.instance_eval(&meta) : meta @meta = meta.respond_to?(:call) ? parent_serializer.instance_eval(&meta) : meta
end 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 :Deserialization
autoload :Logging autoload :Logging
autoload :Test autoload :Test
autoload :Adapter
class << self; attr_accessor :logger; end class << self; attr_accessor :logger; end
self.logger = ActiveSupport::TaggedLogging.new(ActiveSupport::Logger.new(STDOUT)) 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 module_function
def jsonapi_parse(*args) def jsonapi_parse(*args)
ActiveModel::Serializer::Adapter::JsonApi::Deserialization.parse(*args) ActiveModelSerializers::Adapter::JsonApi::Deserialization.parse(*args)
end end
def jsonapi_parse!(*args) def jsonapi_parse!(*args)
ActiveModel::Serializer::Adapter::JsonApi::Deserialization.parse!(*args) ActiveModelSerializers::Adapter::JsonApi::Deserialization.parse!(*args)
end end
end end
end end

View File

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

View File

@ -1,49 +1,47 @@
require 'test_helper' require 'test_helper'
module ActiveModel module ActiveModelSerializers
class Serializer module Adapter
module Adapter class FragmentCacheTest < ActiveSupport::TestCase
class FragmentCacheTest < ActiveSupport::TestCase TypedRoleSerializer = Class.new(ActiveModel::Serializer) do
TypedRoleSerializer = Class.new(ActiveModel::Serializer) do type 'my-roles'
type 'my-roles' cache only: [:name], skip_digest: true
cache only: [:name], skip_digest: true attributes :id, :name, :description
attributes :id, :name, :description
belongs_to :author belongs_to :author
end end
def setup def setup
super super
@spam = Spam::UnrelatedLink.new(id: 'spam-id-1') @spam = Spam::UnrelatedLink.new(id: 'spam-id-1')
@author = Author.new(name: 'Joao M. D. Moura') @author = Author.new(name: 'Joao M. D. Moura')
@role = Role.new(name: 'Great Author', description: nil) @role = Role.new(name: 'Great Author', description: nil)
@role.author = [@author] @role.author = [@author]
@role_serializer = RoleSerializer.new(@role) @role_serializer = RoleSerializer.new(@role)
@spam_serializer = Spam::UnrelatedLinkSerializer.new(@spam) @spam_serializer = Spam::UnrelatedLinkSerializer.new(@spam)
@role_hash = FragmentCache.new(RoleSerializer.adapter.new(@role_serializer), @role_serializer, {}) @role_hash = FragmentCache.new(::ActiveModelSerializers::Adapter.configured_adapter.new(@role_serializer), @role_serializer, {})
@spam_hash = FragmentCache.new(Spam::UnrelatedLinkSerializer.adapter.new(@spam_serializer), @spam_serializer, {}) @spam_hash = FragmentCache.new(::ActiveModelSerializers::Adapter.configured_adapter.new(@spam_serializer), @spam_serializer, {})
end end
def test_fragment_fetch_with_virtual_attributes def test_fragment_fetch_with_virtual_attributes
expected_result = { expected_result = {
id: @role.id, id: @role.id,
description: @role.description, description: @role.description,
slug: "#{@role.name}-#{@role.id}", slug: "#{@role.name}-#{@role.id}",
name: @role.name name: @role.name
} }
assert_equal(@role_hash.fetch, expected_result) assert_equal(@role_hash.fetch, expected_result)
end end
def test_fragment_fetch_with_namespaced_object def test_fragment_fetch_with_namespaced_object
expected_result = { expected_result = {
id: @spam.id id: @spam.id
} }
assert_equal(@spam_hash.fetch, expected_result) assert_equal(@spam_hash.fetch, expected_result)
end end
def test_fragment_fetch_with_type_override def test_fragment_fetch_with_type_override
serialization = serializable(Role.new(name: 'Another Author'), serializer: TypedRoleSerializer, adapter: :json_api).serializable_hash serialization = serializable(Role.new(name: 'Another Author'), serializer: TypedRoleSerializer, adapter: :json_api).serializable_hash
assert_equal(TypedRoleSerializer._type, serialization.fetch(:data).fetch(:type)) assert_equal(TypedRoleSerializer._type, serialization.fetch(:data).fetch(:type))
end
end end
end end
end end

View File

@ -1,45 +1,43 @@
require 'test_helper' require 'test_helper'
module ActiveModel module ActiveModelSerializers
class Serializer module Adapter
module Adapter class Json
class Json class BelongsToTest < ActiveSupport::TestCase
class BelongsToTest < ActiveSupport::TestCase def setup
def setup @post = Post.new(id: 42, title: 'New Post', body: 'Body')
@post = Post.new(id: 42, title: 'New Post', body: 'Body') @anonymous_post = Post.new(id: 43, title: 'Hello!!', body: 'Hello, world!!')
@anonymous_post = Post.new(id: 43, title: 'Hello!!', body: 'Hello, world!!') @comment = Comment.new(id: 1, body: 'ZOMG A COMMENT')
@comment = Comment.new(id: 1, body: 'ZOMG A COMMENT') @post.comments = [@comment]
@post.comments = [@comment] @anonymous_post.comments = []
@anonymous_post.comments = [] @comment.post = @post
@comment.post = @post @comment.author = nil
@comment.author = nil @anonymous_post.author = nil
@anonymous_post.author = nil @blog = Blog.new(id: 1, name: 'My Blog!!')
@blog = Blog.new(id: 1, name: 'My Blog!!') @post.blog = @blog
@post.blog = @blog @anonymous_post.blog = nil
@anonymous_post.blog = nil
@serializer = CommentSerializer.new(@comment) @serializer = CommentSerializer.new(@comment)
@adapter = ActiveModel::Serializer::Adapter::Json.new(@serializer) @adapter = ActiveModelSerializers::Adapter::Json.new(@serializer)
ActionController::Base.cache_store.clear ActionController::Base.cache_store.clear
end end
def test_includes_post def test_includes_post
assert_equal({ id: 42, title: 'New Post', body: 'Body' }, @adapter.serializable_hash[:comment][:post]) assert_equal({ id: 42, title: 'New Post', body: 'Body' }, @adapter.serializable_hash[:comment][:post])
end end
def test_include_nil_author def test_include_nil_author
serializer = PostSerializer.new(@anonymous_post) 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) assert_equal({ post: { title: 'Hello!!', body: 'Hello, world!!', id: 43, comments: [], blog: { id: 999, name: 'Custom blog' }, author: nil } }, adapter.serializable_hash)
end end
def test_include_nil_author_with_specified_serializer def test_include_nil_author_with_specified_serializer
serializer = PostPreviewSerializer.new(@anonymous_post) 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) assert_equal({ post: { title: 'Hello!!', body: 'Hello, world!!', id: 43, comments: [], author: nil } }, adapter.serializable_hash)
end
end end
end end
end end

View File

@ -1,90 +1,88 @@
require 'test_helper' require 'test_helper'
module ActiveModel module ActiveModelSerializers
class Serializer module Adapter
module Adapter class Json
class Json class Collection < ActiveSupport::TestCase
class Collection < ActiveSupport::TestCase def setup
def setup @author = Author.new(id: 1, name: 'Steve K.')
@author = Author.new(id: 1, name: 'Steve K.') @first_post = Post.new(id: 1, title: 'Hello!!', body: 'Hello, world!!')
@first_post = Post.new(id: 1, title: 'Hello!!', body: 'Hello, world!!') @second_post = Post.new(id: 2, title: 'New Post', body: 'Body')
@second_post = Post.new(id: 2, title: 'New Post', body: 'Body') @first_post.comments = []
@first_post.comments = [] @second_post.comments = []
@second_post.comments = [] @first_post.author = @author
@first_post.author = @author @second_post.author = @author
@second_post.author = @author @blog = Blog.new(id: 1, name: 'My Blog!!')
@blog = Blog.new(id: 1, name: 'My Blog!!') @first_post.blog = @blog
@first_post.blog = @blog @second_post.blog = nil
@second_post.blog = nil
ActionController::Base.cache_store.clear ActionController::Base.cache_store.clear
end end
def test_with_serializer_option def test_with_serializer_option
@blog.special_attribute = 'Special' @blog.special_attribute = 'Special'
@blog.articles = [@first_post, @second_post] @blog.articles = [@first_post, @second_post]
serializer = CollectionSerializer.new([@blog], serializer: CustomBlogSerializer) serializer = ActiveModel::Serializer::CollectionSerializer.new([@blog], serializer: CustomBlogSerializer)
adapter = ActiveModel::Serializer::Adapter::Json.new(serializer) adapter = ActiveModelSerializers::Adapter::Json.new(serializer)
expected = { blogs: [{ expected = { blogs: [{
id: 1,
special_attribute: 'Special',
articles: [{ id: 1, title: 'Hello!!', body: 'Hello, world!!' }, { id: 2, title: 'New Post', body: 'Body' }]
}] }
assert_equal expected, adapter.serializable_hash
end
def test_include_multiple_posts
serializer = ActiveModel::Serializer::CollectionSerializer.new([@first_post, @second_post])
adapter = ActiveModelSerializers::Adapter::Json.new(serializer)
expected = { posts: [{
title: 'Hello!!',
body: 'Hello, world!!',
id: 1,
comments: [],
author: {
id: 1, id: 1,
special_attribute: 'Special', name: 'Steve K.'
articles: [{ id: 1, title: 'Hello!!', body: 'Hello, world!!' }, { id: 2, title: 'New Post', body: 'Body' }] },
}] } blog: {
assert_equal expected, adapter.serializable_hash id: 999,
end name: 'Custom blog'
}
def test_include_multiple_posts }, {
serializer = CollectionSerializer.new([@first_post, @second_post]) title: 'New Post',
adapter = ActiveModel::Serializer::Adapter::Json.new(serializer) body: 'Body',
id: 2,
expected = { posts: [{ comments: [],
title: 'Hello!!', author: {
body: 'Hello, world!!',
id: 1, id: 1,
comments: [], name: 'Steve K.'
author: { },
id: 1, blog: {
name: 'Steve K.' id: 999,
}, name: 'Custom blog'
blog: { }
id: 999, }] }
name: 'Custom blog' assert_equal expected, adapter.serializable_hash
} end
}, {
title: 'New Post',
body: 'Body',
id: 2,
comments: [],
author: {
id: 1,
name: 'Steve K.'
},
blog: {
id: 999,
name: 'Custom blog'
}
}] }
assert_equal expected, adapter.serializable_hash
end
def test_root_is_underscored def test_root_is_underscored
virtual_value = VirtualValue.new(id: 1) virtual_value = VirtualValue.new(id: 1)
serializer = CollectionSerializer.new([virtual_value]) serializer = ActiveModel::Serializer::CollectionSerializer.new([virtual_value])
adapter = ActiveModel::Serializer::Adapter::Json.new(serializer) adapter = ActiveModelSerializers::Adapter::Json.new(serializer)
assert_equal 1, adapter.serializable_hash[:virtual_values].length assert_equal 1, adapter.serializable_hash[:virtual_values].length
end end
def test_include_option def test_include_option
serializer = CollectionSerializer.new([@first_post, @second_post]) serializer = ActiveModel::Serializer::CollectionSerializer.new([@first_post, @second_post])
adapter = ActiveModel::Serializer::Adapter::Json.new(serializer, include: '') adapter = ActiveModelSerializers::Adapter::Json.new(serializer, include: '')
actual = adapter.serializable_hash actual = adapter.serializable_hash
expected = { posts: [{ id: 1, title: 'Hello!!', body: 'Hello, world!!' }, expected = { posts: [{ id: 1, title: 'Hello!!', body: 'Hello, world!!' },
{ id: 2, title: 'New Post', body: 'Body' }] } { id: 2, title: 'New Post', body: 'Body' }] }
assert_equal(expected, actual) assert_equal(expected, actual)
end
end end
end end
end end

View File

@ -1,45 +1,43 @@
require 'test_helper' require 'test_helper'
module ActiveModel module ActiveModelSerializers
class Serializer module Adapter
module Adapter class Json
class Json class HasManyTestTest < ActiveSupport::TestCase
class HasManyTestTest < ActiveSupport::TestCase def setup
def setup ActionController::Base.cache_store.clear
ActionController::Base.cache_store.clear @author = Author.new(id: 1, name: 'Steve K.')
@author = Author.new(id: 1, name: 'Steve K.') @post = Post.new(id: 42, title: 'New Post', body: 'Body')
@post = Post.new(id: 42, title: 'New Post', body: 'Body') @first_comment = Comment.new(id: 1, body: 'ZOMG A COMMENT')
@first_comment = Comment.new(id: 1, body: 'ZOMG A COMMENT') @second_comment = Comment.new(id: 2, body: 'ZOMG ANOTHER COMMENT')
@second_comment = Comment.new(id: 2, body: 'ZOMG ANOTHER COMMENT') @post.comments = [@first_comment, @second_comment]
@post.comments = [@first_comment, @second_comment] @post.author = @author
@post.author = @author @first_comment.post = @post
@first_comment.post = @post @second_comment.post = @post
@second_comment.post = @post @blog = Blog.new(id: 1, name: 'My Blog!!')
@blog = Blog.new(id: 1, name: 'My Blog!!') @post.blog = @blog
@post.blog = @blog @tag = Tag.new(id: 1, name: '#hash_tag')
@tag = Tag.new(id: 1, name: '#hash_tag') @post.tags = [@tag]
@post.tags = [@tag] end
end
def test_has_many def test_has_many
serializer = PostSerializer.new(@post) serializer = PostSerializer.new(@post)
adapter = ActiveModel::Serializer::Adapter::Json.new(serializer) adapter = ActiveModelSerializers::Adapter::Json.new(serializer)
assert_equal([ assert_equal([
{ id: 1, body: 'ZOMG A COMMENT' }, { id: 1, body: 'ZOMG A COMMENT' },
{ id: 2, body: 'ZOMG ANOTHER COMMENT' } { id: 2, body: 'ZOMG ANOTHER COMMENT' }
], adapter.serializable_hash[:post][:comments]) ], adapter.serializable_hash[:post][:comments])
end end
def test_has_many_with_no_serializer def test_has_many_with_no_serializer
serializer = PostWithTagsSerializer.new(@post) serializer = PostWithTagsSerializer.new(@post)
adapter = ActiveModel::Serializer::Adapter::Json.new(serializer) adapter = ActiveModelSerializers::Adapter::Json.new(serializer)
assert_equal({ assert_equal({
id: 42, id: 42,
tags: [ tags: [
{ 'id' => 1, 'name' => '#hash_tag' } { 'id' => 1, 'name' => '#hash_tag' }
] ]
}.to_json, adapter.serializable_hash[:post].to_json) }.to_json, adapter.serializable_hash[:post].to_json)
end
end end
end end
end end

View File

@ -1,155 +1,153 @@
require 'test_helper' require 'test_helper'
module ActiveModel module ActiveModelSerializers
class Serializer module Adapter
module Adapter class JsonApi
class JsonApi class BelongsToTest < ActiveSupport::TestCase
class BelongsToTest < ActiveSupport::TestCase def setup
def setup @author = Author.new(id: 1, name: 'Steve K.')
@author = Author.new(id: 1, name: 'Steve K.') @author.bio = nil
@author.bio = nil @author.roles = []
@author.roles = [] @blog = Blog.new(id: 23, name: 'AMS Blog')
@blog = Blog.new(id: 23, name: 'AMS Blog') @post = Post.new(id: 42, title: 'New Post', body: 'Body')
@post = Post.new(id: 42, title: 'New Post', body: 'Body') @anonymous_post = Post.new(id: 43, title: 'Hello!!', body: 'Hello, world!!')
@anonymous_post = Post.new(id: 43, title: 'Hello!!', body: 'Hello, world!!') @comment = Comment.new(id: 1, body: 'ZOMG A COMMENT')
@comment = Comment.new(id: 1, body: 'ZOMG A COMMENT') @post.comments = [@comment]
@post.comments = [@comment] @post.blog = @blog
@post.blog = @blog @anonymous_post.comments = []
@anonymous_post.comments = [] @anonymous_post.blog = nil
@anonymous_post.blog = nil @comment.post = @post
@comment.post = @post @comment.author = nil
@comment.author = nil @post.author = @author
@post.author = @author @anonymous_post.author = nil
@anonymous_post.author = nil @blog = Blog.new(id: 1, name: 'My Blog!!')
@blog = Blog.new(id: 1, name: 'My Blog!!') @blog.writer = @author
@blog.writer = @author @blog.articles = [@post, @anonymous_post]
@blog.articles = [@post, @anonymous_post] @author.posts = []
@author.posts = []
@serializer = CommentSerializer.new(@comment) @serializer = CommentSerializer.new(@comment)
@adapter = ActiveModel::Serializer::Adapter::JsonApi.new(@serializer) @adapter = ActiveModelSerializers::Adapter::JsonApi.new(@serializer)
ActionController::Base.cache_store.clear ActionController::Base.cache_store.clear
end end
def test_includes_post_id def test_includes_post_id
expected = { data: { type: 'posts', id: '42' } } expected = { data: { type: 'posts', id: '42' } }
assert_equal(expected, @adapter.serializable_hash[:data][:relationships][:post]) assert_equal(expected, @adapter.serializable_hash[:data][:relationships][:post])
end end
def test_includes_linked_post def test_includes_linked_post
@adapter = ActiveModel::Serializer::Adapter::JsonApi.new(@serializer, include: [:post]) @adapter = ActiveModelSerializers::Adapter::JsonApi.new(@serializer, include: [:post])
expected = [{ expected = [{
id: '42',
type: 'posts',
attributes: {
title: 'New Post',
body: 'Body',
},
relationships: {
comments: { data: [{ type: 'comments', id: '1' }] },
blog: { data: { type: 'blogs', id: '999' } },
author: { data: { type: 'authors', id: '1' } }
}
}]
assert_equal expected, @adapter.serializable_hash[:included]
end
def test_limiting_linked_post_fields
@adapter = ActiveModelSerializers::Adapter::JsonApi.new(@serializer, include: [:post], fields: { post: [:title, :comments, :blog, :author] })
expected = [{
id: '42',
type: 'posts',
attributes: {
title: 'New Post'
},
relationships: {
comments: { data: [{ type: 'comments', id: '1' }] },
blog: { data: { type: 'blogs', id: '999' } },
author: { data: { type: 'authors', id: '1' } }
}
}]
assert_equal expected, @adapter.serializable_hash[:included]
end
def test_include_nil_author
serializer = PostSerializer.new(@anonymous_post)
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 = ActiveModelSerializers::Adapter::JsonApi.new(serializer)
relationships = adapter.serializable_hash[:data][:relationships]
expected = {
writer: {
data: {
type: 'authors',
id: '1'
}
},
articles: {
data: [
{
type: 'posts',
id: '42'
},
{
type: 'posts',
id: '43'
}
]
}
}
assert_equal expected, relationships
end
def test_include_linked_resources_with_type_name
serializer = BlogSerializer.new(@blog)
adapter = ActiveModelSerializers::Adapter::JsonApi.new(serializer, include: [:writer, :articles])
linked = adapter.serializable_hash[:included]
expected = [
{
id: '1',
type: 'authors',
attributes: {
name: 'Steve K.'
},
relationships: {
posts: { data: [] },
roles: { data: [] },
bio: { data: nil }
}
}, {
id: '42', id: '42',
type: 'posts', type: 'posts',
attributes: { attributes: {
title: 'New Post', title: 'New Post',
body: 'Body', body: 'Body'
}, },
relationships: { relationships: {
comments: { data: [{ type: 'comments', id: '1' }] }, comments: { data: [{ type: 'comments', id: '1' }] },
blog: { data: { type: 'blogs', id: '999' } }, blog: { data: { type: 'blogs', id: '999' } },
author: { data: { type: 'authors', id: '1' } } author: { data: { type: 'authors', id: '1' } }
} }
}] }, {
assert_equal expected, @adapter.serializable_hash[:included] id: '43',
end
def test_limiting_linked_post_fields
@adapter = ActiveModel::Serializer::Adapter::JsonApi.new(@serializer, include: [:post], fields: { post: [:title, :comments, :blog, :author] })
expected = [{
id: '42',
type: 'posts', type: 'posts',
attributes: { attributes: {
title: 'New Post' title: 'Hello!!',
body: 'Hello, world!!'
}, },
relationships: { relationships: {
comments: { data: [{ type: 'comments', id: '1' }] }, comments: { data: [] },
blog: { data: { type: 'blogs', id: '999' } }, blog: { data: { type: 'blogs', id: '999' } },
author: { data: { type: 'authors', id: '1' } } author: { data: nil }
}
}]
assert_equal expected, @adapter.serializable_hash[:included]
end
def test_include_nil_author
serializer = PostSerializer.new(@anonymous_post)
adapter = ActiveModel::Serializer::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)
relationships = adapter.serializable_hash[:data][:relationships]
expected = {
writer: {
data: {
type: 'authors',
id: '1'
}
},
articles: {
data: [
{
type: 'posts',
id: '42'
},
{
type: 'posts',
id: '43'
}
]
} }
} }
assert_equal expected, relationships ]
end assert_equal expected, linked
def test_include_linked_resources_with_type_name
serializer = BlogSerializer.new(@blog)
adapter = ActiveModel::Serializer::Adapter::JsonApi.new(serializer, include: [:writer, :articles])
linked = adapter.serializable_hash[:included]
expected = [
{
id: '1',
type: 'authors',
attributes: {
name: 'Steve K.'
},
relationships: {
posts: { data: [] },
roles: { data: [] },
bio: { data: nil }
}
}, {
id: '42',
type: 'posts',
attributes: {
title: 'New Post',
body: 'Body'
},
relationships: {
comments: { data: [{ type: 'comments', id: '1' }] },
blog: { data: { type: 'blogs', id: '999' } },
author: { data: { type: 'authors', id: '1' } }
}
}, {
id: '43',
type: 'posts',
attributes: {
title: 'Hello!!',
body: 'Hello, world!!'
},
relationships: {
comments: { data: [] },
blog: { data: { type: 'blogs', id: '999' } },
author: { data: nil }
}
}
]
assert_equal expected, linked
end
end end
end end
end end

View File

@ -1,95 +1,93 @@
require 'test_helper' require 'test_helper'
module ActiveModel module ActiveModelSerializers
class Serializer module Adapter
module Adapter class JsonApi
class JsonApi class CollectionTest < ActiveSupport::TestCase
class CollectionTest < ActiveSupport::TestCase def setup
def setup @author = Author.new(id: 1, name: 'Steve K.')
@author = Author.new(id: 1, name: 'Steve K.') @author.bio = nil
@author.bio = nil @blog = Blog.new(id: 23, name: 'AMS Blog')
@blog = Blog.new(id: 23, name: 'AMS Blog') @first_post = Post.new(id: 1, title: 'Hello!!', body: 'Hello, world!!')
@first_post = Post.new(id: 1, title: 'Hello!!', body: 'Hello, world!!') @second_post = Post.new(id: 2, title: 'New Post', body: 'Body')
@second_post = Post.new(id: 2, title: 'New Post', body: 'Body') @first_post.comments = []
@first_post.comments = [] @second_post.comments = []
@second_post.comments = [] @first_post.blog = @blog
@first_post.blog = @blog @second_post.blog = nil
@second_post.blog = nil @first_post.author = @author
@first_post.author = @author @second_post.author = @author
@second_post.author = @author @author.posts = [@first_post, @second_post]
@author.posts = [@first_post, @second_post]
@serializer = CollectionSerializer.new([@first_post, @second_post]) @serializer = ActiveModel::Serializer::CollectionSerializer.new([@first_post, @second_post])
@adapter = ActiveModel::Serializer::Adapter::JsonApi.new(@serializer) @adapter = ActiveModelSerializers::Adapter::JsonApi.new(@serializer)
ActionController::Base.cache_store.clear ActionController::Base.cache_store.clear
end end
def test_include_multiple_posts def test_include_multiple_posts
expected = [ expected = [
{ {
id: '1', id: '1',
type: 'posts', type: 'posts',
attributes: { attributes: {
title: 'Hello!!', title: 'Hello!!',
body: 'Hello, world!!' body: 'Hello, world!!'
},
relationships: {
comments: { data: [] },
blog: { data: { type: 'blogs', id: '999' } },
author: { data: { type: 'authors', id: '1' } }
}
}, },
{ relationships: {
id: '2', comments: { data: [] },
type: 'posts', blog: { data: { type: 'blogs', id: '999' } },
attributes: { author: { data: { type: 'authors', id: '1' } }
title: 'New Post',
body: 'Body'
},
relationships: {
comments: { data: [] },
blog: { data: { type: 'blogs', id: '999' } },
author: { data: { type: 'authors', id: '1' } }
}
} }
] },
{
assert_equal(expected, @adapter.serializable_hash[:data]) id: '2',
end type: 'posts',
attributes: {
def test_limiting_fields title: 'New Post',
actual = ActiveModel::SerializableResource.new( body: 'Body'
[@first_post, @second_post], adapter: :json_api,
fields: { posts: %w(title comments blog author) })
.serializable_hash
expected = [
{
id: '1',
type: 'posts',
attributes: {
title: 'Hello!!'
},
relationships: {
comments: { data: [] },
blog: { data: { type: 'blogs', id: '999' } },
author: { data: { type: 'authors', id: '1' } }
}
}, },
{ relationships: {
id: '2', comments: { data: [] },
type: 'posts', blog: { data: { type: 'blogs', id: '999' } },
attributes: { author: { data: { type: 'authors', id: '1' } }
title: 'New Post'
},
relationships: {
comments: { data: [] },
blog: { data: { type: 'blogs', id: '999' } },
author: { data: { type: 'authors', id: '1' } }
}
} }
] }
assert_equal(expected, actual[:data]) ]
end
assert_equal(expected, @adapter.serializable_hash[:data])
end
def test_limiting_fields
actual = ActiveModel::SerializableResource.new(
[@first_post, @second_post], adapter: :json_api,
fields: { posts: %w(title comments blog author) })
.serializable_hash
expected = [
{
id: '1',
type: 'posts',
attributes: {
title: 'Hello!!'
},
relationships: {
comments: { data: [] },
blog: { data: { type: 'blogs', id: '999' } },
author: { data: { type: 'authors', id: '1' } }
}
},
{
id: '2',
type: 'posts',
attributes: {
title: 'New Post'
},
relationships: {
comments: { data: [] },
blog: { data: { type: 'blogs', id: '999' } },
author: { data: { type: 'authors', id: '1' } }
}
}
]
assert_equal(expected, actual[:data])
end end
end end
end end

View File

@ -1,87 +1,85 @@
require 'test_helper' require 'test_helper'
module ActiveModel module ActiveModelSerializers
class Serializer module Adapter
module Adapter class JsonApi
class JsonApi class FieldsTest < ActiveSupport::TestCase
class FieldsTest < ActiveSupport::TestCase Post = Class.new(::Model)
Post = Class.new(::Model) class PostSerializer < ActiveModel::Serializer
class PostSerializer < ActiveModel::Serializer type 'posts'
type 'posts' attributes :title, :body
attributes :title, :body belongs_to :author
belongs_to :author has_many :comments
has_many :comments end
end
Author = Class.new(::Model) Author = Class.new(::Model)
class AuthorSerializer < ActiveModel::Serializer class AuthorSerializer < ActiveModel::Serializer
type 'authors' type 'authors'
attributes :name, :birthday attributes :name, :birthday
end end
Comment = Class.new(::Model) Comment = Class.new(::Model)
class CommentSerializer < ActiveModel::Serializer class CommentSerializer < ActiveModel::Serializer
type 'comments' type 'comments'
attributes :body attributes :body
belongs_to :author belongs_to :author
end end
def setup def setup
@author = Author.new(id: 1, name: 'Lucas', birthday: '10.01.1990') @author = Author.new(id: 1, name: 'Lucas', birthday: '10.01.1990')
@comment1 = Comment.new(id: 7, body: 'cool', author: @author) @comment1 = Comment.new(id: 7, body: 'cool', author: @author)
@comment2 = Comment.new(id: 12, body: 'awesome', author: @author) @comment2 = Comment.new(id: 12, body: 'awesome', author: @author)
@post = Post.new(id: 1337, title: 'Title 1', body: 'Body 1', @post = Post.new(id: 1337, title: 'Title 1', body: 'Body 1',
author: @author, comments: [@comment1, @comment2]) author: @author, comments: [@comment1, @comment2])
@comment1.post = @post @comment1.post = @post
@comment2.post = @post @comment2.post = @post
end end
def test_fields_attributes def test_fields_attributes
fields = { posts: [:title] } fields = { posts: [:title] }
hash = serializable(@post, adapter: :json_api, fields: fields).serializable_hash hash = serializable(@post, adapter: :json_api, fields: fields).serializable_hash
expected = { expected = {
title: 'Title 1' title: 'Title 1'
} }
assert_equal(expected, hash[:data][:attributes]) assert_equal(expected, hash[:data][:attributes])
end end
def test_fields_relationships def test_fields_relationships
fields = { posts: [:author] } fields = { posts: [:author] }
hash = serializable(@post, adapter: :json_api, fields: fields).serializable_hash hash = serializable(@post, adapter: :json_api, fields: fields).serializable_hash
expected = { expected = {
author: { author: {
data: { data: {
type: 'authors', type: 'authors',
id: '1' id: '1'
}
} }
} }
}
assert_equal(expected, hash[:data][:relationships]) assert_equal(expected, hash[:data][:relationships])
end end
def test_fields_included def test_fields_included
fields = { posts: [:author], comments: [:body] } fields = { posts: [:author], comments: [:body] }
hash = serializable(@post, adapter: :json_api, fields: fields, include: 'comments').serializable_hash hash = serializable(@post, adapter: :json_api, fields: fields, include: 'comments').serializable_hash
expected = [ expected = [
{ {
type: 'comments', type: 'comments',
id: '7', id: '7',
attributes: { attributes: {
body: 'cool' body: 'cool'
}
}, {
type: 'comments',
id: '12',
attributes: {
body: 'awesome'
}
} }
] }, {
type: 'comments',
id: '12',
attributes: {
body: 'awesome'
}
}
]
assert_equal(expected, hash[:included]) assert_equal(expected, hash[:included])
end
end end
end end
end end

View File

@ -1,43 +1,41 @@
require 'test_helper' require 'test_helper'
module ActiveModel module ActiveModelSerializers
class Serializer module Adapter
module Adapter class JsonApi
class JsonApi class HasManyEmbedIdsTest < ActiveSupport::TestCase
class HasManyEmbedIdsTest < ActiveSupport::TestCase def setup
def setup @author = Author.new(name: 'Steve K.')
@author = Author.new(name: 'Steve K.') @author.bio = nil
@author.bio = nil @author.roles = nil
@author.roles = nil @first_post = Post.new(id: 1, title: 'Hello!!', body: 'Hello, world!!')
@first_post = Post.new(id: 1, title: 'Hello!!', body: 'Hello, world!!') @second_post = Post.new(id: 2, title: 'New Post', body: 'Body')
@second_post = Post.new(id: 2, title: 'New Post', body: 'Body') @author.posts = [@first_post, @second_post]
@author.posts = [@first_post, @second_post] @first_post.author = @author
@first_post.author = @author @second_post.author = @author
@second_post.author = @author @first_post.comments = []
@first_post.comments = [] @second_post.comments = []
@second_post.comments = [] @blog = Blog.new(id: 23, name: 'AMS Blog')
@blog = Blog.new(id: 23, name: 'AMS Blog') @first_post.blog = @blog
@first_post.blog = @blog @second_post.blog = nil
@second_post.blog = nil
@serializer = AuthorSerializer.new(@author) @serializer = AuthorSerializer.new(@author)
@adapter = ActiveModel::Serializer::Adapter::JsonApi.new(@serializer) @adapter = ActiveModelSerializers::Adapter::JsonApi.new(@serializer)
end end
def test_includes_comment_ids def test_includes_comment_ids
expected = { expected = {
data: [ data: [
{ type: 'posts', id: '1' }, { type: 'posts', id: '1' },
{ type: 'posts', id: '2' } { type: 'posts', id: '2' }
] ]
} }
assert_equal(expected, @adapter.serializable_hash[:data][:relationships][:posts]) assert_equal(expected, @adapter.serializable_hash[:data][:relationships][:posts])
end end
def test_no_includes_linked_comments def test_no_includes_linked_comments
assert_nil @adapter.serializable_hash[:linked] assert_nil @adapter.serializable_hash[:linked]
end
end end
end end
end end

View File

@ -1,96 +1,94 @@
require 'test_helper' require 'test_helper'
module ActiveModel module ActiveModelSerializers
class Serializer module Adapter
module Adapter class JsonApi
class JsonApi # Test 'has_many :assocs, serializer: AssocXSerializer'
# Test 'has_many :assocs, serializer: AssocXSerializer' class HasManyExplicitSerializerTest < ActiveSupport::TestCase
class HasManyExplicitSerializerTest < ActiveSupport::TestCase def setup
def setup @post = Post.new(title: 'New Post', body: 'Body')
@post = Post.new(title: 'New Post', body: 'Body') @author = Author.new(name: 'Jane Blogger')
@author = Author.new(name: 'Jane Blogger') @author.posts = [@post]
@author.posts = [@post] @post.author = @author
@post.author = @author @first_comment = Comment.new(id: 1, body: 'ZOMG A COMMENT')
@first_comment = Comment.new(id: 1, body: 'ZOMG A COMMENT') @second_comment = Comment.new(id: 2, body: 'ZOMG ANOTHER COMMENT')
@second_comment = Comment.new(id: 2, body: 'ZOMG ANOTHER COMMENT') @post.comments = [@first_comment, @second_comment]
@post.comments = [@first_comment, @second_comment] @first_comment.post = @post
@first_comment.post = @post @first_comment.author = nil
@first_comment.author = nil @second_comment.post = @post
@second_comment.post = @post @second_comment.author = nil
@second_comment.author = nil @blog = Blog.new(id: 23, name: 'AMS Blog')
@blog = Blog.new(id: 23, name: 'AMS Blog') @post.blog = @blog
@post.blog = @blog
@serializer = PostPreviewSerializer.new(@post) @serializer = PostPreviewSerializer.new(@post)
@adapter = ActiveModel::Serializer::Adapter::JsonApi.new( @adapter = ActiveModelSerializers::Adapter::JsonApi.new(
@serializer, @serializer,
include: [:comments, :author] include: [:comments, :author]
) )
end end
def test_includes_comment_ids def test_includes_comment_ids
expected = { expected = {
data: [ data: [
{ type: 'comments', id: '1' }, { type: 'comments', id: '1' },
{ type: 'comments', id: '2' } { type: 'comments', id: '2' }
]
}
assert_equal(expected, @adapter.serializable_hash[:data][:relationships][:comments])
end
def test_includes_linked_data
# If CommentPreviewSerializer is applied correctly the body text will not be present in the output
expected = [
{
id: '1',
type: 'comments',
relationships: {
post: { data: { type: 'posts', id: @post.id.to_s } }
}
},
{
id: '2',
type: 'comments',
relationships: {
post: { data: { type: 'posts', id: @post.id.to_s } }
}
},
{
id: @author.id.to_s,
type: 'authors',
relationships: {
posts: { data: [{ type: 'posts', id: @post.id.to_s }] }
}
}
] ]
}
assert_equal(expected, @adapter.serializable_hash[:included]) assert_equal(expected, @adapter.serializable_hash[:data][:relationships][:comments])
end end
def test_includes_author_id def test_includes_linked_data
expected = { # If CommentPreviewSerializer is applied correctly the body text will not be present in the output
data: { type: 'authors', id: @author.id.to_s } expected = [
{
id: '1',
type: 'comments',
relationships: {
post: { data: { type: 'posts', id: @post.id.to_s } }
}
},
{
id: '2',
type: 'comments',
relationships: {
post: { data: { type: 'posts', id: @post.id.to_s } }
}
},
{
id: @author.id.to_s,
type: 'authors',
relationships: {
posts: { data: [{ type: 'posts', id: @post.id.to_s }] }
}
} }
]
assert_equal(expected, @adapter.serializable_hash[:data][:relationships][:author]) assert_equal(expected, @adapter.serializable_hash[:included])
end end
def test_explicit_serializer_with_null_resource def test_includes_author_id
@post.author = nil expected = {
data: { type: 'authors', id: @author.id.to_s }
}
expected = { data: nil } assert_equal(expected, @adapter.serializable_hash[:data][:relationships][:author])
end
assert_equal(expected, @adapter.serializable_hash[:data][:relationships][:author]) def test_explicit_serializer_with_null_resource
end @post.author = nil
def test_explicit_serializer_with_null_collection expected = { data: nil }
@post.comments = []
expected = { data: [] } assert_equal(expected, @adapter.serializable_hash[:data][:relationships][:author])
end
assert_equal(expected, @adapter.serializable_hash[:data][:relationships][:comments]) def test_explicit_serializer_with_null_collection
end @post.comments = []
expected = { data: [] }
assert_equal(expected, @adapter.serializable_hash[:data][:relationships][:comments])
end end
end end
end end

View File

@ -1,143 +1,141 @@
require 'test_helper' require 'test_helper'
module ActiveModel module ActiveModelSerializers
class Serializer module Adapter
module Adapter class JsonApi
class JsonApi class HasManyTest < ActiveSupport::TestCase
class HasManyTest < ActiveSupport::TestCase def setup
def setup ActionController::Base.cache_store.clear
ActionController::Base.cache_store.clear @author = Author.new(id: 1, name: 'Steve K.')
@author = Author.new(id: 1, name: 'Steve K.') @author.posts = []
@author.posts = [] @author.bio = nil
@author.bio = nil @post = Post.new(id: 1, title: 'New Post', body: 'Body')
@post = Post.new(id: 1, title: 'New Post', body: 'Body') @post_without_comments = Post.new(id: 2, title: 'Second Post', body: 'Second')
@post_without_comments = Post.new(id: 2, title: 'Second Post', body: 'Second') @first_comment = Comment.new(id: 1, body: 'ZOMG A COMMENT')
@first_comment = Comment.new(id: 1, body: 'ZOMG A COMMENT') @first_comment.author = nil
@first_comment.author = nil @second_comment = Comment.new(id: 2, body: 'ZOMG ANOTHER COMMENT')
@second_comment = Comment.new(id: 2, body: 'ZOMG ANOTHER COMMENT') @second_comment.author = nil
@second_comment.author = nil @post.comments = [@first_comment, @second_comment]
@post.comments = [@first_comment, @second_comment] @post_without_comments.comments = []
@post_without_comments.comments = [] @first_comment.post = @post
@first_comment.post = @post @second_comment.post = @post
@second_comment.post = @post @post.author = @author
@post.author = @author @post_without_comments.author = nil
@post_without_comments.author = nil @blog = Blog.new(id: 1, name: 'My Blog!!')
@blog = Blog.new(id: 1, name: 'My Blog!!') @blog.writer = @author
@blog.writer = @author @blog.articles = [@post]
@blog.articles = [@post] @post.blog = @blog
@post.blog = @blog @post_without_comments.blog = nil
@post_without_comments.blog = nil @tag = Tag.new(id: 1, name: '#hash_tag')
@tag = Tag.new(id: 1, name: '#hash_tag') @post.tags = [@tag]
@post.tags = [@tag] @serializer = PostSerializer.new(@post)
@serializer = PostSerializer.new(@post) @adapter = ActiveModelSerializers::Adapter::JsonApi.new(@serializer)
@adapter = ActiveModel::Serializer::Adapter::JsonApi.new(@serializer)
@virtual_value = VirtualValue.new(id: 1) @virtual_value = VirtualValue.new(id: 1)
end end
def test_includes_comment_ids def test_includes_comment_ids
expected = { data: [{ type: 'comments', id: '1' }, { type: 'comments', id: '2' }] } expected = { data: [{ type: 'comments', id: '1' }, { type: 'comments', id: '2' }] }
assert_equal(expected, @adapter.serializable_hash[:data][:relationships][:comments]) assert_equal(expected, @adapter.serializable_hash[:data][:relationships][:comments])
end end
def test_includes_linked_comments def test_includes_linked_comments
@adapter = ActiveModel::Serializer::Adapter::JsonApi.new(@serializer, include: [:comments]) @adapter = ActiveModelSerializers::Adapter::JsonApi.new(@serializer, include: [:comments])
expected = [{ expected = [{
id: '1', id: '1',
type: 'comments', type: 'comments',
attributes: { attributes: {
body: 'ZOMG A COMMENT' body: 'ZOMG A COMMENT'
}, },
relationships: { relationships: {
post: { data: { type: 'posts', id: '1' } }, post: { data: { type: 'posts', id: '1' } },
author: { data: nil } author: { data: nil }
}
}, {
id: '2',
type: 'comments',
attributes: {
body: 'ZOMG ANOTHER COMMENT'
},
relationships: {
post: { data: { type: 'posts', id: '1' } },
author: { data: nil }
}
}]
assert_equal expected, @adapter.serializable_hash[:included]
end
def test_limit_fields_of_linked_comments
@adapter = ActiveModel::Serializer::Adapter::JsonApi.new(@serializer, include: [:comments], fields: { comment: [:id, :post, :author] })
expected = [{
id: '1',
type: 'comments',
relationships: {
post: { data: { type: 'posts', id: '1' } },
author: { data: nil }
}
}, {
id: '2',
type: 'comments',
relationships: {
post: { data: { type: 'posts', id: '1' } },
author: { data: nil }
}
}]
assert_equal expected, @adapter.serializable_hash[:included]
end
def test_no_include_linked_if_comments_is_empty
serializer = PostSerializer.new(@post_without_comments)
adapter = ActiveModel::Serializer::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)
actual = adapter.serializable_hash[:data][:relationships][:articles]
expected = {
data: [{
type: 'posts',
id: '1'
}]
} }
assert_equal expected, actual }, {
end id: '2',
type: 'comments',
attributes: {
body: 'ZOMG ANOTHER COMMENT'
},
relationships: {
post: { data: { type: 'posts', id: '1' } },
author: { data: nil }
}
}]
assert_equal expected, @adapter.serializable_hash[:included]
end
def test_has_many_with_no_serializer def test_limit_fields_of_linked_comments
serializer = PostWithTagsSerializer.new(@post) @adapter = ActiveModelSerializers::Adapter::JsonApi.new(@serializer, include: [:comments], fields: { comment: [:id, :post, :author] })
adapter = ActiveModel::Serializer::Adapter::JsonApi.new(serializer) expected = [{
id: '1',
type: 'comments',
relationships: {
post: { data: { type: 'posts', id: '1' } },
author: { data: nil }
}
}, {
id: '2',
type: 'comments',
relationships: {
post: { data: { type: 'posts', id: '1' } },
author: { data: nil }
}
}]
assert_equal expected, @adapter.serializable_hash[:included]
end
assert_equal({ def test_no_include_linked_if_comments_is_empty
data: { serializer = PostSerializer.new(@post_without_comments)
id: '1', adapter = ActiveModelSerializers::Adapter::JsonApi.new(serializer)
type: 'posts',
relationships: { assert_nil adapter.serializable_hash[:linked]
tags: { data: [@tag.as_json] } end
}
def test_include_type_for_association_when_different_than_name
serializer = BlogSerializer.new(@blog)
adapter = ActiveModelSerializers::Adapter::JsonApi.new(serializer)
actual = adapter.serializable_hash[:data][:relationships][:articles]
expected = {
data: [{
type: 'posts',
id: '1'
}]
}
assert_equal expected, actual
end
def test_has_many_with_no_serializer
serializer = PostWithTagsSerializer.new(@post)
adapter = ActiveModelSerializers::Adapter::JsonApi.new(serializer)
assert_equal({
data: {
id: '1',
type: 'posts',
relationships: {
tags: { data: [@tag.as_json] }
} }
}, adapter.serializable_hash) }
end }, adapter.serializable_hash)
end
def test_has_many_with_virtual_value def test_has_many_with_virtual_value
serializer = VirtualValueSerializer.new(@virtual_value) serializer = VirtualValueSerializer.new(@virtual_value)
adapter = ActiveModel::Serializer::Adapter::JsonApi.new(serializer) adapter = ActiveModelSerializers::Adapter::JsonApi.new(serializer)
assert_equal({ assert_equal({
data: { data: {
id: '1', id: '1',
type: 'virtual_values', type: 'virtual_values',
relationships: { relationships: {
maker: { data: { id: 1 } }, maker: { data: { id: 1 } },
reviews: { data: [{ id: 1 }, { id: 2 }] } reviews: { data: [{ id: 1 }, { id: 2 }] }
}
} }
}, adapter.serializable_hash) }
end }, adapter.serializable_hash)
end end
end end
end end

View File

@ -1,79 +1,77 @@
require 'test_helper' require 'test_helper'
module ActiveModel module ActiveModelSerializers
class Serializer module Adapter
module Adapter class JsonApi
class JsonApi class HasOneTest < ActiveSupport::TestCase
class HasOneTest < ActiveSupport::TestCase def setup
def setup @author = Author.new(id: 1, name: 'Steve K.')
@author = Author.new(id: 1, name: 'Steve K.') @bio = Bio.new(id: 43, content: 'AMS Contributor')
@bio = Bio.new(id: 43, content: 'AMS Contributor') @author.bio = @bio
@author.bio = @bio @bio.author = @author
@bio.author = @author @post = Post.new(id: 42, title: 'New Post', body: 'Body')
@post = Post.new(id: 42, title: 'New Post', body: 'Body') @anonymous_post = Post.new(id: 43, title: 'Hello!!', body: 'Hello, world!!')
@anonymous_post = Post.new(id: 43, title: 'Hello!!', body: 'Hello, world!!') @comment = Comment.new(id: 1, body: 'ZOMG A COMMENT')
@comment = Comment.new(id: 1, body: 'ZOMG A COMMENT') @post.comments = [@comment]
@post.comments = [@comment] @anonymous_post.comments = []
@anonymous_post.comments = [] @comment.post = @post
@comment.post = @post @comment.author = nil
@comment.author = nil @post.author = @author
@post.author = @author @anonymous_post.author = nil
@anonymous_post.author = nil @blog = Blog.new(id: 1, name: 'My Blog!!')
@blog = Blog.new(id: 1, name: 'My Blog!!') @blog.writer = @author
@blog.writer = @author @blog.articles = [@post, @anonymous_post]
@blog.articles = [@post, @anonymous_post] @author.posts = []
@author.posts = [] @author.roles = []
@author.roles = []
@virtual_value = VirtualValue.new(id: 1) @virtual_value = VirtualValue.new(id: 1)
@serializer = AuthorSerializer.new(@author) @serializer = AuthorSerializer.new(@author)
@adapter = ActiveModel::Serializer::Adapter::JsonApi.new(@serializer, include: [:bio, :posts]) @adapter = ActiveModelSerializers::Adapter::JsonApi.new(@serializer, include: [:bio, :posts])
end end
def test_includes_bio_id def test_includes_bio_id
expected = { data: { type: 'bios', id: '43' } } expected = { data: { type: 'bios', id: '43' } }
assert_equal(expected, @adapter.serializable_hash[:data][:relationships][:bio]) assert_equal(expected, @adapter.serializable_hash[:data][:relationships][:bio])
end end
def test_includes_linked_bio def test_includes_linked_bio
@adapter = ActiveModel::Serializer::Adapter::JsonApi.new(@serializer, include: [:bio]) @adapter = ActiveModelSerializers::Adapter::JsonApi.new(@serializer, include: [:bio])
expected = [ expected = [
{ {
id: '43', id: '43',
type: 'bios', type: 'bios',
attributes: { attributes: {
content: 'AMS Contributor', content: 'AMS Contributor',
rating: nil rating: nil
}, },
relationships: { relationships: {
author: { data: { type: 'authors', id: '1' } } author: { data: { type: 'authors', id: '1' } }
}
}
]
assert_equal(expected, @adapter.serializable_hash[:included])
end
def test_has_one_with_virtual_value
serializer = VirtualValueSerializer.new(@virtual_value)
adapter = ActiveModel::Serializer::Adapter::JsonApi.new(serializer)
expected = {
data: {
id: '1',
type: 'virtual_values',
relationships: {
maker: { data: { id: 1 } },
reviews: { data: [{ id: 1 }, { id: 2 }] }
}
} }
} }
]
assert_equal(expected, adapter.serializable_hash) assert_equal(expected, @adapter.serializable_hash[:included])
end end
def test_has_one_with_virtual_value
serializer = VirtualValueSerializer.new(@virtual_value)
adapter = ActiveModelSerializers::Adapter::JsonApi.new(serializer)
expected = {
data: {
id: '1',
type: 'virtual_values',
relationships: {
maker: { data: { id: 1 } },
reviews: { data: [{ id: 1 }, { id: 2 }] }
}
}
}
assert_equal(expected, adapter.serializable_hash)
end end
end end
end end

View File

@ -1,37 +1,35 @@
require 'test_helper' require 'test_helper'
module ActiveModel module ActiveModelSerializers
class Serializer module Adapter
module Adapter class JsonApiTest < ActiveSupport::TestCase
class JsonApiTest < ActiveSupport::TestCase def setup
def setup ActionController::Base.cache_store.clear
ActionController::Base.cache_store.clear @author = Author.new(id: 1, name: 'Steve K.')
@author = Author.new(id: 1, name: 'Steve K.') @post = Post.new(id: 1, title: 'New Post', body: 'Body')
@post = Post.new(id: 1, title: 'New Post', body: 'Body') @first_comment = Comment.new(id: 1, body: 'ZOMG A COMMENT')
@first_comment = Comment.new(id: 1, body: 'ZOMG A COMMENT') @second_comment = Comment.new(id: 2, body: 'ZOMG ANOTHER COMMENT')
@second_comment = Comment.new(id: 2, body: 'ZOMG ANOTHER COMMENT') @post.comments = [@first_comment, @second_comment]
@post.comments = [@first_comment, @second_comment] @first_comment.post = @post
@first_comment.post = @post @second_comment.post = @post
@second_comment.post = @post @post.author = @author
@post.author = @author @blog = Blog.new(id: 1, name: 'My Blog!!')
@blog = Blog.new(id: 1, name: 'My Blog!!') @post.blog = @blog
@post.blog = @blog end
end
def test_custom_keys def test_custom_keys
serializer = PostWithCustomKeysSerializer.new(@post) serializer = PostWithCustomKeysSerializer.new(@post)
adapter = ActiveModel::Serializer::Adapter::JsonApi.new(serializer) adapter = ActiveModelSerializers::Adapter::JsonApi.new(serializer)
assert_equal({ assert_equal({
reviews: { data: [ reviews: { data: [
{ type: 'comments', id: '1' }, { type: 'comments', id: '1' },
{ type: 'comments', id: '2' } { type: 'comments', id: '2' }
] }, ] },
writer: { data: { type: 'authors', id: '1' } }, writer: { data: { type: 'authors', id: '1' } },
site: { data: { type: 'blogs', id: '1' } } site: { data: { type: 'blogs', id: '1' } }
}, adapter.serializable_hash[:data][:relationships]) }, adapter.serializable_hash[:data][:relationships])
end
end end
end end
end end
end end

View File

@ -5,168 +5,106 @@ class NestedPostSerializer < ActiveModel::Serializer
has_many :nested_posts has_many :nested_posts
end end
module ActiveModel module ActiveModelSerializers
class Serializer module Adapter
module Adapter class JsonApi
class JsonApi class LinkedTest < ActiveSupport::TestCase
class LinkedTest < ActiveSupport::TestCase def setup
def setup @author1 = Author.new(id: 1, name: 'Steve K.')
@author1 = Author.new(id: 1, name: 'Steve K.') @author2 = Author.new(id: 2, name: 'Tenderlove')
@author2 = Author.new(id: 2, name: 'Tenderlove') @bio1 = Bio.new(id: 1, content: 'AMS Contributor')
@bio1 = Bio.new(id: 1, content: 'AMS Contributor') @bio2 = Bio.new(id: 2, content: 'Rails Contributor')
@bio2 = Bio.new(id: 2, content: 'Rails Contributor') @first_post = Post.new(id: 10, title: 'Hello!!', body: 'Hello, world!!')
@first_post = Post.new(id: 10, title: 'Hello!!', body: 'Hello, world!!') @second_post = Post.new(id: 20, title: 'New Post', body: 'Body')
@second_post = Post.new(id: 20, title: 'New Post', body: 'Body') @third_post = Post.new(id: 30, title: 'Yet Another Post', body: 'Body')
@third_post = Post.new(id: 30, title: 'Yet Another Post', body: 'Body') @blog = Blog.new({ name: 'AMS Blog' })
@blog = Blog.new({ name: 'AMS Blog' }) @first_comment = Comment.new(id: 1, body: 'ZOMG A COMMENT')
@first_comment = Comment.new(id: 1, body: 'ZOMG A COMMENT') @second_comment = Comment.new(id: 2, body: 'ZOMG ANOTHER COMMENT')
@second_comment = Comment.new(id: 2, body: 'ZOMG ANOTHER COMMENT') @first_post.blog = @blog
@first_post.blog = @blog @second_post.blog = @blog
@second_post.blog = @blog @third_post.blog = nil
@third_post.blog = nil @first_post.comments = [@first_comment, @second_comment]
@first_post.comments = [@first_comment, @second_comment] @second_post.comments = []
@second_post.comments = [] @third_post.comments = []
@third_post.comments = [] @first_post.author = @author1
@first_post.author = @author1 @second_post.author = @author2
@second_post.author = @author2 @third_post.author = @author1
@third_post.author = @author1 @first_comment.post = @first_post
@first_comment.post = @first_post @first_comment.author = nil
@first_comment.author = nil @second_comment.post = @first_post
@second_comment.post = @first_post @second_comment.author = nil
@second_comment.author = nil @author1.posts = [@first_post, @third_post]
@author1.posts = [@first_post, @third_post] @author1.bio = @bio1
@author1.bio = @bio1 @author1.roles = []
@author1.roles = [] @author2.posts = [@second_post]
@author2.posts = [@second_post] @author2.bio = @bio2
@author2.bio = @bio2 @author2.roles = []
@author2.roles = [] @bio1.author = @author1
@bio1.author = @author1 @bio2.author = @author2
@bio2.author = @author2 end
end
def test_include_multiple_posts_and_linked_array def test_include_multiple_posts_and_linked_array
serializer = CollectionSerializer.new([@first_post, @second_post]) serializer = ActiveModel::Serializer::CollectionSerializer.new([@first_post, @second_post])
adapter = ActiveModel::Serializer::Adapter::JsonApi.new( adapter = ActiveModelSerializers::Adapter::JsonApi.new(
serializer, serializer,
include: [:comments, author: [:bio]] include: [:comments, author: [:bio]]
) )
alt_adapter = ActiveModel::Serializer::Adapter::JsonApi.new( alt_adapter = ActiveModelSerializers::Adapter::JsonApi.new(
serializer, serializer,
include: [:comments, author: [:bio]] include: [:comments, author: [:bio]]
) )
expected = { expected = {
data: [ data: [
{
id: '10',
type: 'posts',
attributes: {
title: 'Hello!!',
body: 'Hello, world!!'
},
relationships: {
comments: { data: [{ type: 'comments', id: '1' }, { type: 'comments', id: '2' }] },
blog: { data: { type: 'blogs', id: '999' } },
author: { data: { type: 'authors', id: '1' } }
}
},
{
id: '20',
type: 'posts',
attributes: {
title: 'New Post',
body: 'Body'
},
relationships: {
comments: { data: [] },
blog: { data: { type: 'blogs', id: '999' } },
author: { data: { type: 'authors', id: '2' } }
}
}
],
included: [
{
id: '1',
type: 'comments',
attributes: {
body: 'ZOMG A COMMENT'
},
relationships: {
post: { data: { type: 'posts', id: '10' } },
author: { data: nil }
}
}, {
id: '2',
type: 'comments',
attributes: {
body: 'ZOMG ANOTHER COMMENT',
},
relationships: {
post: { data: { type: 'posts', id: '10' } },
author: { data: nil }
}
}, {
id: '1',
type: 'authors',
attributes: {
name: 'Steve K.'
},
relationships: {
posts: { data: [{ type: 'posts', id: '10' }, { type: 'posts', id: '30' }] },
roles: { data: [] },
bio: { data: { type: 'bios', id: '1' } }
}
}, {
id: '1',
type: 'bios',
attributes: {
content: 'AMS Contributor',
rating: nil
},
relationships: {
author: { data: { type: 'authors', id: '1' } }
}
}, {
id: '2',
type: 'authors',
attributes: {
name: 'Tenderlove'
},
relationships: {
posts: { data: [{ type: 'posts', id: '20' }] },
roles: { data: [] },
bio: { data: { type: 'bios', id: '2' } }
}
}, {
id: '2',
type: 'bios',
attributes: {
rating: nil,
content: 'Rails Contributor',
},
relationships: {
author: { data: { type: 'authors', id: '2' } }
}
}
]
}
assert_equal expected, adapter.serializable_hash
assert_equal expected, alt_adapter.serializable_hash
end
def test_include_multiple_posts_and_linked
serializer = BioSerializer.new @bio1
adapter = ActiveModel::Serializer::Adapter::JsonApi.new(
serializer,
include: [author: [:posts]]
)
alt_adapter = ActiveModel::Serializer::Adapter::JsonApi.new(
serializer,
include: [author: [:posts]]
)
expected = [
{ {
id: '10',
type: 'posts',
attributes: {
title: 'Hello!!',
body: 'Hello, world!!'
},
relationships: {
comments: { data: [{ type: 'comments', id: '1' }, { type: 'comments', id: '2' }] },
blog: { data: { type: 'blogs', id: '999' } },
author: { data: { type: 'authors', id: '1' } }
}
},
{
id: '20',
type: 'posts',
attributes: {
title: 'New Post',
body: 'Body'
},
relationships: {
comments: { data: [] },
blog: { data: { type: 'blogs', id: '999' } },
author: { data: { type: 'authors', id: '2' } }
}
}
],
included: [
{
id: '1',
type: 'comments',
attributes: {
body: 'ZOMG A COMMENT'
},
relationships: {
post: { data: { type: 'posts', id: '10' } },
author: { data: nil }
}
}, {
id: '2',
type: 'comments',
attributes: {
body: 'ZOMG ANOTHER COMMENT',
},
relationships: {
post: { data: { type: 'posts', id: '10' } },
author: { data: nil }
}
}, {
id: '1', id: '1',
type: 'authors', type: 'authors',
attributes: { attributes: {
@ -178,215 +116,275 @@ module ActiveModel
bio: { data: { type: 'bios', id: '1' } } bio: { data: { type: 'bios', id: '1' } }
} }
}, { }, {
id: '10', id: '1',
type: 'posts', type: 'bios',
attributes: { attributes: {
title: 'Hello!!', content: 'AMS Contributor',
body: 'Hello, world!!' rating: nil
}, },
relationships: { relationships: {
comments: { data: [{ type: 'comments', id: '1' }, { type: 'comments', id: '2' }] },
blog: { data: { type: 'blogs', id: '999' } },
author: { data: { type: 'authors', id: '1' } } author: { data: { type: 'authors', id: '1' } }
} }
}, { }, {
id: '30', id: '2',
type: 'posts', type: 'authors',
attributes: { attributes: {
title: 'Yet Another Post', name: 'Tenderlove'
body: 'Body'
}, },
relationships: { relationships: {
comments: { data: [] }, posts: { data: [{ type: 'posts', id: '20' }] },
blog: { data: { type: 'blogs', id: '999' } }, roles: { data: [] },
author: { data: { type: 'authors', id: '1' } } bio: { data: { type: 'bios', id: '2' } }
}
}, {
id: '2',
type: 'bios',
attributes: {
rating: nil,
content: 'Rails Contributor',
},
relationships: {
author: { data: { type: 'authors', id: '2' } }
} }
} }
] ]
}
assert_equal expected, adapter.serializable_hash[:included] assert_equal expected, adapter.serializable_hash
assert_equal expected, alt_adapter.serializable_hash[:included] assert_equal expected, alt_adapter.serializable_hash
end
def test_underscore_model_namespace_for_linked_resource_type
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)
relationships = adapter.serializable_hash[:data][:relationships]
expected = {
related: {
data: [{
type: 'spam_unrelated_links',
id: '456'
}]
}
}
assert_equal expected, relationships
end
def test_multiple_references_to_same_resource
serializer = CollectionSerializer.new([@first_comment, @second_comment])
adapter = ActiveModel::Serializer::Adapter::JsonApi.new(
serializer,
include: [:post]
)
expected = [
{
id: '10',
type: 'posts',
attributes: {
title: 'Hello!!',
body: 'Hello, world!!'
},
relationships: {
comments: {
data: [{ type: 'comments', id: '1' }, { type: 'comments', id: '2' }]
},
blog: {
data: { type: 'blogs', id: '999' }
},
author: {
data: { type: 'authors', id: '1' }
}
}
}
]
assert_equal expected, adapter.serializable_hash[:included]
end
def test_nil_link_with_specified_serializer
@first_post.author = nil
serializer = PostPreviewSerializer.new(@first_post)
adapter = ActiveModel::Serializer::Adapter::JsonApi.new(
serializer,
include: [:author]
)
expected = {
data: {
id: '10',
type: 'posts',
attributes: {
title: 'Hello!!',
body: 'Hello, world!!'
},
relationships: {
comments: { data: [{ type: 'comments', id: '1' }, { type: 'comments', id: '2' }] },
author: { data: nil }
}
}
}
assert_equal expected, adapter.serializable_hash
end
end end
class NoDuplicatesTest < ActiveSupport::TestCase def test_include_multiple_posts_and_linked
Post = Class.new(::Model) serializer = BioSerializer.new @bio1
Author = Class.new(::Model) adapter = ActiveModelSerializers::Adapter::JsonApi.new(
serializer,
include: [author: [:posts]]
)
alt_adapter = ActiveModelSerializers::Adapter::JsonApi.new(
serializer,
include: [author: [:posts]]
)
class PostSerializer < ActiveModel::Serializer expected = [
type 'posts' {
belongs_to :author id: '1',
end type: 'authors',
attributes: {
class AuthorSerializer < ActiveModel::Serializer name: 'Steve K.'
type 'authors'
has_many :posts
end
def setup
@author = Author.new(id: 1, posts: [], roles: [], bio: nil)
@post1 = Post.new(id: 1, author: @author)
@post2 = Post.new(id: 2, author: @author)
@author.posts << @post1
@author.posts << @post2
@nestedpost1 = ::NestedPost.new(id: 1, nested_posts: [])
@nestedpost2 = ::NestedPost.new(id: 2, nested_posts: [])
@nestedpost1.nested_posts << @nestedpost1
@nestedpost1.nested_posts << @nestedpost2
@nestedpost2.nested_posts << @nestedpost1
@nestedpost2.nested_posts << @nestedpost2
end
def test_no_duplicates
hash = ActiveModel::SerializableResource.new(@post1, adapter: :json_api,
include: '*.*')
.serializable_hash
expected = [
{
type: 'authors', id: '1',
relationships: {
posts: {
data: [
{ type: 'posts', id: '1' },
{ type: 'posts', id: '2' }
]
}
}
}, },
{
type: 'posts', id: '2',
relationships: {
author: {
data: { type: 'authors', id: '1' }
}
}
}
]
assert_equal(expected, hash[:included])
end
def test_no_duplicates_collection
hash = ActiveModel::SerializableResource.new(
[@post1, @post2], adapter: :json_api,
include: '*.*')
.serializable_hash
expected = [
{
type: 'authors', id: '1',
relationships: {
posts: {
data: [
{ type: 'posts', id: '1' },
{ type: 'posts', id: '2' }
]
}
}
}
]
assert_equal(expected, hash[:included])
end
def test_no_duplicates_global
hash = ActiveModel::SerializableResource.new(
@nestedpost1,
adapter: :json_api,
include: '*').serializable_hash
expected = [
type: 'nested_posts', id: '2',
relationships: { relationships: {
nested_posts: { posts: { data: [{ type: 'posts', id: '10' }, { type: 'posts', id: '30' }] },
roles: { data: [] },
bio: { data: { type: 'bios', id: '1' } }
}
}, {
id: '10',
type: 'posts',
attributes: {
title: 'Hello!!',
body: 'Hello, world!!'
},
relationships: {
comments: { data: [{ type: 'comments', id: '1' }, { type: 'comments', id: '2' }] },
blog: { data: { type: 'blogs', id: '999' } },
author: { data: { type: 'authors', id: '1' } }
}
}, {
id: '30',
type: 'posts',
attributes: {
title: 'Yet Another Post',
body: 'Body'
},
relationships: {
comments: { data: [] },
blog: { data: { type: 'blogs', id: '999' } },
author: { data: { type: 'authors', id: '1' } }
}
}
]
assert_equal expected, adapter.serializable_hash[:included]
assert_equal expected, alt_adapter.serializable_hash[:included]
end
def test_underscore_model_namespace_for_linked_resource_type
spammy_post = Post.new(id: 123)
spammy_post.related = [Spam::UnrelatedLink.new(id: 456)]
serializer = SpammyPostSerializer.new(spammy_post)
adapter = ActiveModelSerializers::Adapter::JsonApi.new(serializer)
relationships = adapter.serializable_hash[:data][:relationships]
expected = {
related: {
data: [{
type: 'spam_unrelated_links',
id: '456'
}]
}
}
assert_equal expected, relationships
end
def test_multiple_references_to_same_resource
serializer = ActiveModel::Serializer::CollectionSerializer.new([@first_comment, @second_comment])
adapter = ActiveModelSerializers::Adapter::JsonApi.new(
serializer,
include: [:post]
)
expected = [
{
id: '10',
type: 'posts',
attributes: {
title: 'Hello!!',
body: 'Hello, world!!'
},
relationships: {
comments: {
data: [{ type: 'comments', id: '1' }, { type: 'comments', id: '2' }]
},
blog: {
data: { type: 'blogs', id: '999' }
},
author: {
data: { type: 'authors', id: '1' }
}
}
}
]
assert_equal expected, adapter.serializable_hash[:included]
end
def test_nil_link_with_specified_serializer
@first_post.author = nil
serializer = PostPreviewSerializer.new(@first_post)
adapter = ActiveModelSerializers::Adapter::JsonApi.new(
serializer,
include: [:author]
)
expected = {
data: {
id: '10',
type: 'posts',
attributes: {
title: 'Hello!!',
body: 'Hello, world!!'
},
relationships: {
comments: { data: [{ type: 'comments', id: '1' }, { type: 'comments', id: '2' }] },
author: { data: nil }
}
}
}
assert_equal expected, adapter.serializable_hash
end
end
class NoDuplicatesTest < ActiveSupport::TestCase
Post = Class.new(::Model)
Author = Class.new(::Model)
class PostSerializer < ActiveModel::Serializer
type 'posts'
belongs_to :author
end
class AuthorSerializer < ActiveModel::Serializer
type 'authors'
has_many :posts
end
def setup
@author = Author.new(id: 1, posts: [], roles: [], bio: nil)
@post1 = Post.new(id: 1, author: @author)
@post2 = Post.new(id: 2, author: @author)
@author.posts << @post1
@author.posts << @post2
@nestedpost1 = ::NestedPost.new(id: 1, nested_posts: [])
@nestedpost2 = ::NestedPost.new(id: 2, nested_posts: [])
@nestedpost1.nested_posts << @nestedpost1
@nestedpost1.nested_posts << @nestedpost2
@nestedpost2.nested_posts << @nestedpost1
@nestedpost2.nested_posts << @nestedpost2
end
def test_no_duplicates
hash = ActiveModel::SerializableResource.new(@post1, adapter: :json_api,
include: '*.*')
.serializable_hash
expected = [
{
type: 'authors', id: '1',
relationships: {
posts: {
data: [ data: [
{ type: 'nested_posts', id: '1' }, { type: 'posts', id: '1' },
{ type: 'nested_posts', id: '2' } { type: 'posts', id: '2' }
] ]
} }
} }
] },
assert_equal(expected, hash[:included]) {
end type: 'posts', id: '2',
relationships: {
author: {
data: { type: 'authors', id: '1' }
}
}
}
]
assert_equal(expected, hash[:included])
end
def test_no_duplicates_collection_global def test_no_duplicates_collection
hash = ActiveModel::SerializableResource.new( hash = ActiveModel::SerializableResource.new(
[@nestedpost1, @nestedpost2], [@post1, @post2], adapter: :json_api,
adapter: :json_api, include: '*.*')
include: '*').serializable_hash .serializable_hash
assert_nil(hash[:included]) expected = [
end {
type: 'authors', id: '1',
relationships: {
posts: {
data: [
{ type: 'posts', id: '1' },
{ type: 'posts', id: '2' }
]
}
}
}
]
assert_equal(expected, hash[:included])
end
def test_no_duplicates_global
hash = ActiveModel::SerializableResource.new(
@nestedpost1,
adapter: :json_api,
include: '*').serializable_hash
expected = [
type: 'nested_posts', id: '2',
relationships: {
nested_posts: {
data: [
{ type: 'nested_posts', id: '1' },
{ type: 'nested_posts', id: '2' }
]
}
}
]
assert_equal(expected, hash[:included])
end
def test_no_duplicates_collection_global
hash = ActiveModel::SerializableResource.new(
[@nestedpost1, @nestedpost2],
adapter: :json_api,
include: '*').serializable_hash
assert_nil(hash[:included])
end end
end end
end end

View File

@ -1,84 +1,82 @@
require 'test_helper' require 'test_helper'
module ActiveModel module ActiveModelSerializers
class Serializer module Adapter
module Adapter class JsonApi
class JsonApi class LinksTest < ActiveSupport::TestCase
class LinksTest < ActiveSupport::TestCase LinkAuthor = Class.new(::Model)
LinkAuthor = Class.new(::Model) class LinkAuthorSerializer < ActiveModel::Serializer
class LinkAuthorSerializer < ActiveModel::Serializer link :self do
link :self do href "//example.com/link_author/#{object.id}"
href "//example.com/link_author/#{object.id}" meta stuff: 'value'
meta stuff: 'value'
end
link :other, '//example.com/resource'
link :yet_another do
"//example.com/resource/#{object.id}"
end
end end
def setup link :other, '//example.com/resource'
@post = Post.new(id: 1337, comments: [], author: nil)
@author = LinkAuthor.new(id: 1337, posts: [@post])
end
def test_toplevel_links link :yet_another do
hash = ActiveModel::SerializableResource.new( "//example.com/resource/#{object.id}"
@post, end
adapter: :json_api, end
links: {
self: { def setup
href: '//example.com/posts', @post = Post.new(id: 1337, comments: [], author: nil)
meta: { @author = LinkAuthor.new(id: 1337, posts: [@post])
stuff: 'value' end
}
} def test_toplevel_links
}).serializable_hash hash = ActiveModel::SerializableResource.new(
expected = { @post,
adapter: :json_api,
links: {
self: { self: {
href: '//example.com/posts', href: '//example.com/posts',
meta: { meta: {
stuff: 'value' stuff: 'value'
} }
} }
}).serializable_hash
expected = {
self: {
href: '//example.com/posts',
meta: {
stuff: 'value'
}
} }
assert_equal(expected, hash[:links]) }
end assert_equal(expected, hash[:links])
end
def test_nil_toplevel_links def test_nil_toplevel_links
hash = ActiveModel::SerializableResource.new( hash = ActiveModel::SerializableResource.new(
@post, @post,
adapter: :json_api, adapter: :json_api,
links: nil links: nil
).serializable_hash ).serializable_hash
refute hash.key?(:links), 'No links key to be output' refute hash.key?(:links), 'No links key to be output'
end end
def test_nil_toplevel_links_json_adapter def test_nil_toplevel_links_json_adapter
hash = ActiveModel::SerializableResource.new( hash = ActiveModel::SerializableResource.new(
@post, @post,
adapter: :json, adapter: :json,
links: nil links: nil
).serializable_hash ).serializable_hash
refute hash.key?(:links), 'No links key to be output' refute hash.key?(:links), 'No links key to be output'
end end
def test_resource_links def test_resource_links
hash = serializable(@author, adapter: :json_api).serializable_hash hash = serializable(@author, adapter: :json_api).serializable_hash
expected = { expected = {
self: { self: {
href: '//example.com/link_author/1337', href: '//example.com/link_author/1337',
meta: { meta: {
stuff: 'value' stuff: 'value'
} }
}, },
other: '//example.com/resource', other: '//example.com/resource',
yet_another: '//example.com/resource/1337' yet_another: '//example.com/resource/1337'
} }
assert_equal(expected, hash[:data][:links]) assert_equal(expected, hash[:data][:links])
end
end end
end end
end end

View File

@ -4,110 +4,108 @@ require 'kaminari'
require 'kaminari/hooks' require 'kaminari/hooks'
::Kaminari::Hooks.init ::Kaminari::Hooks.init
module ActiveModel module ActiveModelSerializers
class Serializer module Adapter
module Adapter class JsonApi
class JsonApi class PaginationLinksTest < ActiveSupport::TestCase
class PaginationLinksTest < ActiveSupport::TestCase URI = 'http://example.com'
URI = 'http://example.com'
def setup def setup
ActionController::Base.cache_store.clear ActionController::Base.cache_store.clear
@array = [ @array = [
Profile.new({ id: 1, name: 'Name 1', description: 'Description 1', comments: 'Comments 1' }), Profile.new({ id: 1, name: 'Name 1', description: 'Description 1', comments: 'Comments 1' }),
Profile.new({ id: 2, name: 'Name 2', description: 'Description 2', comments: 'Comments 2' }), Profile.new({ id: 2, name: 'Name 2', description: 'Description 2', comments: 'Comments 2' }),
Profile.new({ id: 3, name: 'Name 3', description: 'Description 3', comments: 'Comments 3' }) Profile.new({ id: 3, name: 'Name 3', description: 'Description 3', comments: 'Comments 3' })
]
end
def mock_request(query_parameters = {}, original_url = URI)
context = Minitest::Mock.new
context.expect(:request_url, original_url)
context.expect(:query_parameters, query_parameters)
@options = {}
@options[:serialization_context] = context
end
def load_adapter(paginated_collection, options = {})
options = options.merge(adapter: :json_api)
ActiveModel::SerializableResource.new(paginated_collection, options)
end
def using_kaminari
Kaminari.paginate_array(@array).page(2).per(1)
end
def using_will_paginate
@array.paginate(page: 2, per_page: 1)
end
def data
{ data: [
{ id: '1', type: 'profiles', attributes: { name: 'Name 1', description: 'Description 1' } },
{ id: '2', type: 'profiles', attributes: { name: 'Name 2', description: 'Description 2' } },
{ id: '3', type: 'profiles', attributes: { name: 'Name 3', description: 'Description 3' } }
] ]
end }
end
def mock_request(query_parameters = {}, original_url = URI) def links
context = Minitest::Mock.new {
context.expect(:request_url, original_url) links: {
context.expect(:query_parameters, query_parameters) self: "#{URI}?page%5Bnumber%5D=2&page%5Bsize%5D=1",
@options = {} first: "#{URI}?page%5Bnumber%5D=1&page%5Bsize%5D=1",
@options[:serialization_context] = context prev: "#{URI}?page%5Bnumber%5D=1&page%5Bsize%5D=1",
end next: "#{URI}?page%5Bnumber%5D=3&page%5Bsize%5D=1",
last: "#{URI}?page%5Bnumber%5D=3&page%5Bsize%5D=1"
def load_adapter(paginated_collection, options = {})
options = options.merge(adapter: :json_api)
ActiveModel::SerializableResource.new(paginated_collection, options)
end
def using_kaminari
Kaminari.paginate_array(@array).page(2).per(1)
end
def using_will_paginate
@array.paginate(page: 2, per_page: 1)
end
def data
{ data: [
{ id: '1', type: 'profiles', attributes: { name: 'Name 1', description: 'Description 1' } },
{ id: '2', type: 'profiles', attributes: { name: 'Name 2', description: 'Description 2' } },
{ id: '3', type: 'profiles', attributes: { name: 'Name 3', description: 'Description 3' } }
]
} }
}
end
def expected_response_without_pagination_links
data
end
def expected_response_with_pagination_links
{}.tap do |hash|
hash[:data] = [data.values.flatten.second]
hash.merge! links
end end
end
def links def expected_response_with_pagination_links_and_additional_params
{ new_links = links[:links].each_with_object({}) { |(key, value), hash| hash[key] = "#{value}&test=test" }
links: { {}.tap do |hash|
self: "#{URI}?page%5Bnumber%5D=2&page%5Bsize%5D=1", hash[:data] = [data.values.flatten.second]
first: "#{URI}?page%5Bnumber%5D=1&page%5Bsize%5D=1", hash.merge! links: new_links
prev: "#{URI}?page%5Bnumber%5D=1&page%5Bsize%5D=1",
next: "#{URI}?page%5Bnumber%5D=3&page%5Bsize%5D=1",
last: "#{URI}?page%5Bnumber%5D=3&page%5Bsize%5D=1"
}
}
end end
end
def expected_response_without_pagination_links def test_pagination_links_using_kaminari
data adapter = load_adapter(using_kaminari)
end
def expected_response_with_pagination_links mock_request
{}.tap do |hash| assert_equal expected_response_with_pagination_links, adapter.serializable_hash(@options)
hash[:data] = [data.values.flatten.second] end
hash.merge! links
end
end
def expected_response_with_pagination_links_and_additional_params def test_pagination_links_using_will_paginate
new_links = links[:links].each_with_object({}) { |(key, value), hash| hash[key] = "#{value}&test=test" } adapter = load_adapter(using_will_paginate)
{}.tap do |hash|
hash[:data] = [data.values.flatten.second]
hash.merge! links: new_links
end
end
def test_pagination_links_using_kaminari mock_request
adapter = load_adapter(using_kaminari) assert_equal expected_response_with_pagination_links, adapter.serializable_hash(@options)
end
mock_request def test_pagination_links_with_additional_params
assert_equal expected_response_with_pagination_links, adapter.serializable_hash(@options) adapter = load_adapter(using_will_paginate)
end
def test_pagination_links_using_will_paginate mock_request({ test: 'test' })
adapter = load_adapter(using_will_paginate) assert_equal expected_response_with_pagination_links_and_additional_params,
adapter.serializable_hash(@options)
end
mock_request def test_not_showing_pagination_links
assert_equal expected_response_with_pagination_links, adapter.serializable_hash(@options) adapter = load_adapter(@array)
end
def test_pagination_links_with_additional_params assert_equal expected_response_without_pagination_links, adapter.serializable_hash
adapter = load_adapter(using_will_paginate)
mock_request({ test: 'test' })
assert_equal expected_response_with_pagination_links_and_additional_params,
adapter.serializable_hash(@options)
end
def test_not_showing_pagination_links
adapter = load_adapter(@array)
assert_equal expected_response_without_pagination_links, adapter.serializable_hash
end
end end
end end
end end

View File

@ -1,136 +1,134 @@
require 'test_helper' require 'test_helper'
module ActiveModel module ActiveModelSerializers
class Serializer module Adapter
module Adapter class JsonApi
class JsonApi module Deserialization
module Deserialization class ParseTest < Minitest::Test
class ParseTest < Minitest::Test def setup
def setup @hash = {
@hash = { 'data' => {
'data' => { 'type' => 'photos',
'type' => 'photos', 'id' => 'zorglub',
'id' => 'zorglub', 'attributes' => {
'attributes' => { 'title' => 'Ember Hamster',
'title' => 'Ember Hamster', 'src' => 'http://example.com/images/productivity.png'
'src' => 'http://example.com/images/productivity.png' },
'relationships' => {
'author' => {
'data' => nil
}, },
'relationships' => { 'photographer' => {
'author' => { 'data' => { 'type' => 'people', 'id' => '9' }
'data' => nil },
}, 'comments' => {
'photographer' => { 'data' => [
'data' => { 'type' => 'people', 'id' => '9' } { 'type' => 'comments', 'id' => '1' },
}, { 'type' => 'comments', 'id' => '2' }
'comments' => { ]
'data' => [
{ 'type' => 'comments', 'id' => '1' },
{ 'type' => 'comments', 'id' => '2' }
]
}
} }
} }
} }
@params = ActionController::Parameters.new(@hash) }
@expected = { @params = ActionController::Parameters.new(@hash)
id: 'zorglub', @expected = {
title: 'Ember Hamster', id: 'zorglub',
src: 'http://example.com/images/productivity.png', title: 'Ember Hamster',
author_id: nil, src: 'http://example.com/images/productivity.png',
photographer_id: '9', author_id: nil,
comment_ids: %w(1 2) photographer_id: '9',
} comment_ids: %w(1 2)
}
@illformed_payloads = [nil, @illformed_payloads = [nil,
{}, {},
{ {
'data' => nil 'data' => nil
}, { }, {
'data' => { 'attributes' => [] } 'data' => { 'attributes' => [] }
}, { }, {
'data' => { 'relationships' => [] } 'data' => { 'relationships' => [] }
}, { }, {
'data' => { 'data' => {
'relationships' => { 'rel' => nil } 'relationships' => { 'rel' => nil }
} }
}, { }, {
'data' => { 'data' => {
'relationships' => { 'rel' => {} } 'relationships' => { 'rel' => {} }
} }
}] }]
end
def test_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 = ActiveModelSerializers::Adapter::JsonApi::Deserialization.parse!(@params)
assert_equal(@expected, parsed_hash)
end
def test_illformed_payloads_safe
@illformed_payloads.each do |p|
parsed_hash = ActiveModelSerializers::Adapter::JsonApi::Deserialization.parse(p)
assert_equal({}, parsed_hash)
end end
end
def test_hash def test_illformed_payloads_unsafe
parsed_hash = ActiveModel::Serializer::Adapter::JsonApi::Deserialization.parse!(@hash) @illformed_payloads.each do |p|
assert_equal(@expected, parsed_hash) assert_raises(InvalidDocument) do
end ActiveModelSerializers::Adapter::JsonApi::Deserialization.parse!(p)
def test_actioncontroller_parameters
assert_equal(false, @params.permitted?)
parsed_hash = ActiveModel::Serializer::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)
assert_equal({}, parsed_hash)
end end
end end
end
def test_illformed_payloads_unsafe def test_filter_fields_only
@illformed_payloads.each do |p| parsed_hash = ActiveModelSerializers::Adapter::JsonApi::Deserialization.parse!(@hash, only: [:id, :title, :author])
assert_raises(InvalidDocument) do expected = {
ActiveModel::Serializer::Adapter::JsonApi::Deserialization.parse!(p) id: 'zorglub',
end title: 'Ember Hamster',
end author_id: nil
end }
assert_equal(expected, parsed_hash)
end
def test_filter_fields_only def test_filter_fields_except
parsed_hash = ActiveModel::Serializer::Adapter::JsonApi::Deserialization.parse!(@hash, only: [:id, :title, :author]) parsed_hash = ActiveModelSerializers::Adapter::JsonApi::Deserialization.parse!(@hash, except: [:id, :title, :author])
expected = { expected = {
id: 'zorglub', src: 'http://example.com/images/productivity.png',
title: 'Ember Hamster', photographer_id: '9',
author_id: nil comment_ids: %w(1 2)
} }
assert_equal(expected, parsed_hash) assert_equal(expected, parsed_hash)
end end
def test_filter_fields_except def test_keys
parsed_hash = ActiveModel::Serializer::Adapter::JsonApi::Deserialization.parse!(@hash, except: [:id, :title, :author]) parsed_hash = ActiveModelSerializers::Adapter::JsonApi::Deserialization.parse!(@hash, keys: { author: :user, title: :post_title })
expected = { expected = {
src: 'http://example.com/images/productivity.png', id: 'zorglub',
photographer_id: '9', post_title: 'Ember Hamster',
comment_ids: %w(1 2) src: 'http://example.com/images/productivity.png',
} user_id: nil,
assert_equal(expected, parsed_hash) photographer_id: '9',
end comment_ids: %w(1 2)
}
assert_equal(expected, parsed_hash)
end
def test_keys def test_polymorphic
parsed_hash = ActiveModel::Serializer::Adapter::JsonApi::Deserialization.parse!(@hash, keys: { author: :user, title: :post_title }) parsed_hash = ActiveModelSerializers::Adapter::JsonApi::Deserialization.parse!(@hash, polymorphic: [:photographer])
expected = { expected = {
id: 'zorglub', id: 'zorglub',
post_title: 'Ember Hamster', title: 'Ember Hamster',
src: 'http://example.com/images/productivity.png', src: 'http://example.com/images/productivity.png',
user_id: nil, author_id: nil,
photographer_id: '9', photographer_id: '9',
comment_ids: %w(1 2) photographer_type: 'people',
} comment_ids: %w(1 2)
assert_equal(expected, parsed_hash) }
end assert_equal(expected, parsed_hash)
def test_polymorphic
parsed_hash = ActiveModel::Serializer::Adapter::JsonApi::Deserialization.parse!(@hash, polymorphic: [:photographer])
expected = {
id: 'zorglub',
title: 'Ember Hamster',
src: 'http://example.com/images/productivity.png',
author_id: nil,
photographer_id: '9',
photographer_type: 'people',
comment_ids: %w(1 2)
}
assert_equal(expected, parsed_hash)
end
end end
end end
end end

View File

@ -1,82 +1,80 @@
require 'test_helper' require 'test_helper'
module ActiveModel module ActiveModelSerializers
class Serializer module Adapter
module Adapter class JsonApi
class JsonApi class TopLevelJsonApiTest < ActiveSupport::TestCase
class TopLevelJsonApiTest < ActiveSupport::TestCase def setup
def setup @author = Author.new(id: 1, name: 'Steve K.')
@author = Author.new(id: 1, name: 'Steve K.') @author.bio = nil
@author.bio = nil @author.roles = []
@author.roles = [] @blog = Blog.new(id: 23, name: 'AMS Blog')
@blog = Blog.new(id: 23, name: 'AMS Blog') @post = Post.new(id: 42, title: 'New Post', body: 'Body')
@post = Post.new(id: 42, title: 'New Post', body: 'Body') @anonymous_post = Post.new(id: 43, title: 'Hello!!', body: 'Hello, world!!')
@anonymous_post = Post.new(id: 43, title: 'Hello!!', body: 'Hello, world!!') @comment = Comment.new(id: 1, body: 'ZOMG A COMMENT')
@comment = Comment.new(id: 1, body: 'ZOMG A COMMENT') @post.comments = [@comment]
@post.comments = [@comment] @post.blog = @blog
@post.blog = @blog @anonymous_post.comments = []
@anonymous_post.comments = [] @anonymous_post.blog = nil
@anonymous_post.blog = nil @comment.post = @post
@comment.post = @post @comment.author = nil
@comment.author = nil @post.author = @author
@post.author = @author @anonymous_post.author = nil
@anonymous_post.author = nil @blog = Blog.new(id: 1, name: 'My Blog!!')
@blog = Blog.new(id: 1, name: 'My Blog!!') @blog.writer = @author
@blog.writer = @author @blog.articles = [@post, @anonymous_post]
@blog.articles = [@post, @anonymous_post] @author.posts = []
@author.posts = [] end
end
def test_toplevel_jsonapi_defaults_to_false def test_toplevel_jsonapi_defaults_to_false
assert_equal config.fetch(:jsonapi_include_toplevel_object), false assert_equal config.fetch(:jsonapi_include_toplevel_object), false
end end
def test_disable_toplevel_jsonapi def test_disable_toplevel_jsonapi
with_config(jsonapi_include_toplevel_object: false) do with_config(jsonapi_include_toplevel_object: false) do
hash = serialize(@post) hash = serialize(@post)
assert_nil(hash[:jsonapi]) assert_nil(hash[:jsonapi])
end
end end
end
def test_enable_toplevel_jsonapi def test_enable_toplevel_jsonapi
with_config(jsonapi_include_toplevel_object: true) do with_config(jsonapi_include_toplevel_object: true) do
hash = serialize(@post) hash = serialize(@post)
refute_nil(hash[:jsonapi]) refute_nil(hash[:jsonapi])
end
end end
end
def test_default_toplevel_jsonapi_version def test_default_toplevel_jsonapi_version
with_config(jsonapi_include_toplevel_object: true) do with_config(jsonapi_include_toplevel_object: true) do
hash = serialize(@post) hash = serialize(@post)
assert_equal('1.0', hash[:jsonapi][:version]) assert_equal('1.0', hash[:jsonapi][:version])
end
end end
end
def test_toplevel_jsonapi_no_meta def test_toplevel_jsonapi_no_meta
with_config(jsonapi_include_toplevel_object: true) do with_config(jsonapi_include_toplevel_object: true) do
hash = serialize(@post) hash = serialize(@post)
assert_nil(hash[:jsonapi][:meta]) assert_nil(hash[:jsonapi][:meta])
end
end end
end
def test_toplevel_jsonapi_meta def test_toplevel_jsonapi_meta
new_config = { new_config = {
jsonapi_include_toplevel_object: true, jsonapi_include_toplevel_object: true,
jsonapi_toplevel_meta: { jsonapi_toplevel_meta: {
'copyright' => 'Copyright 2015 Example Corp.' 'copyright' => 'Copyright 2015 Example Corp.'
}
} }
with_config(new_config) do }
hash = serialize(@post) with_config(new_config) do
assert_equal(new_config[:jsonapi_toplevel_meta], hash.fetch(:jsonapi).fetch(:meta)) hash = serialize(@post)
end assert_equal(new_config[:jsonapi_toplevel_meta], hash.fetch(:jsonapi).fetch(:meta))
end end
end
private private
def serialize(resource, options = {}) def serialize(resource, options = {})
serializable(resource, { adapter: :json_api }.merge!(options)).serializable_hash serializable(resource, { adapter: :json_api }.merge!(options)).serializable_hash
end
end end
end end
end end

View File

@ -1,46 +1,44 @@
require 'test_helper' require 'test_helper'
module ActiveModel module ActiveModelSerializers
class Serializer module Adapter
module Adapter class JsonTest < ActiveSupport::TestCase
class JsonTest < ActiveSupport::TestCase def setup
def setup ActionController::Base.cache_store.clear
ActionController::Base.cache_store.clear @author = Author.new(id: 1, name: 'Steve K.')
@author = Author.new(id: 1, name: 'Steve K.') @post = Post.new(id: 1, title: 'New Post', body: 'Body')
@post = Post.new(id: 1, title: 'New Post', body: 'Body') @first_comment = Comment.new(id: 1, body: 'ZOMG A COMMENT')
@first_comment = Comment.new(id: 1, body: 'ZOMG A COMMENT') @second_comment = Comment.new(id: 2, body: 'ZOMG ANOTHER COMMENT')
@second_comment = Comment.new(id: 2, body: 'ZOMG ANOTHER COMMENT') @post.comments = [@first_comment, @second_comment]
@post.comments = [@first_comment, @second_comment] @first_comment.post = @post
@first_comment.post = @post @second_comment.post = @post
@second_comment.post = @post @post.author = @author
@post.author = @author @blog = Blog.new(id: 1, name: 'My Blog!!')
@blog = Blog.new(id: 1, name: 'My Blog!!') @post.blog = @blog
@post.blog = @blog
@serializer = PostSerializer.new(@post) @serializer = PostSerializer.new(@post)
@adapter = ActiveModel::Serializer::Adapter::Json.new(@serializer) @adapter = ActiveModelSerializers::Adapter::Json.new(@serializer)
end end
def test_has_many def test_has_many
assert_equal([ assert_equal([
{ id: 1, body: 'ZOMG A COMMENT' }, { id: 1, body: 'ZOMG A COMMENT' },
{ id: 2, body: 'ZOMG ANOTHER COMMENT' } { id: 2, body: 'ZOMG ANOTHER COMMENT' }
], @adapter.serializable_hash[:post][:comments]) ], @adapter.serializable_hash[:post][:comments])
end end
def test_custom_keys def test_custom_keys
serializer = PostWithCustomKeysSerializer.new(@post) serializer = PostWithCustomKeysSerializer.new(@post)
adapter = ActiveModel::Serializer::Adapter::Json.new(serializer) adapter = ActiveModelSerializers::Adapter::Json.new(serializer)
assert_equal({ assert_equal({
id: 1, id: 1,
reviews: [{ id: 1, body: 'ZOMG A COMMENT' }, reviews: [{ id: 1, body: 'ZOMG A COMMENT' },
{ id: 2, body: 'ZOMG ANOTHER COMMENT' } { id: 2, body: 'ZOMG ANOTHER COMMENT' }
], ],
writer: { id: 1, name: 'Steve K.' }, writer: { id: 1, name: 'Steve K.' },
site: { id: 1, name: 'My Blog!!' } site: { id: 1, name: 'My Blog!!' }
}, adapter.serializable_hash[:post]) }, adapter.serializable_hash[:post])
end
end end
end end
end end

View File

@ -1,23 +1,21 @@
require 'test_helper' require 'test_helper'
module ActiveModel module ActiveModelSerializers
class Serializer module Adapter
module Adapter class NullTest < ActiveSupport::TestCase
class NullTest < ActiveSupport::TestCase def setup
def setup profile = Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' })
profile = Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' }) serializer = ProfileSerializer.new(profile)
serializer = ProfileSerializer.new(profile)
@adapter = Null.new(serializer) @adapter = Null.new(serializer)
end end
def test_serializable_hash def test_serializable_hash
assert_equal({}, @adapter.serializable_hash) assert_equal({}, @adapter.serializable_hash)
end end
def test_it_returns_empty_json def test_it_returns_empty_json
assert_equal('{}', @adapter.to_json) assert_equal('{}', @adapter.to_json)
end
end end
end end
end end

View File

@ -1,42 +1,40 @@
require 'test_helper' require 'test_helper'
module ActiveModel module ActiveModelSerializers
class Serializer class AdapterTest < ActiveSupport::TestCase
class AdapterTest < ActiveSupport::TestCase def setup
def setup profile = Profile.new
profile = Profile.new @serializer = ProfileSerializer.new(profile)
@serializer = ProfileSerializer.new(profile) @adapter = ActiveModelSerializers::Adapter::Base.new(@serializer)
@adapter = ActiveModel::Serializer::Adapter::Base.new(@serializer) end
def test_serializable_hash_is_abstract_method
assert_raises(NotImplementedError) do
@adapter.serializable_hash(only: [:name])
end end
end
def test_serializable_hash_is_abstract_method def test_serializer
assert_raises(NotImplementedError) do assert_equal @serializer, @adapter.serializer
@adapter.serializable_hash(only: [:name]) end
end
end
def test_serializer def test_create_adapter
assert_equal @serializer, @adapter.serializer adapter = ActiveModelSerializers::Adapter.create(@serializer)
end assert_equal ActiveModelSerializers::Adapter::Attributes, adapter.class
end
def test_create_adapter def test_create_adapter_with_override
adapter = ActiveModel::Serializer::Adapter.create(@serializer) adapter = ActiveModelSerializers::Adapter.create(@serializer, { adapter: :json_api })
assert_equal ActiveModel::Serializer::Adapter::Attributes, adapter.class assert_equal ActiveModelSerializers::Adapter::JsonApi, adapter.class
end end
def test_create_adapter_with_override def test_inflected_adapter_class_for_known_adapter
adapter = ActiveModel::Serializer::Adapter.create(@serializer, { adapter: :json_api }) ActiveSupport::Inflector.inflections(:en) { |inflect| inflect.acronym 'API' }
assert_equal ActiveModel::Serializer::Adapter::JsonApi, adapter.class klass = ActiveModelSerializers::Adapter.adapter_class(:json_api)
end
def test_inflected_adapter_class_for_known_adapter ActiveSupport::Inflector.inflections.acronyms.clear
ActiveSupport::Inflector.inflections(:en) { |inflect| inflect.acronym 'API' }
klass = ActiveModel::Serializer::Adapter.adapter_class(:json_api)
ActiveSupport::Inflector.inflections.acronyms.clear assert_equal ActiveModelSerializers::Adapter::JsonApi, klass
assert_equal ActiveModel::Serializer::Adapter::JsonApi, klass
end
end end
end end
end end

View File

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

View File

@ -5,7 +5,7 @@ module ActiveModel
def setup def setup
@resource = Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' }) @resource = Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' })
@serializer = ProfileSerializer.new(@resource) @serializer = ProfileSerializer.new(@resource)
@adapter = ActiveModel::Serializer::Adapter.create(@serializer) @adapter = ActiveModelSerializers::Adapter.create(@serializer)
@serializable_resource = ActiveModel::SerializableResource.new(@resource) @serializable_resource = ActiveModel::SerializableResource.new(@resource)
end 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 end
def test_json_serializable_hash 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) assert_equal({ blog: { id: 1, title: 'AMS Hints' } }, adapter.serializable_hash)
end end
def test_attribute_inheritance_with_key def test_attribute_inheritance_with_key
inherited_klass = Class.new(AlternateBlogSerializer) inherited_klass = Class.new(AlternateBlogSerializer)
blog_serializer = inherited_klass.new(@blog) 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) assert_equal({ :id => 1, :title => 'AMS Hints' }, adapter.serializable_hash)
end end
@ -39,7 +39,7 @@ module ActiveModel
attribute :name, key: :id attribute :name, key: :id
end 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) assert_equal({ blog: { id: 'AMS Hints' } }, adapter.serializable_hash)
end end
@ -48,7 +48,7 @@ module ActiveModel
attribute :name, key: :object attribute :name, key: :object
end 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) assert_equal({ blog: { object: 'AMS Hints' } }, adapter.serializable_hash)
end end
@ -60,10 +60,10 @@ module ActiveModel
attributes :type attributes :type
end 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) 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) assert_equal({ blog: { type: 'stuff' } }, adapter.serializable_hash)
end end

View File

@ -1,241 +1,236 @@
require 'test_helper' require 'test_helper'
require 'tmpdir' require 'tmpdir'
require 'tempfile' require 'tempfile'
module ActiveModel module ActiveModelSerializers
class Serializer class CacheTest < ActiveSupport::TestCase
class CacheTest < ActiveSupport::TestCase def setup
include ActiveSupport::Testing::Stream ActionController::Base.cache_store.clear
@comment = Comment.new(id: 1, body: 'ZOMG A COMMENT')
@post = Post.new(title: 'New Post', body: 'Body')
@bio = Bio.new(id: 1, content: 'AMS Contributor')
@author = Author.new(name: 'Joao M. D. Moura')
@blog = Blog.new(id: 999, name: 'Custom blog', writer: @author, articles: [])
@role = Role.new(name: 'Great Author')
@location = Location.new(lat: '-23.550520', lng: '-46.633309')
@place = Place.new(name: 'Amazing Place')
@author.posts = [@post]
@author.roles = [@role]
@role.author = @author
@author.bio = @bio
@bio.author = @author
@post.comments = [@comment]
@post.author = @author
@comment.post = @post
@comment.author = @author
@post.blog = @blog
@location.place = @place
def setup @location_serializer = LocationSerializer.new(@location)
ActionController::Base.cache_store.clear @bio_serializer = BioSerializer.new(@bio)
@comment = Comment.new(id: 1, body: 'ZOMG A COMMENT') @role_serializer = RoleSerializer.new(@role)
@post = Post.new(title: 'New Post', body: 'Body') @post_serializer = PostSerializer.new(@post)
@bio = Bio.new(id: 1, content: 'AMS Contributor') @author_serializer = AuthorSerializer.new(@author)
@author = Author.new(name: 'Joao M. D. Moura') @comment_serializer = CommentSerializer.new(@comment)
@blog = Blog.new(id: 999, name: 'Custom blog', writer: @author, articles: []) @blog_serializer = BlogSerializer.new(@blog)
@role = Role.new(name: 'Great Author') end
@location = Location.new(lat: '-23.550520', lng: '-46.633309')
@place = Place.new(name: 'Amazing Place')
@author.posts = [@post]
@author.roles = [@role]
@role.author = @author
@author.bio = @bio
@bio.author = @author
@post.comments = [@comment]
@post.author = @author
@comment.post = @post
@comment.author = @author
@post.blog = @blog
@location.place = @place
@location_serializer = LocationSerializer.new(@location) def test_inherited_cache_configuration
@bio_serializer = BioSerializer.new(@bio) inherited_serializer = Class.new(PostSerializer)
@role_serializer = RoleSerializer.new(@role)
@post_serializer = PostSerializer.new(@post) assert_equal PostSerializer._cache_key, inherited_serializer._cache_key
@author_serializer = AuthorSerializer.new(@author) assert_equal PostSerializer._cache_options, inherited_serializer._cache_options
@comment_serializer = CommentSerializer.new(@comment) end
@blog_serializer = BlogSerializer.new(@blog)
def test_override_cache_configuration
inherited_serializer = Class.new(PostSerializer) do
cache key: 'new-key'
end end
def test_inherited_cache_configuration assert_equal PostSerializer._cache_key, 'post'
inherited_serializer = Class.new(PostSerializer) assert_equal inherited_serializer._cache_key, 'new-key'
end
assert_equal PostSerializer._cache_key, inherited_serializer._cache_key def test_cache_definition
assert_equal PostSerializer._cache_options, inherited_serializer._cache_options assert_equal(ActionController::Base.cache_store, @post_serializer.class._cache)
assert_equal(ActionController::Base.cache_store, @author_serializer.class._cache)
assert_equal(ActionController::Base.cache_store, @comment_serializer.class._cache)
end
def test_cache_key_definition
assert_equal('post', @post_serializer.class._cache_key)
assert_equal('writer', @author_serializer.class._cache_key)
assert_equal(nil, @comment_serializer.class._cache_key)
end
def test_cache_key_interpolation_with_updated_at
render_object_with_cache(@author)
assert_equal(nil, ActionController::Base.cache_store.fetch(@author.cache_key))
assert_equal(@author_serializer.attributes.to_json, ActionController::Base.cache_store.fetch("#{@author_serializer.class._cache_key}/#{@author_serializer.object.id}-#{@author_serializer.object.updated_at.strftime("%Y%m%d%H%M%S%9N")}").to_json)
end
def test_default_cache_key_fallback
render_object_with_cache(@comment)
assert_equal(@comment_serializer.attributes.to_json, ActionController::Base.cache_store.fetch(@comment.cache_key).to_json)
end
def test_cache_options_definition
assert_equal({ expires_in: 0.1, skip_digest: true }, @post_serializer.class._cache_options)
assert_equal(nil, @blog_serializer.class._cache_options)
assert_equal({ expires_in: 1.day, skip_digest: true }, @comment_serializer.class._cache_options)
end
def test_fragment_cache_definition
assert_equal([:name], @role_serializer.class._cache_only)
assert_equal([:content], @bio_serializer.class._cache_except)
end
def test_associations_separately_cache
ActionController::Base.cache_store.clear
assert_equal(nil, ActionController::Base.cache_store.fetch(@post.cache_key))
assert_equal(nil, ActionController::Base.cache_store.fetch(@comment.cache_key))
Timecop.freeze(Time.now) do
render_object_with_cache(@post)
assert_equal(@post_serializer.attributes, ActionController::Base.cache_store.fetch(@post.cache_key))
assert_equal(@comment_serializer.attributes, ActionController::Base.cache_store.fetch(@comment.cache_key))
end end
end
def test_override_cache_configuration def test_associations_cache_when_updated
inherited_serializer = Class.new(PostSerializer) do # Clean the Cache
cache key: 'new-key' ActionController::Base.cache_store.clear
end
assert_equal PostSerializer._cache_key, 'post' Timecop.freeze(Time.now) do
assert_equal inherited_serializer._cache_key, 'new-key' # Generate a new Cache of Post object and each objects related to it.
render_object_with_cache(@post)
# Check if it cached the objects separately
assert_equal(@post_serializer.attributes, ActionController::Base.cache_store.fetch(@post.cache_key))
assert_equal(@comment_serializer.attributes, ActionController::Base.cache_store.fetch(@comment.cache_key))
# Simulating update on comments relationship with Post
new_comment = Comment.new(id: 2, body: 'ZOMG A NEW COMMENT')
new_comment_serializer = CommentSerializer.new(new_comment)
@post.comments = [new_comment]
# Ask for the serialized object
render_object_with_cache(@post)
# Check if the the new comment was cached
assert_equal(new_comment_serializer.attributes, ActionController::Base.cache_store.fetch(new_comment.cache_key))
assert_equal(@post_serializer.attributes, ActionController::Base.cache_store.fetch(@post.cache_key))
end end
end
def test_cache_definition def test_fragment_fetch_with_virtual_associations
assert_equal(ActionController::Base.cache_store, @post_serializer.class._cache) expected_result = {
assert_equal(ActionController::Base.cache_store, @author_serializer.class._cache) id: @location.id,
assert_equal(ActionController::Base.cache_store, @comment_serializer.class._cache) lat: @location.lat,
end lng: @location.lng,
place: 'Nowhere'
}
def test_cache_key_definition hash = render_object_with_cache(@location)
assert_equal('post', @post_serializer.class._cache_key)
assert_equal('writer', @author_serializer.class._cache_key)
assert_equal(nil, @comment_serializer.class._cache_key)
end
def test_cache_key_interpolation_with_updated_at assert_equal(hash, expected_result)
render_object_with_cache(@author) assert_equal({ place: 'Nowhere' }, ActionController::Base.cache_store.fetch(@location.cache_key))
assert_equal(nil, ActionController::Base.cache_store.fetch(@author.cache_key)) end
assert_equal(@author_serializer.attributes.to_json, ActionController::Base.cache_store.fetch("#{@author_serializer.class._cache_key}/#{@author_serializer.object.id}-#{@author_serializer.object.updated_at.strftime("%Y%m%d%H%M%S%9N")}").to_json)
end
def test_default_cache_key_fallback def test_uses_file_digest_in_cache_key
render_object_with_cache(@blog)
assert_equal(@blog_serializer.attributes, ActionController::Base.cache_store.fetch(@blog.cache_key_with_digest))
end
def test_cache_digest_definition
assert_equal(::Model::FILE_DIGEST, @post_serializer.class._cache_digest)
end
def test_object_cache_keys
serializer = ActiveModel::Serializer::CollectionSerializer.new([@comment, @comment])
include_tree = ActiveModel::Serializer::IncludeTree.from_include_args('*')
actual = Adapter::CachedSerializer.object_cache_keys(serializer, include_tree)
assert_equal actual.size, 3
assert actual.any? { |key| key == 'comment/1' }
assert actual.any? { |key| key =~ %r{post/post-\d+} }
assert actual.any? { |key| key =~ %r{writer/author-\d+} }
end
def test_cached_attributes
serializer = ActiveModel::Serializer::CollectionSerializer.new([@comment, @comment])
Timecop.freeze(Time.now) do
render_object_with_cache(@comment) render_object_with_cache(@comment)
assert_equal(@comment_serializer.attributes.to_json, ActionController::Base.cache_store.fetch(@comment.cache_key).to_json)
attributes = Adapter::Attributes.new(serializer)
attributes.send(:cache_attributes)
cached_attributes = attributes.instance_variable_get(:@cached_attributes)
assert_equal cached_attributes[@comment.cache_key], Comment.new(id: 1, body: 'ZOMG A COMMENT').attributes
assert_equal cached_attributes[@comment.post.cache_key], Post.new(id: 'post', title: 'New Post', body: 'Body').attributes
writer = @comment.post.blog.writer
writer_cache_key = "writer/#{writer.id}-#{writer.updated_at.strftime("%Y%m%d%H%M%S%9N")}"
assert_equal cached_attributes[writer_cache_key], Author.new(id: 'author', name: 'Joao M. D. Moura').attributes
end end
end
def test_cache_options_definition def test_serializer_file_path_on_nix
assert_equal({ expires_in: 0.1, skip_digest: true }, @post_serializer.class._cache_options) path = '/Users/git/emberjs/ember-crm-backend/app/serializers/lead_serializer.rb'
assert_equal(nil, @blog_serializer.class._cache_options) caller_line = "#{path}:1:in `<top (required)>'"
assert_equal({ expires_in: 1.day, skip_digest: true }, @comment_serializer.class._cache_options) assert_equal caller_line[ActiveModel::Serializer::CALLER_FILE], path
end
def test_serializer_file_path_on_windows
path = 'c:/git/emberjs/ember-crm-backend/app/serializers/lead_serializer.rb'
caller_line = "#{path}:1:in `<top (required)>'"
assert_equal caller_line[ActiveModel::Serializer::CALLER_FILE], path
end
def test_serializer_file_path_with_space
path = '/Users/git/ember js/ember-crm-backend/app/serializers/lead_serializer.rb'
caller_line = "#{path}:1:in `<top (required)>'"
assert_equal caller_line[ActiveModel::Serializer::CALLER_FILE], path
end
def test_serializer_file_path_with_submatch
# The submatch in the path ensures we're using a correctly greedy regexp.
path = '/Users/git/ember js/ember:123:in x/app/serializers/lead_serializer.rb'
caller_line = "#{path}:1:in `<top (required)>'"
assert_equal caller_line[ActiveModel::Serializer::CALLER_FILE], path
end
def test_digest_caller_file
contents = "puts 'AMS rocks'!"
dir = Dir.mktmpdir('space char')
file = Tempfile.new('some_ruby.rb', dir)
file.write(contents)
path = file.path
caller_line = "#{path}:1:in `<top (required)>'"
file.close
assert_equal ActiveModel::Serializer.digest_caller_file(caller_line), Digest::MD5.hexdigest(contents)
ensure
file.unlink
FileUtils.remove_entry dir
end
def test_warn_on_serializer_not_defined_in_file
called = false
serializer = Class.new(ActiveModel::Serializer)
assert_output(nil, /_cache_digest/) do
serializer.digest_caller_file('')
called = true
end end
assert called
end
def test_fragment_cache_definition private
assert_equal([:name], @role_serializer.class._cache_only)
assert_equal([:content], @bio_serializer.class._cache_except)
end
def test_associations_separately_cache def render_object_with_cache(obj)
ActionController::Base.cache_store.clear ActiveModel::SerializableResource.new(obj).serializable_hash
assert_equal(nil, ActionController::Base.cache_store.fetch(@post.cache_key))
assert_equal(nil, ActionController::Base.cache_store.fetch(@comment.cache_key))
Timecop.freeze(Time.now) do
render_object_with_cache(@post)
assert_equal(@post_serializer.attributes, ActionController::Base.cache_store.fetch(@post.cache_key))
assert_equal(@comment_serializer.attributes, ActionController::Base.cache_store.fetch(@comment.cache_key))
end
end
def test_associations_cache_when_updated
# Clean the Cache
ActionController::Base.cache_store.clear
Timecop.freeze(Time.now) do
# Generate a new Cache of Post object and each objects related to it.
render_object_with_cache(@post)
# Check if it cached the objects separately
assert_equal(@post_serializer.attributes, ActionController::Base.cache_store.fetch(@post.cache_key))
assert_equal(@comment_serializer.attributes, ActionController::Base.cache_store.fetch(@comment.cache_key))
# Simulating update on comments relationship with Post
new_comment = Comment.new(id: 2, body: 'ZOMG A NEW COMMENT')
new_comment_serializer = CommentSerializer.new(new_comment)
@post.comments = [new_comment]
# Ask for the serialized object
render_object_with_cache(@post)
# Check if the the new comment was cached
assert_equal(new_comment_serializer.attributes, ActionController::Base.cache_store.fetch(new_comment.cache_key))
assert_equal(@post_serializer.attributes, ActionController::Base.cache_store.fetch(@post.cache_key))
end
end
def test_fragment_fetch_with_virtual_associations
expected_result = {
id: @location.id,
lat: @location.lat,
lng: @location.lng,
place: 'Nowhere'
}
hash = render_object_with_cache(@location)
assert_equal(hash, expected_result)
assert_equal({ place: 'Nowhere' }, ActionController::Base.cache_store.fetch(@location.cache_key))
end
def test_uses_file_digest_in_cache_key
render_object_with_cache(@blog)
assert_equal(@blog_serializer.attributes, ActionController::Base.cache_store.fetch(@blog.cache_key_with_digest))
end
def test_cache_digest_definition
assert_equal(::Model::FILE_DIGEST, @post_serializer.class._cache_digest)
end
def test_object_cache_keys
serializer = CollectionSerializer.new([@comment, @comment])
include_tree = IncludeTree.from_include_args('*')
actual = Serializer::Adapter::CachedSerializer.object_cache_keys(serializer, include_tree)
assert_equal actual.size, 3
assert actual.any? { |key| key == 'comment/1' }
assert actual.any? { |key| key =~ %r{post/post-\d+} }
assert actual.any? { |key| key =~ %r{writer/author-\d+} }
end
def test_cached_attributes
serializer = CollectionSerializer.new([@comment, @comment])
Timecop.freeze(Time.now) do
render_object_with_cache(@comment)
attributes = ActiveModel::Serializer::Adapter::Attributes.new(serializer)
attributes.send(:cache_attributes)
cached_attributes = attributes.instance_variable_get(:@cached_attributes)
assert_equal cached_attributes[@comment.cache_key], Comment.new(id: 1, body: 'ZOMG A COMMENT').attributes
assert_equal cached_attributes[@comment.post.cache_key], Post.new(id: 'post', title: 'New Post', body: 'Body').attributes
writer = @comment.post.blog.writer
writer_cache_key = "writer/#{writer.id}-#{writer.updated_at.strftime("%Y%m%d%H%M%S%9N")}"
assert_equal cached_attributes[writer_cache_key], Author.new(id: 'author', name: 'Joao M. D. Moura').attributes
end
end
def test_serializer_file_path_on_nix
path = '/Users/git/emberjs/ember-crm-backend/app/serializers/lead_serializer.rb'
caller_line = "#{path}:1:in `<top (required)>'"
assert_equal caller_line[ActiveModel::Serializer::CALLER_FILE], path
end
def test_serializer_file_path_on_windows
path = 'c:/git/emberjs/ember-crm-backend/app/serializers/lead_serializer.rb'
caller_line = "#{path}:1:in `<top (required)>'"
assert_equal caller_line[ActiveModel::Serializer::CALLER_FILE], path
end
def test_serializer_file_path_with_space
path = '/Users/git/ember js/ember-crm-backend/app/serializers/lead_serializer.rb'
caller_line = "#{path}:1:in `<top (required)>'"
assert_equal caller_line[ActiveModel::Serializer::CALLER_FILE], path
end
def test_serializer_file_path_with_submatch
# The submatch in the path ensures we're using a correctly greedy regexp.
path = '/Users/git/ember js/ember:123:in x/app/serializers/lead_serializer.rb'
caller_line = "#{path}:1:in `<top (required)>'"
assert_equal caller_line[ActiveModel::Serializer::CALLER_FILE], path
end
def test_digest_caller_file
contents = "puts 'AMS rocks'!"
dir = Dir.mktmpdir('space char')
file = Tempfile.new('some_ruby.rb', dir)
file.write(contents)
path = file.path
caller_line = "#{path}:1:in `<top (required)>'"
file.close
assert_equal ActiveModel::Serializer.digest_caller_file(caller_line), Digest::MD5.hexdigest(contents)
ensure
file.unlink
FileUtils.remove_entry dir
end
def test_warn_on_serializer_not_defined_in_file
called = false
serializer = Class.new(ActiveModel::Serializer)
assert_match(/_cache_digest/, (capture(:stderr) do
serializer.digest_caller_file('')
called = true
end))
assert called
end
private
def render_object_with_cache(obj)
ActiveModel::SerializableResource.new(obj).serializable_hash
end
end end
end end
end end

View File

@ -1,81 +1,79 @@
require 'test_helper' require 'test_helper'
module ActiveModel module ActiveModelSerializers
class Serializer module Adapter
module Adapter class CachedSerializerTest < ActiveSupport::TestCase
class CachedSerializerTest < ActiveSupport::TestCase def test_cached_false_without_cache_store
def test_cached_false_without_cache_store cached_serializer = build do |serializer|
cached_serializer = build do |serializer| serializer._cache = nil
serializer._cache = nil
end
refute cached_serializer.cached?
end end
refute cached_serializer.cached?
end
def test_cached_true_with_cache_store_and_without_cache_only_and_cache_except def test_cached_true_with_cache_store_and_without_cache_only_and_cache_except
cached_serializer = build do |serializer| cached_serializer = build do |serializer|
serializer._cache = Object serializer._cache = Object
end
assert cached_serializer.cached?
end end
assert cached_serializer.cached?
end
def test_cached_false_with_cache_store_and_with_cache_only def test_cached_false_with_cache_store_and_with_cache_only
cached_serializer = build do |serializer| cached_serializer = build do |serializer|
serializer._cache = Object serializer._cache = Object
serializer._cache_only = [:name] serializer._cache_only = [:name]
end
refute cached_serializer.cached?
end end
refute cached_serializer.cached?
end
def test_cached_false_with_cache_store_and_with_cache_except def test_cached_false_with_cache_store_and_with_cache_except
cached_serializer = build do |serializer| cached_serializer = build do |serializer|
serializer._cache = Object serializer._cache = Object
serializer._cache_except = [:content] serializer._cache_except = [:content]
end
refute cached_serializer.cached?
end end
refute cached_serializer.cached?
end
def test_fragment_cached_false_without_cache_store def test_fragment_cached_false_without_cache_store
cached_serializer = build do |serializer| cached_serializer = build do |serializer|
serializer._cache = nil serializer._cache = nil
serializer._cache_only = [:name] serializer._cache_only = [:name]
end
refute cached_serializer.fragment_cached?
end end
refute cached_serializer.fragment_cached?
end
def test_fragment_cached_true_with_cache_store_and_cache_only def test_fragment_cached_true_with_cache_store_and_cache_only
cached_serializer = build do |serializer| cached_serializer = build do |serializer|
serializer._cache = Object serializer._cache = Object
serializer._cache_only = [:name] serializer._cache_only = [:name]
end
assert cached_serializer.fragment_cached?
end end
assert cached_serializer.fragment_cached?
end
def test_fragment_cached_true_with_cache_store_and_cache_except def test_fragment_cached_true_with_cache_store_and_cache_except
cached_serializer = build do |serializer| cached_serializer = build do |serializer|
serializer._cache = Object serializer._cache = Object
serializer._cache_except = [:content] serializer._cache_except = [:content]
end
assert cached_serializer.fragment_cached?
end end
assert cached_serializer.fragment_cached?
end
def test_fragment_cached_false_with_cache_store_and_cache_except_and_cache_only def test_fragment_cached_false_with_cache_store_and_cache_except_and_cache_only
cached_serializer = build do |serializer| cached_serializer = build do |serializer|
serializer._cache = Object serializer._cache = Object
serializer._cache_except = [:content] serializer._cache_except = [:content]
serializer._cache_only = [:name] serializer._cache_only = [:name]
end
refute cached_serializer.fragment_cached?
end end
refute cached_serializer.fragment_cached?
end
private private
def build def build
serializer = Class.new(ActiveModel::Serializer) serializer = Class.new(ActiveModel::Serializer)
serializer._cache_key = nil serializer._cache_key = nil
serializer._cache_options = nil serializer._cache_options = nil
yield serializer if block_given? yield serializer if block_given?
serializer_instance = serializer.new(Object) serializer_instance = serializer.new(Object)
ActiveModel::Serializer::Adapter::CachedSerializer.new(serializer_instance) CachedSerializer.new(serializer_instance)
end
end end
end 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' require 'minitest/reporters'
Minitest::Reporters.use! Minitest::Reporters.use!
require 'support/stream_capture'
require 'support/rails_app' require 'support/rails_app'
require 'support/test_case' require 'support/test_case'