Moved the adapter and adapter folder to active_model_serializers folder and changed the module namespace

Changed the namespace in adapters and folder to active_model_serializers from active_model::serializer

Changed namespace of adapters in serializers and other folders

Moved adapter_for_test file to active_model_serializers folder and changed namespace of adapter inside the test file

Require ActiveSupport's string/inflections

We depend on string/inflections to define String#underscore.

Refactor JsonApi adapter to avoid redundant computations.

Update readme.md to link to v0.10.0.rc4

changed namespace of adapter folder testcases

Changed all namespaces of adapter under active_moder_serializers

Namespaced IncludeTree which is from serializer module, so needed to namespace it properly

Fixed wrong namsepacing of fieldset

namespace change in deserializer json_api

Fixed the namespace for collection serializer when used inside adapter, changed namespace for adapter to new namespace which I had forgotten previously

Modified logging test and adapter test cases to make the testcases pass

Changed the yardoc links,as old links are not taking to documentation pages,proper links for 0.10,0.9 and 0.8 in rubydoc

Rubocop errors are fixed by underscore naming unused variables

Moved the require of adapter to serializable resource

Remoeved adapter dependency inside serializer and added warning to Serializer::adapter method

Fixed frament cache test which is calling Serializer.adapter

Changed the name of lookup_adapter_from_config to configured_adapter

Changed the docs which will show the new namespace of adapters

Rubocop fix
This commit is contained in:
bobba surendranath chowdary 2016-02-04 11:49:27 +05:30 committed by Benjamin Fleischer
parent f5ec8ed9d4
commit 252f9c4ae9
61 changed files with 2693 additions and 2764 deletions

View File

