diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index c9c8e604e..43b34bb91 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -222,7 +222,6 @@ Style/TrailingBlankLines: - 'test/adapter/null_test.rb' - 'test/serializers/cache_test.rb' - 'test/serializers/fieldset_test.rb' - - 'test/support/stream_capture.rb' # Offense count: 5 # Cop supports --auto-correct. diff --git a/README.md b/README.md index 646ed8856..c65b2d8cb 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,9 @@ - [![API Docs](http://img.shields.io/badge/yard-docs-blue.svg)](http://www.rubydoc.info/github/rails-api/active_model_serializers/v0.10.0.rc4) - [Guides](docs) - [0.9 (0-9-stable) Documentation](https://github.com/rails-api/active_model_serializers/tree/0-9-stable) + - [![API Docs](http://img.shields.io/badge/yard-docs-blue.svg)](http://www.rubydoc.info/github/rails-api/active_model_serializers/0-9-stable) - [0.8 (0-8-stable) Documentation](https://github.com/rails-api/active_model_serializers/tree/0-8-stable) + - [![API Docs](http://img.shields.io/badge/yard-docs-blue.svg)](http://www.rubydoc.info/github/rails-api/active_model_serializers/0-8-stable) ## About diff --git a/docs/general/adapters.md b/docs/general/adapters.md index 262c4418f..fa2a15319 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 ba6a1ffd8..560494ace 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 9cff50a34..da07c4c18 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 `ActiveModel::Serializer::Adapter::JsonApi`. Discussion of renaming existing classes / modules and JsonApi objects will happen in separate pull requests, and issues, and in the google doc https://docs.google.com/document/d/1rcrJr0sVcazY2Opd_6Kmv1iIwuHbI84s1P_NzFn-05c/edit?usp=sharing diff --git a/lib/active_model/serializable_resource.rb b/lib/active_model/serializable_resource.rb index c91eea170..ec83fcfc6 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 @@ def serialization_scope_name=(scope_name) 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 d13d6ca7b..64bff0eca 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -23,7 +23,6 @@ class Serializer include Links include Meta include Type - require 'active_model/serializer/adapter' # @param resource [ActiveRecord::Base, ActiveModelSerializers::Model] # @return [ActiveModel::Serializer] @@ -42,9 +41,11 @@ def self.serializer_for(resource, options = {}) 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 61a86e1ce..000000000 --- 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 657cd1f17..000000000 --- a/lib/active_model/serializer/adapter/attributes.rb +++ /dev/null @@ -1,100 +0,0 @@ -module ActiveModel - class Serializer - module Adapter - class Attributes < Base - def initialize(serializer, options = {}) - super - @include_tree = IncludeTree.from_include_args(options[:include] || '*') - @cached_attributes = options[:cache_attributes] || {} - end - - def serializable_hash(options = nil) - options ||= {} - - if serializer.respond_to?(:each) - serializable_hash_for_collection(options) - else - serializable_hash_for_single_resource(options) - end - end - - def fragment_cache(cached_hash, non_cached_hash) - Json::FragmentCache.new.fragment_cache(cached_hash, non_cached_hash) - end - - private - - def serializable_hash_for_collection(options) - cache_attributes - - serializer.map { |s| Attributes.new(s, instance_options).serializable_hash(options) } - end - - # Read cache from cache_store - # @return [Hash] - def cache_read_multi - return {} if ActiveModelSerializers.config.cache_store.blank? - - keys = CachedSerializer.object_cache_keys(serializer, @include_tree) - - return {} if keys.blank? - - ActiveModelSerializers.config.cache_store.read_multi(*keys) - end - - # Set @cached_attributes - def cache_attributes - return if @cached_attributes.present? - - @cached_attributes = cache_read_multi - end - - # Get attributes from @cached_attributes - # @return [Hash] cached attributes - def cached_attributes(cached_serializer) - return yield unless cached_serializer.cached? - - @cached_attributes.fetch(cached_serializer.cache_key) { yield } - end - - def serializable_hash_for_single_resource(options) - resource = resource_object_for(options) - relationships = resource_relationships(options) - resource.merge!(relationships) - end - - def resource_relationships(options) - relationships = {} - serializer.associations(@include_tree).each do |association| - relationships[association.key] = relationship_value_for(association, options) - end - - relationships - end - - def relationship_value_for(association, options) - return association.options[:virtual_value] if association.options[:virtual_value] - return unless association.serializer && association.serializer.object - - opts = instance_options.merge(include: @include_tree[association.key]) - Attributes.new(association.serializer, opts).serializable_hash(options) - end - - # no-op: Attributes adapter does not include meta data, because it does not support root. - def include_meta(json) - json - end - - def resource_object_for(options) - cached_serializer = CachedSerializer.new(serializer) - - cached_attributes(cached_serializer) do - cached_serializer.cache_check(self) do - serializer.attributes(options[:fields]) - end - end - end - end - end - end -end diff --git a/lib/active_model/serializer/adapter/base.rb b/lib/active_model/serializer/adapter/base.rb deleted file mode 100644 index fc06848a9..000000000 --- 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 cc1eff018..000000000 --- a/lib/active_model/serializer/adapter/cached_serializer.rb +++ /dev/null @@ -1,79 +0,0 @@ -module ActiveModel - class Serializer - module Adapter - class CachedSerializer - def initialize(serializer) - @cached_serializer = serializer - @klass = @cached_serializer.class - end - - def cache_check(adapter_instance) - if cached? - @klass._cache.fetch(cache_key, @klass._cache_options) do - yield - end - elsif fragment_cached? - FragmentCache.new(adapter_instance, @cached_serializer, adapter_instance.instance_options).fetch - else - yield - end - end - - def cached? - @klass._cache && !@klass._cache_only && !@klass._cache_except - end - - def fragment_cached? - @klass._cache && (@klass._cache_only && !@klass._cache_except || !@klass._cache_only && @klass._cache_except) - end - - def cache_key - return @cache_key if defined?(@cache_key) - - parts = [] - parts << object_cache_key - parts << @klass._cache_digest unless @klass._cache_options && @klass._cache_options[:skip_digest] - @cache_key = parts.join('/') - end - - def object_cache_key - object_time_safe = @cached_serializer.object.updated_at - object_time_safe = object_time_safe.strftime('%Y%m%d%H%M%S%9N') if object_time_safe.respond_to?(:strftime) - (@klass._cache_key) ? "#{@klass._cache_key}/#{@cached_serializer.object.id}-#{object_time_safe}" : @cached_serializer.object.cache_key - end - - # find all cache_key for the collection_serializer - # @param collection_serializer - # @param include_tree - # @return [Array] all cache_key of collection_serializer - def self.object_cache_keys(serializers, include_tree) - cache_keys = [] - - serializers.each do |serializer| - cache_keys << object_cache_key(serializer) - - serializer.associations(include_tree).each do |association| - if association.serializer.respond_to?(:each) - association.serializer.each do |sub_serializer| - cache_keys << object_cache_key(sub_serializer) - end - else - cache_keys << object_cache_key(association.serializer) - end - end - end - - cache_keys.compact.uniq - end - - # @return [String, nil] the cache_key of the serializer or nil - def self.object_cache_key(serializer) - return unless serializer.present? && serializer.object.present? - - cached_serializer = new(serializer) - cached_serializer.cached? ? cached_serializer.cache_key : nil - end - end - end - end -end 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 02d56eaae..000000000 --- a/lib/active_model/serializer/adapter/fragment_cache.rb +++ /dev/null @@ -1,115 +0,0 @@ -module ActiveModel - class Serializer - module Adapter - class FragmentCache - attr_reader :serializer - - def initialize(adapter, serializer, options) - @instance_options = options - @adapter = adapter - @serializer = serializer - end - - # TODO: Use Serializable::Resource - # TODO: call +constantize+ less - # 1. Create a CachedSerializer and NonCachedSerializer from the serializer class - # 2. Serialize the above two with the given adapter - # 3. Pass their serializations to the adapter +::fragment_cache+ - def fetch - klass = serializer.class - # It will split the serializer into two, one that will be cached and one that will not - serializers = fragment_serializer(serializer.object.class.name, klass) - - # Instantiate both serializers - cached_serializer = serializers[:cached].constantize.new(serializer.object) - non_cached_serializer = serializers[:non_cached].constantize.new(serializer.object) - - cached_adapter = adapter.class.new(cached_serializer, instance_options) - non_cached_adapter = adapter.class.new(non_cached_serializer, instance_options) - - # Get serializable hash from both - cached_hash = cached_adapter.serializable_hash - non_cached_hash = non_cached_adapter.serializable_hash - - # Merge both results - adapter.fragment_cache(cached_hash, non_cached_hash) - end - - protected - - attr_reader :instance_options, :adapter - - private - - # Given a serializer class and a hash of its cached and non-cached serializers - # 1. Determine cached attributes from serializer class options - # 2. Add cached attributes to cached Serializer - # 3. Add non-cached attributes to non-cached Serializer - def cached_attributes(klass, serializers) - attributes = serializer.class._attributes - cached_attributes = (klass._cache_only) ? klass._cache_only : attributes.reject { |attr| klass._cache_except.include?(attr) } - non_cached_attributes = attributes - cached_attributes - - cached_attributes.each do |attribute| - options = serializer.class._attributes_keys[attribute] - options ||= {} - # Add cached attributes to cached Serializer - serializers[:cached].constantize.attribute(attribute, options) - end - - non_cached_attributes.each do |attribute| - options = serializer.class._attributes_keys[attribute] - options ||= {} - # Add non-cached attributes to non-cached Serializer - serializers[:non_cached].constantize.attribute(attribute, options) - end - end - - # Given a resource name and its serializer's class - # 1. Dyanmically creates a CachedSerializer and NonCachedSerializer - # for a given class 'name' - # 2. Call - # CachedSerializer.cache(serializer._cache_options) - # CachedSerializer.fragmented(serializer) - # NontCachedSerializer.cache(serializer._cache_options) - # 3. Build a hash keyed to the +cached+ and +non_cached+ serializers - # 4. Call +cached_attributes+ on the serializer class and the above hash - # 5. Return the hash - # - # @example - # When +name+ is 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) - - # Preserve the type setting in the cached/non-cached serializer classes - cached.constantize.type(klass._type) - non_cached.constantize.type(klass._type) - - cached.constantize.fragmented(serializer) - non_cached.constantize.fragmented(serializer) - - serializers = { cached: cached, non_cached: non_cached } - cached_attributes(klass, serializers) - serializers - end - - def to_valid_const_name(name) - name.gsub('::', '_') - end - end - end - end -end diff --git a/lib/active_model/serializer/adapter/json.rb b/lib/active_model/serializer/adapter/json.rb deleted file mode 100644 index ab81f5710..000000000 --- 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 ff312cd93..000000000 --- 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 cfe4e04dc..000000000 --- a/lib/active_model/serializer/adapter/json_api.rb +++ /dev/null @@ -1,188 +0,0 @@ -module ActiveModel - class Serializer - module Adapter - class JsonApi < Base - extend ActiveSupport::Autoload - autoload :PaginationLinks - autoload :FragmentCache - autoload :Link - autoload :Meta - autoload :Deserialization - require 'active_model/serializer/adapter/json_api/api_objects' - - # TODO: if we like this abstraction and other API objects to it, - # then extract to its own file and require it. - module ApiObjects - module JsonApi - ActiveModelSerializers.config.jsonapi_version = '1.0' - ActiveModelSerializers.config.jsonapi_toplevel_meta = {} - # Make JSON API top-level jsonapi member opt-in - # ref: http://jsonapi.org/format/#document-top-level - ActiveModelSerializers.config.jsonapi_include_toplevel_object = false - - module_function - - def add!(hash) - hash.merge!(object) if include_object? - end - - def include_object? - ActiveModelSerializers.config.jsonapi_include_toplevel_object - end - - # TODO: see if we can cache this - def object - object = { - jsonapi: { - version: ActiveModelSerializers.config.jsonapi_version, - meta: ActiveModelSerializers.config.jsonapi_toplevel_meta - } - } - object[:jsonapi].reject! { |_, v| v.blank? } - - object - end - end - end - - def initialize(serializer, options = {}) - super - @include_tree = IncludeTree.from_include_args(options[:include]) - @fieldset = options[:fieldset] || ActiveModel::Serializer::Fieldset.new(options.delete(:fields)) - end - - def serializable_hash(options = nil) - options ||= {} - - is_collection = serializer.respond_to?(:each) - serializers = is_collection ? serializer : [serializer] - primary_data, included = resource_objects_for(serializers) - - hash = {} - hash[:data] = is_collection ? primary_data : primary_data[0] - hash[:included] = included if included.any? - - ApiObjects::JsonApi.add!(hash) - - if instance_options[:links] - hash[:links] ||= {} - hash[:links].update(instance_options[:links]) - end - - if is_collection && serializer.paginated? - hash[:links] ||= {} - hash[:links].update(pagination_links_for(serializer, options)) - end - - hash - end - - def fragment_cache(cached_hash, non_cached_hash) - root = false if instance_options.include?(:include) - ActiveModel::Serializer::Adapter::JsonApi::FragmentCache.new.fragment_cache(root, cached_hash, non_cached_hash) - end - - protected - - attr_reader :fieldset - - private - - def resource_objects_for(serializers) - @primary = [] - @included = [] - @resource_identifiers = Set.new - serializers.each { |serializer| process_resource(serializer, true) } - serializers.each { |serializer| process_relationships(serializer, @include_tree) } - - [@primary, @included] - end - - def process_resource(serializer, primary) - resource_identifier = ApiObjects::ResourceIdentifier.new(serializer).as_json - return false unless @resource_identifiers.add?(resource_identifier) - - resource_object = resource_object_for(serializer) - if primary - @primary << resource_object - else - @included << resource_object - end - - true - end - - def process_relationships(serializer, include_tree) - serializer.associations(include_tree).each do |association| - process_relationship(association.serializer, include_tree[association.key]) - end - end - - def process_relationship(serializer, include_tree) - if serializer.respond_to?(:each) - serializer.each { |s| process_relationship(s, include_tree) } - return - end - return unless serializer && serializer.object - return unless process_resource(serializer, false) - - process_relationships(serializer, include_tree) - end - - def attributes_for(serializer, fields) - serializer.attributes(fields).except(:id) - end - - def resource_object_for(serializer) - resource_object = cache_check(serializer) do - resource_object = ApiObjects::ResourceIdentifier.new(serializer).as_json - - requested_fields = fieldset && fieldset.fields_for(resource_object[:type]) - attributes = attributes_for(serializer, requested_fields) - resource_object[:attributes] = attributes if attributes.any? - resource_object - end - - requested_associations = fieldset.fields_for(resource_object[:type]) || '*' - relationships = relationships_for(serializer, requested_associations) - resource_object[:relationships] = relationships if relationships.any? - - links = links_for(serializer) - resource_object[:links] = links if links.any? - - meta = meta_for(serializer) - resource_object[:meta] = meta unless meta.nil? - - resource_object - end - - def relationships_for(serializer, requested_associations) - include_tree = IncludeTree.from_include_args(requested_associations) - serializer.associations(include_tree).each_with_object({}) do |association, hash| - hash[association.key] = ApiObjects::Relationship.new( - serializer, - association.serializer, - association.options, - association.links, - association.meta - ).as_json - end - end - - def links_for(serializer) - serializer._links.each_with_object({}) do |(name, value), hash| - hash[name] = Link.new(serializer, value).as_json - end - end - - def pagination_links_for(serializer, options) - JsonApi::PaginationLinks.new(serializer.object, options[:serialization_context]).serializable_hash(options) - end - - def meta_for(serializer) - Meta.new(serializer).as_json - end - end - end - end -end diff --git a/lib/active_model/serializer/adapter/json_api/api_objects/relationship.rb b/lib/active_model/serializer/adapter/json_api/api_objects/relationship.rb index d1ebc1b96..dfaabc39b 100644 --- a/lib/active_model/serializer/adapter/json_api/api_objects/relationship.rb +++ b/lib/active_model/serializer/adapter/json_api/api_objects/relationship.rb @@ -11,7 +11,7 @@ def initialize(parent_serializer, serializer, options = {}, links = {}, meta = n @options = options @data = data_for(serializer, options) @links = links.each_with_object({}) do |(key, value), hash| - hash[key] = Link.new(parent_serializer, value).as_json + hash[key] = ActiveModelSerializers::Adapter::JsonApi::Link.new(parent_serializer, value).as_json end @meta = meta.respond_to?(:call) ? parent_serializer.instance_eval(&meta) : meta end 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 5f35a882d..000000000 --- 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 7dbc11795..000000000 --- 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 f8f7e7ee0..000000000 --- a/lib/active_model/serializer/adapter/json_api/link.rb +++ /dev/null @@ -1,45 +0,0 @@ -module ActiveModel - class Serializer - module Adapter - class JsonApi - class Link - def initialize(serializer, value) - @object = serializer.object - @scope = serializer.scope - - # Use the return value of the block unless it is nil. - if value.respond_to?(:call) - @value = instance_eval(&value) - else - @value = value - end - end - - def href(value) - @href = value - nil - end - - def meta(value) - @meta = value - nil - end - - def as_json - return @value if @value - - hash = {} - hash[:href] = @href if @href - hash[:meta] = @meta if @meta - - hash - end - - protected - - attr_reader :object, :scope - end - end - end - end -end 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 9c437057b..000000000 --- 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 f398380fd..000000000 --- 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 47e14208f..e75e30a0d 100644 --- a/lib/active_model_serializers.rb +++ b/lib/active_model_serializers.rb @@ -9,6 +9,7 @@ module ActiveModelSerializers autoload :Deserialization autoload :Logging autoload :Test + autoload :Adapter class << self; attr_accessor :logger; end self.logger = ActiveSupport::TaggedLogging.new(ActiveSupport::Logger.new(STDOUT)) diff --git a/lib/active_model_serializers/adapter.rb b/lib/active_model_serializers/adapter.rb new file mode 100644 index 000000000..679b862b2 --- /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 000000000..c89417e83 --- /dev/null +++ b/lib/active_model_serializers/adapter/attributes.rb @@ -0,0 +1,98 @@ +module ActiveModelSerializers + module Adapter + class Attributes < Base + def initialize(serializer, options = {}) + super + @include_tree = ActiveModel::Serializer::IncludeTree.from_include_args(options[:include] || '*') + @cached_attributes = options[:cache_attributes] || {} + end + + def serializable_hash(options = nil) + options ||= {} + + if serializer.respond_to?(:each) + serializable_hash_for_collection(options) + else + serializable_hash_for_single_resource(options) + end + end + + def fragment_cache(cached_hash, non_cached_hash) + Json::FragmentCache.new.fragment_cache(cached_hash, non_cached_hash) + end + + private + + def serializable_hash_for_collection(options) + cache_attributes + + serializer.map { |s| Attributes.new(s, instance_options).serializable_hash(options) } + end + + # Read cache from cache_store + # @return [Hash] + def cache_read_multi + return {} if ActiveModelSerializers.config.cache_store.blank? + + keys = CachedSerializer.object_cache_keys(serializer, @include_tree) + + return {} if keys.blank? + + ActiveModelSerializers.config.cache_store.read_multi(*keys) + end + + # Set @cached_attributes + def cache_attributes + return if @cached_attributes.present? + + @cached_attributes = cache_read_multi + end + + # Get attributes from @cached_attributes + # @return [Hash] cached attributes + def cached_attributes(cached_serializer) + return yield unless cached_serializer.cached? + + @cached_attributes.fetch(cached_serializer.cache_key) { yield } + end + + def serializable_hash_for_single_resource(options) + resource = resource_object_for(options) + relationships = resource_relationships(options) + resource.merge!(relationships) + end + + def resource_relationships(options) + relationships = {} + serializer.associations(@include_tree).each do |association| + relationships[association.key] = relationship_value_for(association, options) + end + + relationships + end + + def relationship_value_for(association, options) + return association.options[:virtual_value] if association.options[:virtual_value] + return unless association.serializer && association.serializer.object + + opts = instance_options.merge(include: @include_tree[association.key]) + Attributes.new(association.serializer, opts).serializable_hash(options) + end + + # no-op: Attributes adapter does not include meta data, because it does not support root. + def include_meta(json) + json + end + + def resource_object_for(options) + cached_serializer = CachedSerializer.new(serializer) + + cached_attributes(cached_serializer) do + cached_serializer.cache_check(self) do + serializer.attributes(options[:fields]) + end + end + end + end + end +end diff --git a/lib/active_model_serializers/adapter/base.rb b/lib/active_model_serializers/adapter/base.rb new file mode 100644 index 000000000..9b31cffcf --- /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 000000000..458216688 --- /dev/null +++ b/lib/active_model_serializers/adapter/cached_serializer.rb @@ -0,0 +1,77 @@ +module ActiveModelSerializers + module Adapter + class CachedSerializer + def initialize(serializer) + @cached_serializer = serializer + @klass = @cached_serializer.class + end + + def cache_check(adapter_instance) + if cached? + @klass._cache.fetch(cache_key, @klass._cache_options) do + yield + end + elsif fragment_cached? + FragmentCache.new(adapter_instance, @cached_serializer, adapter_instance.instance_options).fetch + else + yield + end + end + + def cached? + @klass._cache && !@klass._cache_only && !@klass._cache_except + end + + def fragment_cached? + @klass._cache && (@klass._cache_only && !@klass._cache_except || !@klass._cache_only && @klass._cache_except) + end + + def cache_key + return @cache_key if defined?(@cache_key) + + parts = [] + parts << object_cache_key + parts << @klass._cache_digest unless @klass._cache_options && @klass._cache_options[:skip_digest] + @cache_key = parts.join('/') + end + + def object_cache_key + object_time_safe = @cached_serializer.object.updated_at + object_time_safe = object_time_safe.strftime('%Y%m%d%H%M%S%9N') if object_time_safe.respond_to?(:strftime) + (@klass._cache_key) ? "#{@klass._cache_key}/#{@cached_serializer.object.id}-#{object_time_safe}" : @cached_serializer.object.cache_key + end + + # find all cache_key for the collection_serializer + # @param collection_serializer + # @param include_tree + # @return [Array] all cache_key of collection_serializer + def self.object_cache_keys(serializers, include_tree) + cache_keys = [] + + serializers.each do |serializer| + cache_keys << object_cache_key(serializer) + + serializer.associations(include_tree).each do |association| + if association.serializer.respond_to?(:each) + association.serializer.each do |sub_serializer| + cache_keys << object_cache_key(sub_serializer) + end + else + cache_keys << object_cache_key(association.serializer) + end + end + end + + cache_keys.compact.uniq + end + + # @return [String, nil] the cache_key of the serializer or nil + def self.object_cache_key(serializer) + return unless serializer.present? && serializer.object.present? + + cached_serializer = new(serializer) + cached_serializer.cached? ? cached_serializer.cache_key : nil + end + end + end +end 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 000000000..7dbc2a877 --- /dev/null +++ b/lib/active_model_serializers/adapter/fragment_cache.rb @@ -0,0 +1,113 @@ +module ActiveModelSerializers + module Adapter + class FragmentCache + attr_reader :serializer + + def initialize(adapter, serializer, options) + @instance_options = options + @adapter = adapter + @serializer = serializer + end + + # TODO: Use Serializable::Resource + # TODO: call +constantize+ less + # 1. Create a CachedSerializer and NonCachedSerializer from the serializer class + # 2. Serialize the above two with the given adapter + # 3. Pass their serializations to the adapter +::fragment_cache+ + def fetch + klass = serializer.class + # It will split the serializer into two, one that will be cached and one that will not + serializers = fragment_serializer(serializer.object.class.name, klass) + + # Instantiate both serializers + cached_serializer = serializers[:cached].constantize.new(serializer.object) + non_cached_serializer = serializers[:non_cached].constantize.new(serializer.object) + + cached_adapter = adapter.class.new(cached_serializer, instance_options) + non_cached_adapter = adapter.class.new(non_cached_serializer, instance_options) + + # Get serializable hash from both + cached_hash = cached_adapter.serializable_hash + non_cached_hash = non_cached_adapter.serializable_hash + + # Merge both results + adapter.fragment_cache(cached_hash, non_cached_hash) + end + + protected + + attr_reader :instance_options, :adapter + + private + + # Given a serializer class and a hash of its cached and non-cached serializers + # 1. Determine cached attributes from serializer class options + # 2. Add cached attributes to cached Serializer + # 3. Add non-cached attributes to non-cached Serializer + def cached_attributes(klass, serializers) + attributes = serializer.class._attributes + cached_attributes = (klass._cache_only) ? klass._cache_only : attributes.reject { |attr| klass._cache_except.include?(attr) } + non_cached_attributes = attributes - cached_attributes + + cached_attributes.each do |attribute| + options = serializer.class._attributes_keys[attribute] + options ||= {} + # Add cached attributes to cached Serializer + serializers[:cached].constantize.attribute(attribute, options) + end + + non_cached_attributes.each do |attribute| + options = serializer.class._attributes_keys[attribute] + options ||= {} + # Add non-cached attributes to non-cached Serializer + serializers[:non_cached].constantize.attribute(attribute, options) + end + end + + # Given a resource name and its serializer's class + # 1. Dyanmically creates a CachedSerializer and NonCachedSerializer + # for a given class 'name' + # 2. Call + # CachedSerializer.cache(serializer._cache_options) + # CachedSerializer.fragmented(serializer) + # NontCachedSerializer.cache(serializer._cache_options) + # 3. Build a hash keyed to the +cached+ and +non_cached+ serializers + # 4. Call +cached_attributes+ on the serializer class and the above hash + # 5. Return the hash + # + # @example + # When +name+ is 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) + + # Preserve the type setting in the cached/non-cached serializer classes + cached.constantize.type(klass._type) + non_cached.constantize.type(klass._type) + + cached.constantize.fragmented(serializer) + non_cached.constantize.fragmented(serializer) + + serializers = { cached: cached, non_cached: non_cached } + cached_attributes(klass, serializers) + serializers + end + + def to_valid_const_name(name) + name.gsub('::', '_') + end + end + end +end diff --git a/lib/active_model_serializers/adapter/json.rb b/lib/active_model_serializers/adapter/json.rb new file mode 100644 index 000000000..9652a04f0 --- /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 000000000..d042063ad --- /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 000000000..841185f00 --- /dev/null +++ b/lib/active_model_serializers/adapter/json_api.rb @@ -0,0 +1,186 @@ +module ActiveModelSerializers + module Adapter + class JsonApi < Base + extend ActiveSupport::Autoload + autoload :PaginationLinks + autoload :FragmentCache + autoload :Link + require 'active_model/serializer/adapter/json_api/meta' + autoload :Deserialization + require 'active_model/serializer/adapter/json_api/api_objects' + + # TODO: if we like this abstraction and other API objects to it, + # then extract to its own file and require it. + module ApiObjects + module JsonApi + ActiveModelSerializers.config.jsonapi_version = '1.0' + ActiveModelSerializers.config.jsonapi_toplevel_meta = {} + # Make JSON API top-level jsonapi member opt-in + # ref: http://jsonapi.org/format/#document-top-level + ActiveModelSerializers.config.jsonapi_include_toplevel_object = false + + module_function + + def add!(hash) + hash.merge!(object) if include_object? + end + + def include_object? + ActiveModelSerializers.config.jsonapi_include_toplevel_object + end + + # TODO: see if we can cache this + def object + object = { + jsonapi: { + version: ActiveModelSerializers.config.jsonapi_version, + meta: ActiveModelSerializers.config.jsonapi_toplevel_meta + } + } + object[:jsonapi].reject! { |_, v| v.blank? } + + object + end + end + end + + def initialize(serializer, options = {}) + super + @include_tree = ActiveModel::Serializer::IncludeTree.from_include_args(options[:include]) + @fieldset = options[:fieldset] || ActiveModel::Serializer::Fieldset.new(options.delete(:fields)) + end + + def serializable_hash(options = nil) + options ||= {} + + is_collection = serializer.respond_to?(:each) + serializers = is_collection ? serializer : [serializer] + primary_data, included = resource_objects_for(serializers) + + hash = {} + hash[:data] = is_collection ? primary_data : primary_data[0] + hash[:included] = included if included.any? + + ApiObjects::JsonApi.add!(hash) + + if instance_options[:links] + hash[:links] ||= {} + hash[:links].update(instance_options[:links]) + end + + if is_collection && serializer.paginated? + hash[:links] ||= {} + hash[:links].update(pagination_links_for(serializer, options)) + end + + hash + end + + def fragment_cache(cached_hash, non_cached_hash) + root = false if instance_options.include?(:include) + ActiveModelSerializers::Adapter::JsonApi::FragmentCache.new.fragment_cache(root, cached_hash, non_cached_hash) + end + + protected + + attr_reader :fieldset + + private + + def resource_objects_for(serializers) + @primary = [] + @included = [] + @resource_identifiers = Set.new + serializers.each { |serializer| process_resource(serializer, true) } + serializers.each { |serializer| process_relationships(serializer, @include_tree) } + + [@primary, @included] + end + + def process_resource(serializer, primary) + resource_identifier = ActiveModel::Serializer::Adapter::JsonApi::ApiObjects::ResourceIdentifier.new(serializer).as_json + return false unless @resource_identifiers.add?(resource_identifier) + + resource_object = resource_object_for(serializer) + if primary + @primary << resource_object + else + @included << resource_object + end + + true + end + + def process_relationships(serializer, include_tree) + serializer.associations(include_tree).each do |association| + process_relationship(association.serializer, include_tree[association.key]) + end + end + + def process_relationship(serializer, include_tree) + if serializer.respond_to?(:each) + serializer.each { |s| process_relationship(s, include_tree) } + return + end + return unless serializer && serializer.object + return unless process_resource(serializer, false) + + process_relationships(serializer, include_tree) + end + + def attributes_for(serializer, fields) + serializer.attributes(fields).except(:id) + end + + def resource_object_for(serializer) + resource_object = cache_check(serializer) do + resource_object = ActiveModel::Serializer::Adapter::JsonApi::ApiObjects::ResourceIdentifier.new(serializer).as_json + + requested_fields = fieldset && fieldset.fields_for(resource_object[:type]) + attributes = attributes_for(serializer, requested_fields) + resource_object[:attributes] = attributes if attributes.any? + resource_object + end + + requested_associations = fieldset.fields_for(resource_object[:type]) || '*' + relationships = relationships_for(serializer, requested_associations) + resource_object[:relationships] = relationships if relationships.any? + + links = links_for(serializer) + resource_object[:links] = links if links.any? + + meta = meta_for(serializer) + resource_object[:meta] = meta unless meta.nil? + + resource_object + end + + def relationships_for(serializer, requested_associations) + include_tree = ActiveModel::Serializer::IncludeTree.from_include_args(requested_associations) + serializer.associations(include_tree).each_with_object({}) do |association, hash| + hash[association.key] = ActiveModel::Serializer::Adapter::JsonApi::ApiObjects::Relationship.new( + serializer, + association.serializer, + association.options, + association.links, + association.meta + ).as_json + end + end + + def links_for(serializer) + serializer._links.each_with_object({}) do |(name, value), hash| + hash[name] = Link.new(serializer, value).as_json + end + end + + def pagination_links_for(serializer, options) + JsonApi::PaginationLinks.new(serializer.object, options[:serialization_context]).serializable_hash(options) + end + + def meta_for(serializer) + ActiveModel::Serializer::Adapter::JsonApi::Meta.new(serializer).as_json + end + end + end +end 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 000000000..a50aa88fe --- /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 000000000..5ae0b08c0 --- /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 000000000..3408a98e2 --- /dev/null +++ b/lib/active_model_serializers/adapter/json_api/link.rb @@ -0,0 +1,43 @@ +module ActiveModelSerializers + module Adapter + class JsonApi + class Link + def initialize(serializer, value) + @object = serializer.object + @scope = serializer.scope + + # Use the return value of the block unless it is nil. + if value.respond_to?(:call) + @value = instance_eval(&value) + else + @value = value + end + end + + def href(value) + @href = value + nil + end + + def meta(value) + @meta = value + nil + end + + def as_json + return @value if @value + + hash = {} + hash[:href] = @href if @href + hash[:meta] = @meta if @meta + + hash + end + + protected + + attr_reader :object, :scope + end + end + end +end 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 000000000..8f3252c30 --- /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 000000000..6e690b1b0 --- /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 15b8e8985..9eaeef44d 100644 --- a/lib/active_model_serializers/deserialization.rb +++ b/lib/active_model_serializers/deserialization.rb @@ -3,11 +3,11 @@ module Deserialization 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/action_controller/serialization_test.rb b/test/action_controller/serialization_test.rb index 7e6ea6ccd..e8e99dd5e 100644 --- a/test/action_controller/serialization_test.rb +++ b/test/action_controller/serialization_test.rb @@ -3,7 +3,6 @@ module ActionController module Serialization class ImplicitSerializerTest < ActionController::TestCase - include ActiveSupport::Testing::Stream class ImplicitSerializationTestController < ActionController::Base include SerializationTesting def render_using_implicit_serializer @@ -46,7 +45,7 @@ def render_array_using_implicit_serializer_and_meta end def render_array_using_implicit_serializer_and_links - with_adapter ActiveModel::Serializer::Adapter::JsonApi do + with_adapter ActiveModelSerializers::Adapter::JsonApi do @profiles = [ Profile.new(name: 'Name 1', description: 'Description 1', comments: 'Comments 1') ] @@ -438,9 +437,9 @@ def use_adapter? false end end.new - assert_match(/adapter: false/, (capture(:stderr) do + assert_output(nil, /adapter: false/) do controller.get_serializer(Profile.new) - end)) + end end def test_dont_warn_overridding_use_adapter_as_truthy_on_controller_instance @@ -449,9 +448,9 @@ def use_adapter? true end end.new - assert_equal '', (capture(:stderr) do + assert_output(nil, '') do controller.get_serializer(Profile.new) - end) + end end def test_render_event_is_emmited 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 000000000..8dfbc9f3f --- /dev/null +++ b/test/active_model_serializers/adapter_for_test.rb @@ -0,0 +1,203 @@ +module ActiveModelSerializers + class AdapterForTest < ActiveSupport::TestCase + UnknownAdapterError = ::ActiveModelSerializers::Adapter::UnknownAdapterError + + def setup + @previous_adapter = ActiveModelSerializers.config.adapter + end + + def teardown + ActiveModelSerializers.config.adapter = @previous_adapter + end + + def test_serializer_adapter_returns_configured__adapter + assert_output(nil, /ActiveModelSerializers::configured_adapter/) do + assert_equal ActiveModelSerializers::Adapter.configured_adapter, ActiveModel::Serializer.adapter + end + end + + def test_returns_default_adapter + adapter = ActiveModelSerializers::Adapter.configured_adapter + assert_equal ActiveModelSerializers::Adapter::Attributes, adapter + end + + def test_overwrite_adapter_with_symbol + ActiveModelSerializers.config.adapter = :null + + adapter = ActiveModelSerializers::Adapter.configured_adapter + assert_equal ActiveModelSerializers::Adapter::Null, adapter + ensure + ActiveModelSerializers.config.adapter = @previous_adapter + end + + def test_overwrite_adapter_with_camelcased_symbol + ActiveModelSerializers.config.adapter = :JsonApi + + adapter = ActiveModelSerializers::Adapter.configured_adapter + assert_equal ActiveModelSerializers::Adapter::JsonApi, adapter + ensure + ActiveModelSerializers.config.adapter = @previous_adapter + end + + def test_overwrite_adapter_with_string + ActiveModelSerializers.config.adapter = 'json_api' + + adapter = ActiveModelSerializers::Adapter.configured_adapter + assert_equal ActiveModelSerializers::Adapter::JsonApi, adapter + ensure + ActiveModelSerializers.config.adapter = @previous_adapter + end + + def test_overwrite_adapter_with_a_camelcased_string + ActiveModelSerializers.config.adapter = 'JsonApi' + + adapter = ActiveModelSerializers::Adapter.configured_adapter + assert_equal ActiveModelSerializers::Adapter::JsonApi, adapter + ensure + ActiveModelSerializers.config.adapter = @previous_adapter + end + + def test_overwrite_adapter_with_class + ActiveModelSerializers.config.adapter = ActiveModelSerializers::Adapter::Null + + adapter = ActiveModelSerializers::Adapter.configured_adapter + assert_equal ActiveModelSerializers::Adapter::Null, adapter + ensure + ActiveModelSerializers.config.adapter = @previous_adapter + end + + def test_raises_exception_if_invalid_symbol_given + ActiveModelSerializers.config.adapter = :unknown + + assert_raises UnknownAdapterError do + ActiveModelSerializers::Adapter.configured_adapter + end + ensure + ActiveModelSerializers.config.adapter = @previous_adapter + end + + def test_raises_exception_if_it_does_not_know_hot_to_infer_adapter + ActiveModelSerializers.config.adapter = 42 + + assert_raises UnknownAdapterError do + ActiveModelSerializers::Adapter.configured_adapter + end + ensure + ActiveModelSerializers.config.adapter = @previous_adapter + end + + def test_adapter_class_for_known_adapter + klass = ActiveModelSerializers::Adapter.adapter_class(:json_api) + assert_equal ActiveModelSerializers::Adapter::JsonApi, klass + end + + def test_adapter_class_for_unknown_adapter + assert_raises UnknownAdapterError do + ActiveModelSerializers::Adapter.adapter_class(:json_simple) + end + end + + def test_adapter_map + expected_adapter_map = { + 'null'.freeze => ActiveModelSerializers::Adapter::Null, + 'json'.freeze => ActiveModelSerializers::Adapter::Json, + 'attributes'.freeze => ActiveModelSerializers::Adapter::Attributes, + 'json_api'.freeze => ActiveModelSerializers::Adapter::JsonApi + } + actual = ActiveModelSerializers::Adapter.adapter_map + assert_equal actual, expected_adapter_map + end + + def test_adapters + assert_equal ActiveModelSerializers::Adapter.adapters.sort, [ + 'attributes'.freeze, + 'json'.freeze, + 'json_api'.freeze, + 'null'.freeze + ] + end + + def test_lookup_adapter_by_string_name + assert_equal ActiveModelSerializers::Adapter.lookup('json'.freeze), ActiveModelSerializers::Adapter::Json + end + + def test_lookup_adapter_by_symbol_name + assert_equal ActiveModelSerializers::Adapter.lookup(:json), ActiveModelSerializers::Adapter::Json + end + + def test_lookup_adapter_by_class + klass = ActiveModelSerializers::Adapter::Json + assert_equal ActiveModelSerializers::Adapter.lookup(klass), klass + end + + def test_lookup_adapter_from_environment_registers_adapter + ActiveModelSerializers::Adapter.const_set(:AdapterFromEnvironment, Class.new) + klass = ::ActiveModelSerializers::Adapter::AdapterFromEnvironment + name = 'adapter_from_environment'.freeze + assert_equal ActiveModelSerializers::Adapter.lookup(name), klass + assert ActiveModelSerializers::Adapter.adapters.include?(name) + ensure + ActiveModelSerializers::Adapter.adapter_map.delete(name) + ActiveModelSerializers::Adapter.send(:remove_const, :AdapterFromEnvironment) + end + + def test_lookup_adapter_for_unknown_name + assert_raises UnknownAdapterError do + ActiveModelSerializers::Adapter.lookup(:json_simple) + end + end + + def test_adapter + assert_equal ActiveModelSerializers.config.adapter, :attributes + assert_equal ActiveModelSerializers::Adapter.configured_adapter, ActiveModelSerializers::Adapter::Attributes + end + + def test_register_adapter + new_adapter_name = :foo + new_adapter_klass = Class.new + ActiveModelSerializers::Adapter.register(new_adapter_name, new_adapter_klass) + assert ActiveModelSerializers::Adapter.adapters.include?('foo'.freeze) + assert ActiveModelSerializers::Adapter.lookup(:foo), new_adapter_klass + ensure + ActiveModelSerializers::Adapter.adapter_map.delete(new_adapter_name.to_s) + end + + def test_inherited_adapter_hooks_register_adapter + Object.const_set(:MyAdapter, Class.new) + my_adapter = MyAdapter + ActiveModelSerializers::Adapter::Base.inherited(my_adapter) + assert_equal ActiveModelSerializers::Adapter.lookup(:my_adapter), my_adapter + ensure + ActiveModelSerializers::Adapter.adapter_map.delete('my_adapter'.freeze) + Object.send(:remove_const, :MyAdapter) + end + + def test_inherited_adapter_hooks_register_namespaced_adapter + Object.const_set(:MyNamespace, Module.new) + MyNamespace.const_set(:MyAdapter, Class.new) + my_adapter = MyNamespace::MyAdapter + ActiveModelSerializers::Adapter::Base.inherited(my_adapter) + assert_equal ActiveModelSerializers::Adapter.lookup(:'my_namespace/my_adapter'), my_adapter + ensure + ActiveModelSerializers::Adapter.adapter_map.delete('my_namespace/my_adapter'.freeze) + MyNamespace.send(:remove_const, :MyAdapter) + Object.send(:remove_const, :MyNamespace) + end + + def test_inherited_adapter_hooks_register_subclass_of_registered_adapter + Object.const_set(:MyAdapter, Class.new) + my_adapter = MyAdapter + Object.const_set(:MySubclassedAdapter, Class.new(MyAdapter)) + my_subclassed_adapter = MySubclassedAdapter + ActiveModelSerializers::Adapter::Base.inherited(my_adapter) + ActiveModelSerializers::Adapter::Base.inherited(my_subclassed_adapter) + assert_equal ActiveModelSerializers::Adapter.lookup(:my_adapter), my_adapter + assert_equal ActiveModelSerializers::Adapter.lookup(:my_subclassed_adapter), my_subclassed_adapter + ensure + ActiveModelSerializers::Adapter.adapter_map.delete('my_adapter'.freeze) + ActiveModelSerializers::Adapter.adapter_map.delete('my_subclassed_adapter'.freeze) + Object.send(:remove_const, :MyAdapter) + Object.send(:remove_const, :MySubclassedAdapter) + end + end +end diff --git a/test/active_model_serializers/logging_test.rb b/test/active_model_serializers/logging_test.rb index e0f3e85ae..aa50e985f 100644 --- a/test/active_model_serializers/logging_test.rb +++ b/test/active_model_serializers/logging_test.rb @@ -65,7 +65,7 @@ def test_logs_correct_serializer 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 aded06d4f..a240b56e3 100644 --- a/test/adapter/fragment_cache_test.rb +++ b/test/adapter/fragment_cache_test.rb @@ -1,49 +1,47 @@ require 'test_helper' -module ActiveModel - class Serializer - module Adapter - class FragmentCacheTest < ActiveSupport::TestCase - TypedRoleSerializer = Class.new(ActiveModel::Serializer) do - type 'my-roles' - cache only: [:name], skip_digest: true - attributes :id, :name, :description +module ActiveModelSerializers + module Adapter + class FragmentCacheTest < ActiveSupport::TestCase + TypedRoleSerializer = Class.new(ActiveModel::Serializer) do + type 'my-roles' + cache only: [:name], skip_digest: true + attributes :id, :name, :description - belongs_to :author - end + belongs_to :author + end - 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 + 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 - def test_fragment_fetch_with_type_override - serialization = serializable(Role.new(name: 'Another Author'), serializer: TypedRoleSerializer, adapter: :json_api).serializable_hash - assert_equal(TypedRoleSerializer._type, serialization.fetch(:data).fetch(:type)) - end + def test_fragment_fetch_with_type_override + serialization = serializable(Role.new(name: 'Another Author'), serializer: TypedRoleSerializer, adapter: :json_api).serializable_hash + assert_equal(TypedRoleSerializer._type, serialization.fetch(:data).fetch(:type)) end end end diff --git a/test/adapter/json/belongs_to_test.rb b/test/adapter/json/belongs_to_test.rb index 940770b29..0f096f0b3 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 6be3d505a..2ff23336f 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: [{ - 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 + 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 = CollectionSerializer.new([@first_post, @second_post]) - adapter = ActiveModel::Serializer::Adapter::Json.new(serializer) + 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!!', + expected = { posts: [{ + title: 'Hello!!', + body: 'Hello, world!!', + id: 1, + 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' + } + }, { + title: 'New Post', + body: 'Body', + id: 2, + comments: [], + author: { + id: 1, + name: 'Steve K.' + }, + blog: { + id: 999, + name: 'Custom blog' + } + }] } + assert_equal expected, adapter.serializable_hash + end - def test_root_is_underscored - 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 72f29e5cb..3f6fa546e 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 ba7253e52..c501b4d8d 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' } } + + assert_equal(expected, @adapter.serializable_hash[:data][:relationships][:post]) + end - def test_includes_post_id - expected = { data: { type: 'posts', id: '42' } } + 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 - assert_equal(expected, @adapter.serializable_hash[:data][:relationships][:post]) - 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_includes_linked_post - @adapter = ActiveModel::Serializer::Adapter::JsonApi.new(@serializer, include: [:post]) - expected = [{ + 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 05d74bd16..b534108a3 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!!' + }, + 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', - 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 + 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' } } - } + 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' }, - { - 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, actual[:data]) end end end diff --git a/test/adapter/json_api/fields_test.rb b/test/adapter/json_api/fields_test.rb index b92ab590a..ad356a533 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 b80448f70..e016de284 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 2d2a885aa..f598bc9b0 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' } - ] - } + 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_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_linked_data + # If CommentPreviewSerializer is applied correctly the body text will not be present in the output + expected = [ + { + id: '1', + type: 'comments', + relationships: { + post: { data: { type: 'posts', id: @post.id.to_s } } } - ] + }, + { + id: '2', + type: 'comments', + relationships: { + post: { data: { type: 'posts', id: @post.id.to_s } } + } + }, + { + id: @author.id.to_s, + type: 'authors', + relationships: { + posts: { data: [{ type: 'posts', id: @post.id.to_s }] } + } + } + ] - assert_equal(expected, @adapter.serializable_hash[:included]) - end + assert_equal(expected, @adapter.serializable_hash[:included]) + end - def test_includes_author_id - expected = { - data: { type: 'authors', id: @author.id.to_s } - } + def test_includes_author_id + expected = { + data: { type: 'authors', id: @author.id.to_s } + } - 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_resource + @post.author = nil - expected = { data: nil } + 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_collection - @post.comments = [] + def test_explicit_serializer_with_null_collection + @post.comments = [] - expected = { data: [] } + expected = { data: [] } - assert_equal(expected, @adapter.serializable_hash[:data][:relationships][:comments]) - end + 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 a5753c6ed..d590b8dfd 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_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 } + } + }, { + 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_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 - def test_no_include_linked_if_comments_is_empty - serializer = PostSerializer.new(@post_without_comments) - adapter = ActiveModel::Serializer::Adapter::JsonApi.new(serializer) + 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 + 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] + 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 + expected = { + data: [{ + type: 'posts', + id: '1' + }] + } + assert_equal expected, actual + end - def test_has_many_with_no_serializer - serializer = PostWithTagsSerializer.new(@post) - adapter = ActiveModel::Serializer::Adapter::JsonApi.new(serializer) + 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] } - } + 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 7bc25ff6f..b346dcd08 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' } } - } + 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 + 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) + 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 }] } - } + 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 + 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 b205a4ec1..1b9e89ad8 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 c16147d66..bcf181232 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' } } - } + expected = { + data: [ + { + id: '10', + type: 'posts', + attributes: { + title: 'Hello!!', + body: 'Hello, world!!' }, - { - 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' } } - } + relationships: { + comments: { data: [{ type: 'comments', id: '1' }, { type: 'comments', id: '2' }] }, + blog: { data: { type: 'blogs', id: '999' } }, + author: { data: { type: 'authors', id: '1' } } } - ], - 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' } } - } + }, + { + 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' } } } - ] - } - 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 = [ + } + ], + 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 @@ def test_include_multiple_posts_and_linked 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 + assert_equal expected, alt_adapter.serializable_hash + end - assert_equal expected, adapter.serializable_hash[:included] - assert_equal expected, alt_adapter.serializable_hash[:included] - end + 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]] + ) - 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' - }] + expected = [ + { + 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: '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, 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] - ) + assert_equal expected, adapter.serializable_hash[:included] + assert_equal expected, alt_adapter.serializable_hash[:included] + end - expected = [ - { - id: '10', - type: 'posts', - attributes: { - title: 'Hello!!', - body: 'Hello, world!!' + 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' }] }, - relationships: { - comments: { - data: [{ type: 'comments', id: '1' }, { type: 'comments', id: '2' }] - }, - blog: { - data: { type: 'blogs', id: '999' } - }, - author: { - data: { type: 'authors', id: '1' } - } + blog: { + data: { type: 'blogs', id: '999' } + }, + author: { + data: { type: 'authors', id: '1' } } } - ] + } + ] - assert_equal expected, adapter.serializable_hash[:included] - end + 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] - ) + 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 } - } + 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 end + end - class NoDuplicatesTest < ActiveSupport::TestCase - Post = Class.new(::Model) - Author = Class.new(::Model) + class NoDuplicatesTest < ActiveSupport::TestCase + Post = Class.new(::Model) + Author = Class.new(::Model) - class PostSerializer < ActiveModel::Serializer - type 'posts' - belongs_to :author - end + class PostSerializer < ActiveModel::Serializer + type 'posts' + belongs_to :author + end - class AuthorSerializer < ActiveModel::Serializer - type 'authors' - has_many :posts - 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 + 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 + @nestedpost1 = ::NestedPost.new(id: 1, nested_posts: []) + @nestedpost2 = ::NestedPost.new(id: 2, nested_posts: []) + @nestedpost1.nested_posts << @nestedpost1 + @nestedpost1.nested_posts << @nestedpost2 + @nestedpost2.nested_posts << @nestedpost1 + @nestedpost2.nested_posts << @nestedpost2 + end - def test_no_duplicates - hash = ActiveModel::SerializableResource.new(@post1, adapter: :json_api, - include: '*.*') - .serializable_hash - expected = [ - { - type: 'authors', id: '1', - relationships: { - posts: { - data: [ - { type: 'posts', id: '1' }, - { type: 'posts', id: '2' } - ] - } - } - }, - { - type: 'posts', id: '2', - relationships: { - author: { - data: { type: 'authors', id: '1' } - } + 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' } + ] } } - ] - 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' } - ] - } + }, + { + type: 'posts', id: '2', + relationships: { + author: { + data: { type: 'authors', id: '1' } } } - ] - assert_equal(expected, hash[:included]) - end + } + ] + 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', + def test_no_duplicates_collection + hash = ActiveModel::SerializableResource.new( + [@post1, @post2], adapter: :json_api, + include: '*.*') + .serializable_hash + expected = [ + { + type: 'authors', id: '1', relationships: { - nested_posts: { + 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 + } + ] + 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 + 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 43e37dd7e..09c499ed3 100644 --- a/test/adapter/json_api/links_test.rb +++ b/test/adapter/json_api/links_test.rb @@ -1,84 +1,82 @@ 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 +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 - link :other, '//example.com/resource' + link :other, '//example.com/resource' - link :yet_another do - "//example.com/resource/#{object.id}" - end + 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, posts: [@post]) - end + def setup + @post = Post.new(id: 1337, comments: [], author: nil) + @author = LinkAuthor.new(id: 1337, posts: [@post]) + 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 = { + 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_nil_toplevel_links - hash = ActiveModel::SerializableResource.new( - @post, - adapter: :json_api, - links: nil - ).serializable_hash - refute hash.key?(:links), 'No links key to be output' - end + def test_nil_toplevel_links + hash = ActiveModel::SerializableResource.new( + @post, + adapter: :json_api, + links: nil + ).serializable_hash + refute hash.key?(:links), 'No links key to be output' + end - def test_nil_toplevel_links_json_adapter - hash = ActiveModel::SerializableResource.new( - @post, - adapter: :json, - links: nil - ).serializable_hash - refute hash.key?(:links), 'No links key to be output' - end + def test_nil_toplevel_links_json_adapter + hash = ActiveModel::SerializableResource.new( + @post, + adapter: :json, + links: nil + ).serializable_hash + refute hash.key?(:links), 'No links key to be output' + 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 805e5eb30..80046e102 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/hooks' ::Kaminari::Hooks.init -module ActiveModel - class Serializer - 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' }) - ] - end +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' }) + ] + 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 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 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_kaminari + Kaminari.paginate_array(@array).page(2).per(1) + end - def using_will_paginate - @array.paginate(page: 2, per_page: 1) - end + def using_will_paginate + @array.paginate(page: 2, per_page: 1) + end - def data - { data: [ - { id: '1', type: 'profiles', attributes: { name: 'Name 1', description: 'Description 1' } }, - { id: '2', type: 'profiles', attributes: { name: 'Name 2', description: 'Description 2' } }, - { id: '3', type: 'profiles', attributes: { name: 'Name 3', description: 'Description 3' } } - ] - } - end + def data + { data: [ + { id: '1', type: 'profiles', attributes: { name: 'Name 1', description: 'Description 1' } }, + { id: '2', type: 'profiles', attributes: { name: 'Name 2', description: 'Description 2' } }, + { id: '3', type: 'profiles', attributes: { name: 'Name 3', description: 'Description 3' } } + ] + } + end - def 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 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 + } + end - def expected_response_without_pagination_links - data - 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 + def expected_response_with_pagination_links + {}.tap do |hash| + hash[:data] = [data.values.flatten.second] + hash.merge! links end + 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 + 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_kaminari - adapter = load_adapter(using_kaminari) + 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_using_will_paginate - adapter = load_adapter(using_will_paginate) + def test_pagination_links_using_will_paginate + adapter = load_adapter(using_will_paginate) - 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_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 + 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) + 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 c80988168..bee79c8c1 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' => {} } - } - }] - end + @illformed_payloads = [nil, + {}, + { + 'data' => nil + }, { + 'data' => { 'attributes' => [] } + }, { + 'data' => { 'relationships' => [] } + }, { + 'data' => { + 'relationships' => { 'rel' => nil } + } + }, { + 'data' => { + 'relationships' => { 'rel' => {} } + } + }] + end - def test_hash - parsed_hash = ActiveModel::Serializer::Adapter::JsonApi::Deserialization.parse!(@hash) - assert_equal(@expected, parsed_hash) - 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 = ActiveModel::Serializer::Adapter::JsonApi::Deserialization.parse!(@params) - 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 = ActiveModel::Serializer::Adapter::JsonApi::Deserialization.parse(p) - assert_equal({}, 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_illformed_payloads_unsafe - @illformed_payloads.each do |p| - assert_raises(InvalidDocument) do - ActiveModel::Serializer::Adapter::JsonApi::Deserialization.parse!(p) - end + 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_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_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_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_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_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_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_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/toplevel_jsonapi_test.rb b/test/adapter/json_api/toplevel_jsonapi_test.rb index e0c410ac3..7b0357e52 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 ab4e756aa..a7d3bc834 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 9d9896035..3dd666b07 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 1253882af..a3700c154 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) - end +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 + 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 = ActiveModel::Serializer::Adapter.create(@serializer) - assert_equal ActiveModel::Serializer::Adapter::Attributes, adapter.class - end + def test_create_adapter + adapter = ActiveModelSerializers::Adapter.create(@serializer) + assert_equal ActiveModelSerializers::Adapter::Attributes, adapter.class + end - def test_create_adapter_with_override - adapter = ActiveModel::Serializer::Adapter.create(@serializer, { adapter: :json_api }) - assert_equal ActiveModel::Serializer::Adapter::JsonApi, adapter.class - 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_inflected_adapter_class_for_known_adapter - ActiveSupport::Inflector.inflections(:en) { |inflect| inflect.acronym 'API' } - klass = ActiveModel::Serializer::Adapter.adapter_class(:json_api) + def test_inflected_adapter_class_for_known_adapter + ActiveSupport::Inflector.inflections(:en) { |inflect| inflect.acronym 'API' } + klass = ActiveModelSerializers::Adapter.adapter_class(:json_api) - ActiveSupport::Inflector.inflections.acronyms.clear + ActiveSupport::Inflector.inflections.acronyms.clear - assert_equal ActiveModel::Serializer::Adapter::JsonApi, klass - end + assert_equal ActiveModelSerializers::Adapter::JsonApi, klass end end end diff --git a/test/array_serializer_test.rb b/test/array_serializer_test.rb index 350e44473..50028371a 100644 --- a/test/array_serializer_test.rb +++ b/test/array_serializer_test.rb @@ -6,11 +6,11 @@ class Serializer # Minitest.run_one_method isn't present in minitest 4 if $minitest_version > 4 # rubocop:disable Style/GlobalVars class ArraySerializerTest < CollectionSerializerTest - extend ActiveSupport::Testing::Stream + extend Minitest::Assertions def self.run_one_method(*) - stderr = (capture(:stderr) do + _, stderr = capture_io do super - end) + end if stderr !~ /Calling deprecated ArraySerializer/ fail Minitest::Assertion, stderr end @@ -22,14 +22,13 @@ def collection_serializer end else class ArraySerializerTest < ActiveSupport::TestCase - extend ActiveSupport::Testing::Stream def test_json_key_with_root_warns_when_using_array_serializer - stderr = (capture(:stderr) do + _, stderr = capture_io do comment = Comment.new post = Post.new serializer = ArraySerializer.new([comment, post]) assert_equal 'comments', serializer.json_key - end) + end assert_match(/Calling deprecated ArraySerializer/, stderr) end end diff --git a/test/serializable_resource_test.rb b/test/serializable_resource_test.rb index 339b22a59..698795040 100644 --- a/test/serializable_resource_test.rb +++ b/test/serializable_resource_test.rb @@ -5,7 +5,7 @@ class SerializableResourceTest < ActiveSupport::TestCase 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 1775620d2..000000000 --- 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 c675e0aca..198be84cd 100644 --- a/test/serializers/attribute_test.rb +++ b/test/serializers/attribute_test.rb @@ -14,14 +14,14 @@ def test_attributes_definition 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 @@ def test_id_attribute_override 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 @@ def test_object_attribute_override 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 @@ def test_type_attribute 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 diff --git a/test/serializers/cache_test.rb b/test/serializers/cache_test.rb index 4290947bc..7c76d2704 100644 --- a/test/serializers/cache_test.rb +++ b/test/serializers/cache_test.rb @@ -1,241 +1,236 @@ require 'test_helper' require 'tmpdir' require 'tempfile' -module ActiveModel - class Serializer - class CacheTest < ActiveSupport::TestCase - include ActiveSupport::Testing::Stream - - def setup - ActionController::Base.cache_store.clear - @comment = Comment.new(id: 1, body: 'ZOMG A COMMENT') - @post = Post.new(title: 'New Post', body: 'Body') - @bio = Bio.new(id: 1, content: 'AMS Contributor') - @author = Author.new(name: 'Joao M. D. Moura') - @blog = Blog.new(id: 999, name: 'Custom blog', writer: @author, articles: []) - @role = Role.new(name: 'Great Author') - @location = Location.new(lat: '-23.550520', lng: '-46.633309') - @place = Place.new(name: 'Amazing Place') - @author.posts = [@post] - @author.roles = [@role] - @role.author = @author - @author.bio = @bio - @bio.author = @author - @post.comments = [@comment] - @post.author = @author - @comment.post = @post - @comment.author = @author - @post.blog = @blog - @location.place = @place - - @location_serializer = LocationSerializer.new(@location) - @bio_serializer = BioSerializer.new(@bio) - @role_serializer = RoleSerializer.new(@role) - @post_serializer = PostSerializer.new(@post) - @author_serializer = AuthorSerializer.new(@author) - @comment_serializer = CommentSerializer.new(@comment) - @blog_serializer = BlogSerializer.new(@blog) - end - - def test_inherited_cache_configuration - inherited_serializer = Class.new(PostSerializer) +module ActiveModelSerializers + class CacheTest < ActiveSupport::TestCase + def setup + ActionController::Base.cache_store.clear + @comment = Comment.new(id: 1, body: 'ZOMG A COMMENT') + @post = Post.new(title: 'New Post', body: 'Body') + @bio = Bio.new(id: 1, content: 'AMS Contributor') + @author = Author.new(name: 'Joao M. D. Moura') + @blog = Blog.new(id: 999, name: 'Custom blog', writer: @author, articles: []) + @role = Role.new(name: 'Great Author') + @location = Location.new(lat: '-23.550520', lng: '-46.633309') + @place = Place.new(name: 'Amazing Place') + @author.posts = [@post] + @author.roles = [@role] + @role.author = @author + @author.bio = @bio + @bio.author = @author + @post.comments = [@comment] + @post.author = @author + @comment.post = @post + @comment.author = @author + @post.blog = @blog + @location.place = @place + + @location_serializer = LocationSerializer.new(@location) + @bio_serializer = BioSerializer.new(@bio) + @role_serializer = RoleSerializer.new(@role) + @post_serializer = PostSerializer.new(@post) + @author_serializer = AuthorSerializer.new(@author) + @comment_serializer = CommentSerializer.new(@comment) + @blog_serializer = BlogSerializer.new(@blog) + end - assert_equal PostSerializer._cache_key, inherited_serializer._cache_key - assert_equal PostSerializer._cache_options, inherited_serializer._cache_options - end + def test_inherited_cache_configuration + inherited_serializer = Class.new(PostSerializer) - def test_override_cache_configuration - inherited_serializer = Class.new(PostSerializer) do - cache key: 'new-key' - end + assert_equal PostSerializer._cache_key, inherited_serializer._cache_key + assert_equal PostSerializer._cache_options, inherited_serializer._cache_options + end - assert_equal PostSerializer._cache_key, 'post' - assert_equal inherited_serializer._cache_key, 'new-key' + def test_override_cache_configuration + inherited_serializer = Class.new(PostSerializer) do + cache key: 'new-key' end - def test_cache_definition - assert_equal(ActionController::Base.cache_store, @post_serializer.class._cache) - assert_equal(ActionController::Base.cache_store, @author_serializer.class._cache) - assert_equal(ActionController::Base.cache_store, @comment_serializer.class._cache) - end + assert_equal PostSerializer._cache_key, 'post' + assert_equal inherited_serializer._cache_key, 'new-key' + end - def test_cache_key_definition - assert_equal('post', @post_serializer.class._cache_key) - assert_equal('writer', @author_serializer.class._cache_key) - assert_equal(nil, @comment_serializer.class._cache_key) - end + def test_cache_definition + assert_equal(ActionController::Base.cache_store, @post_serializer.class._cache) + assert_equal(ActionController::Base.cache_store, @author_serializer.class._cache) + assert_equal(ActionController::Base.cache_store, @comment_serializer.class._cache) + end - def test_cache_key_interpolation_with_updated_at - render_object_with_cache(@author) - assert_equal(nil, ActionController::Base.cache_store.fetch(@author.cache_key)) - assert_equal(@author_serializer.attributes.to_json, ActionController::Base.cache_store.fetch("#{@author_serializer.class._cache_key}/#{@author_serializer.object.id}-#{@author_serializer.object.updated_at.strftime("%Y%m%d%H%M%S%9N")}").to_json) - end + def test_cache_key_definition + assert_equal('post', @post_serializer.class._cache_key) + assert_equal('writer', @author_serializer.class._cache_key) + assert_equal(nil, @comment_serializer.class._cache_key) + end - def test_default_cache_key_fallback - render_object_with_cache(@comment) - assert_equal(@comment_serializer.attributes.to_json, ActionController::Base.cache_store.fetch(@comment.cache_key).to_json) - end + def test_cache_key_interpolation_with_updated_at + render_object_with_cache(@author) + assert_equal(nil, ActionController::Base.cache_store.fetch(@author.cache_key)) + assert_equal(@author_serializer.attributes.to_json, ActionController::Base.cache_store.fetch("#{@author_serializer.class._cache_key}/#{@author_serializer.object.id}-#{@author_serializer.object.updated_at.strftime("%Y%m%d%H%M%S%9N")}").to_json) + end - def test_cache_options_definition - assert_equal({ expires_in: 0.1, skip_digest: true }, @post_serializer.class._cache_options) - assert_equal(nil, @blog_serializer.class._cache_options) - assert_equal({ expires_in: 1.day, skip_digest: true }, @comment_serializer.class._cache_options) - end + def test_default_cache_key_fallback + render_object_with_cache(@comment) + assert_equal(@comment_serializer.attributes.to_json, ActionController::Base.cache_store.fetch(@comment.cache_key).to_json) + end - def test_fragment_cache_definition - assert_equal([:name], @role_serializer.class._cache_only) - assert_equal([:content], @bio_serializer.class._cache_except) - end + def test_cache_options_definition + assert_equal({ expires_in: 0.1, skip_digest: true }, @post_serializer.class._cache_options) + assert_equal(nil, @blog_serializer.class._cache_options) + assert_equal({ expires_in: 1.day, skip_digest: true }, @comment_serializer.class._cache_options) + end - def test_associations_separately_cache - ActionController::Base.cache_store.clear - assert_equal(nil, ActionController::Base.cache_store.fetch(@post.cache_key)) - assert_equal(nil, ActionController::Base.cache_store.fetch(@comment.cache_key)) + def test_fragment_cache_definition + assert_equal([:name], @role_serializer.class._cache_only) + assert_equal([:content], @bio_serializer.class._cache_except) + end + + def test_associations_separately_cache + ActionController::Base.cache_store.clear + assert_equal(nil, ActionController::Base.cache_store.fetch(@post.cache_key)) + assert_equal(nil, ActionController::Base.cache_store.fetch(@comment.cache_key)) - Timecop.freeze(Time.now) do - render_object_with_cache(@post) + Timecop.freeze(Time.now) do + render_object_with_cache(@post) - assert_equal(@post_serializer.attributes, ActionController::Base.cache_store.fetch(@post.cache_key)) - assert_equal(@comment_serializer.attributes, ActionController::Base.cache_store.fetch(@comment.cache_key)) - end + assert_equal(@post_serializer.attributes, ActionController::Base.cache_store.fetch(@post.cache_key)) + assert_equal(@comment_serializer.attributes, ActionController::Base.cache_store.fetch(@comment.cache_key)) end + end - def test_associations_cache_when_updated - # Clean the Cache - ActionController::Base.cache_store.clear + def test_associations_cache_when_updated + # Clean the Cache + ActionController::Base.cache_store.clear - Timecop.freeze(Time.now) do - # Generate a new Cache of Post object and each objects related to it. - render_object_with_cache(@post) + Timecop.freeze(Time.now) do + # Generate a new Cache of Post object and each objects related to it. + render_object_with_cache(@post) - # Check if it cached the objects separately - assert_equal(@post_serializer.attributes, ActionController::Base.cache_store.fetch(@post.cache_key)) - assert_equal(@comment_serializer.attributes, ActionController::Base.cache_store.fetch(@comment.cache_key)) + # Check if it cached the objects separately + assert_equal(@post_serializer.attributes, ActionController::Base.cache_store.fetch(@post.cache_key)) + assert_equal(@comment_serializer.attributes, ActionController::Base.cache_store.fetch(@comment.cache_key)) - # Simulating update on comments relationship with Post - new_comment = Comment.new(id: 2, body: 'ZOMG A NEW COMMENT') - new_comment_serializer = CommentSerializer.new(new_comment) - @post.comments = [new_comment] + # Simulating update on comments relationship with Post + new_comment = Comment.new(id: 2, body: 'ZOMG A NEW COMMENT') + new_comment_serializer = CommentSerializer.new(new_comment) + @post.comments = [new_comment] - # Ask for the serialized object - render_object_with_cache(@post) + # Ask for the serialized object + render_object_with_cache(@post) - # Check if the the new comment was cached - assert_equal(new_comment_serializer.attributes, ActionController::Base.cache_store.fetch(new_comment.cache_key)) - assert_equal(@post_serializer.attributes, ActionController::Base.cache_store.fetch(@post.cache_key)) - end + # Check if the the new comment was cached + assert_equal(new_comment_serializer.attributes, ActionController::Base.cache_store.fetch(new_comment.cache_key)) + assert_equal(@post_serializer.attributes, ActionController::Base.cache_store.fetch(@post.cache_key)) end + end - def test_fragment_fetch_with_virtual_associations - expected_result = { - id: @location.id, - lat: @location.lat, - lng: @location.lng, - place: 'Nowhere' - } + def test_fragment_fetch_with_virtual_associations + expected_result = { + id: @location.id, + lat: @location.lat, + lng: @location.lng, + place: 'Nowhere' + } - hash = render_object_with_cache(@location) + hash = render_object_with_cache(@location) - assert_equal(hash, expected_result) - assert_equal({ place: 'Nowhere' }, ActionController::Base.cache_store.fetch(@location.cache_key)) - end + assert_equal(hash, expected_result) + assert_equal({ place: 'Nowhere' }, ActionController::Base.cache_store.fetch(@location.cache_key)) + end - def test_uses_file_digest_in_cache_key - render_object_with_cache(@blog) - assert_equal(@blog_serializer.attributes, ActionController::Base.cache_store.fetch(@blog.cache_key_with_digest)) - end + def test_uses_file_digest_in_cache_key + render_object_with_cache(@blog) + assert_equal(@blog_serializer.attributes, ActionController::Base.cache_store.fetch(@blog.cache_key_with_digest)) + end - def test_cache_digest_definition - assert_equal(::Model::FILE_DIGEST, @post_serializer.class._cache_digest) - end + def test_cache_digest_definition + assert_equal(::Model::FILE_DIGEST, @post_serializer.class._cache_digest) + end - def test_object_cache_keys - serializer = CollectionSerializer.new([@comment, @comment]) - include_tree = IncludeTree.from_include_args('*') + def test_object_cache_keys + serializer = ActiveModel::Serializer::CollectionSerializer.new([@comment, @comment]) + include_tree = ActiveModel::Serializer::IncludeTree.from_include_args('*') - actual = Serializer::Adapter::CachedSerializer.object_cache_keys(serializer, include_tree) + actual = Adapter::CachedSerializer.object_cache_keys(serializer, include_tree) - assert_equal actual.size, 3 - assert actual.any? { |key| key == 'comment/1' } - assert actual.any? { |key| key =~ %r{post/post-\d+} } - assert actual.any? { |key| key =~ %r{writer/author-\d+} } - end + assert_equal actual.size, 3 + assert actual.any? { |key| key == 'comment/1' } + assert actual.any? { |key| key =~ %r{post/post-\d+} } + assert actual.any? { |key| key =~ %r{writer/author-\d+} } + end - def test_cached_attributes - serializer = CollectionSerializer.new([@comment, @comment]) + def test_cached_attributes + serializer = ActiveModel::Serializer::CollectionSerializer.new([@comment, @comment]) - Timecop.freeze(Time.now) do - render_object_with_cache(@comment) + Timecop.freeze(Time.now) do + render_object_with_cache(@comment) - attributes = ActiveModel::Serializer::Adapter::Attributes.new(serializer) - attributes.send(:cache_attributes) - cached_attributes = attributes.instance_variable_get(:@cached_attributes) + attributes = Adapter::Attributes.new(serializer) + attributes.send(:cache_attributes) + cached_attributes = attributes.instance_variable_get(:@cached_attributes) - assert_equal cached_attributes[@comment.cache_key], Comment.new(id: 1, body: 'ZOMG A COMMENT').attributes - assert_equal cached_attributes[@comment.post.cache_key], Post.new(id: 'post', title: 'New Post', body: 'Body').attributes + assert_equal cached_attributes[@comment.cache_key], Comment.new(id: 1, body: 'ZOMG A COMMENT').attributes + assert_equal cached_attributes[@comment.post.cache_key], Post.new(id: 'post', title: 'New Post', body: 'Body').attributes - writer = @comment.post.blog.writer - writer_cache_key = "writer/#{writer.id}-#{writer.updated_at.strftime("%Y%m%d%H%M%S%9N")}" + writer = @comment.post.blog.writer + writer_cache_key = "writer/#{writer.id}-#{writer.updated_at.strftime("%Y%m%d%H%M%S%9N")}" - assert_equal cached_attributes[writer_cache_key], Author.new(id: 'author', name: 'Joao M. D. Moura').attributes - end + assert_equal cached_attributes[writer_cache_key], Author.new(id: 'author', name: 'Joao M. D. Moura').attributes end + end - def test_serializer_file_path_on_nix - path = '/Users/git/emberjs/ember-crm-backend/app/serializers/lead_serializer.rb' - caller_line = "#{path}:1:in `'" - assert_equal caller_line[ActiveModel::Serializer::CALLER_FILE], path - end + def test_serializer_file_path_on_nix + path = '/Users/git/emberjs/ember-crm-backend/app/serializers/lead_serializer.rb' + caller_line = "#{path}:1:in `'" + assert_equal caller_line[ActiveModel::Serializer::CALLER_FILE], path + end - def test_serializer_file_path_on_windows - path = 'c:/git/emberjs/ember-crm-backend/app/serializers/lead_serializer.rb' - caller_line = "#{path}:1:in `'" - assert_equal caller_line[ActiveModel::Serializer::CALLER_FILE], path - end + def test_serializer_file_path_on_windows + path = 'c:/git/emberjs/ember-crm-backend/app/serializers/lead_serializer.rb' + caller_line = "#{path}:1:in `'" + assert_equal caller_line[ActiveModel::Serializer::CALLER_FILE], path + end - def test_serializer_file_path_with_space - path = '/Users/git/ember js/ember-crm-backend/app/serializers/lead_serializer.rb' - caller_line = "#{path}:1:in `'" - assert_equal caller_line[ActiveModel::Serializer::CALLER_FILE], path - end + def test_serializer_file_path_with_space + path = '/Users/git/ember js/ember-crm-backend/app/serializers/lead_serializer.rb' + caller_line = "#{path}:1:in `'" + assert_equal caller_line[ActiveModel::Serializer::CALLER_FILE], path + end - def test_serializer_file_path_with_submatch - # The submatch in the path ensures we're using a correctly greedy regexp. - path = '/Users/git/ember js/ember:123:in x/app/serializers/lead_serializer.rb' - caller_line = "#{path}:1:in `'" - assert_equal caller_line[ActiveModel::Serializer::CALLER_FILE], path - end + def test_serializer_file_path_with_submatch + # The submatch in the path ensures we're using a correctly greedy regexp. + path = '/Users/git/ember js/ember:123:in x/app/serializers/lead_serializer.rb' + caller_line = "#{path}:1:in `'" + assert_equal caller_line[ActiveModel::Serializer::CALLER_FILE], path + end - def test_digest_caller_file - contents = "puts 'AMS rocks'!" - dir = Dir.mktmpdir('space char') - file = Tempfile.new('some_ruby.rb', dir) - file.write(contents) - path = file.path - caller_line = "#{path}:1:in `'" - file.close - assert_equal ActiveModel::Serializer.digest_caller_file(caller_line), Digest::MD5.hexdigest(contents) - ensure - file.unlink - FileUtils.remove_entry dir - end + def test_digest_caller_file + contents = "puts 'AMS rocks'!" + dir = Dir.mktmpdir('space char') + file = Tempfile.new('some_ruby.rb', dir) + file.write(contents) + path = file.path + caller_line = "#{path}:1:in `'" + file.close + assert_equal ActiveModel::Serializer.digest_caller_file(caller_line), Digest::MD5.hexdigest(contents) + ensure + file.unlink + FileUtils.remove_entry dir + end - def test_warn_on_serializer_not_defined_in_file - called = false - serializer = Class.new(ActiveModel::Serializer) - assert_match(/_cache_digest/, (capture(:stderr) do - serializer.digest_caller_file('') - called = true - end)) - assert called + def test_warn_on_serializer_not_defined_in_file + called = false + serializer = Class.new(ActiveModel::Serializer) + assert_output(nil, /_cache_digest/) do + serializer.digest_caller_file('') + called = true end + assert called + end - private + private - def render_object_with_cache(obj) - ActiveModel::SerializableResource.new(obj).serializable_hash - end + def render_object_with_cache(obj) + ActiveModel::SerializableResource.new(obj).serializable_hash end end end - diff --git a/test/serializers/cached_serializer_test.rb b/test/serializers/cached_serializer_test.rb index 6f31f9e24..e9b3ff455 100644 --- a/test/serializers/cached_serializer_test.rb +++ b/test/serializers/cached_serializer_test.rb @@ -1,81 +1,79 @@ require 'test_helper' -module ActiveModel - class Serializer - module Adapter - class CachedSerializerTest < ActiveSupport::TestCase - def test_cached_false_without_cache_store - cached_serializer = build do |serializer| - serializer._cache = nil - end - refute cached_serializer.cached? +module ActiveModelSerializers + module Adapter + class CachedSerializerTest < ActiveSupport::TestCase + def test_cached_false_without_cache_store + cached_serializer = build do |serializer| + serializer._cache = nil end + refute cached_serializer.cached? + end - def test_cached_true_with_cache_store_and_without_cache_only_and_cache_except - cached_serializer = build do |serializer| - serializer._cache = Object - end - assert cached_serializer.cached? + def test_cached_true_with_cache_store_and_without_cache_only_and_cache_except + cached_serializer = build do |serializer| + serializer._cache = Object end + assert cached_serializer.cached? + end - def test_cached_false_with_cache_store_and_with_cache_only - cached_serializer = build do |serializer| - serializer._cache = Object - serializer._cache_only = [:name] - end - refute cached_serializer.cached? + def test_cached_false_with_cache_store_and_with_cache_only + cached_serializer = build do |serializer| + serializer._cache = Object + serializer._cache_only = [:name] end + refute cached_serializer.cached? + end - def test_cached_false_with_cache_store_and_with_cache_except - cached_serializer = build do |serializer| - serializer._cache = Object - serializer._cache_except = [:content] - end - refute cached_serializer.cached? + def test_cached_false_with_cache_store_and_with_cache_except + cached_serializer = build do |serializer| + serializer._cache = Object + serializer._cache_except = [:content] end + refute cached_serializer.cached? + end - def test_fragment_cached_false_without_cache_store - cached_serializer = build do |serializer| - serializer._cache = nil - serializer._cache_only = [:name] - end - refute cached_serializer.fragment_cached? + def test_fragment_cached_false_without_cache_store + cached_serializer = build do |serializer| + serializer._cache = nil + serializer._cache_only = [:name] end + refute cached_serializer.fragment_cached? + end - def test_fragment_cached_true_with_cache_store_and_cache_only - cached_serializer = build do |serializer| - serializer._cache = Object - serializer._cache_only = [:name] - end - assert cached_serializer.fragment_cached? + def test_fragment_cached_true_with_cache_store_and_cache_only + cached_serializer = build do |serializer| + serializer._cache = Object + serializer._cache_only = [:name] end + assert cached_serializer.fragment_cached? + end - def test_fragment_cached_true_with_cache_store_and_cache_except - cached_serializer = build do |serializer| - serializer._cache = Object - serializer._cache_except = [:content] - end - assert cached_serializer.fragment_cached? + def test_fragment_cached_true_with_cache_store_and_cache_except + cached_serializer = build do |serializer| + serializer._cache = Object + serializer._cache_except = [:content] end + assert cached_serializer.fragment_cached? + end - def test_fragment_cached_false_with_cache_store_and_cache_except_and_cache_only - cached_serializer = build do |serializer| - serializer._cache = Object - serializer._cache_except = [:content] - serializer._cache_only = [:name] - end - refute cached_serializer.fragment_cached? + def test_fragment_cached_false_with_cache_store_and_cache_except_and_cache_only + cached_serializer = build do |serializer| + serializer._cache = Object + serializer._cache_except = [:content] + serializer._cache_only = [:name] end + refute cached_serializer.fragment_cached? + end - private + private - def build - serializer = Class.new(ActiveModel::Serializer) - serializer._cache_key = nil - serializer._cache_options = nil - yield serializer if block_given? - serializer_instance = serializer.new(Object) - ActiveModel::Serializer::Adapter::CachedSerializer.new(serializer_instance) - end + def build + serializer = Class.new(ActiveModel::Serializer) + serializer._cache_key = nil + serializer._cache_options = nil + yield serializer if block_given? + serializer_instance = serializer.new(Object) + CachedSerializer.new(serializer_instance) end end end diff --git a/test/support/stream_capture.rb b/test/support/stream_capture.rb deleted file mode 100644 index da6acba3b..000000000 --- a/test/support/stream_capture.rb +++ /dev/null @@ -1,50 +0,0 @@ -# Use cleaner stream testing interface from Rails 5 if available -# see https://github.com/rails/rails/blob/29959eb59d/activesupport/lib/active_support/testing/stream.rb -begin - require 'active_support/testing/stream' -rescue LoadError - require 'tempfile' - module ActiveSupport - module Testing - module Stream #:nodoc: - private - - def silence_stream(stream) - old_stream = stream.dup - stream.reopen(IO::NULL) - stream.sync = true - yield - ensure - stream.reopen(old_stream) - old_stream.close - end - - def quietly - silence_stream(STDOUT) do - silence_stream(STDERR) do - yield - end - end - end - - def capture(stream) - stream = stream.to_s - captured_stream = Tempfile.new(stream) - stream_io = eval("$#{stream}") # rubocop:disable Lint/Eval - origin_stream = stream_io.dup - stream_io.reopen(captured_stream) - - yield - - stream_io.rewind - return captured_stream.read - ensure - captured_stream.close - captured_stream.unlink - stream_io.reopen(origin_stream) - end - end - end - end -end - diff --git a/test/test_helper.rb b/test/test_helper.rb index 59c0d3f41..4a6950d35 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -43,8 +43,6 @@ require 'minitest/reporters' Minitest::Reporters.use! -require 'support/stream_capture' - require 'support/rails_app' require 'support/test_case'