diff --git a/CHANGELOG.md b/CHANGELOG.md index 7fd7e5fc..721f42ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ Breaking changes: Features: Fixes: +- [#1488](https://github.com/rails-api/active_model_serializers/pull/1488) Require ActiveSupport's string inflections (@nate00) Misc: ### v0.10.0.rc4 (2016/01/27 11:00 +00:00) diff --git a/README.md b/README.md index 2106d34e..8a48cae1 100644 --- a/README.md +++ b/README.md @@ -8,10 +8,12 @@ ## Documentation - [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) - [0.9 (0-9-stable) Documentation](https://github.com/rails-api/active_model_serializers/tree/0-9-stable) + - [![API Docs](http://img.shields.io/badge/yard-docs-blue.svg)](http://www.rubydoc.info/github/rails-api/active_model_serializers/0-9-stable) - [0.8 (0-8-stable) Documentation](https://github.com/rails-api/active_model_serializers/tree/0-8-stable) + - [![API Docs](http://img.shields.io/badge/yard-docs-blue.svg)](http://www.rubydoc.info/github/rails-api/active_model_serializers/0-8-stable) ## About diff --git a/docs/general/adapters.md b/docs/general/adapters.md index 262c4418..fa2a1531 100644 --- a/docs/general/adapters.md +++ b/docs/general/adapters.md @@ -11,7 +11,7 @@ It should be set only once, preferably at initialization. For example: ```ruby -ActiveModelSerializers.config.adapter = ActiveModel::Serializer::Adapter::JsonApi +ActiveModelSerializers.config.adapter = ActiveModelSerializers::Adapter::JsonApi ``` or @@ -117,46 +117,46 @@ The default adapter can be configured, as above, to use any class given to it. An adapter may also be specified, e.g. when rendering, as a class or as a symbol. If a symbol, then the adapter must be, e.g. `:great_example`, -`ActiveModel::Serializer::Adapter::GreatExample`, or registered. +`ActiveModelSerializers::Adapter::GreatExample`, or registered. There are two ways to register an adapter: -1) The simplest, is to subclass `ActiveModel::Serializer::Adapter::Base`, e.g. the below will +1) The simplest, is to subclass `ActiveModelSerializers::Adapter::Base`, e.g. the below will register the `Example::UsefulAdapter` as `"example/useful_adapter"`. ```ruby module Example - class UsefulAdapter < ActiveModel::Serializer::Adapter::Base + class UsefulAdapter < ActiveModelSerializers::Adapter::Base end end ``` You'll notice that the name it registers is the underscored namespace and class. -Under the covers, when the `ActiveModel::Serializer::Adapter::Base` is subclassed, it registers +Under the covers, when the `ActiveModelSerializers::Adapter::Base` is subclassed, it registers the subclass as `register("example/useful_adapter", Example::UsefulAdapter)` 2) Any class can be registered as an adapter by calling `register` directly on the -`ActiveModel::Serializer::Adapter` class. e.g., the below registers `MyAdapter` as +`ActiveModelSerializers::Adapter` class. e.g., the below registers `MyAdapter` as `:special_adapter`. ```ruby class MyAdapter; end -ActiveModel::Serializer::Adapter.register(:special_adapter, MyAdapter) +ActiveModelSerializers::Adapter.register(:special_adapter, MyAdapter) ``` ### Looking up an adapter | Method | Return value | | :------------ |:---------------| -| `ActiveModel::Serializer::Adapter.adapter_map` | A Hash of all known adapters `{ adapter_name => adapter_class }` | -| `ActiveModel::Serializer::Adapter.adapters` | A (sorted) Array of all known `adapter_names` | -| `ActiveModel::Serializer::Adapter.lookup(name_or_klass)` | The `adapter_class`, else raises an `ActiveModel::Serializer::Adapter::UnknownAdapter` error | -| `ActiveModel::Serializer::Adapter.adapter_class(adapter)` | Delegates to `ActiveModel::Serializer::Adapter.lookup(adapter)` | -| `ActiveModel::Serializer.adapter` | A convenience method for `ActiveModel::Serializer::Adapter.lookup(config.adapter)` | +| `ActiveModelSerializers::Adapter.adapter_map` | A Hash of all known adapters `{ adapter_name => adapter_class }` | +| `ActiveModelSerializers::Adapter.adapters` | A (sorted) Array of all known `adapter_names` | +| `ActiveModelSerializers::Adapter.lookup(name_or_klass)` | The `adapter_class`, else raises an `ActiveModelSerializers::Adapter::UnknownAdapter` error | +| `ActiveModelSerializers::Adapter.adapter_class(adapter)` | Delegates to `ActiveModelSerializers::Adapter.lookup(adapter)` | +| `ActiveModelSerializers::Adapter.configured_adapter` | A convenience method for `ActiveModelSerializers::Adapter.lookup(config.adapter)` | The registered adapter name is always a String, but may be looked up as a Symbol or String. Helpfully, the Symbol or String is underscored, so that `get(:my_adapter)` and `get("MyAdapter")` may both be used. -For more information, see [the Adapter class on GitHub](https://github.com/rails-api/active_model_serializers/blob/master/lib/active_model/serializer/adapter.rb) +For more information, see [the Adapter class on GitHub](https://github.com/rails-api/active_model_serializers/blob/master/lib/active_model_serializers/adapter.rb) diff --git a/docs/general/instrumentation.md b/docs/general/instrumentation.md index ba6a1ffd..560494ac 100644 --- a/docs/general/instrumentation.md +++ b/docs/general/instrumentation.md @@ -17,7 +17,7 @@ Payload (example): ```ruby { serializer: PostSerializer, - adapter: ActiveModel::Serializer::Adapter::Attributes + adapter: ActiveModelSerializers::Adapter::Attributes } ``` diff --git a/docs/rfcs/0000-namespace.md b/docs/rfcs/0000-namespace.md index 9cff50a3..9532cae5 100644 --- a/docs/rfcs/0000-namespace.md +++ b/docs/rfcs/0000-namespace.md @@ -70,7 +70,7 @@ at the first moment. ## Renaming of class and modules When moving some content to the new namespace we can find some names that does -not make much sense like `ActiveModelSerializers::Serializer::Adapter::JsonApi`. +not make much sense like `ActiveModelSerializers::Adapter::JsonApi`. Discussion of renaming existing classes / modules and JsonApi objects will happen in separate pull requests, and issues, and in the google doc https://docs.google.com/document/d/1rcrJr0sVcazY2Opd_6Kmv1iIwuHbI84s1P_NzFn-05c/edit?usp=sharing diff --git a/lib/active_model/serializable_resource.rb b/lib/active_model/serializable_resource.rb index c91eea17..ec83fcfc 100644 --- a/lib/active_model/serializable_resource.rb +++ b/lib/active_model/serializable_resource.rb @@ -1,4 +1,5 @@ require 'set' +require 'active_model_serializers/adapter' module ActiveModel class SerializableResource ADAPTER_OPTION_KEYS = Set.new([:include, :fields, :adapter, :meta, :meta_key, :links]) @@ -30,7 +31,7 @@ module ActiveModel end def adapter - @adapter ||= ActiveModel::Serializer::Adapter.create(serializer_instance, adapter_opts) + @adapter ||= ActiveModelSerializers::Adapter.create(serializer_instance, adapter_opts) end alias_method :adapter_instance, :adapter diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index 714ff65d..29b76511 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -21,7 +21,8 @@ module ActiveModel include Caching include Links include Type - require 'active_model/serializer/adapter' + # Deprecated + require 'active_model_serializers/adapter' # @param resource [ActiveRecord::Base, ActiveModelSerializers::Model] # @return [ActiveModel::Serializer] @@ -40,9 +41,11 @@ module ActiveModel end end - # @see ActiveModel::Serializer::Adapter.lookup + # @see ActiveModelSerializers::Adapter.lookup + # Deprecated def self.adapter - ActiveModel::Serializer::Adapter.lookup(config.adapter) + warn 'Calling adapter method in Serializer, please use the ActiveModelSerializers::configured_adapter' + ActiveModelSerializers::Adapter.lookup(config.adapter) end # @api private diff --git a/lib/active_model/serializer/adapter.rb b/lib/active_model/serializer/adapter.rb deleted file mode 100644 index 61a86e1c..00000000 --- a/lib/active_model/serializer/adapter.rb +++ /dev/null @@ -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 - def adapter_map - ADAPTER_MAP - end - - # @return [Array] 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 diff --git a/lib/active_model/serializer/adapter/attributes.rb b/lib/active_model/serializer/adapter/attributes.rb deleted file mode 100644 index 49dea860..00000000 --- a/lib/active_model/serializer/adapter/attributes.rb +++ /dev/null @@ -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 diff --git a/lib/active_model/serializer/adapter/base.rb b/lib/active_model/serializer/adapter/base.rb deleted file mode 100644 index fc06848a..00000000 --- a/lib/active_model/serializer/adapter/base.rb +++ /dev/null @@ -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 diff --git a/lib/active_model/serializer/adapter/cached_serializer.rb b/lib/active_model/serializer/adapter/cached_serializer.rb deleted file mode 100644 index 35b10168..00000000 --- a/lib/active_model/serializer/adapter/cached_serializer.rb +++ /dev/null @@ -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 diff --git a/lib/active_model/serializer/adapter/fragment_cache.rb b/lib/active_model/serializer/adapter/fragment_cache.rb deleted file mode 100644 index 5c97a64a..00000000 --- a/lib/active_model/serializer/adapter/fragment_cache.rb +++ /dev/null @@ -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 User::Admin - # 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 diff --git a/lib/active_model/serializer/adapter/json.rb b/lib/active_model/serializer/adapter/json.rb deleted file mode 100644 index ab81f571..00000000 --- a/lib/active_model/serializer/adapter/json.rb +++ /dev/null @@ -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 diff --git a/lib/active_model/serializer/adapter/json/fragment_cache.rb b/lib/active_model/serializer/adapter/json/fragment_cache.rb deleted file mode 100644 index ff312cd9..00000000 --- a/lib/active_model/serializer/adapter/json/fragment_cache.rb +++ /dev/null @@ -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 diff --git a/lib/active_model/serializer/adapter/json_api.rb b/lib/active_model/serializer/adapter/json_api.rb deleted file mode 100644 index 744d62e4..00000000 --- a/lib/active_model/serializer/adapter/json_api.rb +++ /dev/null @@ -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 diff --git a/lib/active_model/serializer/adapter/json_api/deserialization.rb b/lib/active_model/serializer/adapter/json_api/deserialization.rb deleted file mode 100644 index 5f35a882..00000000 --- a/lib/active_model/serializer/adapter/json_api/deserialization.rb +++ /dev/null @@ -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 diff --git a/lib/active_model/serializer/adapter/json_api/fragment_cache.rb b/lib/active_model/serializer/adapter/json_api/fragment_cache.rb deleted file mode 100644 index 7dbc1179..00000000 --- a/lib/active_model/serializer/adapter/json_api/fragment_cache.rb +++ /dev/null @@ -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 diff --git a/lib/active_model/serializer/adapter/json_api/link.rb b/lib/active_model/serializer/adapter/json_api/link.rb deleted file mode 100644 index bed230c3..00000000 --- a/lib/active_model/serializer/adapter/json_api/link.rb +++ /dev/null @@ -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 diff --git a/lib/active_model/serializer/adapter/json_api/pagination_links.rb b/lib/active_model/serializer/adapter/json_api/pagination_links.rb deleted file mode 100644 index 9c437057..00000000 --- a/lib/active_model/serializer/adapter/json_api/pagination_links.rb +++ /dev/null @@ -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 diff --git a/lib/active_model/serializer/adapter/null.rb b/lib/active_model/serializer/adapter/null.rb deleted file mode 100644 index f398380f..00000000 --- a/lib/active_model/serializer/adapter/null.rb +++ /dev/null @@ -1,11 +0,0 @@ -module ActiveModel - class Serializer - module Adapter - class Null < Base - def serializable_hash(options = nil) - {} - end - end - end - end -end diff --git a/lib/active_model_serializers.rb b/lib/active_model_serializers.rb index d92823b5..47e14208 100644 --- a/lib/active_model_serializers.rb +++ b/lib/active_model_serializers.rb @@ -1,6 +1,7 @@ require 'active_model' require 'active_support' require 'active_support/core_ext/object/with_options' +require 'active_support/core_ext/string/inflections' module ActiveModelSerializers extend ActiveSupport::Autoload autoload :Model diff --git a/lib/active_model_serializers/adapter.rb b/lib/active_model_serializers/adapter.rb new file mode 100644 index 00000000..679b862b --- /dev/null +++ b/lib/active_model_serializers/adapter.rb @@ -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 + def adapter_map + ADAPTER_MAP + end + + # @return [Array] 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 diff --git a/lib/active_model_serializers/adapter/attributes.rb b/lib/active_model_serializers/adapter/attributes.rb new file mode 100644 index 00000000..34fb2583 --- /dev/null +++ b/lib/active_model_serializers/adapter/attributes.rb @@ -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 diff --git a/lib/active_model_serializers/adapter/base.rb b/lib/active_model_serializers/adapter/base.rb new file mode 100644 index 00000000..9b31cffc --- /dev/null +++ b/lib/active_model_serializers/adapter/base.rb @@ -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 diff --git a/lib/active_model_serializers/adapter/cached_serializer.rb b/lib/active_model_serializers/adapter/cached_serializer.rb new file mode 100644 index 00000000..685c5ef4 --- /dev/null +++ b/lib/active_model_serializers/adapter/cached_serializer.rb @@ -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 diff --git a/lib/active_model_serializers/adapter/fragment_cache.rb b/lib/active_model_serializers/adapter/fragment_cache.rb new file mode 100644 index 00000000..c7a2b059 --- /dev/null +++ b/lib/active_model_serializers/adapter/fragment_cache.rb @@ -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 User::Admin + # 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 diff --git a/lib/active_model_serializers/adapter/json.rb b/lib/active_model_serializers/adapter/json.rb new file mode 100644 index 00000000..9652a04f --- /dev/null +++ b/lib/active_model_serializers/adapter/json.rb @@ -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 diff --git a/lib/active_model_serializers/adapter/json/fragment_cache.rb b/lib/active_model_serializers/adapter/json/fragment_cache.rb new file mode 100644 index 00000000..d042063a --- /dev/null +++ b/lib/active_model_serializers/adapter/json/fragment_cache.rb @@ -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 diff --git a/lib/active_model_serializers/adapter/json_api.rb b/lib/active_model_serializers/adapter/json_api.rb new file mode 100644 index 00000000..c85b6523 --- /dev/null +++ b/lib/active_model_serializers/adapter/json_api.rb @@ -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 diff --git a/lib/active_model_serializers/adapter/json_api/deserialization.rb b/lib/active_model_serializers/adapter/json_api/deserialization.rb new file mode 100644 index 00000000..a50aa88f --- /dev/null +++ b/lib/active_model_serializers/adapter/json_api/deserialization.rb @@ -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 diff --git a/lib/active_model_serializers/adapter/json_api/fragment_cache.rb b/lib/active_model_serializers/adapter/json_api/fragment_cache.rb new file mode 100644 index 00000000..5ae0b08c --- /dev/null +++ b/lib/active_model_serializers/adapter/json_api/fragment_cache.rb @@ -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 diff --git a/lib/active_model_serializers/adapter/json_api/link.rb b/lib/active_model_serializers/adapter/json_api/link.rb new file mode 100644 index 00000000..bb490e29 --- /dev/null +++ b/lib/active_model_serializers/adapter/json_api/link.rb @@ -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 diff --git a/lib/active_model_serializers/adapter/json_api/pagination_links.rb b/lib/active_model_serializers/adapter/json_api/pagination_links.rb new file mode 100644 index 00000000..8f3252c3 --- /dev/null +++ b/lib/active_model_serializers/adapter/json_api/pagination_links.rb @@ -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 diff --git a/lib/active_model_serializers/adapter/null.rb b/lib/active_model_serializers/adapter/null.rb new file mode 100644 index 00000000..6e690b1b --- /dev/null +++ b/lib/active_model_serializers/adapter/null.rb @@ -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 diff --git a/lib/active_model_serializers/deserialization.rb b/lib/active_model_serializers/deserialization.rb index 15b8e898..9eaeef44 100644 --- a/lib/active_model_serializers/deserialization.rb +++ b/lib/active_model_serializers/deserialization.rb @@ -3,11 +3,11 @@ module ActiveModelSerializers module_function def jsonapi_parse(*args) - ActiveModel::Serializer::Adapter::JsonApi::Deserialization.parse(*args) + ActiveModelSerializers::Adapter::JsonApi::Deserialization.parse(*args) end def jsonapi_parse!(*args) - ActiveModel::Serializer::Adapter::JsonApi::Deserialization.parse!(*args) + ActiveModelSerializers::Adapter::JsonApi::Deserialization.parse!(*args) end end end diff --git a/test/active_model_serializers/adapter_for_test.rb b/test/active_model_serializers/adapter_for_test.rb new file mode 100644 index 00000000..5fe3b857 --- /dev/null +++ b/test/active_model_serializers/adapter_for_test.rb @@ -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 diff --git a/test/active_model_serializers/logging_test.rb b/test/active_model_serializers/logging_test.rb index e0f3e85a..aa50e985 100644 --- a/test/active_model_serializers/logging_test.rb +++ b/test/active_model_serializers/logging_test.rb @@ -65,7 +65,7 @@ module ActiveModel def test_logs_correct_adapter ActiveModel::SerializableResource.new(@post).serializable_hash - assert_match(/ActiveModel::Serializer::Adapter::Attributes/, @logger.messages) + assert_match(/ActiveModelSerializers::Adapter::Attributes/, @logger.messages) end def test_logs_the_duration diff --git a/test/adapter/fragment_cache_test.rb b/test/adapter/fragment_cache_test.rb index 0ad78eb5..15c99d48 100644 --- a/test/adapter/fragment_cache_test.rb +++ b/test/adapter/fragment_cache_test.rb @@ -1,36 +1,34 @@ require 'test_helper' -module ActiveModel - class Serializer - module Adapter - class FragmentCacheTest < ActiveSupport::TestCase - def setup - super - @spam = Spam::UnrelatedLink.new(id: 'spam-id-1') - @author = Author.new(name: 'Joao M. D. Moura') - @role = Role.new(name: 'Great Author', description: nil) - @role.author = [@author] - @role_serializer = RoleSerializer.new(@role) - @spam_serializer = Spam::UnrelatedLinkSerializer.new(@spam) - @role_hash = FragmentCache.new(RoleSerializer.adapter.new(@role_serializer), @role_serializer, {}) - @spam_hash = FragmentCache.new(Spam::UnrelatedLinkSerializer.adapter.new(@spam_serializer), @spam_serializer, {}) - end +module ActiveModelSerializers + module Adapter + class FragmentCacheTest < ActiveSupport::TestCase + def setup + super + @spam = Spam::UnrelatedLink.new(id: 'spam-id-1') + @author = Author.new(name: 'Joao M. D. Moura') + @role = Role.new(name: 'Great Author', description: nil) + @role.author = [@author] + @role_serializer = RoleSerializer.new(@role) + @spam_serializer = Spam::UnrelatedLinkSerializer.new(@spam) + @role_hash = FragmentCache.new(::ActiveModelSerializers::Adapter.configured_adapter.new(@role_serializer), @role_serializer, {}) + @spam_hash = FragmentCache.new(::ActiveModelSerializers::Adapter.configured_adapter.new(@spam_serializer), @spam_serializer, {}) + end - def test_fragment_fetch_with_virtual_attributes - expected_result = { - id: @role.id, - description: @role.description, - slug: "#{@role.name}-#{@role.id}", - name: @role.name - } - assert_equal(@role_hash.fetch, expected_result) - end + def test_fragment_fetch_with_virtual_attributes + expected_result = { + id: @role.id, + description: @role.description, + slug: "#{@role.name}-#{@role.id}", + name: @role.name + } + assert_equal(@role_hash.fetch, expected_result) + end - def test_fragment_fetch_with_namespaced_object - expected_result = { - id: @spam.id - } - assert_equal(@spam_hash.fetch, expected_result) - end + def test_fragment_fetch_with_namespaced_object + expected_result = { + id: @spam.id + } + assert_equal(@spam_hash.fetch, expected_result) end end end diff --git a/test/adapter/json/belongs_to_test.rb b/test/adapter/json/belongs_to_test.rb index 940770b2..0f096f0b 100644 --- a/test/adapter/json/belongs_to_test.rb +++ b/test/adapter/json/belongs_to_test.rb @@ -1,45 +1,43 @@ require 'test_helper' -module ActiveModel - class Serializer - module Adapter - class Json - class BelongsToTest < ActiveSupport::TestCase - def setup - @post = Post.new(id: 42, title: 'New Post', body: 'Body') - @anonymous_post = Post.new(id: 43, title: 'Hello!!', body: 'Hello, world!!') - @comment = Comment.new(id: 1, body: 'ZOMG A COMMENT') - @post.comments = [@comment] - @anonymous_post.comments = [] - @comment.post = @post - @comment.author = nil - @anonymous_post.author = nil - @blog = Blog.new(id: 1, name: 'My Blog!!') - @post.blog = @blog - @anonymous_post.blog = nil +module ActiveModelSerializers + module Adapter + class Json + class BelongsToTest < ActiveSupport::TestCase + def setup + @post = Post.new(id: 42, title: 'New Post', body: 'Body') + @anonymous_post = Post.new(id: 43, title: 'Hello!!', body: 'Hello, world!!') + @comment = Comment.new(id: 1, body: 'ZOMG A COMMENT') + @post.comments = [@comment] + @anonymous_post.comments = [] + @comment.post = @post + @comment.author = nil + @anonymous_post.author = nil + @blog = Blog.new(id: 1, name: 'My Blog!!') + @post.blog = @blog + @anonymous_post.blog = nil - @serializer = CommentSerializer.new(@comment) - @adapter = ActiveModel::Serializer::Adapter::Json.new(@serializer) - ActionController::Base.cache_store.clear - end + @serializer = CommentSerializer.new(@comment) + @adapter = ActiveModelSerializers::Adapter::Json.new(@serializer) + ActionController::Base.cache_store.clear + end - def test_includes_post - assert_equal({ id: 42, title: 'New Post', body: 'Body' }, @adapter.serializable_hash[:comment][:post]) - end + def test_includes_post + assert_equal({ id: 42, title: 'New Post', body: 'Body' }, @adapter.serializable_hash[:comment][:post]) + end - def test_include_nil_author - serializer = PostSerializer.new(@anonymous_post) - adapter = ActiveModel::Serializer::Adapter::Json.new(serializer) + def test_include_nil_author + serializer = PostSerializer.new(@anonymous_post) + adapter = ActiveModelSerializers::Adapter::Json.new(serializer) - assert_equal({ post: { title: 'Hello!!', body: 'Hello, world!!', id: 43, comments: [], blog: { id: 999, name: 'Custom blog' }, author: nil } }, adapter.serializable_hash) - end + assert_equal({ post: { title: 'Hello!!', body: 'Hello, world!!', id: 43, comments: [], blog: { id: 999, name: 'Custom blog' }, author: nil } }, adapter.serializable_hash) + end - def test_include_nil_author_with_specified_serializer - serializer = PostPreviewSerializer.new(@anonymous_post) - adapter = ActiveModel::Serializer::Adapter::Json.new(serializer) + def test_include_nil_author_with_specified_serializer + serializer = PostPreviewSerializer.new(@anonymous_post) + adapter = ActiveModelSerializers::Adapter::Json.new(serializer) - assert_equal({ post: { title: 'Hello!!', body: 'Hello, world!!', id: 43, comments: [], author: nil } }, adapter.serializable_hash) - end + assert_equal({ post: { title: 'Hello!!', body: 'Hello, world!!', id: 43, comments: [], author: nil } }, adapter.serializable_hash) end end end diff --git a/test/adapter/json/collection_test.rb b/test/adapter/json/collection_test.rb index 6be3d505..2ff23336 100644 --- a/test/adapter/json/collection_test.rb +++ b/test/adapter/json/collection_test.rb @@ -1,90 +1,88 @@ require 'test_helper' -module ActiveModel - class Serializer - module Adapter - class Json - class Collection < ActiveSupport::TestCase - def setup - @author = Author.new(id: 1, name: 'Steve K.') - @first_post = Post.new(id: 1, title: 'Hello!!', body: 'Hello, world!!') - @second_post = Post.new(id: 2, title: 'New Post', body: 'Body') - @first_post.comments = [] - @second_post.comments = [] - @first_post.author = @author - @second_post.author = @author - @blog = Blog.new(id: 1, name: 'My Blog!!') - @first_post.blog = @blog - @second_post.blog = nil +module ActiveModelSerializers + module Adapter + class Json + class Collection < ActiveSupport::TestCase + def setup + @author = Author.new(id: 1, name: 'Steve K.') + @first_post = Post.new(id: 1, title: 'Hello!!', body: 'Hello, world!!') + @second_post = Post.new(id: 2, title: 'New Post', body: 'Body') + @first_post.comments = [] + @second_post.comments = [] + @first_post.author = @author + @second_post.author = @author + @blog = Blog.new(id: 1, name: 'My Blog!!') + @first_post.blog = @blog + @second_post.blog = nil - ActionController::Base.cache_store.clear - end + ActionController::Base.cache_store.clear + end - def test_with_serializer_option - @blog.special_attribute = 'Special' - @blog.articles = [@first_post, @second_post] - serializer = CollectionSerializer.new([@blog], serializer: CustomBlogSerializer) - adapter = ActiveModel::Serializer::Adapter::Json.new(serializer) + def test_with_serializer_option + @blog.special_attribute = 'Special' + @blog.articles = [@first_post, @second_post] + serializer = ActiveModel::Serializer::CollectionSerializer.new([@blog], serializer: CustomBlogSerializer) + adapter = ActiveModelSerializers::Adapter::Json.new(serializer) - expected = { blogs: [{ + expected = { blogs: [{ + id: 1, + special_attribute: 'Special', + articles: [{ id: 1, title: 'Hello!!', body: 'Hello, world!!' }, { id: 2, title: 'New Post', body: 'Body' }] + }] } + assert_equal expected, adapter.serializable_hash + end + + def test_include_multiple_posts + serializer = ActiveModel::Serializer::CollectionSerializer.new([@first_post, @second_post]) + adapter = ActiveModelSerializers::Adapter::Json.new(serializer) + + expected = { posts: [{ + title: 'Hello!!', + body: 'Hello, world!!', + id: 1, + comments: [], + author: { id: 1, - special_attribute: 'Special', - articles: [{ id: 1, title: 'Hello!!', body: 'Hello, world!!' }, { id: 2, title: 'New Post', body: 'Body' }] - }] } - assert_equal expected, adapter.serializable_hash - end - - def test_include_multiple_posts - serializer = CollectionSerializer.new([@first_post, @second_post]) - adapter = ActiveModel::Serializer::Adapter::Json.new(serializer) - - expected = { posts: [{ - title: 'Hello!!', - body: 'Hello, world!!', + name: 'Steve K.' + }, + blog: { + id: 999, + name: 'Custom blog' + } + }, { + title: 'New Post', + body: 'Body', + id: 2, + comments: [], + author: { id: 1, - comments: [], - author: { - id: 1, - name: 'Steve K.' - }, - blog: { - id: 999, - name: 'Custom blog' - } - }, { - title: 'New Post', - body: 'Body', - id: 2, - comments: [], - author: { - id: 1, - name: 'Steve K.' - }, - blog: { - id: 999, - name: 'Custom blog' - } - }] } - assert_equal expected, adapter.serializable_hash - end + name: 'Steve K.' + }, + blog: { + id: 999, + name: 'Custom blog' + } + }] } + assert_equal expected, adapter.serializable_hash + end - def test_root_is_underscored - virtual_value = VirtualValue.new(id: 1) - serializer = CollectionSerializer.new([virtual_value]) - adapter = ActiveModel::Serializer::Adapter::Json.new(serializer) + def test_root_is_underscored + virtual_value = VirtualValue.new(id: 1) + serializer = ActiveModel::Serializer::CollectionSerializer.new([virtual_value]) + adapter = ActiveModelSerializers::Adapter::Json.new(serializer) - assert_equal 1, adapter.serializable_hash[:virtual_values].length - end + assert_equal 1, adapter.serializable_hash[:virtual_values].length + end - def test_include_option - serializer = CollectionSerializer.new([@first_post, @second_post]) - adapter = ActiveModel::Serializer::Adapter::Json.new(serializer, include: '') - actual = adapter.serializable_hash - expected = { posts: [{ id: 1, title: 'Hello!!', body: 'Hello, world!!' }, - { id: 2, title: 'New Post', body: 'Body' }] } + def test_include_option + serializer = ActiveModel::Serializer::CollectionSerializer.new([@first_post, @second_post]) + adapter = ActiveModelSerializers::Adapter::Json.new(serializer, include: '') + actual = adapter.serializable_hash + expected = { posts: [{ id: 1, title: 'Hello!!', body: 'Hello, world!!' }, + { id: 2, title: 'New Post', body: 'Body' }] } - assert_equal(expected, actual) - end + assert_equal(expected, actual) end end end diff --git a/test/adapter/json/has_many_test.rb b/test/adapter/json/has_many_test.rb index 72f29e5c..3f6fa546 100644 --- a/test/adapter/json/has_many_test.rb +++ b/test/adapter/json/has_many_test.rb @@ -1,45 +1,43 @@ require 'test_helper' -module ActiveModel - class Serializer - module Adapter - class Json - class HasManyTestTest < ActiveSupport::TestCase - def setup - ActionController::Base.cache_store.clear - @author = Author.new(id: 1, name: 'Steve K.') - @post = Post.new(id: 42, title: 'New Post', body: 'Body') - @first_comment = Comment.new(id: 1, body: 'ZOMG A COMMENT') - @second_comment = Comment.new(id: 2, body: 'ZOMG ANOTHER COMMENT') - @post.comments = [@first_comment, @second_comment] - @post.author = @author - @first_comment.post = @post - @second_comment.post = @post - @blog = Blog.new(id: 1, name: 'My Blog!!') - @post.blog = @blog - @tag = Tag.new(id: 1, name: '#hash_tag') - @post.tags = [@tag] - end +module ActiveModelSerializers + module Adapter + class Json + class HasManyTestTest < ActiveSupport::TestCase + def setup + ActionController::Base.cache_store.clear + @author = Author.new(id: 1, name: 'Steve K.') + @post = Post.new(id: 42, title: 'New Post', body: 'Body') + @first_comment = Comment.new(id: 1, body: 'ZOMG A COMMENT') + @second_comment = Comment.new(id: 2, body: 'ZOMG ANOTHER COMMENT') + @post.comments = [@first_comment, @second_comment] + @post.author = @author + @first_comment.post = @post + @second_comment.post = @post + @blog = Blog.new(id: 1, name: 'My Blog!!') + @post.blog = @blog + @tag = Tag.new(id: 1, name: '#hash_tag') + @post.tags = [@tag] + end - def test_has_many - serializer = PostSerializer.new(@post) - adapter = ActiveModel::Serializer::Adapter::Json.new(serializer) - assert_equal([ - { id: 1, body: 'ZOMG A COMMENT' }, - { id: 2, body: 'ZOMG ANOTHER COMMENT' } - ], adapter.serializable_hash[:post][:comments]) - end + def test_has_many + serializer = PostSerializer.new(@post) + adapter = ActiveModelSerializers::Adapter::Json.new(serializer) + assert_equal([ + { id: 1, body: 'ZOMG A COMMENT' }, + { id: 2, body: 'ZOMG ANOTHER COMMENT' } + ], adapter.serializable_hash[:post][:comments]) + end - def test_has_many_with_no_serializer - serializer = PostWithTagsSerializer.new(@post) - adapter = ActiveModel::Serializer::Adapter::Json.new(serializer) - assert_equal({ - id: 42, - tags: [ - { 'id' => 1, 'name' => '#hash_tag' } - ] - }.to_json, adapter.serializable_hash[:post].to_json) - end + def test_has_many_with_no_serializer + serializer = PostWithTagsSerializer.new(@post) + adapter = ActiveModelSerializers::Adapter::Json.new(serializer) + assert_equal({ + id: 42, + tags: [ + { 'id' => 1, 'name' => '#hash_tag' } + ] + }.to_json, adapter.serializable_hash[:post].to_json) end end end diff --git a/test/adapter/json_api/belongs_to_test.rb b/test/adapter/json_api/belongs_to_test.rb index ba7253e5..c501b4d8 100644 --- a/test/adapter/json_api/belongs_to_test.rb +++ b/test/adapter/json_api/belongs_to_test.rb @@ -1,155 +1,153 @@ require 'test_helper' -module ActiveModel - class Serializer - module Adapter - class JsonApi - class BelongsToTest < ActiveSupport::TestCase - def setup - @author = Author.new(id: 1, name: 'Steve K.') - @author.bio = nil - @author.roles = [] - @blog = Blog.new(id: 23, name: 'AMS Blog') - @post = Post.new(id: 42, title: 'New Post', body: 'Body') - @anonymous_post = Post.new(id: 43, title: 'Hello!!', body: 'Hello, world!!') - @comment = Comment.new(id: 1, body: 'ZOMG A COMMENT') - @post.comments = [@comment] - @post.blog = @blog - @anonymous_post.comments = [] - @anonymous_post.blog = nil - @comment.post = @post - @comment.author = nil - @post.author = @author - @anonymous_post.author = nil - @blog = Blog.new(id: 1, name: 'My Blog!!') - @blog.writer = @author - @blog.articles = [@post, @anonymous_post] - @author.posts = [] +module ActiveModelSerializers + module Adapter + class JsonApi + class BelongsToTest < ActiveSupport::TestCase + def setup + @author = Author.new(id: 1, name: 'Steve K.') + @author.bio = nil + @author.roles = [] + @blog = Blog.new(id: 23, name: 'AMS Blog') + @post = Post.new(id: 42, title: 'New Post', body: 'Body') + @anonymous_post = Post.new(id: 43, title: 'Hello!!', body: 'Hello, world!!') + @comment = Comment.new(id: 1, body: 'ZOMG A COMMENT') + @post.comments = [@comment] + @post.blog = @blog + @anonymous_post.comments = [] + @anonymous_post.blog = nil + @comment.post = @post + @comment.author = nil + @post.author = @author + @anonymous_post.author = nil + @blog = Blog.new(id: 1, name: 'My Blog!!') + @blog.writer = @author + @blog.articles = [@post, @anonymous_post] + @author.posts = [] - @serializer = CommentSerializer.new(@comment) - @adapter = ActiveModel::Serializer::Adapter::JsonApi.new(@serializer) - ActionController::Base.cache_store.clear - end + @serializer = CommentSerializer.new(@comment) + @adapter = ActiveModelSerializers::Adapter::JsonApi.new(@serializer) + ActionController::Base.cache_store.clear + end - def test_includes_post_id - expected = { data: { type: 'posts', id: '42' } } + def test_includes_post_id + expected = { data: { type: 'posts', id: '42' } } - assert_equal(expected, @adapter.serializable_hash[:data][:relationships][:post]) - end + assert_equal(expected, @adapter.serializable_hash[:data][:relationships][:post]) + end - def test_includes_linked_post - @adapter = ActiveModel::Serializer::Adapter::JsonApi.new(@serializer, include: [:post]) - expected = [{ + def test_includes_linked_post + @adapter = ActiveModelSerializers::Adapter::JsonApi.new(@serializer, include: [:post]) + expected = [{ + id: '42', + type: 'posts', + attributes: { + title: 'New Post', + body: 'Body', + }, + relationships: { + comments: { data: [{ type: 'comments', id: '1' }] }, + blog: { data: { type: 'blogs', id: '999' } }, + author: { data: { type: 'authors', id: '1' } } + } + }] + assert_equal expected, @adapter.serializable_hash[:included] + end + + def test_limiting_linked_post_fields + @adapter = ActiveModelSerializers::Adapter::JsonApi.new(@serializer, include: [:post], fields: { post: [:title, :comments, :blog, :author] }) + expected = [{ + id: '42', + type: 'posts', + attributes: { + title: 'New Post' + }, + relationships: { + comments: { data: [{ type: 'comments', id: '1' }] }, + blog: { data: { type: 'blogs', id: '999' } }, + author: { data: { type: 'authors', id: '1' } } + } + }] + assert_equal expected, @adapter.serializable_hash[:included] + end + + def test_include_nil_author + serializer = PostSerializer.new(@anonymous_post) + adapter = ActiveModelSerializers::Adapter::JsonApi.new(serializer) + + assert_equal({ comments: { data: [] }, blog: { data: { type: 'blogs', id: '999' } }, author: { data: nil } }, adapter.serializable_hash[:data][:relationships]) + end + + def test_include_type_for_association_when_different_than_name + serializer = BlogSerializer.new(@blog) + adapter = ActiveModelSerializers::Adapter::JsonApi.new(serializer) + relationships = adapter.serializable_hash[:data][:relationships] + expected = { + writer: { + data: { + type: 'authors', + id: '1' + } + }, + articles: { + data: [ + { + type: 'posts', + id: '42' + }, + { + type: 'posts', + id: '43' + } + ] + } + } + assert_equal expected, relationships + end + + def test_include_linked_resources_with_type_name + serializer = BlogSerializer.new(@blog) + adapter = ActiveModelSerializers::Adapter::JsonApi.new(serializer, include: [:writer, :articles]) + linked = adapter.serializable_hash[:included] + expected = [ + { + id: '1', + type: 'authors', + attributes: { + name: 'Steve K.' + }, + relationships: { + posts: { data: [] }, + roles: { data: [] }, + bio: { data: nil } + } + }, { id: '42', type: 'posts', attributes: { title: 'New Post', - body: 'Body', + body: 'Body' }, relationships: { comments: { data: [{ type: 'comments', id: '1' }] }, blog: { data: { type: 'blogs', id: '999' } }, author: { data: { type: 'authors', id: '1' } } } - }] - assert_equal expected, @adapter.serializable_hash[:included] - end - - def test_limiting_linked_post_fields - @adapter = ActiveModel::Serializer::Adapter::JsonApi.new(@serializer, include: [:post], fields: { post: [:title, :comments, :blog, :author] }) - expected = [{ - id: '42', + }, { + id: '43', type: 'posts', attributes: { - title: 'New Post' + title: 'Hello!!', + body: 'Hello, world!!' }, relationships: { - comments: { data: [{ type: 'comments', id: '1' }] }, + comments: { data: [] }, blog: { data: { type: 'blogs', id: '999' } }, - author: { data: { type: 'authors', id: '1' } } - } - }] - assert_equal expected, @adapter.serializable_hash[:included] - end - - def test_include_nil_author - serializer = PostSerializer.new(@anonymous_post) - adapter = ActiveModel::Serializer::Adapter::JsonApi.new(serializer) - - assert_equal({ comments: { data: [] }, blog: { data: { type: 'blogs', id: '999' } }, author: { data: nil } }, adapter.serializable_hash[:data][:relationships]) - end - - def test_include_type_for_association_when_different_than_name - serializer = BlogSerializer.new(@blog) - adapter = ActiveModel::Serializer::Adapter::JsonApi.new(serializer) - relationships = adapter.serializable_hash[:data][:relationships] - expected = { - writer: { - data: { - type: 'authors', - id: '1' - } - }, - articles: { - data: [ - { - type: 'posts', - id: '42' - }, - { - type: 'posts', - id: '43' - } - ] + author: { data: nil } } } - assert_equal expected, relationships - end - - def test_include_linked_resources_with_type_name - serializer = BlogSerializer.new(@blog) - adapter = ActiveModel::Serializer::Adapter::JsonApi.new(serializer, include: [:writer, :articles]) - linked = adapter.serializable_hash[:included] - expected = [ - { - id: '1', - type: 'authors', - attributes: { - name: 'Steve K.' - }, - relationships: { - posts: { data: [] }, - roles: { data: [] }, - bio: { data: nil } - } - }, { - id: '42', - type: 'posts', - attributes: { - title: 'New Post', - body: 'Body' - }, - relationships: { - comments: { data: [{ type: 'comments', id: '1' }] }, - blog: { data: { type: 'blogs', id: '999' } }, - author: { data: { type: 'authors', id: '1' } } - } - }, { - id: '43', - type: 'posts', - attributes: { - title: 'Hello!!', - body: 'Hello, world!!' - }, - relationships: { - comments: { data: [] }, - blog: { data: { type: 'blogs', id: '999' } }, - author: { data: nil } - } - } - ] - assert_equal expected, linked - end + ] + assert_equal expected, linked end end end diff --git a/test/adapter/json_api/collection_test.rb b/test/adapter/json_api/collection_test.rb index 05d74bd1..b534108a 100644 --- a/test/adapter/json_api/collection_test.rb +++ b/test/adapter/json_api/collection_test.rb @@ -1,95 +1,93 @@ require 'test_helper' -module ActiveModel - class Serializer - module Adapter - class JsonApi - class CollectionTest < ActiveSupport::TestCase - def setup - @author = Author.new(id: 1, name: 'Steve K.') - @author.bio = nil - @blog = Blog.new(id: 23, name: 'AMS Blog') - @first_post = Post.new(id: 1, title: 'Hello!!', body: 'Hello, world!!') - @second_post = Post.new(id: 2, title: 'New Post', body: 'Body') - @first_post.comments = [] - @second_post.comments = [] - @first_post.blog = @blog - @second_post.blog = nil - @first_post.author = @author - @second_post.author = @author - @author.posts = [@first_post, @second_post] +module ActiveModelSerializers + module Adapter + class JsonApi + class CollectionTest < ActiveSupport::TestCase + def setup + @author = Author.new(id: 1, name: 'Steve K.') + @author.bio = nil + @blog = Blog.new(id: 23, name: 'AMS Blog') + @first_post = Post.new(id: 1, title: 'Hello!!', body: 'Hello, world!!') + @second_post = Post.new(id: 2, title: 'New Post', body: 'Body') + @first_post.comments = [] + @second_post.comments = [] + @first_post.blog = @blog + @second_post.blog = nil + @first_post.author = @author + @second_post.author = @author + @author.posts = [@first_post, @second_post] - @serializer = CollectionSerializer.new([@first_post, @second_post]) - @adapter = ActiveModel::Serializer::Adapter::JsonApi.new(@serializer) - ActionController::Base.cache_store.clear - end + @serializer = ActiveModel::Serializer::CollectionSerializer.new([@first_post, @second_post]) + @adapter = ActiveModelSerializers::Adapter::JsonApi.new(@serializer) + ActionController::Base.cache_store.clear + end - def test_include_multiple_posts - expected = [ - { - id: '1', - type: 'posts', - attributes: { - title: 'Hello!!', - body: 'Hello, world!!' - }, - relationships: { - comments: { data: [] }, - blog: { data: { type: 'blogs', id: '999' } }, - author: { data: { type: 'authors', id: '1' } } - } + def test_include_multiple_posts + expected = [ + { + id: '1', + type: 'posts', + attributes: { + title: 'Hello!!', + body: 'Hello, world!!' }, - { - id: '2', - type: 'posts', - attributes: { - title: 'New Post', - body: 'Body' - }, - relationships: { - comments: { data: [] }, - blog: { data: { type: 'blogs', id: '999' } }, - author: { data: { type: 'authors', id: '1' } } - } + relationships: { + comments: { data: [] }, + blog: { data: { type: 'blogs', id: '999' } }, + author: { data: { type: 'authors', id: '1' } } } - ] - - assert_equal(expected, @adapter.serializable_hash[:data]) - end - - def test_limiting_fields - actual = ActiveModel::SerializableResource.new( - [@first_post, @second_post], adapter: :json_api, - fields: { posts: %w(title comments blog author) }) - .serializable_hash - expected = [ - { - id: '1', - type: 'posts', - attributes: { - title: 'Hello!!' - }, - relationships: { - comments: { data: [] }, - blog: { data: { type: 'blogs', id: '999' } }, - author: { data: { type: 'authors', id: '1' } } - } + }, + { + id: '2', + type: 'posts', + attributes: { + title: 'New Post', + body: 'Body' }, - { - id: '2', - type: 'posts', - attributes: { - title: 'New Post' - }, - relationships: { - comments: { data: [] }, - blog: { data: { type: 'blogs', id: '999' } }, - author: { data: { type: 'authors', id: '1' } } - } + relationships: { + comments: { data: [] }, + blog: { data: { type: 'blogs', id: '999' } }, + author: { data: { type: 'authors', id: '1' } } } - ] - assert_equal(expected, actual[:data]) - end + } + ] + + assert_equal(expected, @adapter.serializable_hash[:data]) + end + + def test_limiting_fields + actual = ActiveModel::SerializableResource.new( + [@first_post, @second_post], adapter: :json_api, + fields: { posts: %w(title comments blog author) }) + .serializable_hash + expected = [ + { + id: '1', + type: 'posts', + attributes: { + title: 'Hello!!' + }, + relationships: { + comments: { data: [] }, + blog: { data: { type: 'blogs', id: '999' } }, + author: { data: { type: 'authors', id: '1' } } + } + }, + { + id: '2', + type: 'posts', + attributes: { + title: 'New Post' + }, + relationships: { + comments: { data: [] }, + blog: { data: { type: 'blogs', id: '999' } }, + author: { data: { type: 'authors', id: '1' } } + } + } + ] + assert_equal(expected, actual[:data]) end end end diff --git a/test/adapter/json_api/fields_test.rb b/test/adapter/json_api/fields_test.rb index b92ab590..ad356a53 100644 --- a/test/adapter/json_api/fields_test.rb +++ b/test/adapter/json_api/fields_test.rb @@ -1,87 +1,85 @@ require 'test_helper' -module ActiveModel - class Serializer - module Adapter - class JsonApi - class FieldsTest < ActiveSupport::TestCase - Post = Class.new(::Model) - class PostSerializer < ActiveModel::Serializer - type 'posts' - attributes :title, :body - belongs_to :author - has_many :comments - end +module ActiveModelSerializers + module Adapter + class JsonApi + class FieldsTest < ActiveSupport::TestCase + Post = Class.new(::Model) + class PostSerializer < ActiveModel::Serializer + type 'posts' + attributes :title, :body + belongs_to :author + has_many :comments + end - Author = Class.new(::Model) - class AuthorSerializer < ActiveModel::Serializer - type 'authors' - attributes :name, :birthday - end + Author = Class.new(::Model) + class AuthorSerializer < ActiveModel::Serializer + type 'authors' + attributes :name, :birthday + end - Comment = Class.new(::Model) - class CommentSerializer < ActiveModel::Serializer - type 'comments' - attributes :body - belongs_to :author - end + Comment = Class.new(::Model) + class CommentSerializer < ActiveModel::Serializer + type 'comments' + attributes :body + belongs_to :author + end - def setup - @author = Author.new(id: 1, name: 'Lucas', birthday: '10.01.1990') - @comment1 = Comment.new(id: 7, body: 'cool', author: @author) - @comment2 = Comment.new(id: 12, body: 'awesome', author: @author) - @post = Post.new(id: 1337, title: 'Title 1', body: 'Body 1', - author: @author, comments: [@comment1, @comment2]) - @comment1.post = @post - @comment2.post = @post - end + def setup + @author = Author.new(id: 1, name: 'Lucas', birthday: '10.01.1990') + @comment1 = Comment.new(id: 7, body: 'cool', author: @author) + @comment2 = Comment.new(id: 12, body: 'awesome', author: @author) + @post = Post.new(id: 1337, title: 'Title 1', body: 'Body 1', + author: @author, comments: [@comment1, @comment2]) + @comment1.post = @post + @comment2.post = @post + end - def test_fields_attributes - fields = { posts: [:title] } - hash = serializable(@post, adapter: :json_api, fields: fields).serializable_hash - expected = { - title: 'Title 1' - } + def test_fields_attributes + fields = { posts: [:title] } + hash = serializable(@post, adapter: :json_api, fields: fields).serializable_hash + expected = { + title: 'Title 1' + } - assert_equal(expected, hash[:data][:attributes]) - end + assert_equal(expected, hash[:data][:attributes]) + end - def test_fields_relationships - fields = { posts: [:author] } - hash = serializable(@post, adapter: :json_api, fields: fields).serializable_hash - expected = { - author: { - data: { - type: 'authors', - id: '1' - } + def test_fields_relationships + fields = { posts: [:author] } + hash = serializable(@post, adapter: :json_api, fields: fields).serializable_hash + expected = { + author: { + data: { + type: 'authors', + id: '1' } } + } - assert_equal(expected, hash[:data][:relationships]) - end + assert_equal(expected, hash[:data][:relationships]) + end - def test_fields_included - fields = { posts: [:author], comments: [:body] } - hash = serializable(@post, adapter: :json_api, fields: fields, include: 'comments').serializable_hash - expected = [ - { - type: 'comments', - id: '7', - attributes: { - body: 'cool' - } - }, { - type: 'comments', - id: '12', - attributes: { - body: 'awesome' - } + def test_fields_included + fields = { posts: [:author], comments: [:body] } + hash = serializable(@post, adapter: :json_api, fields: fields, include: 'comments').serializable_hash + expected = [ + { + type: 'comments', + id: '7', + attributes: { + body: 'cool' } - ] + }, { + type: 'comments', + id: '12', + attributes: { + body: 'awesome' + } + } + ] - assert_equal(expected, hash[:included]) - end + assert_equal(expected, hash[:included]) end end end diff --git a/test/adapter/json_api/has_many_embed_ids_test.rb b/test/adapter/json_api/has_many_embed_ids_test.rb index b80448f7..e016de28 100644 --- a/test/adapter/json_api/has_many_embed_ids_test.rb +++ b/test/adapter/json_api/has_many_embed_ids_test.rb @@ -1,43 +1,41 @@ require 'test_helper' -module ActiveModel - class Serializer - module Adapter - class JsonApi - class HasManyEmbedIdsTest < ActiveSupport::TestCase - def setup - @author = Author.new(name: 'Steve K.') - @author.bio = nil - @author.roles = nil - @first_post = Post.new(id: 1, title: 'Hello!!', body: 'Hello, world!!') - @second_post = Post.new(id: 2, title: 'New Post', body: 'Body') - @author.posts = [@first_post, @second_post] - @first_post.author = @author - @second_post.author = @author - @first_post.comments = [] - @second_post.comments = [] - @blog = Blog.new(id: 23, name: 'AMS Blog') - @first_post.blog = @blog - @second_post.blog = nil +module ActiveModelSerializers + module Adapter + class JsonApi + class HasManyEmbedIdsTest < ActiveSupport::TestCase + def setup + @author = Author.new(name: 'Steve K.') + @author.bio = nil + @author.roles = nil + @first_post = Post.new(id: 1, title: 'Hello!!', body: 'Hello, world!!') + @second_post = Post.new(id: 2, title: 'New Post', body: 'Body') + @author.posts = [@first_post, @second_post] + @first_post.author = @author + @second_post.author = @author + @first_post.comments = [] + @second_post.comments = [] + @blog = Blog.new(id: 23, name: 'AMS Blog') + @first_post.blog = @blog + @second_post.blog = nil - @serializer = AuthorSerializer.new(@author) - @adapter = ActiveModel::Serializer::Adapter::JsonApi.new(@serializer) - end + @serializer = AuthorSerializer.new(@author) + @adapter = ActiveModelSerializers::Adapter::JsonApi.new(@serializer) + end - def test_includes_comment_ids - expected = { - data: [ - { type: 'posts', id: '1' }, - { type: 'posts', id: '2' } - ] - } + def test_includes_comment_ids + expected = { + data: [ + { type: 'posts', id: '1' }, + { type: 'posts', id: '2' } + ] + } - assert_equal(expected, @adapter.serializable_hash[:data][:relationships][:posts]) - end + assert_equal(expected, @adapter.serializable_hash[:data][:relationships][:posts]) + end - def test_no_includes_linked_comments - assert_nil @adapter.serializable_hash[:linked] - end + def test_no_includes_linked_comments + assert_nil @adapter.serializable_hash[:linked] end end end diff --git a/test/adapter/json_api/has_many_explicit_serializer_test.rb b/test/adapter/json_api/has_many_explicit_serializer_test.rb index 2d2a885a..f598bc9b 100644 --- a/test/adapter/json_api/has_many_explicit_serializer_test.rb +++ b/test/adapter/json_api/has_many_explicit_serializer_test.rb @@ -1,96 +1,94 @@ require 'test_helper' -module ActiveModel - class Serializer - module Adapter - class JsonApi - # Test 'has_many :assocs, serializer: AssocXSerializer' - class HasManyExplicitSerializerTest < ActiveSupport::TestCase - def setup - @post = Post.new(title: 'New Post', body: 'Body') - @author = Author.new(name: 'Jane Blogger') - @author.posts = [@post] - @post.author = @author - @first_comment = Comment.new(id: 1, body: 'ZOMG A COMMENT') - @second_comment = Comment.new(id: 2, body: 'ZOMG ANOTHER COMMENT') - @post.comments = [@first_comment, @second_comment] - @first_comment.post = @post - @first_comment.author = nil - @second_comment.post = @post - @second_comment.author = nil - @blog = Blog.new(id: 23, name: 'AMS Blog') - @post.blog = @blog +module ActiveModelSerializers + module Adapter + class JsonApi + # Test 'has_many :assocs, serializer: AssocXSerializer' + class HasManyExplicitSerializerTest < ActiveSupport::TestCase + def setup + @post = Post.new(title: 'New Post', body: 'Body') + @author = Author.new(name: 'Jane Blogger') + @author.posts = [@post] + @post.author = @author + @first_comment = Comment.new(id: 1, body: 'ZOMG A COMMENT') + @second_comment = Comment.new(id: 2, body: 'ZOMG ANOTHER COMMENT') + @post.comments = [@first_comment, @second_comment] + @first_comment.post = @post + @first_comment.author = nil + @second_comment.post = @post + @second_comment.author = nil + @blog = Blog.new(id: 23, name: 'AMS Blog') + @post.blog = @blog - @serializer = PostPreviewSerializer.new(@post) - @adapter = ActiveModel::Serializer::Adapter::JsonApi.new( - @serializer, - include: [:comments, :author] - ) - end + @serializer = PostPreviewSerializer.new(@post) + @adapter = ActiveModelSerializers::Adapter::JsonApi.new( + @serializer, + include: [:comments, :author] + ) + end - def test_includes_comment_ids - expected = { - data: [ - { type: 'comments', id: '1' }, - { type: 'comments', id: '2' } - ] - } - - assert_equal(expected, @adapter.serializable_hash[:data][:relationships][:comments]) - end - - def test_includes_linked_data - # If CommentPreviewSerializer is applied correctly the body text will not be present in the output - expected = [ - { - id: '1', - type: 'comments', - relationships: { - post: { data: { type: 'posts', id: @post.id.to_s } } - } - }, - { - id: '2', - type: 'comments', - relationships: { - post: { data: { type: 'posts', id: @post.id.to_s } } - } - }, - { - id: @author.id.to_s, - type: 'authors', - relationships: { - posts: { data: [{ type: 'posts', id: @post.id.to_s }] } - } - } + def test_includes_comment_ids + expected = { + data: [ + { type: 'comments', id: '1' }, + { type: 'comments', id: '2' } ] + } - assert_equal(expected, @adapter.serializable_hash[:included]) - end + assert_equal(expected, @adapter.serializable_hash[:data][:relationships][:comments]) + end - def test_includes_author_id - expected = { - data: { type: 'authors', id: @author.id.to_s } + def test_includes_linked_data + # If CommentPreviewSerializer is applied correctly the body text will not be present in the output + expected = [ + { + id: '1', + type: 'comments', + relationships: { + post: { data: { type: 'posts', id: @post.id.to_s } } + } + }, + { + id: '2', + type: 'comments', + relationships: { + post: { data: { type: 'posts', id: @post.id.to_s } } + } + }, + { + id: @author.id.to_s, + type: 'authors', + relationships: { + posts: { data: [{ type: 'posts', id: @post.id.to_s }] } + } } + ] - assert_equal(expected, @adapter.serializable_hash[:data][:relationships][:author]) - end + assert_equal(expected, @adapter.serializable_hash[:included]) + end - def test_explicit_serializer_with_null_resource - @post.author = nil + def test_includes_author_id + expected = { + data: { type: 'authors', id: @author.id.to_s } + } - expected = { data: nil } + assert_equal(expected, @adapter.serializable_hash[:data][:relationships][:author]) + end - assert_equal(expected, @adapter.serializable_hash[:data][:relationships][:author]) - end + def test_explicit_serializer_with_null_resource + @post.author = nil - def test_explicit_serializer_with_null_collection - @post.comments = [] + expected = { data: nil } - expected = { data: [] } + assert_equal(expected, @adapter.serializable_hash[:data][:relationships][:author]) + end - assert_equal(expected, @adapter.serializable_hash[:data][:relationships][:comments]) - end + def test_explicit_serializer_with_null_collection + @post.comments = [] + + expected = { data: [] } + + assert_equal(expected, @adapter.serializable_hash[:data][:relationships][:comments]) end end end diff --git a/test/adapter/json_api/has_many_test.rb b/test/adapter/json_api/has_many_test.rb index a5753c6e..d590b8df 100644 --- a/test/adapter/json_api/has_many_test.rb +++ b/test/adapter/json_api/has_many_test.rb @@ -1,143 +1,141 @@ require 'test_helper' -module ActiveModel - class Serializer - module Adapter - class JsonApi - class HasManyTest < ActiveSupport::TestCase - def setup - ActionController::Base.cache_store.clear - @author = Author.new(id: 1, name: 'Steve K.') - @author.posts = [] - @author.bio = nil - @post = Post.new(id: 1, title: 'New Post', body: 'Body') - @post_without_comments = Post.new(id: 2, title: 'Second Post', body: 'Second') - @first_comment = Comment.new(id: 1, body: 'ZOMG A COMMENT') - @first_comment.author = nil - @second_comment = Comment.new(id: 2, body: 'ZOMG ANOTHER COMMENT') - @second_comment.author = nil - @post.comments = [@first_comment, @second_comment] - @post_without_comments.comments = [] - @first_comment.post = @post - @second_comment.post = @post - @post.author = @author - @post_without_comments.author = nil - @blog = Blog.new(id: 1, name: 'My Blog!!') - @blog.writer = @author - @blog.articles = [@post] - @post.blog = @blog - @post_without_comments.blog = nil - @tag = Tag.new(id: 1, name: '#hash_tag') - @post.tags = [@tag] - @serializer = PostSerializer.new(@post) - @adapter = ActiveModel::Serializer::Adapter::JsonApi.new(@serializer) +module ActiveModelSerializers + module Adapter + class JsonApi + class HasManyTest < ActiveSupport::TestCase + def setup + ActionController::Base.cache_store.clear + @author = Author.new(id: 1, name: 'Steve K.') + @author.posts = [] + @author.bio = nil + @post = Post.new(id: 1, title: 'New Post', body: 'Body') + @post_without_comments = Post.new(id: 2, title: 'Second Post', body: 'Second') + @first_comment = Comment.new(id: 1, body: 'ZOMG A COMMENT') + @first_comment.author = nil + @second_comment = Comment.new(id: 2, body: 'ZOMG ANOTHER COMMENT') + @second_comment.author = nil + @post.comments = [@first_comment, @second_comment] + @post_without_comments.comments = [] + @first_comment.post = @post + @second_comment.post = @post + @post.author = @author + @post_without_comments.author = nil + @blog = Blog.new(id: 1, name: 'My Blog!!') + @blog.writer = @author + @blog.articles = [@post] + @post.blog = @blog + @post_without_comments.blog = nil + @tag = Tag.new(id: 1, name: '#hash_tag') + @post.tags = [@tag] + @serializer = PostSerializer.new(@post) + @adapter = ActiveModelSerializers::Adapter::JsonApi.new(@serializer) - @virtual_value = VirtualValue.new(id: 1) - end + @virtual_value = VirtualValue.new(id: 1) + end - def test_includes_comment_ids - expected = { data: [{ type: 'comments', id: '1' }, { type: 'comments', id: '2' }] } + def test_includes_comment_ids + expected = { data: [{ type: 'comments', id: '1' }, { type: 'comments', id: '2' }] } - assert_equal(expected, @adapter.serializable_hash[:data][:relationships][:comments]) - end + assert_equal(expected, @adapter.serializable_hash[:data][:relationships][:comments]) + end - def test_includes_linked_comments - @adapter = ActiveModel::Serializer::Adapter::JsonApi.new(@serializer, include: [:comments]) - expected = [{ - id: '1', - type: 'comments', - attributes: { - body: 'ZOMG A COMMENT' - }, - relationships: { - post: { data: { type: 'posts', id: '1' } }, - author: { data: nil } - } - }, { - id: '2', - type: 'comments', - attributes: { - body: 'ZOMG ANOTHER COMMENT' - }, - relationships: { - post: { data: { type: 'posts', id: '1' } }, - author: { data: nil } - } - }] - assert_equal expected, @adapter.serializable_hash[:included] - end - - def test_limit_fields_of_linked_comments - @adapter = ActiveModel::Serializer::Adapter::JsonApi.new(@serializer, include: [:comments], fields: { comment: [:id, :post, :author] }) - expected = [{ - id: '1', - type: 'comments', - relationships: { - post: { data: { type: 'posts', id: '1' } }, - author: { data: nil } - } - }, { - id: '2', - type: 'comments', - relationships: { - post: { data: { type: 'posts', id: '1' } }, - author: { data: nil } - } - }] - assert_equal expected, @adapter.serializable_hash[:included] - end - - def test_no_include_linked_if_comments_is_empty - serializer = PostSerializer.new(@post_without_comments) - adapter = ActiveModel::Serializer::Adapter::JsonApi.new(serializer) - - assert_nil adapter.serializable_hash[:linked] - end - - def test_include_type_for_association_when_different_than_name - serializer = BlogSerializer.new(@blog) - adapter = ActiveModel::Serializer::Adapter::JsonApi.new(serializer) - actual = adapter.serializable_hash[:data][:relationships][:articles] - - expected = { - data: [{ - type: 'posts', - id: '1' - }] + def test_includes_linked_comments + @adapter = ActiveModelSerializers::Adapter::JsonApi.new(@serializer, include: [:comments]) + expected = [{ + id: '1', + type: 'comments', + attributes: { + body: 'ZOMG A COMMENT' + }, + relationships: { + post: { data: { type: 'posts', id: '1' } }, + author: { data: nil } } - assert_equal expected, actual - end + }, { + id: '2', + type: 'comments', + attributes: { + body: 'ZOMG ANOTHER COMMENT' + }, + relationships: { + post: { data: { type: 'posts', id: '1' } }, + author: { data: nil } + } + }] + assert_equal expected, @adapter.serializable_hash[:included] + end - def test_has_many_with_no_serializer - serializer = PostWithTagsSerializer.new(@post) - adapter = ActiveModel::Serializer::Adapter::JsonApi.new(serializer) + def test_limit_fields_of_linked_comments + @adapter = ActiveModelSerializers::Adapter::JsonApi.new(@serializer, include: [:comments], fields: { comment: [:id, :post, :author] }) + expected = [{ + id: '1', + type: 'comments', + relationships: { + post: { data: { type: 'posts', id: '1' } }, + author: { data: nil } + } + }, { + id: '2', + type: 'comments', + relationships: { + post: { data: { type: 'posts', id: '1' } }, + author: { data: nil } + } + }] + assert_equal expected, @adapter.serializable_hash[:included] + end - assert_equal({ - data: { - id: '1', - type: 'posts', - relationships: { - tags: { data: [@tag.as_json] } - } + def test_no_include_linked_if_comments_is_empty + serializer = PostSerializer.new(@post_without_comments) + adapter = ActiveModelSerializers::Adapter::JsonApi.new(serializer) + + assert_nil adapter.serializable_hash[:linked] + end + + def test_include_type_for_association_when_different_than_name + serializer = BlogSerializer.new(@blog) + adapter = ActiveModelSerializers::Adapter::JsonApi.new(serializer) + actual = adapter.serializable_hash[:data][:relationships][:articles] + + expected = { + data: [{ + type: 'posts', + id: '1' + }] + } + assert_equal expected, actual + end + + def test_has_many_with_no_serializer + serializer = PostWithTagsSerializer.new(@post) + adapter = ActiveModelSerializers::Adapter::JsonApi.new(serializer) + + assert_equal({ + data: { + id: '1', + type: 'posts', + relationships: { + tags: { data: [@tag.as_json] } } - }, adapter.serializable_hash) - end + } + }, adapter.serializable_hash) + end - def test_has_many_with_virtual_value - serializer = VirtualValueSerializer.new(@virtual_value) - adapter = ActiveModel::Serializer::Adapter::JsonApi.new(serializer) + def test_has_many_with_virtual_value + serializer = VirtualValueSerializer.new(@virtual_value) + adapter = ActiveModelSerializers::Adapter::JsonApi.new(serializer) - assert_equal({ - data: { - id: '1', - type: 'virtual_values', - relationships: { - maker: { data: { id: 1 } }, - reviews: { data: [{ id: 1 }, { id: 2 }] } - } + assert_equal({ + data: { + id: '1', + type: 'virtual_values', + relationships: { + maker: { data: { id: 1 } }, + reviews: { data: [{ id: 1 }, { id: 2 }] } } - }, adapter.serializable_hash) - end + } + }, adapter.serializable_hash) end end end diff --git a/test/adapter/json_api/has_one_test.rb b/test/adapter/json_api/has_one_test.rb index 7bc25ff6..b346dcd0 100644 --- a/test/adapter/json_api/has_one_test.rb +++ b/test/adapter/json_api/has_one_test.rb @@ -1,79 +1,77 @@ require 'test_helper' -module ActiveModel - class Serializer - module Adapter - class JsonApi - class HasOneTest < ActiveSupport::TestCase - def setup - @author = Author.new(id: 1, name: 'Steve K.') - @bio = Bio.new(id: 43, content: 'AMS Contributor') - @author.bio = @bio - @bio.author = @author - @post = Post.new(id: 42, title: 'New Post', body: 'Body') - @anonymous_post = Post.new(id: 43, title: 'Hello!!', body: 'Hello, world!!') - @comment = Comment.new(id: 1, body: 'ZOMG A COMMENT') - @post.comments = [@comment] - @anonymous_post.comments = [] - @comment.post = @post - @comment.author = nil - @post.author = @author - @anonymous_post.author = nil - @blog = Blog.new(id: 1, name: 'My Blog!!') - @blog.writer = @author - @blog.articles = [@post, @anonymous_post] - @author.posts = [] - @author.roles = [] +module ActiveModelSerializers + module Adapter + class JsonApi + class HasOneTest < ActiveSupport::TestCase + def setup + @author = Author.new(id: 1, name: 'Steve K.') + @bio = Bio.new(id: 43, content: 'AMS Contributor') + @author.bio = @bio + @bio.author = @author + @post = Post.new(id: 42, title: 'New Post', body: 'Body') + @anonymous_post = Post.new(id: 43, title: 'Hello!!', body: 'Hello, world!!') + @comment = Comment.new(id: 1, body: 'ZOMG A COMMENT') + @post.comments = [@comment] + @anonymous_post.comments = [] + @comment.post = @post + @comment.author = nil + @post.author = @author + @anonymous_post.author = nil + @blog = Blog.new(id: 1, name: 'My Blog!!') + @blog.writer = @author + @blog.articles = [@post, @anonymous_post] + @author.posts = [] + @author.roles = [] - @virtual_value = VirtualValue.new(id: 1) + @virtual_value = VirtualValue.new(id: 1) - @serializer = AuthorSerializer.new(@author) - @adapter = ActiveModel::Serializer::Adapter::JsonApi.new(@serializer, include: [:bio, :posts]) - end + @serializer = AuthorSerializer.new(@author) + @adapter = ActiveModelSerializers::Adapter::JsonApi.new(@serializer, include: [:bio, :posts]) + end - def test_includes_bio_id - expected = { data: { type: 'bios', id: '43' } } + def test_includes_bio_id + expected = { data: { type: 'bios', id: '43' } } - assert_equal(expected, @adapter.serializable_hash[:data][:relationships][:bio]) - end + assert_equal(expected, @adapter.serializable_hash[:data][:relationships][:bio]) + end - def test_includes_linked_bio - @adapter = ActiveModel::Serializer::Adapter::JsonApi.new(@serializer, include: [:bio]) + def test_includes_linked_bio + @adapter = ActiveModelSerializers::Adapter::JsonApi.new(@serializer, include: [:bio]) - expected = [ - { - id: '43', - type: 'bios', - attributes: { - content: 'AMS Contributor', - rating: nil - }, - relationships: { - author: { data: { type: 'authors', id: '1' } } - } - } - ] - - assert_equal(expected, @adapter.serializable_hash[:included]) - end - - def test_has_one_with_virtual_value - serializer = VirtualValueSerializer.new(@virtual_value) - adapter = ActiveModel::Serializer::Adapter::JsonApi.new(serializer) - - expected = { - data: { - id: '1', - type: 'virtual_values', - relationships: { - maker: { data: { id: 1 } }, - reviews: { data: [{ id: 1 }, { id: 2 }] } - } + expected = [ + { + id: '43', + type: 'bios', + attributes: { + content: 'AMS Contributor', + rating: nil + }, + relationships: { + author: { data: { type: 'authors', id: '1' } } } } + ] - assert_equal(expected, adapter.serializable_hash) - end + assert_equal(expected, @adapter.serializable_hash[:included]) + end + + def test_has_one_with_virtual_value + serializer = VirtualValueSerializer.new(@virtual_value) + adapter = ActiveModelSerializers::Adapter::JsonApi.new(serializer) + + expected = { + data: { + id: '1', + type: 'virtual_values', + relationships: { + maker: { data: { id: 1 } }, + reviews: { data: [{ id: 1 }, { id: 2 }] } + } + } + } + + assert_equal(expected, adapter.serializable_hash) end end end diff --git a/test/adapter/json_api/json_api_test.rb b/test/adapter/json_api/json_api_test.rb index b205a4ec..1b9e89ad 100644 --- a/test/adapter/json_api/json_api_test.rb +++ b/test/adapter/json_api/json_api_test.rb @@ -1,37 +1,35 @@ require 'test_helper' -module ActiveModel - class Serializer - module Adapter - class JsonApiTest < ActiveSupport::TestCase - def setup - ActionController::Base.cache_store.clear - @author = Author.new(id: 1, name: 'Steve K.') - @post = Post.new(id: 1, title: 'New Post', body: 'Body') - @first_comment = Comment.new(id: 1, body: 'ZOMG A COMMENT') - @second_comment = Comment.new(id: 2, body: 'ZOMG ANOTHER COMMENT') - @post.comments = [@first_comment, @second_comment] - @first_comment.post = @post - @second_comment.post = @post - @post.author = @author - @blog = Blog.new(id: 1, name: 'My Blog!!') - @post.blog = @blog - end +module ActiveModelSerializers + module Adapter + class JsonApiTest < ActiveSupport::TestCase + def setup + ActionController::Base.cache_store.clear + @author = Author.new(id: 1, name: 'Steve K.') + @post = Post.new(id: 1, title: 'New Post', body: 'Body') + @first_comment = Comment.new(id: 1, body: 'ZOMG A COMMENT') + @second_comment = Comment.new(id: 2, body: 'ZOMG ANOTHER COMMENT') + @post.comments = [@first_comment, @second_comment] + @first_comment.post = @post + @second_comment.post = @post + @post.author = @author + @blog = Blog.new(id: 1, name: 'My Blog!!') + @post.blog = @blog + end - def test_custom_keys - serializer = PostWithCustomKeysSerializer.new(@post) - adapter = ActiveModel::Serializer::Adapter::JsonApi.new(serializer) + def test_custom_keys + serializer = PostWithCustomKeysSerializer.new(@post) + adapter = ActiveModelSerializers::Adapter::JsonApi.new(serializer) - assert_equal({ - reviews: { data: [ - { type: 'comments', id: '1' }, - { type: 'comments', id: '2' } - ] }, - writer: { data: { type: 'authors', id: '1' } }, - site: { data: { type: 'blogs', id: '1' } } - }, adapter.serializable_hash[:data][:relationships]) - end + assert_equal({ + reviews: { data: [ + { type: 'comments', id: '1' }, + { type: 'comments', id: '2' } + ] }, + writer: { data: { type: 'authors', id: '1' } }, + site: { data: { type: 'blogs', id: '1' } } + }, adapter.serializable_hash[:data][:relationships]) end end end -end \ No newline at end of file +end diff --git a/test/adapter/json_api/linked_test.rb b/test/adapter/json_api/linked_test.rb index c16147d6..bcf18123 100644 --- a/test/adapter/json_api/linked_test.rb +++ b/test/adapter/json_api/linked_test.rb @@ -5,168 +5,106 @@ class NestedPostSerializer < ActiveModel::Serializer has_many :nested_posts end -module ActiveModel - class Serializer - module Adapter - class JsonApi - class LinkedTest < ActiveSupport::TestCase - def setup - @author1 = Author.new(id: 1, name: 'Steve K.') - @author2 = Author.new(id: 2, name: 'Tenderlove') - @bio1 = Bio.new(id: 1, content: 'AMS Contributor') - @bio2 = Bio.new(id: 2, content: 'Rails Contributor') - @first_post = Post.new(id: 10, title: 'Hello!!', body: 'Hello, world!!') - @second_post = Post.new(id: 20, title: 'New Post', body: 'Body') - @third_post = Post.new(id: 30, title: 'Yet Another Post', body: 'Body') - @blog = Blog.new({ name: 'AMS Blog' }) - @first_comment = Comment.new(id: 1, body: 'ZOMG A COMMENT') - @second_comment = Comment.new(id: 2, body: 'ZOMG ANOTHER COMMENT') - @first_post.blog = @blog - @second_post.blog = @blog - @third_post.blog = nil - @first_post.comments = [@first_comment, @second_comment] - @second_post.comments = [] - @third_post.comments = [] - @first_post.author = @author1 - @second_post.author = @author2 - @third_post.author = @author1 - @first_comment.post = @first_post - @first_comment.author = nil - @second_comment.post = @first_post - @second_comment.author = nil - @author1.posts = [@first_post, @third_post] - @author1.bio = @bio1 - @author1.roles = [] - @author2.posts = [@second_post] - @author2.bio = @bio2 - @author2.roles = [] - @bio1.author = @author1 - @bio2.author = @author2 - end +module ActiveModelSerializers + module Adapter + class JsonApi + class LinkedTest < ActiveSupport::TestCase + def setup + @author1 = Author.new(id: 1, name: 'Steve K.') + @author2 = Author.new(id: 2, name: 'Tenderlove') + @bio1 = Bio.new(id: 1, content: 'AMS Contributor') + @bio2 = Bio.new(id: 2, content: 'Rails Contributor') + @first_post = Post.new(id: 10, title: 'Hello!!', body: 'Hello, world!!') + @second_post = Post.new(id: 20, title: 'New Post', body: 'Body') + @third_post = Post.new(id: 30, title: 'Yet Another Post', body: 'Body') + @blog = Blog.new({ name: 'AMS Blog' }) + @first_comment = Comment.new(id: 1, body: 'ZOMG A COMMENT') + @second_comment = Comment.new(id: 2, body: 'ZOMG ANOTHER COMMENT') + @first_post.blog = @blog + @second_post.blog = @blog + @third_post.blog = nil + @first_post.comments = [@first_comment, @second_comment] + @second_post.comments = [] + @third_post.comments = [] + @first_post.author = @author1 + @second_post.author = @author2 + @third_post.author = @author1 + @first_comment.post = @first_post + @first_comment.author = nil + @second_comment.post = @first_post + @second_comment.author = nil + @author1.posts = [@first_post, @third_post] + @author1.bio = @bio1 + @author1.roles = [] + @author2.posts = [@second_post] + @author2.bio = @bio2 + @author2.roles = [] + @bio1.author = @author1 + @bio2.author = @author2 + end - def test_include_multiple_posts_and_linked_array - serializer = CollectionSerializer.new([@first_post, @second_post]) - adapter = ActiveModel::Serializer::Adapter::JsonApi.new( - serializer, - include: [:comments, author: [:bio]] - ) - alt_adapter = ActiveModel::Serializer::Adapter::JsonApi.new( - serializer, - include: [:comments, author: [:bio]] - ) + def test_include_multiple_posts_and_linked_array + serializer = ActiveModel::Serializer::CollectionSerializer.new([@first_post, @second_post]) + adapter = ActiveModelSerializers::Adapter::JsonApi.new( + serializer, + include: [:comments, author: [:bio]] + ) + alt_adapter = ActiveModelSerializers::Adapter::JsonApi.new( + serializer, + include: [:comments, author: [:bio]] + ) - expected = { - data: [ - { - id: '10', - type: 'posts', - attributes: { - title: 'Hello!!', - body: 'Hello, world!!' - }, - relationships: { - comments: { data: [{ type: 'comments', id: '1' }, { type: 'comments', id: '2' }] }, - blog: { data: { type: 'blogs', id: '999' } }, - author: { data: { type: 'authors', id: '1' } } - } - }, - { - id: '20', - type: 'posts', - attributes: { - title: 'New Post', - body: 'Body' - }, - relationships: { - comments: { data: [] }, - blog: { data: { type: 'blogs', id: '999' } }, - author: { data: { type: 'authors', id: '2' } } - } - } - ], - included: [ - { - id: '1', - type: 'comments', - attributes: { - body: 'ZOMG A COMMENT' - }, - relationships: { - post: { data: { type: 'posts', id: '10' } }, - author: { data: nil } - } - }, { - id: '2', - type: 'comments', - attributes: { - body: 'ZOMG ANOTHER COMMENT', - }, - relationships: { - post: { data: { type: 'posts', id: '10' } }, - author: { data: nil } - } - }, { - id: '1', - type: 'authors', - attributes: { - name: 'Steve K.' - }, - relationships: { - posts: { data: [{ type: 'posts', id: '10' }, { type: 'posts', id: '30' }] }, - roles: { data: [] }, - bio: { data: { type: 'bios', id: '1' } } - } - }, { - id: '1', - type: 'bios', - attributes: { - content: 'AMS Contributor', - rating: nil - }, - relationships: { - author: { data: { type: 'authors', id: '1' } } - } - }, { - id: '2', - type: 'authors', - attributes: { - name: 'Tenderlove' - }, - relationships: { - posts: { data: [{ type: 'posts', id: '20' }] }, - roles: { data: [] }, - bio: { data: { type: 'bios', id: '2' } } - } - }, { - id: '2', - type: 'bios', - attributes: { - rating: nil, - content: 'Rails Contributor', - }, - relationships: { - author: { data: { type: 'authors', id: '2' } } - } - } - ] - } - assert_equal expected, adapter.serializable_hash - assert_equal expected, alt_adapter.serializable_hash - end - - def test_include_multiple_posts_and_linked - serializer = BioSerializer.new @bio1 - adapter = ActiveModel::Serializer::Adapter::JsonApi.new( - serializer, - include: [author: [:posts]] - ) - alt_adapter = ActiveModel::Serializer::Adapter::JsonApi.new( - serializer, - include: [author: [:posts]] - ) - - expected = [ + expected = { + data: [ { + id: '10', + type: 'posts', + attributes: { + title: 'Hello!!', + body: 'Hello, world!!' + }, + relationships: { + comments: { data: [{ type: 'comments', id: '1' }, { type: 'comments', id: '2' }] }, + blog: { data: { type: 'blogs', id: '999' } }, + author: { data: { type: 'authors', id: '1' } } + } + }, + { + id: '20', + type: 'posts', + attributes: { + title: 'New Post', + body: 'Body' + }, + relationships: { + comments: { data: [] }, + blog: { data: { type: 'blogs', id: '999' } }, + author: { data: { type: 'authors', id: '2' } } + } + } + ], + included: [ + { + id: '1', + type: 'comments', + attributes: { + body: 'ZOMG A COMMENT' + }, + relationships: { + post: { data: { type: 'posts', id: '10' } }, + author: { data: nil } + } + }, { + id: '2', + type: 'comments', + attributes: { + body: 'ZOMG ANOTHER COMMENT', + }, + relationships: { + post: { data: { type: 'posts', id: '10' } }, + author: { data: nil } + } + }, { id: '1', type: 'authors', attributes: { @@ -178,215 +116,275 @@ module ActiveModel bio: { data: { type: 'bios', id: '1' } } } }, { - id: '10', - type: 'posts', + id: '1', + type: 'bios', attributes: { - title: 'Hello!!', - body: 'Hello, world!!' + content: 'AMS Contributor', + rating: nil }, relationships: { - comments: { data: [{ type: 'comments', id: '1' }, { type: 'comments', id: '2' }] }, - blog: { data: { type: 'blogs', id: '999' } }, author: { data: { type: 'authors', id: '1' } } } }, { - id: '30', - type: 'posts', + id: '2', + type: 'authors', attributes: { - title: 'Yet Another Post', - body: 'Body' + name: 'Tenderlove' }, relationships: { - comments: { data: [] }, - blog: { data: { type: 'blogs', id: '999' } }, - author: { data: { type: 'authors', id: '1' } } + posts: { data: [{ type: 'posts', id: '20' }] }, + roles: { data: [] }, + bio: { data: { type: 'bios', id: '2' } } + } + }, { + id: '2', + type: 'bios', + attributes: { + rating: nil, + content: 'Rails Contributor', + }, + relationships: { + author: { data: { type: 'authors', id: '2' } } } } ] - - assert_equal expected, adapter.serializable_hash[:included] - assert_equal expected, alt_adapter.serializable_hash[:included] - end - - def test_underscore_model_namespace_for_linked_resource_type - spammy_post = Post.new(id: 123) - spammy_post.related = [Spam::UnrelatedLink.new(id: 456)] - serializer = SpammyPostSerializer.new(spammy_post) - adapter = ActiveModel::Serializer::Adapter::JsonApi.new(serializer) - relationships = adapter.serializable_hash[:data][:relationships] - expected = { - related: { - data: [{ - type: 'spam_unrelated_links', - id: '456' - }] - } - } - assert_equal expected, relationships - end - - def test_multiple_references_to_same_resource - serializer = CollectionSerializer.new([@first_comment, @second_comment]) - adapter = ActiveModel::Serializer::Adapter::JsonApi.new( - serializer, - include: [:post] - ) - - expected = [ - { - id: '10', - type: 'posts', - attributes: { - title: 'Hello!!', - body: 'Hello, world!!' - }, - relationships: { - comments: { - data: [{ type: 'comments', id: '1' }, { type: 'comments', id: '2' }] - }, - blog: { - data: { type: 'blogs', id: '999' } - }, - author: { - data: { type: 'authors', id: '1' } - } - } - } - ] - - assert_equal expected, adapter.serializable_hash[:included] - end - - def test_nil_link_with_specified_serializer - @first_post.author = nil - serializer = PostPreviewSerializer.new(@first_post) - adapter = ActiveModel::Serializer::Adapter::JsonApi.new( - serializer, - include: [:author] - ) - - expected = { - data: { - id: '10', - type: 'posts', - attributes: { - title: 'Hello!!', - body: 'Hello, world!!' - }, - relationships: { - comments: { data: [{ type: 'comments', id: '1' }, { type: 'comments', id: '2' }] }, - author: { data: nil } - } - } - } - assert_equal expected, adapter.serializable_hash - end + } + assert_equal expected, adapter.serializable_hash + assert_equal expected, alt_adapter.serializable_hash end - class NoDuplicatesTest < ActiveSupport::TestCase - Post = Class.new(::Model) - Author = Class.new(::Model) + def test_include_multiple_posts_and_linked + serializer = BioSerializer.new @bio1 + adapter = ActiveModelSerializers::Adapter::JsonApi.new( + serializer, + include: [author: [:posts]] + ) + alt_adapter = ActiveModelSerializers::Adapter::JsonApi.new( + serializer, + include: [author: [:posts]] + ) - class PostSerializer < ActiveModel::Serializer - type 'posts' - belongs_to :author - end - - class AuthorSerializer < ActiveModel::Serializer - type 'authors' - has_many :posts - end - - def setup - @author = Author.new(id: 1, posts: [], roles: [], bio: nil) - @post1 = Post.new(id: 1, author: @author) - @post2 = Post.new(id: 2, author: @author) - @author.posts << @post1 - @author.posts << @post2 - - @nestedpost1 = ::NestedPost.new(id: 1, nested_posts: []) - @nestedpost2 = ::NestedPost.new(id: 2, nested_posts: []) - @nestedpost1.nested_posts << @nestedpost1 - @nestedpost1.nested_posts << @nestedpost2 - @nestedpost2.nested_posts << @nestedpost1 - @nestedpost2.nested_posts << @nestedpost2 - end - - def test_no_duplicates - hash = ActiveModel::SerializableResource.new(@post1, adapter: :json_api, - include: '*.*') - .serializable_hash - expected = [ - { - type: 'authors', id: '1', - relationships: { - posts: { - data: [ - { type: 'posts', id: '1' }, - { type: 'posts', id: '2' } - ] - } - } + expected = [ + { + id: '1', + type: 'authors', + attributes: { + name: 'Steve K.' }, - { - type: 'posts', id: '2', - relationships: { - author: { - data: { type: 'authors', id: '1' } - } - } - } - ] - assert_equal(expected, hash[:included]) - end - - def test_no_duplicates_collection - hash = ActiveModel::SerializableResource.new( - [@post1, @post2], adapter: :json_api, - include: '*.*') - .serializable_hash - expected = [ - { - type: 'authors', id: '1', - relationships: { - posts: { - data: [ - { type: 'posts', id: '1' }, - { type: 'posts', id: '2' } - ] - } - } - } - ] - assert_equal(expected, hash[:included]) - end - - def test_no_duplicates_global - hash = ActiveModel::SerializableResource.new( - @nestedpost1, - adapter: :json_api, - include: '*').serializable_hash - expected = [ - type: 'nested_posts', id: '2', relationships: { - nested_posts: { + posts: { data: [{ type: 'posts', id: '10' }, { type: 'posts', id: '30' }] }, + roles: { data: [] }, + bio: { data: { type: 'bios', id: '1' } } + } + }, { + id: '10', + type: 'posts', + attributes: { + title: 'Hello!!', + body: 'Hello, world!!' + }, + relationships: { + comments: { data: [{ type: 'comments', id: '1' }, { type: 'comments', id: '2' }] }, + blog: { data: { type: 'blogs', id: '999' } }, + author: { data: { type: 'authors', id: '1' } } + } + }, { + id: '30', + type: 'posts', + attributes: { + title: 'Yet Another Post', + body: 'Body' + }, + relationships: { + comments: { data: [] }, + blog: { data: { type: 'blogs', id: '999' } }, + author: { data: { type: 'authors', id: '1' } } + } + } + ] + + assert_equal expected, adapter.serializable_hash[:included] + assert_equal expected, alt_adapter.serializable_hash[:included] + end + + def test_underscore_model_namespace_for_linked_resource_type + spammy_post = Post.new(id: 123) + spammy_post.related = [Spam::UnrelatedLink.new(id: 456)] + serializer = SpammyPostSerializer.new(spammy_post) + adapter = ActiveModelSerializers::Adapter::JsonApi.new(serializer) + relationships = adapter.serializable_hash[:data][:relationships] + expected = { + related: { + data: [{ + type: 'spam_unrelated_links', + id: '456' + }] + } + } + assert_equal expected, relationships + end + + def test_multiple_references_to_same_resource + serializer = ActiveModel::Serializer::CollectionSerializer.new([@first_comment, @second_comment]) + adapter = ActiveModelSerializers::Adapter::JsonApi.new( + serializer, + include: [:post] + ) + + expected = [ + { + id: '10', + type: 'posts', + attributes: { + title: 'Hello!!', + body: 'Hello, world!!' + }, + relationships: { + comments: { + data: [{ type: 'comments', id: '1' }, { type: 'comments', id: '2' }] + }, + blog: { + data: { type: 'blogs', id: '999' } + }, + author: { + data: { type: 'authors', id: '1' } + } + } + } + ] + + assert_equal expected, adapter.serializable_hash[:included] + end + + def test_nil_link_with_specified_serializer + @first_post.author = nil + serializer = PostPreviewSerializer.new(@first_post) + adapter = ActiveModelSerializers::Adapter::JsonApi.new( + serializer, + include: [:author] + ) + + expected = { + data: { + id: '10', + type: 'posts', + attributes: { + title: 'Hello!!', + body: 'Hello, world!!' + }, + relationships: { + comments: { data: [{ type: 'comments', id: '1' }, { type: 'comments', id: '2' }] }, + author: { data: nil } + } + } + } + assert_equal expected, adapter.serializable_hash + end + end + + class NoDuplicatesTest < ActiveSupport::TestCase + Post = Class.new(::Model) + Author = Class.new(::Model) + + class PostSerializer < ActiveModel::Serializer + type 'posts' + belongs_to :author + end + + class AuthorSerializer < ActiveModel::Serializer + type 'authors' + has_many :posts + end + + def setup + @author = Author.new(id: 1, posts: [], roles: [], bio: nil) + @post1 = Post.new(id: 1, author: @author) + @post2 = Post.new(id: 2, author: @author) + @author.posts << @post1 + @author.posts << @post2 + + @nestedpost1 = ::NestedPost.new(id: 1, nested_posts: []) + @nestedpost2 = ::NestedPost.new(id: 2, nested_posts: []) + @nestedpost1.nested_posts << @nestedpost1 + @nestedpost1.nested_posts << @nestedpost2 + @nestedpost2.nested_posts << @nestedpost1 + @nestedpost2.nested_posts << @nestedpost2 + end + + def test_no_duplicates + hash = ActiveModel::SerializableResource.new(@post1, adapter: :json_api, + include: '*.*') + .serializable_hash + expected = [ + { + type: 'authors', id: '1', + relationships: { + posts: { data: [ - { type: 'nested_posts', id: '1' }, - { type: 'nested_posts', id: '2' } + { type: 'posts', id: '1' }, + { type: 'posts', id: '2' } ] } } - ] - assert_equal(expected, hash[:included]) - end + }, + { + type: 'posts', id: '2', + relationships: { + author: { + data: { type: 'authors', id: '1' } + } + } + } + ] + assert_equal(expected, hash[:included]) + end - def test_no_duplicates_collection_global - hash = ActiveModel::SerializableResource.new( - [@nestedpost1, @nestedpost2], - adapter: :json_api, - include: '*').serializable_hash - assert_nil(hash[:included]) - end + def test_no_duplicates_collection + hash = ActiveModel::SerializableResource.new( + [@post1, @post2], adapter: :json_api, + include: '*.*') + .serializable_hash + expected = [ + { + type: 'authors', id: '1', + relationships: { + posts: { + data: [ + { type: 'posts', id: '1' }, + { type: 'posts', id: '2' } + ] + } + } + } + ] + assert_equal(expected, hash[:included]) + end + + def test_no_duplicates_global + hash = ActiveModel::SerializableResource.new( + @nestedpost1, + adapter: :json_api, + include: '*').serializable_hash + expected = [ + type: 'nested_posts', id: '2', + relationships: { + nested_posts: { + data: [ + { type: 'nested_posts', id: '1' }, + { type: 'nested_posts', id: '2' } + ] + } + } + ] + assert_equal(expected, hash[:included]) + end + + def test_no_duplicates_collection_global + hash = ActiveModel::SerializableResource.new( + [@nestedpost1, @nestedpost2], + adapter: :json_api, + include: '*').serializable_hash + assert_nil(hash[:included]) end end end diff --git a/test/adapter/json_api/links_test.rb b/test/adapter/json_api/links_test.rb index dbda88ea..4e97caa9 100644 --- a/test/adapter/json_api/links_test.rb +++ b/test/adapter/json_api/links_test.rb @@ -1,66 +1,64 @@ require 'test_helper' -module ActiveModel - class Serializer - module Adapter - class JsonApi - class LinksTest < ActiveSupport::TestCase - LinkAuthor = Class.new(::Model) - class LinkAuthorSerializer < ActiveModel::Serializer - link :self do - href "//example.com/link_author/#{object.id}" - meta stuff: 'value' - end - - link :other, '//example.com/resource' - - link :yet_another do - "//example.com/resource/#{object.id}" - end +module ActiveModelSerializers + module Adapter + class JsonApi + class LinksTest < ActiveSupport::TestCase + LinkAuthor = Class.new(::Model) + class LinkAuthorSerializer < ActiveModel::Serializer + link :self do + href "//example.com/link_author/#{object.id}" + meta stuff: 'value' end - def setup - @post = Post.new(id: 1337, comments: [], author: nil) - @author = LinkAuthor.new(id: 1337) - end + link :other, '//example.com/resource' - def test_toplevel_links - hash = ActiveModel::SerializableResource.new( - @post, - adapter: :json_api, - links: { - self: { - href: '//example.com/posts', - meta: { - stuff: 'value' - } - } - }).serializable_hash - expected = { + link :yet_another do + "//example.com/resource/#{object.id}" + end + end + + def setup + @post = Post.new(id: 1337, comments: [], author: nil) + @author = LinkAuthor.new(id: 1337) + end + + def test_toplevel_links + hash = ActiveModel::SerializableResource.new( + @post, + adapter: :json_api, + links: { self: { href: '//example.com/posts', meta: { stuff: 'value' } } + }).serializable_hash + expected = { + self: { + href: '//example.com/posts', + meta: { + stuff: 'value' + } } - assert_equal(expected, hash[:links]) - end + } + assert_equal(expected, hash[:links]) + end - def test_resource_links - hash = serializable(@author, adapter: :json_api).serializable_hash - expected = { - self: { - href: '//example.com/link_author/1337', - meta: { - stuff: 'value' - } - }, - other: '//example.com/resource', - yet_another: '//example.com/resource/1337' - } - assert_equal(expected, hash[:data][:links]) - end + def test_resource_links + hash = serializable(@author, adapter: :json_api).serializable_hash + expected = { + self: { + href: '//example.com/link_author/1337', + meta: { + stuff: 'value' + } + }, + other: '//example.com/resource', + yet_another: '//example.com/resource/1337' + } + assert_equal(expected, hash[:data][:links]) end end end diff --git a/test/adapter/json_api/pagination_links_test.rb b/test/adapter/json_api/pagination_links_test.rb index 805e5eb3..80046e10 100644 --- a/test/adapter/json_api/pagination_links_test.rb +++ b/test/adapter/json_api/pagination_links_test.rb @@ -4,110 +4,108 @@ require 'kaminari' require 'kaminari/hooks' ::Kaminari::Hooks.init -module ActiveModel - class Serializer - module Adapter - class JsonApi - class PaginationLinksTest < ActiveSupport::TestCase - URI = 'http://example.com' +module ActiveModelSerializers + module Adapter + class JsonApi + class PaginationLinksTest < ActiveSupport::TestCase + URI = 'http://example.com' - def setup - ActionController::Base.cache_store.clear - @array = [ - Profile.new({ id: 1, name: 'Name 1', description: 'Description 1', comments: 'Comments 1' }), - Profile.new({ id: 2, name: 'Name 2', description: 'Description 2', comments: 'Comments 2' }), - Profile.new({ id: 3, name: 'Name 3', description: 'Description 3', comments: 'Comments 3' }) + def setup + ActionController::Base.cache_store.clear + @array = [ + Profile.new({ id: 1, name: 'Name 1', description: 'Description 1', comments: 'Comments 1' }), + Profile.new({ id: 2, name: 'Name 2', description: 'Description 2', comments: 'Comments 2' }), + Profile.new({ id: 3, name: 'Name 3', description: 'Description 3', comments: 'Comments 3' }) + ] + end + + def mock_request(query_parameters = {}, original_url = URI) + context = Minitest::Mock.new + context.expect(:request_url, original_url) + context.expect(:query_parameters, query_parameters) + @options = {} + @options[:serialization_context] = context + end + + def load_adapter(paginated_collection, options = {}) + options = options.merge(adapter: :json_api) + ActiveModel::SerializableResource.new(paginated_collection, options) + end + + def using_kaminari + Kaminari.paginate_array(@array).page(2).per(1) + end + + def using_will_paginate + @array.paginate(page: 2, per_page: 1) + end + + def data + { data: [ + { id: '1', type: 'profiles', attributes: { name: 'Name 1', description: 'Description 1' } }, + { id: '2', type: 'profiles', attributes: { name: 'Name 2', description: 'Description 2' } }, + { id: '3', type: 'profiles', attributes: { name: 'Name 3', description: 'Description 3' } } ] - end + } + end - def mock_request(query_parameters = {}, original_url = URI) - context = Minitest::Mock.new - context.expect(:request_url, original_url) - context.expect(:query_parameters, query_parameters) - @options = {} - @options[:serialization_context] = context - end - - def load_adapter(paginated_collection, options = {}) - options = options.merge(adapter: :json_api) - ActiveModel::SerializableResource.new(paginated_collection, options) - end - - def using_kaminari - Kaminari.paginate_array(@array).page(2).per(1) - end - - def using_will_paginate - @array.paginate(page: 2, per_page: 1) - end - - def data - { data: [ - { id: '1', type: 'profiles', attributes: { name: 'Name 1', description: 'Description 1' } }, - { id: '2', type: 'profiles', attributes: { name: 'Name 2', description: 'Description 2' } }, - { id: '3', type: 'profiles', attributes: { name: 'Name 3', description: 'Description 3' } } - ] + def links + { + links: { + self: "#{URI}?page%5Bnumber%5D=2&page%5Bsize%5D=1", + first: "#{URI}?page%5Bnumber%5D=1&page%5Bsize%5D=1", + prev: "#{URI}?page%5Bnumber%5D=1&page%5Bsize%5D=1", + next: "#{URI}?page%5Bnumber%5D=3&page%5Bsize%5D=1", + last: "#{URI}?page%5Bnumber%5D=3&page%5Bsize%5D=1" } + } + end + + def expected_response_without_pagination_links + data + end + + def expected_response_with_pagination_links + {}.tap do |hash| + hash[:data] = [data.values.flatten.second] + hash.merge! links end + end - def links - { - links: { - self: "#{URI}?page%5Bnumber%5D=2&page%5Bsize%5D=1", - first: "#{URI}?page%5Bnumber%5D=1&page%5Bsize%5D=1", - prev: "#{URI}?page%5Bnumber%5D=1&page%5Bsize%5D=1", - next: "#{URI}?page%5Bnumber%5D=3&page%5Bsize%5D=1", - last: "#{URI}?page%5Bnumber%5D=3&page%5Bsize%5D=1" - } - } + def expected_response_with_pagination_links_and_additional_params + new_links = links[:links].each_with_object({}) { |(key, value), hash| hash[key] = "#{value}&test=test" } + {}.tap do |hash| + hash[:data] = [data.values.flatten.second] + hash.merge! links: new_links end + end - def expected_response_without_pagination_links - data - end + def test_pagination_links_using_kaminari + adapter = load_adapter(using_kaminari) - def expected_response_with_pagination_links - {}.tap do |hash| - hash[:data] = [data.values.flatten.second] - hash.merge! links - end - end + mock_request + assert_equal expected_response_with_pagination_links, adapter.serializable_hash(@options) + end - def expected_response_with_pagination_links_and_additional_params - new_links = links[:links].each_with_object({}) { |(key, value), hash| hash[key] = "#{value}&test=test" } - {}.tap do |hash| - hash[:data] = [data.values.flatten.second] - hash.merge! links: new_links - end - end + def test_pagination_links_using_will_paginate + adapter = load_adapter(using_will_paginate) - def test_pagination_links_using_kaminari - adapter = load_adapter(using_kaminari) + mock_request + assert_equal expected_response_with_pagination_links, adapter.serializable_hash(@options) + end - mock_request - assert_equal expected_response_with_pagination_links, adapter.serializable_hash(@options) - end + def test_pagination_links_with_additional_params + adapter = load_adapter(using_will_paginate) - def test_pagination_links_using_will_paginate - adapter = load_adapter(using_will_paginate) + mock_request({ test: 'test' }) + assert_equal expected_response_with_pagination_links_and_additional_params, + adapter.serializable_hash(@options) + end - mock_request - assert_equal expected_response_with_pagination_links, adapter.serializable_hash(@options) - end + def test_not_showing_pagination_links + adapter = load_adapter(@array) - def test_pagination_links_with_additional_params - adapter = load_adapter(using_will_paginate) - - mock_request({ test: 'test' }) - assert_equal expected_response_with_pagination_links_and_additional_params, - adapter.serializable_hash(@options) - end - - def test_not_showing_pagination_links - adapter = load_adapter(@array) - - assert_equal expected_response_without_pagination_links, adapter.serializable_hash - end + assert_equal expected_response_without_pagination_links, adapter.serializable_hash end end end diff --git a/test/adapter/json_api/parse_test.rb b/test/adapter/json_api/parse_test.rb index c8098816..bee79c8c 100644 --- a/test/adapter/json_api/parse_test.rb +++ b/test/adapter/json_api/parse_test.rb @@ -1,136 +1,134 @@ require 'test_helper' -module ActiveModel - class Serializer - module Adapter - class JsonApi - module Deserialization - class ParseTest < Minitest::Test - def setup - @hash = { - 'data' => { - 'type' => 'photos', - 'id' => 'zorglub', - 'attributes' => { - 'title' => 'Ember Hamster', - 'src' => 'http://example.com/images/productivity.png' +module ActiveModelSerializers + module Adapter + class JsonApi + module Deserialization + class ParseTest < Minitest::Test + def setup + @hash = { + 'data' => { + 'type' => 'photos', + 'id' => 'zorglub', + 'attributes' => { + 'title' => 'Ember Hamster', + 'src' => 'http://example.com/images/productivity.png' + }, + 'relationships' => { + 'author' => { + 'data' => nil }, - 'relationships' => { - 'author' => { - 'data' => nil - }, - 'photographer' => { - 'data' => { 'type' => 'people', 'id' => '9' } - }, - 'comments' => { - 'data' => [ - { 'type' => 'comments', 'id' => '1' }, - { 'type' => 'comments', 'id' => '2' } - ] - } + 'photographer' => { + 'data' => { 'type' => 'people', 'id' => '9' } + }, + 'comments' => { + 'data' => [ + { 'type' => 'comments', 'id' => '1' }, + { 'type' => 'comments', 'id' => '2' } + ] } } } - @params = ActionController::Parameters.new(@hash) - @expected = { - id: 'zorglub', - title: 'Ember Hamster', - src: 'http://example.com/images/productivity.png', - author_id: nil, - photographer_id: '9', - comment_ids: %w(1 2) - } + } + @params = ActionController::Parameters.new(@hash) + @expected = { + id: 'zorglub', + title: 'Ember Hamster', + src: 'http://example.com/images/productivity.png', + author_id: nil, + photographer_id: '9', + comment_ids: %w(1 2) + } - @illformed_payloads = [nil, - {}, - { - 'data' => nil - }, { - 'data' => { 'attributes' => [] } - }, { - 'data' => { 'relationships' => [] } - }, { - 'data' => { - 'relationships' => { 'rel' => nil } - } - }, { - 'data' => { - 'relationships' => { 'rel' => {} } - } - }] + @illformed_payloads = [nil, + {}, + { + 'data' => nil + }, { + 'data' => { 'attributes' => [] } + }, { + 'data' => { 'relationships' => [] } + }, { + 'data' => { + 'relationships' => { 'rel' => nil } + } + }, { + 'data' => { + 'relationships' => { 'rel' => {} } + } + }] + end + + def test_hash + parsed_hash = ActiveModelSerializers::Adapter::JsonApi::Deserialization.parse!(@hash) + assert_equal(@expected, parsed_hash) + end + + def test_actioncontroller_parameters + assert_equal(false, @params.permitted?) + parsed_hash = ActiveModelSerializers::Adapter::JsonApi::Deserialization.parse!(@params) + assert_equal(@expected, parsed_hash) + end + + def test_illformed_payloads_safe + @illformed_payloads.each do |p| + parsed_hash = ActiveModelSerializers::Adapter::JsonApi::Deserialization.parse(p) + assert_equal({}, parsed_hash) end + end - def test_hash - parsed_hash = ActiveModel::Serializer::Adapter::JsonApi::Deserialization.parse!(@hash) - assert_equal(@expected, parsed_hash) - end - - def test_actioncontroller_parameters - assert_equal(false, @params.permitted?) - parsed_hash = ActiveModel::Serializer::Adapter::JsonApi::Deserialization.parse!(@params) - assert_equal(@expected, parsed_hash) - end - - def test_illformed_payloads_safe - @illformed_payloads.each do |p| - parsed_hash = ActiveModel::Serializer::Adapter::JsonApi::Deserialization.parse(p) - assert_equal({}, parsed_hash) + def test_illformed_payloads_unsafe + @illformed_payloads.each do |p| + assert_raises(InvalidDocument) do + ActiveModelSerializers::Adapter::JsonApi::Deserialization.parse!(p) end end + end - def test_illformed_payloads_unsafe - @illformed_payloads.each do |p| - assert_raises(InvalidDocument) do - ActiveModel::Serializer::Adapter::JsonApi::Deserialization.parse!(p) - end - end - end + def test_filter_fields_only + parsed_hash = ActiveModelSerializers::Adapter::JsonApi::Deserialization.parse!(@hash, only: [:id, :title, :author]) + expected = { + id: 'zorglub', + title: 'Ember Hamster', + author_id: nil + } + assert_equal(expected, parsed_hash) + end - def test_filter_fields_only - parsed_hash = ActiveModel::Serializer::Adapter::JsonApi::Deserialization.parse!(@hash, only: [:id, :title, :author]) - expected = { - id: 'zorglub', - title: 'Ember Hamster', - author_id: nil - } - assert_equal(expected, parsed_hash) - end + def test_filter_fields_except + parsed_hash = ActiveModelSerializers::Adapter::JsonApi::Deserialization.parse!(@hash, except: [:id, :title, :author]) + expected = { + src: 'http://example.com/images/productivity.png', + photographer_id: '9', + comment_ids: %w(1 2) + } + assert_equal(expected, parsed_hash) + end - def test_filter_fields_except - parsed_hash = ActiveModel::Serializer::Adapter::JsonApi::Deserialization.parse!(@hash, except: [:id, :title, :author]) - expected = { - src: 'http://example.com/images/productivity.png', - photographer_id: '9', - comment_ids: %w(1 2) - } - assert_equal(expected, parsed_hash) - end + def test_keys + parsed_hash = ActiveModelSerializers::Adapter::JsonApi::Deserialization.parse!(@hash, keys: { author: :user, title: :post_title }) + expected = { + id: 'zorglub', + post_title: 'Ember Hamster', + src: 'http://example.com/images/productivity.png', + user_id: nil, + photographer_id: '9', + comment_ids: %w(1 2) + } + assert_equal(expected, parsed_hash) + end - def test_keys - parsed_hash = ActiveModel::Serializer::Adapter::JsonApi::Deserialization.parse!(@hash, keys: { author: :user, title: :post_title }) - expected = { - id: 'zorglub', - post_title: 'Ember Hamster', - src: 'http://example.com/images/productivity.png', - user_id: nil, - photographer_id: '9', - comment_ids: %w(1 2) - } - assert_equal(expected, parsed_hash) - end - - def test_polymorphic - parsed_hash = ActiveModel::Serializer::Adapter::JsonApi::Deserialization.parse!(@hash, polymorphic: [:photographer]) - expected = { - id: 'zorglub', - title: 'Ember Hamster', - src: 'http://example.com/images/productivity.png', - author_id: nil, - photographer_id: '9', - photographer_type: 'people', - comment_ids: %w(1 2) - } - assert_equal(expected, parsed_hash) - end + def test_polymorphic + parsed_hash = ActiveModelSerializers::Adapter::JsonApi::Deserialization.parse!(@hash, polymorphic: [:photographer]) + expected = { + id: 'zorglub', + title: 'Ember Hamster', + src: 'http://example.com/images/productivity.png', + author_id: nil, + photographer_id: '9', + photographer_type: 'people', + comment_ids: %w(1 2) + } + assert_equal(expected, parsed_hash) end end end diff --git a/test/adapter/json_api/resource_type_config_test.rb b/test/adapter/json_api/resource_type_config_test.rb index d4301c75..571552d9 100644 --- a/test/adapter/json_api/resource_type_config_test.rb +++ b/test/adapter/json_api/resource_type_config_test.rb @@ -1,69 +1,67 @@ require 'test_helper' -module ActiveModel - class Serializer - module Adapter - class JsonApi - class ResourceTypeConfigTest < ActiveSupport::TestCase - class ProfileTypeSerializer < ActiveModel::Serializer - attributes :name - type 'profile' - end +module ActiveModelSerializers + module Adapter + class JsonApi + class ResourceTypeConfigTest < ActiveSupport::TestCase + class ProfileTypeSerializer < ActiveModel::Serializer + attributes :name + type 'profile' + end - def setup - @author = Author.new(id: 1, name: 'Steve K.') - @author.bio = nil - @author.roles = [] - @blog = Blog.new(id: 23, name: 'AMS Blog') - @post = Post.new(id: 42, title: 'New Post', body: 'Body') - @anonymous_post = Post.new(id: 43, title: 'Hello!!', body: 'Hello, world!!') - @comment = Comment.new(id: 1, body: 'ZOMG A COMMENT') - @post.comments = [@comment] - @post.blog = @blog - @anonymous_post.comments = [] - @anonymous_post.blog = nil - @comment.post = @post - @comment.author = nil - @post.author = @author - @anonymous_post.author = nil - @blog = Blog.new(id: 1, name: 'My Blog!!') - @blog.writer = @author - @blog.articles = [@post, @anonymous_post] - @author.posts = [] - end + def setup + @author = Author.new(id: 1, name: 'Steve K.') + @author.bio = nil + @author.roles = [] + @blog = Blog.new(id: 23, name: 'AMS Blog') + @post = Post.new(id: 42, title: 'New Post', body: 'Body') + @anonymous_post = Post.new(id: 43, title: 'Hello!!', body: 'Hello, world!!') + @comment = Comment.new(id: 1, body: 'ZOMG A COMMENT') + @post.comments = [@comment] + @post.blog = @blog + @anonymous_post.comments = [] + @anonymous_post.blog = nil + @comment.post = @post + @comment.author = nil + @post.author = @author + @anonymous_post.author = nil + @blog = Blog.new(id: 1, name: 'My Blog!!') + @blog.writer = @author + @blog.articles = [@post, @anonymous_post] + @author.posts = [] + end - def with_jsonapi_resource_type type - old_type = ActiveModelSerializers.config.jsonapi_resource_type - ActiveModelSerializers.config.jsonapi_resource_type = type - yield - ensure - ActiveModelSerializers.config.jsonapi_resource_type = old_type - end + def with_jsonapi_resource_type type + old_type = ActiveModelSerializers.config.jsonapi_resource_type + ActiveModelSerializers.config.jsonapi_resource_type = type + yield + ensure + ActiveModelSerializers.config.jsonapi_resource_type = old_type + end - def test_config_plural - with_jsonapi_resource_type :plural do - hash = serializable(@comment, adapter: :json_api).serializable_hash - assert_equal('comments', hash[:data][:type]) - end + def test_config_plural + with_jsonapi_resource_type :plural do + hash = serializable(@comment, adapter: :json_api).serializable_hash + assert_equal('comments', hash[:data][:type]) end + end - def test_config_singular - with_jsonapi_resource_type :singular do - hash = serializable(@comment, adapter: :json_api).serializable_hash - assert_equal('comment', hash[:data][:type]) - end + def test_config_singular + with_jsonapi_resource_type :singular do + hash = serializable(@comment, adapter: :json_api).serializable_hash + assert_equal('comment', hash[:data][:type]) end + end - def test_explicit_type_value - hash = serializable(@author, serializer: ProfileTypeSerializer, adapter: :json_api).serializable_hash - assert_equal('profile', hash.fetch(:data).fetch(:type)) - end + def test_explicit_type_value + hash = serializable(@author, serializer: ProfileTypeSerializer, adapter: :json_api).serializable_hash + assert_equal('profile', hash.fetch(:data).fetch(:type)) + end - private + private - def serializable(resource, options = {}) - ActiveModel::SerializableResource.new(resource, options) - end + def serializable(resource, options = {}) + ActiveModel::SerializableResource.new(resource, options) end end end diff --git a/test/adapter/json_api/toplevel_jsonapi_test.rb b/test/adapter/json_api/toplevel_jsonapi_test.rb index e0c410ac..7b0357e5 100644 --- a/test/adapter/json_api/toplevel_jsonapi_test.rb +++ b/test/adapter/json_api/toplevel_jsonapi_test.rb @@ -1,82 +1,80 @@ require 'test_helper' -module ActiveModel - class Serializer - module Adapter - class JsonApi - class TopLevelJsonApiTest < ActiveSupport::TestCase - def setup - @author = Author.new(id: 1, name: 'Steve K.') - @author.bio = nil - @author.roles = [] - @blog = Blog.new(id: 23, name: 'AMS Blog') - @post = Post.new(id: 42, title: 'New Post', body: 'Body') - @anonymous_post = Post.new(id: 43, title: 'Hello!!', body: 'Hello, world!!') - @comment = Comment.new(id: 1, body: 'ZOMG A COMMENT') - @post.comments = [@comment] - @post.blog = @blog - @anonymous_post.comments = [] - @anonymous_post.blog = nil - @comment.post = @post - @comment.author = nil - @post.author = @author - @anonymous_post.author = nil - @blog = Blog.new(id: 1, name: 'My Blog!!') - @blog.writer = @author - @blog.articles = [@post, @anonymous_post] - @author.posts = [] - end +module ActiveModelSerializers + module Adapter + class JsonApi + class TopLevelJsonApiTest < ActiveSupport::TestCase + def setup + @author = Author.new(id: 1, name: 'Steve K.') + @author.bio = nil + @author.roles = [] + @blog = Blog.new(id: 23, name: 'AMS Blog') + @post = Post.new(id: 42, title: 'New Post', body: 'Body') + @anonymous_post = Post.new(id: 43, title: 'Hello!!', body: 'Hello, world!!') + @comment = Comment.new(id: 1, body: 'ZOMG A COMMENT') + @post.comments = [@comment] + @post.blog = @blog + @anonymous_post.comments = [] + @anonymous_post.blog = nil + @comment.post = @post + @comment.author = nil + @post.author = @author + @anonymous_post.author = nil + @blog = Blog.new(id: 1, name: 'My Blog!!') + @blog.writer = @author + @blog.articles = [@post, @anonymous_post] + @author.posts = [] + end - def test_toplevel_jsonapi_defaults_to_false - assert_equal config.fetch(:jsonapi_include_toplevel_object), false - end + def test_toplevel_jsonapi_defaults_to_false + assert_equal config.fetch(:jsonapi_include_toplevel_object), false + end - def test_disable_toplevel_jsonapi - with_config(jsonapi_include_toplevel_object: false) do - hash = serialize(@post) - assert_nil(hash[:jsonapi]) - end + def test_disable_toplevel_jsonapi + with_config(jsonapi_include_toplevel_object: false) do + hash = serialize(@post) + assert_nil(hash[:jsonapi]) end + end - def test_enable_toplevel_jsonapi - with_config(jsonapi_include_toplevel_object: true) do - hash = serialize(@post) - refute_nil(hash[:jsonapi]) - end + def test_enable_toplevel_jsonapi + with_config(jsonapi_include_toplevel_object: true) do + hash = serialize(@post) + refute_nil(hash[:jsonapi]) end + end - def test_default_toplevel_jsonapi_version - with_config(jsonapi_include_toplevel_object: true) do - hash = serialize(@post) - assert_equal('1.0', hash[:jsonapi][:version]) - end + def test_default_toplevel_jsonapi_version + with_config(jsonapi_include_toplevel_object: true) do + hash = serialize(@post) + assert_equal('1.0', hash[:jsonapi][:version]) end + end - def test_toplevel_jsonapi_no_meta - with_config(jsonapi_include_toplevel_object: true) do - hash = serialize(@post) - assert_nil(hash[:jsonapi][:meta]) - end + def test_toplevel_jsonapi_no_meta + with_config(jsonapi_include_toplevel_object: true) do + hash = serialize(@post) + assert_nil(hash[:jsonapi][:meta]) end + end - def test_toplevel_jsonapi_meta - new_config = { - jsonapi_include_toplevel_object: true, - jsonapi_toplevel_meta: { - 'copyright' => 'Copyright 2015 Example Corp.' - } + def test_toplevel_jsonapi_meta + new_config = { + jsonapi_include_toplevel_object: true, + jsonapi_toplevel_meta: { + 'copyright' => 'Copyright 2015 Example Corp.' } - with_config(new_config) do - hash = serialize(@post) - assert_equal(new_config[:jsonapi_toplevel_meta], hash.fetch(:jsonapi).fetch(:meta)) - end + } + with_config(new_config) do + hash = serialize(@post) + assert_equal(new_config[:jsonapi_toplevel_meta], hash.fetch(:jsonapi).fetch(:meta)) end + end - private + private - def serialize(resource, options = {}) - serializable(resource, { adapter: :json_api }.merge!(options)).serializable_hash - end + def serialize(resource, options = {}) + serializable(resource, { adapter: :json_api }.merge!(options)).serializable_hash end end end diff --git a/test/adapter/json_test.rb b/test/adapter/json_test.rb index ab4e756a..a7d3bc83 100644 --- a/test/adapter/json_test.rb +++ b/test/adapter/json_test.rb @@ -1,46 +1,44 @@ require 'test_helper' -module ActiveModel - class Serializer - module Adapter - class JsonTest < ActiveSupport::TestCase - def setup - ActionController::Base.cache_store.clear - @author = Author.new(id: 1, name: 'Steve K.') - @post = Post.new(id: 1, title: 'New Post', body: 'Body') - @first_comment = Comment.new(id: 1, body: 'ZOMG A COMMENT') - @second_comment = Comment.new(id: 2, body: 'ZOMG ANOTHER COMMENT') - @post.comments = [@first_comment, @second_comment] - @first_comment.post = @post - @second_comment.post = @post - @post.author = @author - @blog = Blog.new(id: 1, name: 'My Blog!!') - @post.blog = @blog +module ActiveModelSerializers + module Adapter + class JsonTest < ActiveSupport::TestCase + def setup + ActionController::Base.cache_store.clear + @author = Author.new(id: 1, name: 'Steve K.') + @post = Post.new(id: 1, title: 'New Post', body: 'Body') + @first_comment = Comment.new(id: 1, body: 'ZOMG A COMMENT') + @second_comment = Comment.new(id: 2, body: 'ZOMG ANOTHER COMMENT') + @post.comments = [@first_comment, @second_comment] + @first_comment.post = @post + @second_comment.post = @post + @post.author = @author + @blog = Blog.new(id: 1, name: 'My Blog!!') + @post.blog = @blog - @serializer = PostSerializer.new(@post) - @adapter = ActiveModel::Serializer::Adapter::Json.new(@serializer) - end + @serializer = PostSerializer.new(@post) + @adapter = ActiveModelSerializers::Adapter::Json.new(@serializer) + end - def test_has_many - assert_equal([ - { id: 1, body: 'ZOMG A COMMENT' }, - { id: 2, body: 'ZOMG ANOTHER COMMENT' } - ], @adapter.serializable_hash[:post][:comments]) - end + def test_has_many + assert_equal([ + { id: 1, body: 'ZOMG A COMMENT' }, + { id: 2, body: 'ZOMG ANOTHER COMMENT' } + ], @adapter.serializable_hash[:post][:comments]) + end - def test_custom_keys - serializer = PostWithCustomKeysSerializer.new(@post) - adapter = ActiveModel::Serializer::Adapter::Json.new(serializer) + def test_custom_keys + serializer = PostWithCustomKeysSerializer.new(@post) + adapter = ActiveModelSerializers::Adapter::Json.new(serializer) - assert_equal({ - id: 1, - reviews: [{ id: 1, body: 'ZOMG A COMMENT' }, - { id: 2, body: 'ZOMG ANOTHER COMMENT' } - ], - writer: { id: 1, name: 'Steve K.' }, - site: { id: 1, name: 'My Blog!!' } - }, adapter.serializable_hash[:post]) - end + assert_equal({ + id: 1, + reviews: [{ id: 1, body: 'ZOMG A COMMENT' }, + { id: 2, body: 'ZOMG ANOTHER COMMENT' } + ], + writer: { id: 1, name: 'Steve K.' }, + site: { id: 1, name: 'My Blog!!' } + }, adapter.serializable_hash[:post]) end end end diff --git a/test/adapter/null_test.rb b/test/adapter/null_test.rb index 9d989603..3dd666b0 100644 --- a/test/adapter/null_test.rb +++ b/test/adapter/null_test.rb @@ -1,23 +1,21 @@ require 'test_helper' -module ActiveModel - class Serializer - module Adapter - class NullTest < ActiveSupport::TestCase - def setup - profile = Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' }) - serializer = ProfileSerializer.new(profile) +module ActiveModelSerializers + module Adapter + class NullTest < ActiveSupport::TestCase + def setup + profile = Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' }) + serializer = ProfileSerializer.new(profile) - @adapter = Null.new(serializer) - end + @adapter = Null.new(serializer) + end - def test_serializable_hash - assert_equal({}, @adapter.serializable_hash) - end + def test_serializable_hash + assert_equal({}, @adapter.serializable_hash) + end - def test_it_returns_empty_json - assert_equal('{}', @adapter.to_json) - end + def test_it_returns_empty_json + assert_equal('{}', @adapter.to_json) end end end diff --git a/test/adapter_test.rb b/test/adapter_test.rb index 1253882a..a3700c15 100644 --- a/test/adapter_test.rb +++ b/test/adapter_test.rb @@ -1,42 +1,40 @@ require 'test_helper' -module ActiveModel - class Serializer - class AdapterTest < ActiveSupport::TestCase - def setup - profile = Profile.new - @serializer = ProfileSerializer.new(profile) - @adapter = ActiveModel::Serializer::Adapter::Base.new(@serializer) +module ActiveModelSerializers + class AdapterTest < ActiveSupport::TestCase + def setup + profile = Profile.new + @serializer = ProfileSerializer.new(profile) + @adapter = ActiveModelSerializers::Adapter::Base.new(@serializer) + end + + def test_serializable_hash_is_abstract_method + assert_raises(NotImplementedError) do + @adapter.serializable_hash(only: [:name]) end + end - def test_serializable_hash_is_abstract_method - assert_raises(NotImplementedError) do - @adapter.serializable_hash(only: [:name]) - end - end + def test_serializer + assert_equal @serializer, @adapter.serializer + end - def test_serializer - assert_equal @serializer, @adapter.serializer - end + def test_create_adapter + adapter = ActiveModelSerializers::Adapter.create(@serializer) + assert_equal ActiveModelSerializers::Adapter::Attributes, adapter.class + end - def test_create_adapter - adapter = ActiveModel::Serializer::Adapter.create(@serializer) - assert_equal ActiveModel::Serializer::Adapter::Attributes, adapter.class - end + def test_create_adapter_with_override + adapter = ActiveModelSerializers::Adapter.create(@serializer, { adapter: :json_api }) + assert_equal ActiveModelSerializers::Adapter::JsonApi, adapter.class + end - def test_create_adapter_with_override - adapter = ActiveModel::Serializer::Adapter.create(@serializer, { adapter: :json_api }) - assert_equal ActiveModel::Serializer::Adapter::JsonApi, adapter.class - end + def test_inflected_adapter_class_for_known_adapter + ActiveSupport::Inflector.inflections(:en) { |inflect| inflect.acronym 'API' } + klass = ActiveModelSerializers::Adapter.adapter_class(:json_api) - def test_inflected_adapter_class_for_known_adapter - ActiveSupport::Inflector.inflections(:en) { |inflect| inflect.acronym 'API' } - klass = ActiveModel::Serializer::Adapter.adapter_class(:json_api) + ActiveSupport::Inflector.inflections.acronyms.clear - ActiveSupport::Inflector.inflections.acronyms.clear - - assert_equal ActiveModel::Serializer::Adapter::JsonApi, klass - end + assert_equal ActiveModelSerializers::Adapter::JsonApi, klass end end end diff --git a/test/serializable_resource_test.rb b/test/serializable_resource_test.rb index 62bc5d91..d32e9fcf 100644 --- a/test/serializable_resource_test.rb +++ b/test/serializable_resource_test.rb @@ -5,7 +5,7 @@ module ActiveModel def setup @resource = Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' }) @serializer = ProfileSerializer.new(@resource) - @adapter = ActiveModel::Serializer::Adapter.create(@serializer) + @adapter = ActiveModelSerializers::Adapter.create(@serializer) @serializable_resource = ActiveModel::SerializableResource.new(@resource) end diff --git a/test/serializers/adapter_for_test.rb b/test/serializers/adapter_for_test.rb deleted file mode 100644 index 1775620d..00000000 --- a/test/serializers/adapter_for_test.rb +++ /dev/null @@ -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 diff --git a/test/serializers/attribute_test.rb b/test/serializers/attribute_test.rb index c675e0ac..198be84c 100644 --- a/test/serializers/attribute_test.rb +++ b/test/serializers/attribute_test.rb @@ -14,14 +14,14 @@ module ActiveModel end def test_json_serializable_hash - adapter = ActiveModel::Serializer::Adapter::Json.new(@blog_serializer) + adapter = ActiveModelSerializers::Adapter::Json.new(@blog_serializer) assert_equal({ blog: { id: 1, title: 'AMS Hints' } }, adapter.serializable_hash) end def test_attribute_inheritance_with_key inherited_klass = Class.new(AlternateBlogSerializer) blog_serializer = inherited_klass.new(@blog) - adapter = ActiveModel::Serializer::Adapter::Attributes.new(blog_serializer) + adapter = ActiveModelSerializers::Adapter::Attributes.new(blog_serializer) assert_equal({ :id => 1, :title => 'AMS Hints' }, adapter.serializable_hash) end @@ -39,7 +39,7 @@ module ActiveModel attribute :name, key: :id end - adapter = ActiveModel::Serializer::Adapter::Json.new(serializer.new(@blog)) + adapter = ActiveModelSerializers::Adapter::Json.new(serializer.new(@blog)) assert_equal({ blog: { id: 'AMS Hints' } }, adapter.serializable_hash) end @@ -48,7 +48,7 @@ module ActiveModel attribute :name, key: :object end - adapter = ActiveModel::Serializer::Adapter::Json.new(serializer.new(@blog)) + adapter = ActiveModelSerializers::Adapter::Json.new(serializer.new(@blog)) assert_equal({ blog: { object: 'AMS Hints' } }, adapter.serializable_hash) end @@ -60,10 +60,10 @@ module ActiveModel attributes :type end - adapter = ActiveModel::Serializer::Adapter::Json.new(attribute_serializer.new(@blog)) + adapter = ActiveModelSerializers::Adapter::Json.new(attribute_serializer.new(@blog)) assert_equal({ blog: { type: 1 } }, adapter.serializable_hash) - adapter = ActiveModel::Serializer::Adapter::Json.new(attributes_serializer.new(@blog)) + adapter = ActiveModelSerializers::Adapter::Json.new(attributes_serializer.new(@blog)) assert_equal({ blog: { type: 'stuff' } }, adapter.serializable_hash) end