@ -3,6 +3,7 @@
Breaking changes: Breaking changes:
Features: Features:
Fixes: Fixes:
- [#1488](https://github.com/rails-api/active_model_serializers/pull/1488) Require ActiveSupport's string inflections (@nate00)
Misc: Misc:
### v0.10.0.rc4 (2016/01/27 11:00 +00:00) ### v0.10.0.rc4 (2016/01/27 11:00 +00:00)

View File

@ -8,10 +8,12 @@
## Documentation ## Documentation
- [0.10 (master) Documentation](https://github.com/rails-api/active_model_serializers/tree/master) - [0.10 (master) Documentation](https://github.com/rails-api/active_model_serializers/tree/master)
- [![API Docs](http://img.shields.io/badge/yard-docs-blue.svg)](http://www.rubydoc.info/github/rails-api/active_model_serializers) - [![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 `ActiveModelSerializers::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

@ -21,7 +21,8 @@ module ActiveModel
include Caching include Caching
include Links include Links
include Type include Type
require 'active_model/serializer/adapter' # Deprecated
require 'active_model_serializers/adapter'
# @param resource [ActiveRecord::Base, ActiveModelSerializers::Model] # @param resource [ActiveRecord::Base, ActiveModelSerializers::Model]
# @return [ActiveModel::Serializer] # @return [ActiveModel::Serializer]
@ -40,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,66 +0,0 @@
module ActiveModel
class Serializer
module Adapter
class Attributes < Base
def initialize(serializer, options = {})
super
@include_tree = IncludeTree.from_include_args(options[:include] || '*')
end
def serializable_hash(options = nil)
options ||= {}
if serializer.respond_to?(:each)
serializable_hash_for_collection(options)
else
serializable_hash_for_single_resource(options)
end
end
def fragment_cache(cached_hash, non_cached_hash)
Json::FragmentCache.new.fragment_cache(cached_hash, non_cached_hash)
end
private
def serializable_hash_for_collection(options)
serializer.map { |s| Attributes.new(s, instance_options).serializable_hash(options) }
end
def serializable_hash_for_single_resource(options)
resource = resource_object_for(options)
relationships = resource_relationships(options)
resource.merge!(relationships)
end
def resource_relationships(options)
relationships = {}
serializer.associations(@include_tree).each do |association|
relationships[association.key] = relationship_value_for(association, options)
end
relationships
end
def relationship_value_for(association, options)
return association.options[:virtual_value] if association.options[:virtual_value]
return unless association.serializer && association.serializer.object
opts = instance_options.merge(include: @include_tree[association.key])
Attributes.new(association.serializer, opts).serializable_hash(options)
end
# no-op: Attributes adapter does not include meta data, because it does not support root.
def include_meta(json)
json
end
def resource_object_for(options)
cache_check(serializer) do
serializer.attributes(options[:fields])
end
end
end
end
end
end

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,45 +0,0 @@
module ActiveModel
class Serializer
module Adapter
class CachedSerializer
def initialize(serializer)
@cached_serializer = serializer
@klass = @cached_serializer.class
end
def cache_check(adapter_instance)
if cached?
@klass._cache.fetch(cache_key, @klass._cache_options) do
yield
end
elsif fragment_cached?
FragmentCache.new(adapter_instance, @cached_serializer, adapter_instance.instance_options).fetch
else
yield
end
end
def cached?
@klass._cache && !@klass._cache_only && !@klass._cache_except
end
def fragment_cached?
@klass._cache_only && !@klass._cache_except || !@klass._cache_only && @klass._cache_except
end
def cache_key
parts = []
parts << object_cache_key
parts << @klass._cache_digest unless @klass._cache_options && @klass._cache_options[:skip_digest]
parts.join('/')
end
def object_cache_key
object_time_safe = @cached_serializer.object.updated_at
object_time_safe = object_time_safe.strftime('%Y%m%d%H%M%S%9N') if object_time_safe.respond_to?(:strftime)
(@klass._cache_key) ? "#{@klass._cache_key}/#{@cached_serializer.object.id}-#{object_time_safe}" : @cached_serializer.object.cache_key
end
end
end
end
end

View File

@ -1,111 +0,0 @@
module ActiveModel
class Serializer
module Adapter
class FragmentCache
attr_reader :serializer
def initialize(adapter, serializer, options)
@instance_options = options
@adapter = adapter
@serializer = serializer
end
# TODO: Use Serializable::Resource
# TODO: call +constantize+ less
# 1. Create a CachedSerializer and NonCachedSerializer from the serializer class
# 2. Serialize the above two with the given adapter
# 3. Pass their serializations to the adapter +::fragment_cache+
def fetch
klass = serializer.class
# It will split the serializer into two, one that will be cached and one that will not
serializers = fragment_serializer(serializer.object.class.name, klass)
# Instantiate both serializers
cached_serializer = serializers[:cached].constantize.new(serializer.object)
non_cached_serializer = serializers[:non_cached].constantize.new(serializer.object)
cached_adapter = adapter.class.new(cached_serializer, instance_options)
non_cached_adapter = adapter.class.new(non_cached_serializer, instance_options)
# Get serializable hash from both
cached_hash = cached_adapter.serializable_hash
non_cached_hash = non_cached_adapter.serializable_hash
# Merge both results
adapter.fragment_cache(cached_hash, non_cached_hash)
end
protected
attr_reader :instance_options, :adapter
private
# Given a serializer class and a hash of its cached and non-cached serializers
# 1. Determine cached attributes from serializer class options
# 2. Add cached attributes to cached Serializer
# 3. Add non-cached attributes to non-cached Serializer
def cached_attributes(klass, serializers)
attributes = serializer.class._attributes
cached_attributes = (klass._cache_only) ? klass._cache_only : attributes.reject { |attr| klass._cache_except.include?(attr) }
non_cached_attributes = attributes - cached_attributes
cached_attributes.each do |attribute|
options = serializer.class._attributes_keys[attribute]
options ||= {}
# Add cached attributes to cached Serializer
serializers[:cached].constantize.attribute(attribute, options)
end
non_cached_attributes.each do |attribute|
options = serializer.class._attributes_keys[attribute]
options ||= {}
# Add non-cached attributes to non-cached Serializer
serializers[:non_cached].constantize.attribute(attribute, options)
end
end
# Given a resource name and its serializer's class
# 1. Dyanmically creates a CachedSerializer and NonCachedSerializer
# for a given class 'name'
# 2. Call
# CachedSerializer.cache(serializer._cache_options)
# CachedSerializer.fragmented(serializer)
# NontCachedSerializer.cache(serializer._cache_options)
# 3. Build a hash keyed to the +cached+ and +non_cached+ serializers
# 4. Call +cached_attributes+ on the serializer class and the above hash
# 5. Return the hash
#
# @example
# When +name+ is <tt>User::Admin</tt>
# creates the Serializer classes (if they don't exist).
# User_AdminCachedSerializer
# User_AdminNOnCachedSerializer
#
def fragment_serializer(name, klass)
cached = "#{to_valid_const_name(name)}CachedSerializer"
non_cached = "#{to_valid_const_name(name)}NonCachedSerializer"
Object.const_set cached, Class.new(ActiveModel::Serializer) unless Object.const_defined?(cached)
Object.const_set non_cached, Class.new(ActiveModel::Serializer) unless Object.const_defined?(non_cached)
klass._cache_options ||= {}
klass._cache_options[:key] = klass._cache_key if klass._cache_key
cached.constantize.cache(klass._cache_options)
cached.constantize.fragmented(serializer)
non_cached.constantize.fragmented(serializer)
serializers = { cached: cached, non_cached: non_cached }
cached_attributes(klass, serializers)
serializers
end
def to_valid_const_name(name)
name.gsub('::', '_')
end
end
end
end
end

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,223 +0,0 @@
module ActiveModel
class Serializer
module Adapter
class JsonApi < Base
extend ActiveSupport::Autoload
autoload :PaginationLinks
autoload :FragmentCache
autoload :Link
autoload :Deserialization
# TODO: if we like this abstraction and other API objects to it,
# then extract to its own file and require it.
module ApiObjects
module JsonApi
ActiveModelSerializers.config.jsonapi_version = '1.0'
ActiveModelSerializers.config.jsonapi_toplevel_meta = {}
# Make JSON API top-level jsonapi member opt-in
# ref: http://jsonapi.org/format/#document-top-level
ActiveModelSerializers.config.jsonapi_include_toplevel_object = false
module_function
def add!(hash)
hash.merge!(object) if include_object?
end
def include_object?
ActiveModelSerializers.config.jsonapi_include_toplevel_object
end
# TODO: see if we can cache this
def object
object = {
jsonapi: {
version: ActiveModelSerializers.config.jsonapi_version,
meta: ActiveModelSerializers.config.jsonapi_toplevel_meta
}
}
object[:jsonapi].reject! { |_, v| v.blank? }
object
end
end
end
def initialize(serializer, options = {})
super
@include_tree = IncludeTree.from_include_args(options[:include])
@fieldset = options[:fieldset] || ActiveModel::Serializer::Fieldset.new(options.delete(:fields))
end
def serializable_hash(options = nil)
options ||= {}
hash =
if serializer.respond_to?(:each)
serializable_hash_for_collection(options)
else
serializable_hash_for_single_resource
end
ApiObjects::JsonApi.add!(hash)
if instance_options[:links]
hash[:links] ||= {}
hash[:links].update(instance_options[:links])
end
hash
end
def fragment_cache(cached_hash, non_cached_hash)
root = false if instance_options.include?(:include)
ActiveModel::Serializer::Adapter::JsonApi::FragmentCache.new.fragment_cache(root, cached_hash, non_cached_hash)
end
protected
attr_reader :fieldset
private
def serializable_hash_for_collection(options)
hash = { data: [] }
included = []
serializer.each do |s|
result = self.class.new(s, instance_options.merge(fieldset: fieldset)).serializable_hash(options)
hash[:data] << result[:data]
next unless result[:included]
included |= result[:included]
end
included.delete_if { |resource| hash[:data].include?(resource) }
hash[:included] = included if included.any?
if serializer.paginated?
hash[:links] ||= {}
hash[:links].update(pagination_links_for(serializer, options))
end
hash
end
def serializable_hash_for_single_resource
primary_data = resource_object_for(serializer)
hash = { data: primary_data }
included = included_resources(@include_tree, [primary_data])
hash[:included] = included if included.any?
hash
end
def resource_identifier_type_for(serializer)
return serializer._type if serializer._type
if ActiveModelSerializers.config.jsonapi_resource_type == :singular
serializer.object.class.model_name.singular
else
serializer.object.class.model_name.plural
end
end
def resource_identifier_id_for(serializer)
if serializer.respond_to?(:id)
serializer.id
else
serializer.object.id
end
end
def resource_identifier_for(serializer)
type = resource_identifier_type_for(serializer)
id = resource_identifier_id_for(serializer)
{ id: id.to_s, type: type }
end
def attributes_for(serializer, fields)
serializer.attributes(fields).except(:id)
end
def resource_object_for(serializer)
resource_object = cache_check(serializer) do
resource_object = resource_identifier_for(serializer)
requested_fields = fieldset && fieldset.fields_for(resource_object[:type])
attributes = attributes_for(serializer, requested_fields)
resource_object[:attributes] = attributes if attributes.any?
resource_object
end
relationships = relationships_for(serializer)
resource_object[:relationships] = relationships if relationships.any?
links = links_for(serializer)
resource_object[:links] = links if links.any?
resource_object
end
def relationship_value_for(serializer, options = {})
if serializer.respond_to?(:each)
serializer.map { |s| resource_identifier_for(s) }
else
if options[:virtual_value]
options[:virtual_value]
elsif serializer && serializer.object
resource_identifier_for(serializer)
end
end
end
def relationships_for(serializer)
resource_type = resource_identifier_type_for(serializer)
requested_associations = fieldset.fields_for(resource_type) || '*'
include_tree = IncludeTree.from_include_args(requested_associations)
serializer.associations(include_tree).each_with_object({}) do |association, hash|
hash[association.key] = { data: relationship_value_for(association.serializer, association.options) }
end
end
def included_resources(include_tree, primary_data)
included = []
serializer.associations(include_tree).each do |association|
add_included_resources_for(association.serializer, include_tree[association.key], primary_data, included)
end
included
end
def add_included_resources_for(serializer, include_tree, primary_data, included)
if serializer.respond_to?(:each)
serializer.each { |s| add_included_resources_for(s, include_tree, primary_data, included) }
else
return unless serializer && serializer.object
resource_object = resource_object_for(serializer)
return if included.include?(resource_object) || primary_data.include?(resource_object)
included.push(resource_object)
serializer.associations(include_tree).each do |association|
add_included_resources_for(association.serializer, include_tree[association.key], primary_data, included)
end
end
end
def links_for(serializer)
serializer._links.each_with_object({}) do |(name, value), hash|
hash[name] = Link.new(serializer, value).as_json
end
end
def pagination_links_for(serializer, options)
JsonApi::PaginationLinks.new(serializer.object, options[:serialization_context]).serializable_hash(options)
end
end
end
end
end

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,44 +0,0 @@
module ActiveModel
class Serializer
module Adapter
class JsonApi
class Link
def initialize(serializer, value)
@object = serializer.object
@scope = serializer.scope
# Use the return value of the block unless it is nil.
if value.respond_to?(:call)
@value = instance_eval(&value)
else
@value = value
end
end
def href(value)
@href = value
nil
end
def meta(value)
@meta = value
nil
end
def as_json
return @value if @value
hash = { href: @href }
hash.merge!(meta: @meta) if @meta
hash
end
protected
attr_reader :object, :scope
end
end
end
end
end

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

@ -1,6 +1,7 @@
require 'active_model' require 'active_model'
require 'active_support' require 'active_support'
require 'active_support/core_ext/object/with_options' require 'active_support/core_ext/object/with_options'
require 'active_support/core_ext/string/inflections'
module ActiveModelSerializers module ActiveModelSerializers
extend ActiveSupport::Autoload extend ActiveSupport::Autoload
autoload :Model autoload :Model

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,64 @@
module ActiveModelSerializers
module Adapter
class Attributes < Base
def initialize(serializer, options = {})
super
@include_tree = ActiveModel::Serializer::IncludeTree.from_include_args(options[:include] || '*')
end
def serializable_hash(options = nil)
options ||= {}
if serializer.respond_to?(:each)
serializable_hash_for_collection(options)
else
serializable_hash_for_single_resource(options)
end
end
def fragment_cache(cached_hash, non_cached_hash)
Json::FragmentCache.new.fragment_cache(cached_hash, non_cached_hash)
end
private
def serializable_hash_for_collection(options)
serializer.map { |s| Attributes.new(s, instance_options).serializable_hash(options) }
end
def serializable_hash_for_single_resource(options)
resource = resource_object_for(options)
relationships = resource_relationships(options)
resource.merge!(relationships)
end
def resource_relationships(options)
relationships = {}
serializer.associations(@include_tree).each do |association|
relationships[association.key] = relationship_value_for(association, options)
end
relationships
end
def relationship_value_for(association, options)
return association.options[:virtual_value] if association.options[:virtual_value]
return unless association.serializer && association.serializer.object
opts = instance_options.merge(include: @include_tree[association.key])
Attributes.new(association.serializer, opts).serializable_hash(options)
end
# no-op: Attributes adapter does not include meta data, because it does not support root.
def include_meta(json)
json
end
def resource_object_for(options)
cache_check(serializer) do
serializer.attributes(options[:fields])
end
end
end
end
end

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,43 @@
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_only && !@klass._cache_except || !@klass._cache_only && @klass._cache_except
end
def cache_key
parts = []
parts << object_cache_key
parts << @klass._cache_digest unless @klass._cache_options && @klass._cache_options[:skip_digest]
parts.join('/')
end
def object_cache_key
object_time_safe = @cached_serializer.object.updated_at
object_time_safe = object_time_safe.strftime('%Y%m%d%H%M%S%9N') if object_time_safe.respond_to?(:strftime)
(@klass._cache_key) ? "#{@klass._cache_key}/#{@cached_serializer.object.id}-#{object_time_safe}" : @cached_serializer.object.cache_key
end
end
end
end

View File

@ -0,0 +1,109 @@
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)
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,208 @@
module ActiveModelSerializers
module Adapter
class JsonApi < Base
extend ActiveSupport::Autoload
autoload :PaginationLinks
autoload :FragmentCache
autoload :Link
autoload :Deserialization
# TODO: if we like this abstraction and other API objects to it,
# then extract to its own file and require it.
module ApiObjects
module JsonApi
ActiveModelSerializers.config.jsonapi_version = '1.0'
ActiveModelSerializers.config.jsonapi_toplevel_meta = {}
# Make JSON API top-level jsonapi member opt-in
# ref: http://jsonapi.org/format/#document-top-level
ActiveModelSerializers.config.jsonapi_include_toplevel_object = false
module_function
def add!(hash)
hash.merge!(object) if include_object?
end
def include_object?
ActiveModelSerializers.config.jsonapi_include_toplevel_object
end
# TODO: see if we can cache this
def object
object = {
jsonapi: {
version: ActiveModelSerializers.config.jsonapi_version,
meta: ActiveModelSerializers.config.jsonapi_toplevel_meta
}
}
object[:jsonapi].reject! { |_, v| v.blank? }
object
end
end
end
def initialize(serializer, options = {})
super
@include_tree = 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 = resource_identifier_for(serializer)
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 resource_identifier_type_for(serializer)
return serializer._type if serializer._type
if ActiveModelSerializers.config.jsonapi_resource_type == :singular
serializer.object.class.model_name.singular
else
serializer.object.class.model_name.plural
end
end
def resource_identifier_id_for(serializer)
if serializer.respond_to?(:id)
serializer.id
else
serializer.object.id
end
end
def resource_identifier_for(serializer)
type = resource_identifier_type_for(serializer)
id = resource_identifier_id_for(serializer)
{ id: id.to_s, type: type }
end
def attributes_for(serializer, fields)
serializer.attributes(fields).except(:id)
end
def resource_object_for(serializer)
resource_object = cache_check(serializer) do
resource_object = resource_identifier_for(serializer)
requested_fields = fieldset && fieldset.fields_for(resource_object[:type])
attributes = attributes_for(serializer, requested_fields)
resource_object[:attributes] = attributes if attributes.any?
resource_object
end
relationships = relationships_for(serializer)
resource_object[:relationships] = relationships if relationships.any?
links = links_for(serializer)
resource_object[:links] = links if links.any?
resource_object
end
def relationship_value_for(serializer, options = {})
if serializer.respond_to?(:each)
serializer.map { |s| resource_identifier_for(s) }
else
if options[:virtual_value]
options[:virtual_value]
elsif serializer && serializer.object
resource_identifier_for(serializer)
end
end
end
def relationships_for(serializer)
resource_type = resource_identifier_type_for(serializer)
requested_associations = fieldset.fields_for(resource_type) || '*'
include_tree = ActiveModel::Serializer::IncludeTree.from_include_args(requested_associations)
serializer.associations(include_tree).each_with_object({}) do |association, hash|
hash[association.key] = { data: relationship_value_for(association.serializer, association.options) }
end
end
def links_for(serializer)
serializer._links.each_with_object({}) do |(name, value), hash|
hash[name] = Link.new(serializer, value).as_json
end
end
def pagination_links_for(serializer, options)
JsonApi::PaginationLinks.new(serializer.object, options[:serialization_context]).serializable_hash(options)
end
end
end
end

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,42 @@
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 = { href: @href }
hash.merge!(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

@ -0,0 +1,164 @@
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_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_class
ActiveModelSerializers.config.adapter = ActiveModelSerializers::Adapter::Null
adapter = ActiveModelSerializers::Adapter.configured_adapter
assert_equal ActiveModelSerializers::Adapter::Null, adapter
end
def test_raises_exception_if_invalid_symbol_given
ActiveModelSerializers.config.adapter = :unknown
assert_raises UnknownAdapterError do
ActiveModelSerializers::Adapter.configured_adapter
end
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
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,6 +1,5 @@
require 'test_helper' require 'test_helper'
module ActiveModel module ActiveModelSerializers
class Serializer
module Adapter module Adapter
class FragmentCacheTest < ActiveSupport::TestCase class FragmentCacheTest < ActiveSupport::TestCase
def setup def setup
@ -11,8 +10,8 @@ module ActiveModel
@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
@ -33,6 +32,5 @@ module ActiveModel
end end
end end
end end
end
end end

View File

@ -1,7 +1,6 @@
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
@ -19,7 +18,7 @@ module ActiveModel
@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
@ -29,19 +28,18 @@ module ActiveModel
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 end
end
end end

View File

@ -1,7 +1,6 @@
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
@ -23,8 +22,8 @@ module ActiveModel
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, id: 1,
@ -35,8 +34,8 @@ module ActiveModel
end end
def test_include_multiple_posts def test_include_multiple_posts
serializer = CollectionSerializer.new([@first_post, @second_post]) serializer = ActiveModel::Serializer::CollectionSerializer.new([@first_post, @second_post])
adapter = ActiveModel::Serializer::Adapter::Json.new(serializer) adapter = ActiveModelSerializers::Adapter::Json.new(serializer)
expected = { posts: [{ expected = { posts: [{
title: 'Hello!!', title: 'Hello!!',
@ -70,15 +69,15 @@ module ActiveModel
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' }] }
@ -88,5 +87,4 @@ module ActiveModel
end end
end end
end end
end
end end

View File

@ -1,7 +1,6 @@
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
@ -23,7 +22,7 @@ module ActiveModel
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' }
@ -32,7 +31,7 @@ module ActiveModel
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: [
@ -43,5 +42,4 @@ module ActiveModel
end end
end end
end end
end
end end

View File

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

View File

@ -1,7 +1,6 @@
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
@ -19,8 +18,8 @@ module ActiveModel
@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
@ -93,5 +92,4 @@ module ActiveModel
end end
end end
end end
end
end end

View File

@ -1,7 +1,6 @@
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
@ -85,5 +84,4 @@ module ActiveModel
end end
end end
end end
end
end end

View File

@ -1,7 +1,6 @@
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
@ -21,7 +20,7 @@ module ActiveModel
@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
@ -41,5 +40,4 @@ module ActiveModel
end end
end end
end end
end
end end

View File

@ -1,7 +1,6 @@
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'
@ -22,7 +21,7 @@ module ActiveModel
@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]
) )
@ -94,5 +93,4 @@ module ActiveModel
end end
end end
end end
end
end end

View File

@ -1,7 +1,6 @@
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
@ -30,7 +29,7 @@ module ActiveModel
@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 = ActiveModel::Serializer::Adapter::JsonApi.new(@serializer) @adapter = ActiveModelSerializers::Adapter::JsonApi.new(@serializer)
@virtual_value = VirtualValue.new(id: 1) @virtual_value = VirtualValue.new(id: 1)
end end
@ -42,7 +41,7 @@ module ActiveModel
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',
@ -68,7 +67,7 @@ module ActiveModel
end end
def test_limit_fields_of_linked_comments def test_limit_fields_of_linked_comments
@adapter = ActiveModel::Serializer::Adapter::JsonApi.new(@serializer, include: [:comments], fields: { comment: [:id, :post, :author] }) @adapter = ActiveModelSerializers::Adapter::JsonApi.new(@serializer, include: [:comments], fields: { comment: [:id, :post, :author] })
expected = [{ expected = [{
id: '1', id: '1',
type: 'comments', type: 'comments',
@ -89,14 +88,14 @@ module ActiveModel
def test_no_include_linked_if_comments_is_empty def test_no_include_linked_if_comments_is_empty
serializer = PostSerializer.new(@post_without_comments) serializer = PostSerializer.new(@post_without_comments)
adapter = ActiveModel::Serializer::Adapter::JsonApi.new(serializer) adapter = ActiveModelSerializers::Adapter::JsonApi.new(serializer)
assert_nil adapter.serializable_hash[:linked] assert_nil adapter.serializable_hash[:linked]
end end
def test_include_type_for_association_when_different_than_name def test_include_type_for_association_when_different_than_name
serializer = BlogSerializer.new(@blog) serializer = BlogSerializer.new(@blog)
adapter = ActiveModel::Serializer::Adapter::JsonApi.new(serializer) adapter = ActiveModelSerializers::Adapter::JsonApi.new(serializer)
actual = adapter.serializable_hash[:data][:relationships][:articles] actual = adapter.serializable_hash[:data][:relationships][:articles]
expected = { expected = {
@ -110,7 +109,7 @@ module ActiveModel
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::JsonApi.new(serializer) adapter = ActiveModelSerializers::Adapter::JsonApi.new(serializer)
assert_equal({ assert_equal({
data: { data: {
@ -125,7 +124,7 @@ module ActiveModel
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: {
@ -141,5 +140,4 @@ module ActiveModel
end end
end end
end end
end
end end

View File

@ -1,7 +1,6 @@
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
@ -28,7 +27,7 @@ module ActiveModel
@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
@ -38,7 +37,7 @@ module ActiveModel
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 = [
{ {
@ -59,7 +58,7 @@ module ActiveModel
def test_has_one_with_virtual_value def test_has_one_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)
expected = { expected = {
data: { data: {
@ -77,5 +76,4 @@ module ActiveModel
end end
end end
end end
end
end end

View File

@ -1,7 +1,6 @@
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
@ -20,7 +19,7 @@ module ActiveModel
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: [
@ -33,5 +32,4 @@ module ActiveModel
end end
end end
end end
end
end end

View File

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

View File

@ -1,7 +1,6 @@
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
@ -64,5 +63,4 @@ module ActiveModel
end end
end end
end end
end
end end

View File

@ -4,8 +4,7 @@ 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
@ -111,5 +110,4 @@ module ActiveModel
end end
end end
end end
end
end end

View File

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

View File

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

View File

@ -1,7 +1,6 @@
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
@ -80,5 +79,4 @@ module ActiveModel
end end
end end
end end
end
end end

View File

@ -1,7 +1,6 @@
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
@ -18,7 +17,7 @@ module ActiveModel
@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
@ -30,7 +29,7 @@ module ActiveModel
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,
@ -43,5 +42,4 @@ module ActiveModel
end end
end end
end end
end
end end

View File

@ -1,7 +1,6 @@
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
@ -20,6 +19,5 @@ module ActiveModel
end end
end end
end end
end
end end

View File

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