From 589a5806ab06db4baeac3540241ef5d359e50a79 Mon Sep 17 00:00:00 2001 From: Josh Smith Date: Sat, 28 Mar 2015 00:28:55 -0700 Subject: [PATCH 01/33] Add issue stats to README We should probably make it clearer how active the development is and also how quickly we close issues. Will help motivate us to do better and also clarify to everyone else that we are _really_ active. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index dae1b7e0..660a6db0 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # ActiveModel::Serializers -[![Build Status](https://travis-ci.org/rails-api/active_model_serializers.svg)](https://travis-ci.org/rails-api/active_model_serializers) +[![Build Status](https://travis-ci.org/rails-api/active_model_serializers.svg)](https://travis-ci.org/rails-api/active_model_serializers) [![Issue Stats](http://issuestats.com/github/rails-api/active_model_serializers/badge/pr)](http://issuestats.com/github/rails-api/active_model_serializers) [![Issue Stats](http://issuestats.com/github/rails-api/active_model_serializers/badge/issue)](http://issuestats.com/github/rails-api/active_model_serializers) ActiveModel::Serializers brings convention over configuration to your JSON generation. From 43312fa083a28729f4314e9496a92a3bc7b1f507 Mon Sep 17 00:00:00 2001 From: lcp Date: Fri, 11 Dec 2015 17:38:01 +0800 Subject: [PATCH 02/33] support read_multi --- .../serializer/adapter/attributes.rb | 38 ++++++++++++++++++- .../serializer/adapter/cached_serializer.rb | 36 +++++++++++++++++- test/serializers/cache_test.rb | 32 ++++++++++++++++ 3 files changed, 103 insertions(+), 3 deletions(-) diff --git a/lib/active_model/serializer/adapter/attributes.rb b/lib/active_model/serializer/adapter/attributes.rb index 49dea860..657cd1f1 100644 --- a/lib/active_model/serializer/adapter/attributes.rb +++ b/lib/active_model/serializer/adapter/attributes.rb @@ -5,6 +5,7 @@ module ActiveModel def initialize(serializer, options = {}) super @include_tree = IncludeTree.from_include_args(options[:include] || '*') + @cached_attributes = options[:cache_attributes] || {} end def serializable_hash(options = nil) @@ -24,9 +25,38 @@ module ActiveModel 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) @@ -56,8 +86,12 @@ module ActiveModel end def resource_object_for(options) - cache_check(serializer) do - serializer.attributes(options[:fields]) + cached_serializer = CachedSerializer.new(serializer) + + cached_attributes(cached_serializer) do + cached_serializer.cache_check(self) do + serializer.attributes(options[:fields]) + end end end end diff --git a/lib/active_model/serializer/adapter/cached_serializer.rb b/lib/active_model/serializer/adapter/cached_serializer.rb index 35b10168..358942bf 100644 --- a/lib/active_model/serializer/adapter/cached_serializer.rb +++ b/lib/active_model/serializer/adapter/cached_serializer.rb @@ -28,10 +28,12 @@ module ActiveModel 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] - parts.join('/') + @cache_key = parts.join('/') end def object_cache_key @@ -39,6 +41,38 @@ module ActiveModel 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/test/serializers/cache_test.rb b/test/serializers/cache_test.rb index fea7f8f1..4290947b 100644 --- a/test/serializers/cache_test.rb +++ b/test/serializers/cache_test.rb @@ -149,6 +149,38 @@ module ActiveModel assert_equal(::Model::FILE_DIGEST, @post_serializer.class._cache_digest) end + def test_object_cache_keys + serializer = CollectionSerializer.new([@comment, @comment]) + include_tree = IncludeTree.from_include_args('*') + + actual = Serializer::Adapter::CachedSerializer.object_cache_keys(serializer, include_tree) + + assert_equal actual.size, 3 + assert actual.any? { |key| key == 'comment/1' } + assert actual.any? { |key| key =~ %r{post/post-\d+} } + assert actual.any? { |key| key =~ %r{writer/author-\d+} } + end + + def test_cached_attributes + serializer = CollectionSerializer.new([@comment, @comment]) + + Timecop.freeze(Time.now) do + render_object_with_cache(@comment) + + attributes = ActiveModel::Serializer::Adapter::Attributes.new(serializer) + attributes.send(:cache_attributes) + cached_attributes = attributes.instance_variable_get(:@cached_attributes) + + assert_equal cached_attributes[@comment.cache_key], Comment.new(id: 1, body: 'ZOMG A COMMENT').attributes + assert_equal cached_attributes[@comment.post.cache_key], Post.new(id: 'post', title: 'New Post', body: 'Body').attributes + + writer = @comment.post.blog.writer + writer_cache_key = "writer/#{writer.id}-#{writer.updated_at.strftime("%Y%m%d%H%M%S%9N")}" + + assert_equal cached_attributes[writer_cache_key], Author.new(id: 'author', name: 'Joao M. D. Moura').attributes + end + end + def test_serializer_file_path_on_nix path = '/Users/git/emberjs/ember-crm-backend/app/serializers/lead_serializer.rb' caller_line = "#{path}:1:in `'" From 20ddc5e102f45b3f731159ea9bc86b9dc6a96b10 Mon Sep 17 00:00:00 2001 From: Lucas Hosseini Date: Wed, 20 Jan 2016 00:28:07 +0100 Subject: [PATCH 03/33] Refactor JsonApi adapter to avoid redundant computations. --- .../serializer/adapter/json_api.rb | 103 ++++++++---------- 1 file changed, 45 insertions(+), 58 deletions(-) diff --git a/lib/active_model/serializer/adapter/json_api.rb b/lib/active_model/serializer/adapter/json_api.rb index 744d62e4..1343e123 100644 --- a/lib/active_model/serializer/adapter/json_api.rb +++ b/lib/active_model/serializer/adapter/json_api.rb @@ -52,12 +52,13 @@ module ActiveModel def serializable_hash(options = nil) options ||= {} - hash = - if serializer.respond_to?(:each) - serializable_hash_for_collection(options) - else - serializable_hash_for_single_resource - end + 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) @@ -66,6 +67,11 @@ module ActiveModel 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 @@ -80,37 +86,45 @@ module ActiveModel private - def serializable_hash_for_collection(options) - hash = { data: [] } - included = [] - serializer.each do |s| - result = self.class.new(s, instance_options.merge(fieldset: fieldset)).serializable_hash(options) - hash[:data] << result[:data] - next unless result[:included] + 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) } - included |= result[:included] - end - - included.delete_if { |resource| hash[:data].include?(resource) } - hash[:included] = included if included.any? - - if serializer.paginated? - hash[:links] ||= {} - hash[:links].update(pagination_links_for(serializer, options)) - end - - hash + [@primary, @included] end - def serializable_hash_for_single_resource - primary_data = resource_object_for(serializer) + def process_resource(serializer, primary) + resource_identifier = resource_identifier_for(serializer) + return false unless @resource_identifiers.add?(resource_identifier) - hash = { data: primary_data } + resource_object = resource_object_for(serializer) + if primary + @primary << resource_object + else + @included << resource_object + end - included = included_resources(@include_tree, [primary_data]) - hash[:included] = included if included.any? + true + end - hash + def process_relationships(serializer, include_tree) + serializer.associations(include_tree).each do |association| + process_relationship(association.serializer, include_tree[association.key]) + end + end + + def process_relationship(serializer, include_tree) + if serializer.respond_to?(:each) + serializer.each { |s| process_relationship(s, include_tree) } + return + end + return unless serializer && serializer.object + return unless process_resource(serializer, false) + + process_relationships(serializer, include_tree) end def resource_identifier_type_for(serializer) @@ -181,33 +195,6 @@ module ActiveModel end end - def included_resources(include_tree, primary_data) - included = [] - - serializer.associations(include_tree).each do |association| - add_included_resources_for(association.serializer, include_tree[association.key], primary_data, included) - end - - included - end - - def add_included_resources_for(serializer, include_tree, primary_data, included) - if serializer.respond_to?(:each) - serializer.each { |s| add_included_resources_for(s, include_tree, primary_data, included) } - else - return unless serializer && serializer.object - - resource_object = resource_object_for(serializer) - - return if included.include?(resource_object) || primary_data.include?(resource_object) - included.push(resource_object) - - serializer.associations(include_tree).each do |association| - add_included_resources_for(association.serializer, include_tree[association.key], primary_data, included) - end - end - end - def links_for(serializer) serializer._links.each_with_object({}) do |(name, value), hash| hash[name] = Link.new(serializer, value).as_json From 0bd5c6584fc9f258782927e905115e4ee72a1a8e Mon Sep 17 00:00:00 2001 From: Lucas Hosseini Date: Sun, 22 Nov 2015 23:21:13 +0100 Subject: [PATCH 04/33] Add support for resource-level meta. --- lib/active_model/serializer.rb | 25 +++++++++++++++++++ .../serializer/adapter/json_api.rb | 3 +++ 2 files changed, 28 insertions(+) diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index 714ff65d..5e56ec17 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -23,6 +23,23 @@ module ActiveModel include Type require 'active_model/serializer/adapter' + with_options instance_writer: false, instance_reader: false do |serializer| + serializer.class_attribute :_meta # @api private : meta definition, @see Serializer#meta + end + + # Register a meta attribute for the corresponding resource. + # + # @param [Hash] hash Optional hash + # @param [Block] block Optional block + def self.meta(hash = nil, &block) + self._meta = + if !block.nil? + block + else + hash + end + end + # @param resource [ActiveRecord::Base, ActiveModelSerializers::Model] # @return [ActiveModel::Serializer] # Preferentially returns @@ -128,6 +145,14 @@ module ActiveModel end end + def meta + if self.class._meta.respond_to?(:call) + instance_eval(&self.class._meta) + else + self.class._meta + end + end + protected attr_accessor :instance_options diff --git a/lib/active_model/serializer/adapter/json_api.rb b/lib/active_model/serializer/adapter/json_api.rb index 744d62e4..b72ec6e1 100644 --- a/lib/active_model/serializer/adapter/json_api.rb +++ b/lib/active_model/serializer/adapter/json_api.rb @@ -157,6 +157,9 @@ module ActiveModel links = links_for(serializer) resource_object[:links] = links if links.any? + meta = serializer.meta + resource_object[:meta] = meta unless meta.nil? + resource_object end From 207c85f0fd3fc75277406f257ea72421c5990623 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Chac=C3=B3n?= Date: Fri, 27 Nov 2015 09:56:39 -0800 Subject: [PATCH 05/33] Add tests for meta on resource objects. --- test/adapter/json_api/resource_meta_test.rb | 63 +++++++++++++++++++++ test/serializers/meta_test.rb | 16 ++++++ 2 files changed, 79 insertions(+) create mode 100644 test/adapter/json_api/resource_meta_test.rb diff --git a/test/adapter/json_api/resource_meta_test.rb b/test/adapter/json_api/resource_meta_test.rb new file mode 100644 index 00000000..4298f03c --- /dev/null +++ b/test/adapter/json_api/resource_meta_test.rb @@ -0,0 +1,63 @@ +require 'test_helper' + +module ActiveModel + class Serializer + module Adapter + class JsonApi + class ResourceMetaTest < Minitest::Test + class MetaHashPostSerializer < ActiveModel::Serializer + attributes :id + meta stuff: 'value' + end + + class MetaBlockPostSerializer < ActiveModel::Serializer + attributes :id + meta do + { comments_count: object.comments.count } + end + end + + def setup + @post = Post.new(id: 1337, comments: [], author: nil) + end + + def test_meta_hash_object_resource + hash = ActiveModel::SerializableResource.new( + @post, + serializer: MetaHashPostSerializer, + adapter: :json_api + ).serializable_hash + expected = { + stuff: 'value' + } + assert_equal(expected, hash[:data][:meta]) + end + + def test_meta_block_object_resource + hash = ActiveModel::SerializableResource.new( + @post, + serializer: MetaBlockPostSerializer, + adapter: :json_api + ).serializable_hash + expected = { + comments_count: @post.comments.count + } + assert_equal(expected, hash[:data][:meta]) + end + + def test_meta_object_resource_in_array + hash = ActiveModel::SerializableResource.new( + [@post, @post], + each_serializer: MetaBlockPostSerializer, + adapter: :json_api + ).serializable_hash + expected = { + comments_count: @post.comments.count + } + assert_equal([expected, expected], hash[:data].map { |obj| obj[:meta] }) + end + end + end + end + end +end diff --git a/test/serializers/meta_test.rb b/test/serializers/meta_test.rb index a555adb7..e7595476 100644 --- a/test/serializers/meta_test.rb +++ b/test/serializers/meta_test.rb @@ -3,6 +3,8 @@ require 'test_helper' module ActiveModel class Serializer class MetaTest < ActiveSupport::TestCase + MetaBlogSerializer = Class.new(ActiveModel::Serializer) + def setup @blog = Blog.new(id: 1, name: 'AMS Hints', @@ -125,6 +127,20 @@ module ActiveModel } assert_equal(expected, actual) end + + def test_meta_is_set_with_direct_attributes + MetaBlogSerializer.meta stuff: 'value' + blog_meta_serializer = MetaBlogSerializer.new(@blog) + assert_equal(blog_meta_serializer.meta, stuff: 'value') + end + + def test_meta_is_set_with_block + MetaBlogSerializer.meta do + { articles_count: object.articles.count } + end + blog_meta_serializer = MetaBlogSerializer.new(@blog) + assert_equal(blog_meta_serializer.meta, articles_count: @blog.articles.count) + end end end end From 701404f757d6bf3795ac57df6d144fa2fab0ed7a Mon Sep 17 00:00:00 2001 From: Lucas Hosseini Date: Wed, 20 Jan 2016 01:00:14 +0100 Subject: [PATCH 06/33] Clean up meta handling. --- lib/active_model/serializer.rb | 27 ++--------------- .../serializer/adapter/json_api.rb | 7 ++++- .../serializer/adapter/json_api/meta.rb | 29 +++++++++++++++++++ lib/active_model/serializer/meta.rb | 29 +++++++++++++++++++ 4 files changed, 66 insertions(+), 26 deletions(-) create mode 100644 lib/active_model/serializer/adapter/json_api/meta.rb create mode 100644 lib/active_model/serializer/meta.rb diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index 5e56ec17..3a0abe9c 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -9,6 +9,7 @@ require 'active_model/serializer/configuration' require 'active_model/serializer/fieldset' require 'active_model/serializer/lint' require 'active_model/serializer/links' +require 'active_model/serializer/meta' require 'active_model/serializer/type' # ActiveModel::Serializer is an abstract class that is @@ -20,26 +21,10 @@ module ActiveModel include Attributes include Caching include Links + include Meta include Type require 'active_model/serializer/adapter' - with_options instance_writer: false, instance_reader: false do |serializer| - serializer.class_attribute :_meta # @api private : meta definition, @see Serializer#meta - end - - # Register a meta attribute for the corresponding resource. - # - # @param [Hash] hash Optional hash - # @param [Block] block Optional block - def self.meta(hash = nil, &block) - self._meta = - if !block.nil? - block - else - hash - end - end - # @param resource [ActiveRecord::Base, ActiveModelSerializers::Model] # @return [ActiveModel::Serializer] # Preferentially returns @@ -145,14 +130,6 @@ module ActiveModel end end - def meta - if self.class._meta.respond_to?(:call) - instance_eval(&self.class._meta) - else - self.class._meta - end - end - protected attr_accessor :instance_options diff --git a/lib/active_model/serializer/adapter/json_api.rb b/lib/active_model/serializer/adapter/json_api.rb index b72ec6e1..d5ceaa91 100644 --- a/lib/active_model/serializer/adapter/json_api.rb +++ b/lib/active_model/serializer/adapter/json_api.rb @@ -6,6 +6,7 @@ module ActiveModel autoload :PaginationLinks autoload :FragmentCache autoload :Link + autoload :Meta autoload :Deserialization # TODO: if we like this abstraction and other API objects to it, @@ -157,7 +158,7 @@ module ActiveModel links = links_for(serializer) resource_object[:links] = links if links.any? - meta = serializer.meta + meta = meta_for(serializer) resource_object[:meta] = meta unless meta.nil? resource_object @@ -220,6 +221,10 @@ module ActiveModel 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 diff --git a/lib/active_model/serializer/adapter/json_api/meta.rb b/lib/active_model/serializer/adapter/json_api/meta.rb new file mode 100644 index 00000000..8fba8986 --- /dev/null +++ b/lib/active_model/serializer/adapter/json_api/meta.rb @@ -0,0 +1,29 @@ +module ActiveModel + class Serializer + module Adapter + class JsonApi + class Meta + def initialize(serializer) + @object = serializer.object + @scope = serializer.scope + + # Use the return value of the block unless it is nil. + if serializer._meta.respond_to?(:call) + @value = instance_eval(&serializer._meta) + else + @value = serializer._meta + end + end + + def as_json + @value + end + + protected + + attr_reader :object, :scope + end + end + end + end +end diff --git a/lib/active_model/serializer/meta.rb b/lib/active_model/serializer/meta.rb new file mode 100644 index 00000000..5160585e --- /dev/null +++ b/lib/active_model/serializer/meta.rb @@ -0,0 +1,29 @@ +module ActiveModel + class Serializer + module Meta + extend ActiveSupport::Concern + + included do + with_options instance_writer: false, instance_reader: true do |serializer| + serializer.class_attribute :_meta # @api private + end + + extend ActiveSupport::Autoload + end + + module ClassMethods + # Set the JSON API meta attribute of a serializer. + # @example + # class AdminAuthorSerializer < ActiveModel::Serializer + # meta { stuff: 'value' } + # @example + # meta do + # { comment_count: object.comments.count } + # end + def meta(value = nil, &block) + self._meta = block || value + end + end + end + end +end From 061f1c0f597013851cb469086fe932a0e5ec58b1 Mon Sep 17 00:00:00 2001 From: Lucas Hosseini Date: Thu, 21 Jan 2016 01:10:02 +0100 Subject: [PATCH 07/33] Add support for relationship-level links and meta. --- .../serializer/adapter/json_api.rb | 56 +++++-------------- .../adapter/json_api/association.rb | 48 ++++++++++++++++ .../adapter/json_api/resource_identifier.rb | 41 ++++++++++++++ lib/active_model/serializer/association.rb | 2 +- lib/active_model/serializer/reflection.rb | 39 ++++++++++++- test/adapter/json_api/links_test.rb | 31 +++++++++- test/serializers/associations_test.rb | 8 +-- 7 files changed, 175 insertions(+), 50 deletions(-) create mode 100644 lib/active_model/serializer/adapter/json_api/association.rb create mode 100644 lib/active_model/serializer/adapter/json_api/resource_identifier.rb diff --git a/lib/active_model/serializer/adapter/json_api.rb b/lib/active_model/serializer/adapter/json_api.rb index 1343e123..e00a46ea 100644 --- a/lib/active_model/serializer/adapter/json_api.rb +++ b/lib/active_model/serializer/adapter/json_api.rb @@ -6,6 +6,8 @@ module ActiveModel autoload :PaginationLinks autoload :FragmentCache autoload :Link + autoload :Association + autoload :ResourceIdentifier autoload :Deserialization # TODO: if we like this abstraction and other API objects to it, @@ -97,7 +99,7 @@ module ActiveModel end def process_resource(serializer, primary) - resource_identifier = resource_identifier_for(serializer) + resource_identifier = JsonApi::ResourceIdentifier.new(serializer).as_json return false unless @resource_identifiers.add?(resource_identifier) resource_object = resource_object_for(serializer) @@ -127,37 +129,13 @@ module ActiveModel process_relationships(serializer, include_tree) end - def resource_identifier_type_for(serializer) - return serializer._type if serializer._type - if ActiveModelSerializers.config.jsonapi_resource_type == :singular - serializer.object.class.model_name.singular - else - serializer.object.class.model_name.plural - end - end - - def resource_identifier_id_for(serializer) - if serializer.respond_to?(:id) - serializer.id - else - serializer.object.id - end - end - - def resource_identifier_for(serializer) - type = resource_identifier_type_for(serializer) - id = resource_identifier_id_for(serializer) - - { id: id.to_s, type: type } - end - def attributes_for(serializer, fields) serializer.attributes(fields).except(:id) end def resource_object_for(serializer) resource_object = cache_check(serializer) do - resource_object = resource_identifier_for(serializer) + resource_object = JsonApi::ResourceIdentifier.new(serializer).as_json requested_fields = fieldset && fieldset.fields_for(resource_object[:type]) attributes = attributes_for(serializer, requested_fields) @@ -165,7 +143,8 @@ module ActiveModel resource_object end - relationships = relationships_for(serializer) + 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) @@ -174,24 +153,15 @@ module ActiveModel resource_object end - def relationship_value_for(serializer, options = {}) - if serializer.respond_to?(:each) - serializer.map { |s| resource_identifier_for(s) } - else - if options[:virtual_value] - options[:virtual_value] - elsif serializer && serializer.object - resource_identifier_for(serializer) - end - end - end - - def relationships_for(serializer) - resource_type = resource_identifier_type_for(serializer) - requested_associations = fieldset.fields_for(resource_type) || '*' + 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] = { data: relationship_value_for(association.serializer, association.options) } + hash[association.key] = JsonApi::Association.new(serializer, + association.serializer, + association.options, + association.links, + association.meta) + .as_json end end diff --git a/lib/active_model/serializer/adapter/json_api/association.rb b/lib/active_model/serializer/adapter/json_api/association.rb new file mode 100644 index 00000000..b6cfc70d --- /dev/null +++ b/lib/active_model/serializer/adapter/json_api/association.rb @@ -0,0 +1,48 @@ +module ActiveModel + class Serializer + module Adapter + class JsonApi + class Association + def initialize(parent_serializer, serializer, options, links, meta) + @object = parent_serializer.object + @scope = parent_serializer.scope + + @options = options + @data = data_for(serializer, options) + @links = links + .map { |key, value| { key => Link.new(parent_serializer, value).as_json } } + .reduce({}, :merge) + @meta = meta.respond_to?(:call) ? parent_serializer.instance_eval(&meta) : meta + end + + def as_json + hash = {} + hash[:data] = @data if @options[:include_data] + hash[:links] = @links if @links.any? + hash[:meta] = @meta if @meta + + hash + end + + protected + + attr_reader :object, :scope + + private + + def data_for(serializer, options) + if serializer.respond_to?(:each) + serializer.map { |s| ResourceIdentifier.new(s).as_json } + else + if options[:virtual_value] + options[:virtual_value] + elsif serializer && serializer.object + ResourceIdentifier.new(serializer).as_json + end + end + end + end + end + end + end +end diff --git a/lib/active_model/serializer/adapter/json_api/resource_identifier.rb b/lib/active_model/serializer/adapter/json_api/resource_identifier.rb new file mode 100644 index 00000000..99bff298 --- /dev/null +++ b/lib/active_model/serializer/adapter/json_api/resource_identifier.rb @@ -0,0 +1,41 @@ +module ActiveModel + class Serializer + module Adapter + class JsonApi + class ResourceIdentifier + def initialize(serializer) + @id = id_for(serializer) + @type = type_for(serializer) + end + + def as_json + { id: @id.to_s, type: @type } + end + + protected + + attr_reader :object, :scope + + private + + def type_for(serializer) + return serializer._type if serializer._type + if ActiveModelSerializers.config.jsonapi_resource_type == :singular + serializer.object.class.model_name.singular + else + serializer.object.class.model_name.plural + end + end + + def id_for(serializer) + if serializer.respond_to?(:id) + serializer.id + else + serializer.object.id + end + end + end + end + end + end +end diff --git a/lib/active_model/serializer/association.rb b/lib/active_model/serializer/association.rb index 1003f0a6..cbe16752 100644 --- a/lib/active_model/serializer/association.rb +++ b/lib/active_model/serializer/association.rb @@ -9,7 +9,7 @@ module ActiveModel # @example # Association.new(:comments, CommentSummarySerializer) # - Association = Struct.new(:name, :serializer, :options) do + Association = Struct.new(:name, :serializer, :options, :links, :meta) do # @return [Symbol] # def key diff --git a/lib/active_model/serializer/reflection.rb b/lib/active_model/serializer/reflection.rb index c0287b64..89fa4074 100644 --- a/lib/active_model/serializer/reflection.rb +++ b/lib/active_model/serializer/reflection.rb @@ -34,6 +34,38 @@ module ActiveModel # So you can inspect reflections in your Adapters. # class Reflection < Field + def initialize(*) + super + @_links = {} + @_include_data = true + end + + def link(name, value = nil, &block) + @_links[name] = block || value + nil + end + + def meta(value = nil, &block) + @_meta = block || value + nil + end + + def include_data(value = true) + @_include_data = value + nil + end + + def value(serializer) + @object = serializer.object + @scope = serializer.scope + + if block + instance_eval(&block) + else + serializer.read_attribute_for_serialization(name) + end + end + # Build association. This method is used internally to # build serializer's association by its reflection. # @@ -59,6 +91,7 @@ module ActiveModel association_value = value(subject) reflection_options = options.dup serializer_class = subject.class.serializer_for(association_value, reflection_options) + reflection_options[:include_data] = @_include_data if serializer_class begin @@ -73,9 +106,13 @@ module ActiveModel reflection_options[:virtual_value] = association_value end - Association.new(name, serializer, reflection_options) + Association.new(name, serializer, reflection_options, @_links, @_meta) end + protected + + attr_accessor :object, :scope + private def serializer_options(subject, parent_serializer_options, reflection_options) diff --git a/test/adapter/json_api/links_test.rb b/test/adapter/json_api/links_test.rb index dbda88ea..0e7d5e32 100644 --- a/test/adapter/json_api/links_test.rb +++ b/test/adapter/json_api/links_test.rb @@ -17,11 +17,23 @@ module ActiveModel link :yet_another do "//example.com/resource/#{object.id}" end + + has_many :posts do + link :self do + href '//example.com/link_author/relationships/posts' + meta stuff: 'value' + end + link :related do + href '//example.com/link_author/posts' + meta count: object.posts.count + end + include_data false + end end def setup @post = Post.new(id: 1337, comments: [], author: nil) - @author = LinkAuthor.new(id: 1337) + @author = LinkAuthor.new(id: 1337, posts: [@post]) end def test_toplevel_links @@ -61,6 +73,23 @@ module ActiveModel } assert_equal(expected, hash[:data][:links]) end + + def test_relationship_links + hash = serializable(@author, adapter: :json_api).serializable_hash + expected = { + links: { + self: { + href: '//example.com/link_author/relationships/posts', + meta: { stuff: 'value' } + }, + related: { + href: '//example.com/link_author/posts', + meta: { count: 1 } + } + } + } + assert_equal(expected, hash[:data][:relationships][:posts]) + end end end end diff --git a/test/serializers/associations_test.rb b/test/serializers/associations_test.rb index aa0cae08..f62da8b8 100644 --- a/test/serializers/associations_test.rb +++ b/test/serializers/associations_test.rb @@ -32,13 +32,13 @@ module ActiveModel case key when :posts - assert_equal({}, options) + assert_equal({ include_data: true }, options) assert_kind_of(ActiveModelSerializers.config.collection_serializer, serializer) when :bio - assert_equal({}, options) + assert_equal({ include_data: true }, options) assert_nil serializer when :roles - assert_equal({}, options) + assert_equal({ include_data: true }, options) assert_kind_of(ActiveModelSerializers.config.collection_serializer, serializer) else flunk "Unknown association: #{key}" @@ -80,7 +80,7 @@ module ActiveModel flunk "Unknown association: #{key}" end - assert_equal({}, association.options) + assert_equal({ include_data: true }, association.options) end end From 2678896a9ca5a0e563cb18233d7cb4ae6aaf8652 Mon Sep 17 00:00:00 2001 From: Edwin Lunando Date: Thu, 28 Jan 2016 13:51:19 +0700 Subject: [PATCH 08/33] update JSON adapter pagination links --- docs/howto/add_pagination_links.md | 36 ++++++++++++++---------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/docs/howto/add_pagination_links.md b/docs/howto/add_pagination_links.md index 64b903fb..b0552f4b 100644 --- a/docs/howto/add_pagination_links.md +++ b/docs/howto/add_pagination_links.md @@ -74,32 +74,30 @@ ActiveModelSerializers pagination relies on a paginated collection with the meth If you are using `JSON` adapter, pagination links will not be included automatically, but it is possible to do so using `meta` key. -In your action specify a custom serializer. -```ruby -render json: @posts, serializer: PaginatedSerializer, each_serializer: PostPreviewSerializer -``` +Add this method to your base API controller. -And then, you could do something like the following class. ```ruby -class PaginatedSerializer < ActiveModel::Serializer::CollectionSerializer - def initialize(object, options={}) - meta_key = options[:meta_key] || :meta - options[meta_key] ||= {} - options[meta_key] = { - current_page: object.current_page, - next_page: object.next_page, - prev_page: object.prev_page, - total_pages: object.total_pages, - total_count: object.total_count - } - super(object, options) - end +def pagination_dict(object) + { + current_page: object.current_page, + next_page: object.next_page, + prev_page: object.prev_page, + total_pages: object.total_pages, + total_count: object.total_count + } end ``` + +Then, use it on your render method. + +```ruby +render json: posts, meta: pagination_dict(posts) +``` + ex. ```json { - "articles": [ + "posts": [ { "id": 2, "title": "JSON API paints my bikeshed!", From 3a092c9b4b4e4c2ed9e568631689e53eac663c0c Mon Sep 17 00:00:00 2001 From: Brian McManus Date: Thu, 28 Jan 2016 13:47:17 -0800 Subject: [PATCH 09/33] Fixed fragment_cached? method to check if caching I noticed that fragment caching does not actually check if caching is enabled as it seemingly should. The way CachedSerializer#fragment_cached? worked previously would return true even in an environment where caching was disabled as defined by `ActiveModelSerializers.config.perform_caching`. Added check for `_cache` like in the `cached?` method before checking whether `_cache_only` or `_cache_except` is set. There were no existing tests for any of these methods but it's a pretty trivial change. --- lib/active_model/serializer/adapter/cached_serializer.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/active_model/serializer/adapter/cached_serializer.rb b/lib/active_model/serializer/adapter/cached_serializer.rb index 35b10168..076c057e 100644 --- a/lib/active_model/serializer/adapter/cached_serializer.rb +++ b/lib/active_model/serializer/adapter/cached_serializer.rb @@ -24,7 +24,7 @@ module ActiveModel end def fragment_cached? - @klass._cache_only && !@klass._cache_except || !@klass._cache_only && @klass._cache_except + @klass._cache && (@klass._cache_only && !@klass._cache_except || !@klass._cache_only && @klass._cache_except) end def cache_key From 3c1fe0fd0f106c0127a6ef4bc93a9de487fbbd3c Mon Sep 17 00:00:00 2001 From: Nate Sullivan Date: Sat, 30 Jan 2016 23:29:43 -0800 Subject: [PATCH 10/33] Require ActiveSupport's string/inflections We depend on string/inflections to define String#underscore. --- CHANGELOG.md | 1 + lib/active_model_serializers.rb | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7fd7e5fc..721f42ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ Breaking changes: Features: Fixes: +- [#1488](https://github.com/rails-api/active_model_serializers/pull/1488) Require ActiveSupport's string inflections (@nate00) Misc: ### v0.10.0.rc4 (2016/01/27 11:00 +00:00) diff --git a/lib/active_model_serializers.rb b/lib/active_model_serializers.rb index d92823b5..47e14208 100644 --- a/lib/active_model_serializers.rb +++ b/lib/active_model_serializers.rb @@ -1,6 +1,7 @@ require 'active_model' require 'active_support' require 'active_support/core_ext/object/with_options' +require 'active_support/core_ext/string/inflections' module ActiveModelSerializers extend ActiveSupport::Autoload autoload :Model From 211646b0071ae2872c1f647df44ca99fd0e35f33 Mon Sep 17 00:00:00 2001 From: bobba surendranath chowdary Date: Tue, 2 Feb 2016 22:39:53 +0530 Subject: [PATCH 11/33] Changed the yardoc links,as old links are not taking to documentation pages,proper links for 0.10,0.9 and 0.8 in rubydoc --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 2106d34e..ef52ba28 100644 --- a/README.md +++ b/README.md @@ -7,11 +7,11 @@ ## Documentation -- [0.10 (master) Documentation](https://github.com/rails-api/active_model_serializers/tree/master) +- [0.10 (master) Documentation](http://www.rubydoc.info/github/rails-api/active_model_serializers/v0.10.0.rc4) - [![API Docs](http://img.shields.io/badge/yard-docs-blue.svg)](http://www.rubydoc.info/github/rails-api/active_model_serializers) - [Guides](docs) -- [0.9 (0-9-stable) Documentation](https://github.com/rails-api/active_model_serializers/tree/0-9-stable) -- [0.8 (0-8-stable) Documentation](https://github.com/rails-api/active_model_serializers/tree/0-8-stable) +- [0.9 (0-9-stable) Documentation](http://www.rubydoc.info/github/rails-api/active_model_serializers/0-9-stable) +- [0.8 (0-8-stable) Documentation](http://www.rubydoc.info/github/rails-api/active_model_serializers/0-8-stable) ## About From a86227d4eefb926949b74a5fa4b4a7c2cd7204b4 Mon Sep 17 00:00:00 2001 From: Scott Kobewka Date: Tue, 2 Feb 2016 12:58:25 -0500 Subject: [PATCH 12/33] Update readme.md to link to v0.10.0.rc4 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2106d34e..9e12bfba 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ ## Documentation - [0.10 (master) Documentation](https://github.com/rails-api/active_model_serializers/tree/master) - - [![API Docs](http://img.shields.io/badge/yard-docs-blue.svg)](http://www.rubydoc.info/github/rails-api/active_model_serializers) + - [![API Docs](http://img.shields.io/badge/yard-docs-blue.svg)](http://www.rubydoc.info/github/rails-api/active_model_serializers/v0.10.0.rc4) - [Guides](docs) - [0.9 (0-9-stable) Documentation](https://github.com/rails-api/active_model_serializers/tree/0-9-stable) - [0.8 (0-8-stable) Documentation](https://github.com/rails-api/active_model_serializers/tree/0-8-stable) From 7c26c1e09fd00dd5d0ae68a043e9d71056f2a20b Mon Sep 17 00:00:00 2001 From: bobba surendranath chowdary Date: Wed, 3 Feb 2016 11:32:51 +0530 Subject: [PATCH 13/33] Changed the yardoc link, and removed the changes to made to documentation links --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index ef52ba28..9e12bfba 100644 --- a/README.md +++ b/README.md @@ -7,11 +7,11 @@ ## Documentation -- [0.10 (master) Documentation](http://www.rubydoc.info/github/rails-api/active_model_serializers/v0.10.0.rc4) - - [![API Docs](http://img.shields.io/badge/yard-docs-blue.svg)](http://www.rubydoc.info/github/rails-api/active_model_serializers) +- [0.10 (master) Documentation](https://github.com/rails-api/active_model_serializers/tree/master) + - [![API Docs](http://img.shields.io/badge/yard-docs-blue.svg)](http://www.rubydoc.info/github/rails-api/active_model_serializers/v0.10.0.rc4) - [Guides](docs) -- [0.9 (0-9-stable) Documentation](http://www.rubydoc.info/github/rails-api/active_model_serializers/0-9-stable) -- [0.8 (0-8-stable) Documentation](http://www.rubydoc.info/github/rails-api/active_model_serializers/0-8-stable) +- [0.9 (0-9-stable) Documentation](https://github.com/rails-api/active_model_serializers/tree/0-9-stable) +- [0.8 (0-8-stable) Documentation](https://github.com/rails-api/active_model_serializers/tree/0-8-stable) ## About From 1844c162f14e64adb20bda20371efc7ff844c9ef Mon Sep 17 00:00:00 2001 From: Leandro Cesquini Pereira Date: Fri, 24 Jul 2015 23:05:52 -0300 Subject: [PATCH 14/33] Adds support for top-level links to JsonApi adapter http://jsonapi.org/format/#document-top-level fix failing tests support for top-level links limited to jsonapi adapter Move docs from README to docs/ dir move links to json-api adapter & create Links class to hold links data --- .gitignore | 1 + CHANGELOG.md | 2 + docs/README.md | 1 + docs/howto/add_top_level_links.md | 31 ++++++ .../serializer/adapter/json_api/links.rb | 25 +++++ test/action_controller/serialization_test.rb | 34 ++++++ test/adapter/json_api/top_level_links_test.rb | 101 ++++++++++++++++++ 7 files changed, 195 insertions(+) create mode 100644 docs/howto/add_top_level_links.md create mode 100644 lib/active_model/serializer/adapter/json_api/links.rb create mode 100644 test/adapter/json_api/top_level_links_test.rb diff --git a/.gitignore b/.gitignore index cd2acb28..2bc7e6c8 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,4 @@ tmp .ruby-version .ruby-gemset vendor/bundle +tags diff --git a/CHANGELOG.md b/CHANGELOG.md index 721f42ac..fb9ae71b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -65,6 +65,8 @@ Features: CollectionSerializer for clarity, add ActiveModelSerializers.config.collection_serializer (@bf4) - [#1295](https://github.com/rails-api/active_model_serializers/pull/1295) Add config `serializer_lookup_enabled` that, when disabled, requires serializers to explicitly specified. (@trek) +- [#1247](https://github.com/rails-api/active_model_serializers/pull/1247) Add top-level links (@beauby) + * Add more tests and docs for top-level links (@leandrocp) Fixes: diff --git a/docs/README.md b/docs/README.md index 7f0a8ac0..cf658fb6 100644 --- a/docs/README.md +++ b/docs/README.md @@ -23,6 +23,7 @@ This is the documentation of ActiveModelSerializers, it's focused on the **0.10. - [How to add pagination links](howto/add_pagination_links.md) - [Using ActiveModelSerializers Outside Of Controllers](howto/outside_controller_use.md) - [Testing ActiveModelSerializers](howto/test.md) +- [How to add top-level links](howto/add_top_level_links.md) (```JSON-API``` only) ## Integrations diff --git a/docs/howto/add_top_level_links.md b/docs/howto/add_top_level_links.md new file mode 100644 index 00000000..a61775d2 --- /dev/null +++ b/docs/howto/add_top_level_links.md @@ -0,0 +1,31 @@ +# How to add top-level links + +JsonApi supports a [links object](http://jsonapi.org/format/#document-links) to be specified at top-level, that you can specify in the `render`: + +```ruby + render json: @posts, links: { "self": "http://example.com/api/posts" } +``` + +That's the result: + +```json +{ + "data": [ + { + "type": "posts", + "id": "1", + "attributes": { + "title": "JSON API is awesome!", + "body": "You should be using JSON API", + "created": "2015-05-22T14:56:29.000Z", + "updated": "2015-05-22T14:56:28.000Z" + } + } + ], + "links": { + "self": "http://example.com/api/posts" + } +} +``` + +This feature is specific to JsonApi, so you have to use the use the [JsonApi Adapter](https://github.com/rails-api/active_model_serializers/blob/master/docs/general/adapters.md#jsonapi) diff --git a/lib/active_model/serializer/adapter/json_api/links.rb b/lib/active_model/serializer/adapter/json_api/links.rb new file mode 100644 index 00000000..4e4c6127 --- /dev/null +++ b/lib/active_model/serializer/adapter/json_api/links.rb @@ -0,0 +1,25 @@ +module ActiveModel + class Serializer + class Adapter + class JsonApi < Adapter + class Links + def initialize(links = {}) + @links = links + end + + def serializable_hash + @links + end + + def update(links = {}) + @links.update(links) + end + + def present? + !@links.empty? + end + end + end + end + end +end diff --git a/test/action_controller/serialization_test.rb b/test/action_controller/serialization_test.rb index d2fe3959..c6489b29 100644 --- a/test/action_controller/serialization_test.rb +++ b/test/action_controller/serialization_test.rb @@ -45,6 +45,17 @@ module ActionController render json: @profiles, meta: { total: 10 } end + def render_array_using_implicit_serializer_and_links + with_adapter ActiveModel::Serializer::Adapter::JsonApi do + + @profiles = [ + Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' }) + ] + + render json: @profiles, links: { self: "http://example.com/api/profiles/1" } + end + end + def render_object_with_cache_enabled @comment = Comment.new(id: 1, body: 'ZOMG A COMMENT') @author = Author.new(id: 1, name: 'Joao Moura.') @@ -254,6 +265,29 @@ module ActionController assert_equal expected.to_json, @response.body end + def test_render_array_using_implicit_serializer_and_links + get :render_array_using_implicit_serializer_and_links + + expected = { + data: [ + { + id: assigns(:profiles).first.id.to_s, + type: "profiles", + attributes: { + name: "Name 1", + description: "Description 1" + } + } + ], + links: { + self: "http://example.com/api/profiles/1" + } + } + + assert_equal 'application/json', @response.content_type + assert_equal expected.to_json, @response.body + end + def test_render_with_cache_enable expected = { id: 1, diff --git a/test/adapter/json_api/top_level_links_test.rb b/test/adapter/json_api/top_level_links_test.rb new file mode 100644 index 00000000..3b849d5b --- /dev/null +++ b/test/adapter/json_api/top_level_links_test.rb @@ -0,0 +1,101 @@ +require 'test_helper' + +module ActiveModel + class Serializer + class Adapter + class JsonApi + class TopLevelLinksTest < Minitest::Test + URI = 'http://example.com' + + def setup + ActionController::Base.cache_store.clear + @blog = Blog.new(id: 1, + name: 'AMS Hints', + writer: Author.new(id: 2, name: "Steve"), + articles: [Post.new(id: 3, title: "AMS")]) + end + + def load_adapter(paginated_collection, options = {}) + options = options.merge(adapter: :json_api) + ActiveModel::SerializableResource.new(paginated_collection, options) + end + + def test_links_is_not_present_when_not_defined + adapter = load_adapter(@blog) + + expected = { + :data => { + :id => "1", + :type => "blogs", + :attributes => { + :name => "AMS Hints" + }, + :relationships => { + :writer=> {:data => {:type => "authors", :id => "2"}}, + :articles => {:data => [{:type => "posts", :id => "3"}]} + } + }} + + assert_equal expected, adapter.serializable_hash(@options) + end + + def test_links_is_present_when_defined + adapter = load_adapter(@blog, {links: links}) + + expected = { + :data => { + :id => "1", + :type => "blogs", + :attributes => { + :name => "AMS Hints" + }, + :relationships => { + :writer=> {:data => {:type => "authors", :id => "2"}}, + :articles => {:data => [{:type => "posts", :id => "3"}]} + } + }, + :links => {:self => "http://example.com/blogs/1"} + } + + assert_equal expected, adapter.serializable_hash(@options) + end + + def links + { + self: "#{URI}/blogs/1" + } + end + + # def test_links_is_not_present_when_not_declared + # serializer = AlternateBlogSerializer.new(@blog) + # adapter = ActiveModel::Serializer::Adapter::JsonApi.new(serializer) + # expected = { + # data: { + # id: "1", + # type: "blogs", + # attributes: { + # title: "AMS Hints" + # } + # } + # } + # assert_equal expected, adapter.as_json + # end + + # def test_links_is_not_present_on_flattenjson_adapter + # serializer = AlternateBlogSerializer.new(@blog, :links => {:self => "/blogs/1"}) + # adapter = ActiveModel::Serializer::Adapter::FlattenJson.new(serializer) + # expected = {:id=>1, :title=>"AMS Hints"} + # assert_equal expected, adapter.as_json + # end + + # def test_links_is_not_present_on_json_adapter + # serializer = AlternateBlogSerializer.new(@blog, :links => {:self => "/blogs/1"}) + # adapter = ActiveModel::Serializer::Adapter::Json.new(serializer) + # expected = {:blog=>{:id=>1, :title=>"AMS Hints"}} + # assert_equal expected, adapter.as_json + # end + end + end + end + end +end From 37e4f1c30c20d6b29ea2f40b3264f8da26e88685 Mon Sep 17 00:00:00 2001 From: Leandro Cesquini Pereira Date: Wed, 23 Dec 2015 09:32:53 -0200 Subject: [PATCH 15/33] Update top-level link with PR #1247 update according rubocop rules --- .../serializer/adapter/json_api/links.rb | 25 ----- test/action_controller/serialization_test.rb | 13 ++- test/adapter/json_api/links_test.rb | 9 ++ test/adapter/json_api/top_level_links_test.rb | 101 ------------------ 4 files changed, 15 insertions(+), 133 deletions(-) delete mode 100644 lib/active_model/serializer/adapter/json_api/links.rb delete mode 100644 test/adapter/json_api/top_level_links_test.rb diff --git a/lib/active_model/serializer/adapter/json_api/links.rb b/lib/active_model/serializer/adapter/json_api/links.rb deleted file mode 100644 index 4e4c6127..00000000 --- a/lib/active_model/serializer/adapter/json_api/links.rb +++ /dev/null @@ -1,25 +0,0 @@ -module ActiveModel - class Serializer - class Adapter - class JsonApi < Adapter - class Links - def initialize(links = {}) - @links = links - end - - def serializable_hash - @links - end - - def update(links = {}) - @links.update(links) - end - - def present? - !@links.empty? - end - end - end - end - end -end diff --git a/test/action_controller/serialization_test.rb b/test/action_controller/serialization_test.rb index c6489b29..7e6ea6cc 100644 --- a/test/action_controller/serialization_test.rb +++ b/test/action_controller/serialization_test.rb @@ -47,12 +47,11 @@ module ActionController def render_array_using_implicit_serializer_and_links with_adapter ActiveModel::Serializer::Adapter::JsonApi do - @profiles = [ - Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' }) + Profile.new(name: 'Name 1', description: 'Description 1', comments: 'Comments 1') ] - render json: @profiles, links: { self: "http://example.com/api/profiles/1" } + render json: @profiles, links: { self: 'http://example.com/api/profiles/1' } end end @@ -272,15 +271,15 @@ module ActionController data: [ { id: assigns(:profiles).first.id.to_s, - type: "profiles", + type: 'profiles', attributes: { - name: "Name 1", - description: "Description 1" + name: 'Name 1', + description: 'Description 1' } } ], links: { - self: "http://example.com/api/profiles/1" + self: 'http://example.com/api/profiles/1' } } diff --git a/test/adapter/json_api/links_test.rb b/test/adapter/json_api/links_test.rb index dbda88ea..5e65f3d5 100644 --- a/test/adapter/json_api/links_test.rb +++ b/test/adapter/json_api/links_test.rb @@ -47,6 +47,15 @@ module ActiveModel assert_equal(expected, hash[:links]) end + def test_nil_toplevel_links + hash = ActiveModel::SerializableResource.new( + @post, + adapter: :json_api, + links: nil + ).serializable_hash + assert_equal(nil, hash[:links]) + end + def test_resource_links hash = serializable(@author, adapter: :json_api).serializable_hash expected = { diff --git a/test/adapter/json_api/top_level_links_test.rb b/test/adapter/json_api/top_level_links_test.rb deleted file mode 100644 index 3b849d5b..00000000 --- a/test/adapter/json_api/top_level_links_test.rb +++ /dev/null @@ -1,101 +0,0 @@ -require 'test_helper' - -module ActiveModel - class Serializer - class Adapter - class JsonApi - class TopLevelLinksTest < Minitest::Test - URI = 'http://example.com' - - def setup - ActionController::Base.cache_store.clear - @blog = Blog.new(id: 1, - name: 'AMS Hints', - writer: Author.new(id: 2, name: "Steve"), - articles: [Post.new(id: 3, title: "AMS")]) - end - - def load_adapter(paginated_collection, options = {}) - options = options.merge(adapter: :json_api) - ActiveModel::SerializableResource.new(paginated_collection, options) - end - - def test_links_is_not_present_when_not_defined - adapter = load_adapter(@blog) - - expected = { - :data => { - :id => "1", - :type => "blogs", - :attributes => { - :name => "AMS Hints" - }, - :relationships => { - :writer=> {:data => {:type => "authors", :id => "2"}}, - :articles => {:data => [{:type => "posts", :id => "3"}]} - } - }} - - assert_equal expected, adapter.serializable_hash(@options) - end - - def test_links_is_present_when_defined - adapter = load_adapter(@blog, {links: links}) - - expected = { - :data => { - :id => "1", - :type => "blogs", - :attributes => { - :name => "AMS Hints" - }, - :relationships => { - :writer=> {:data => {:type => "authors", :id => "2"}}, - :articles => {:data => [{:type => "posts", :id => "3"}]} - } - }, - :links => {:self => "http://example.com/blogs/1"} - } - - assert_equal expected, adapter.serializable_hash(@options) - end - - def links - { - self: "#{URI}/blogs/1" - } - end - - # def test_links_is_not_present_when_not_declared - # serializer = AlternateBlogSerializer.new(@blog) - # adapter = ActiveModel::Serializer::Adapter::JsonApi.new(serializer) - # expected = { - # data: { - # id: "1", - # type: "blogs", - # attributes: { - # title: "AMS Hints" - # } - # } - # } - # assert_equal expected, adapter.as_json - # end - - # def test_links_is_not_present_on_flattenjson_adapter - # serializer = AlternateBlogSerializer.new(@blog, :links => {:self => "/blogs/1"}) - # adapter = ActiveModel::Serializer::Adapter::FlattenJson.new(serializer) - # expected = {:id=>1, :title=>"AMS Hints"} - # assert_equal expected, adapter.as_json - # end - - # def test_links_is_not_present_on_json_adapter - # serializer = AlternateBlogSerializer.new(@blog, :links => {:self => "/blogs/1"}) - # adapter = ActiveModel::Serializer::Adapter::Json.new(serializer) - # expected = {:blog=>{:id=>1, :title=>"AMS Hints"}} - # assert_equal expected, adapter.as_json - # end - end - end - end - end -end From b55fc32ba25857c88d5b91d2158026a2f444ee26 Mon Sep 17 00:00:00 2001 From: Leandro Cesquini Pereira Date: Wed, 3 Feb 2016 10:22:17 -0200 Subject: [PATCH 16/33] improve doc as suggested by @bf4 --- docs/howto/add_top_level_links.md | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/docs/howto/add_top_level_links.md b/docs/howto/add_top_level_links.md index a61775d2..bcb0ea18 100644 --- a/docs/howto/add_top_level_links.md +++ b/docs/howto/add_top_level_links.md @@ -3,7 +3,13 @@ JsonApi supports a [links object](http://jsonapi.org/format/#document-links) to be specified at top-level, that you can specify in the `render`: ```ruby - render json: @posts, links: { "self": "http://example.com/api/posts" } + links_object = { + href: "http://example.com/api/posts", + meta: { + count: 10 + } + } + render json: @posts, links: links_object ``` That's the result: @@ -23,7 +29,10 @@ That's the result: } ], "links": { - "self": "http://example.com/api/posts" + "href": "http://example.com/api/posts", + "meta": { + "count": 10 + } } } ``` From a18c99fe87f02b675a74aac2b9f4ad990463e278 Mon Sep 17 00:00:00 2001 From: Benjamin Fleischer Date: Sun, 7 Feb 2016 22:19:24 -0600 Subject: [PATCH 17/33] Fix typo in changelog link ref [ci skip] --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 721f42ac..14658513 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -415,7 +415,7 @@ Features: '[Revert the serializers API as other alternatives are now also under discussion](https://github.com/rails/rails/commit/0a4035b12a6c59253cb60f9e3456513c6a6a9d33)'. - [Proposed Implementation to Rails 3.2 by @wycats and @josevalim (November 25, 2011)](https://github.com/rails/rails/pull/3753) - [Creation of `ActionController::Serialization`, initial serializer - support (September, 26 2011)](https://github.com/rails/rails/commit/8ff7693a8dc61f43fc4eaf72ed24d3b8699191fe0). + support (September, 26 2011)](https://github.com/rails/rails/commit/8ff7693a8dc61f43fc4eaf72ed24d3b8699191fe). - [Docs and CHANGELOG](https://github.com/rails/rails/commit/696d01f7f4a8ed787924a41cce6df836cd73c46f) - [Deprecation of ActiveModel::Serialization to ActiveModel::Serializable](https://github.com/rails/rails/blob/696d01f7f4a8ed787924a41cce6df836cd73c46f/activemodel/lib/active_model/serialization.rb) - [Creation of `ActiveModel::Serialization` from `ActiveModel::Serializer` in Rails (2009)](https://github.com/rails/rails/commit/c6bc8e662614be711f45a8d4b231d5f993b024a7#diff-d029b9768d8df0407a35804a468e3ae5) From 68f09e59c4a54d02c8154c933d46f011b2e1f7a2 Mon Sep 17 00:00:00 2001 From: bobba surendranath chowdary Date: Sat, 6 Feb 2016 23:10:51 +0530 Subject: [PATCH 18/33] Fixed a documentation error regarding adapter key constant, added tests for SerializableResource::use_adapter? --- CHANGELOG.md | 1 + docs/ARCHITECTURE.md | 4 ++-- lib/active_model/serializer.rb | 2 +- test/serializable_resource_test.rb | 8 ++++++++ 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 721f42ac..71614c68 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ Breaking changes: Features: Fixes: +- [#1501](https://github.com/rails-api/active_model_serializers/pull/1501) Adds tests for SerializableResource::use_adapter?,doc typos (@domitian) - [#1488](https://github.com/rails-api/active_model_serializers/pull/1488) Require ActiveSupport's string inflections (@nate00) Misc: diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 46f1fbfd..25bb88a4 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -56,7 +56,7 @@ High-level overview: - `:each_serializer` specifies the serializer for each resource in the collection. - For a single resource, the `:serializer` option is the resource serializer. - Options are partitioned in serializer options and adapter options. Keys for adapter options are specified by - [`ADAPTER_OPTIONS`](https://github.com/rails-api/active_model_serializers/blob/master/lib/active_model/serializable_resource.rb#L4). + [`ADAPTER_OPTION_KEYS`](https://github.com/rails-api/active_model_serializers/blob/master/lib/active_model/serializable_resource.rb#L4). The remaining options are serializer options. Details: @@ -64,7 +64,7 @@ Details: 1. **ActionController::Serialization** 1. `serializable_resource = ActiveModel::SerializableResource.new(resource, options)` 1. `options` are partitioned into `adapter_opts` and everything else (`serializer_opts`). - The adapter options keys for the are defined by `ADAPTER_OPTIONS`. + The `adapter_opts` keys are defined in `ActiveModel::SerializableResource::ADAPTER_OPTION_KEYS`. 1. **ActiveModel::SerializableResource** 1. `if serializable_resource.serializer?` (there is a serializer for the resource, and an adapter is used.) - Where `serializer?` is `use_adapter? && !!(serializer)` diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index 714ff65d..21b53cfc 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -67,7 +67,7 @@ module ActiveModel # @api private # Find a serializer from a class and caches the lookup. - # Preferentially retuns: + # Preferentially returns: # 1. class name appended with "Serializer" # 2. try again with superclass, if present # 3. nil diff --git a/test/serializable_resource_test.rb b/test/serializable_resource_test.rb index 62bc5d91..339b22a5 100644 --- a/test/serializable_resource_test.rb +++ b/test/serializable_resource_test.rb @@ -23,5 +23,13 @@ module ActiveModel options = nil assert_equal @adapter.as_json(options), @serializable_resource.as_json(options) end + + def test_use_adapter_with_adapter_option + assert ActiveModel::SerializableResource.new(@resource, { adapter: 'json' }).use_adapter? + end + + def test_use_adapter_with_adapter_option_as_false + refute ActiveModel::SerializableResource.new(@resource, { adapter: false }).use_adapter? + end end end From dcbe4ef9e271c1fb76b367d05d5938d37b62e85f Mon Sep 17 00:00:00 2001 From: Benjamin Fleischer Date: Mon, 8 Feb 2016 17:39:45 -0600 Subject: [PATCH 19/33] Rubocop autocorrect indentation --- lib/active_model/serializer/adapter/json_api.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/active_model/serializer/adapter/json_api.rb b/lib/active_model/serializer/adapter/json_api.rb index e00a46ea..9d379d76 100644 --- a/lib/active_model/serializer/adapter/json_api.rb +++ b/lib/active_model/serializer/adapter/json_api.rb @@ -157,10 +157,10 @@ module ActiveModel include_tree = IncludeTree.from_include_args(requested_associations) serializer.associations(include_tree).each_with_object({}) do |association, hash| hash[association.key] = JsonApi::Association.new(serializer, - association.serializer, - association.options, - association.links, - association.meta) + association.serializer, + association.options, + association.links, + association.meta) .as_json end end From 5b953ff40f1e284ebeec49a16437ebfaf3d12f24 Mon Sep 17 00:00:00 2001 From: Benjamin Fleischer Date: Mon, 8 Feb 2016 17:55:15 -0600 Subject: [PATCH 20/33] Address concerns from #1018 commit c59668e --- CHANGELOG.md | 3 +-- docs/README.md | 1 - docs/general/rendering.md | 41 ++++++++++++++++++++++++++++- docs/howto/add_top_level_links.md | 40 ---------------------------- test/adapter/json_api/links_test.rb | 11 +++++++- 5 files changed, 51 insertions(+), 45 deletions(-) delete mode 100644 docs/howto/add_top_level_links.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 042a35b0..4468d782 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ Breaking changes: Features: +- [#1018](https://github.com/rails-api/active_model_serializers/pull/1018) Add more tests and docs for top-level links (@leandrocp) Fixes: - [#1501](https://github.com/rails-api/active_model_serializers/pull/1501) Adds tests for SerializableResource::use_adapter?,doc typos (@domitian) - [#1488](https://github.com/rails-api/active_model_serializers/pull/1488) Require ActiveSupport's string inflections (@nate00) @@ -66,8 +67,6 @@ Features: CollectionSerializer for clarity, add ActiveModelSerializers.config.collection_serializer (@bf4) - [#1295](https://github.com/rails-api/active_model_serializers/pull/1295) Add config `serializer_lookup_enabled` that, when disabled, requires serializers to explicitly specified. (@trek) -- [#1247](https://github.com/rails-api/active_model_serializers/pull/1247) Add top-level links (@beauby) - * Add more tests and docs for top-level links (@leandrocp) Fixes: diff --git a/docs/README.md b/docs/README.md index cf658fb6..7f0a8ac0 100644 --- a/docs/README.md +++ b/docs/README.md @@ -23,7 +23,6 @@ This is the documentation of ActiveModelSerializers, it's focused on the **0.10. - [How to add pagination links](howto/add_pagination_links.md) - [Using ActiveModelSerializers Outside Of Controllers](howto/outside_controller_use.md) - [Testing ActiveModelSerializers](howto/test.md) -- [How to add top-level links](howto/add_top_level_links.md) (```JSON-API``` only) ## Integrations diff --git a/docs/general/rendering.md b/docs/general/rendering.md index 1eefe652..b8349325 100644 --- a/docs/general/rendering.md +++ b/docs/general/rendering.md @@ -103,7 +103,46 @@ PR please :) #### links -PR please :) +##### How to add top-level links + +JsonApi supports a [links object](http://jsonapi.org/format/#document-links) to be specified at top-level, that you can specify in the `render`: + +```ruby + links_object = { + href: "http://example.com/api/posts", + meta: { + count: 10 + } + } + render json: @posts, links: links_object +``` + +That's the result: + +```json +{ + "data": [ + { + "type": "posts", + "id": "1", + "attributes": { + "title": "JSON API is awesome!", + "body": "You should be using JSON API", + "created": "2015-05-22T14:56:29.000Z", + "updated": "2015-05-22T14:56:28.000Z" + } + } + ], + "links": { + "href": "http://example.com/api/posts", + "meta": { + "count": 10 + } + } +} +``` + +This feature is specific to JsonApi, so you have to use the use the [JsonApi Adapter](adapters.md#jsonapi) ### serializer_opts diff --git a/docs/howto/add_top_level_links.md b/docs/howto/add_top_level_links.md deleted file mode 100644 index bcb0ea18..00000000 --- a/docs/howto/add_top_level_links.md +++ /dev/null @@ -1,40 +0,0 @@ -# How to add top-level links - -JsonApi supports a [links object](http://jsonapi.org/format/#document-links) to be specified at top-level, that you can specify in the `render`: - -```ruby - links_object = { - href: "http://example.com/api/posts", - meta: { - count: 10 - } - } - render json: @posts, links: links_object -``` - -That's the result: - -```json -{ - "data": [ - { - "type": "posts", - "id": "1", - "attributes": { - "title": "JSON API is awesome!", - "body": "You should be using JSON API", - "created": "2015-05-22T14:56:29.000Z", - "updated": "2015-05-22T14:56:28.000Z" - } - } - ], - "links": { - "href": "http://example.com/api/posts", - "meta": { - "count": 10 - } - } -} -``` - -This feature is specific to JsonApi, so you have to use the use the [JsonApi Adapter](https://github.com/rails-api/active_model_serializers/blob/master/docs/general/adapters.md#jsonapi) diff --git a/test/adapter/json_api/links_test.rb b/test/adapter/json_api/links_test.rb index 5b9b872c..81dde4a3 100644 --- a/test/adapter/json_api/links_test.rb +++ b/test/adapter/json_api/links_test.rb @@ -65,7 +65,16 @@ module ActiveModel adapter: :json_api, links: nil ).serializable_hash - assert_equal(nil, hash[:links]) + 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 From 1cc2e04cf646ad64e89162d99546dfc026666f86 Mon Sep 17 00:00:00 2001 From: Benjamin Fleischer Date: Mon, 8 Feb 2016 18:14:25 -0600 Subject: [PATCH 21/33] Address issues in 50950d95333da #1340 - Add changelog entry - Remove superseded and incorrect tests - Fix array serialization test --- CHANGELOG.md | 1 + test/adapter/json_api/resource_meta_test.rb | 11 ++++++++--- test/serializers/meta_test.rb | 16 ---------------- 3 files changed, 9 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4468d782..00af8554 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ Breaking changes: Features: - [#1018](https://github.com/rails-api/active_model_serializers/pull/1018) Add more tests and docs for top-level links (@leandrocp) +- [#1340](https://github.com/rails-api/active_model_serializers/pull/1340) Add support for resource-level meta (@beauby) Fixes: - [#1501](https://github.com/rails-api/active_model_serializers/pull/1501) Adds tests for SerializableResource::use_adapter?,doc typos (@domitian) - [#1488](https://github.com/rails-api/active_model_serializers/pull/1488) Require ActiveSupport's string inflections (@nate00) diff --git a/test/adapter/json_api/resource_meta_test.rb b/test/adapter/json_api/resource_meta_test.rb index 4298f03c..7eec4365 100644 --- a/test/adapter/json_api/resource_meta_test.rb +++ b/test/adapter/json_api/resource_meta_test.rb @@ -46,15 +46,20 @@ module ActiveModel end def test_meta_object_resource_in_array + post2 = Post.new(id: 1339, comments: [Comment.new]) + posts = [@post, post2] hash = ActiveModel::SerializableResource.new( - [@post, @post], + posts, each_serializer: MetaBlockPostSerializer, adapter: :json_api ).serializable_hash expected = { - comments_count: @post.comments.count + :data => [ + { :id => '1337', :type => 'posts', :meta => { :comments_count => 0 } }, + { :id => '1339', :type => 'posts', :meta => { :comments_count => 1 } } + ] } - assert_equal([expected, expected], hash[:data].map { |obj| obj[:meta] }) + assert_equal(expected, hash) end end end diff --git a/test/serializers/meta_test.rb b/test/serializers/meta_test.rb index e7595476..a555adb7 100644 --- a/test/serializers/meta_test.rb +++ b/test/serializers/meta_test.rb @@ -3,8 +3,6 @@ require 'test_helper' module ActiveModel class Serializer class MetaTest < ActiveSupport::TestCase - MetaBlogSerializer = Class.new(ActiveModel::Serializer) - def setup @blog = Blog.new(id: 1, name: 'AMS Hints', @@ -127,20 +125,6 @@ module ActiveModel } assert_equal(expected, actual) end - - def test_meta_is_set_with_direct_attributes - MetaBlogSerializer.meta stuff: 'value' - blog_meta_serializer = MetaBlogSerializer.new(@blog) - assert_equal(blog_meta_serializer.meta, stuff: 'value') - end - - def test_meta_is_set_with_block - MetaBlogSerializer.meta do - { articles_count: object.articles.count } - end - blog_meta_serializer = MetaBlogSerializer.new(@blog) - assert_equal(blog_meta_serializer.meta, articles_count: @blog.articles.count) - end end end end From fe6d2da46f0a44dddfff4078090250799abee582 Mon Sep 17 00:00:00 2001 From: Benjamin Fleischer Date: Mon, 8 Feb 2016 18:17:33 -0600 Subject: [PATCH 22/33] Add missing changelog for #1454 [ci skip] --- CHANGELOG.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 00af8554..b6a852b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,10 @@ Breaking changes: Features: -- [#1018](https://github.com/rails-api/active_model_serializers/pull/1018) Add more tests and docs for top-level links (@leandrocp) -- [#1340](https://github.com/rails-api/active_model_serializers/pull/1340) Add support for resource-level meta (@beauby) +- [#1018](https://github.com/rails-api/active_model_serializers/pull/1018) Add more tests and docs for top-level links. (@leandrocp) +- [#1454](https://github.com/rails-api/active_model_serializers/pull/1454) Add support for + relationship-level links and meta attributes. (@beauby) +- [#1340](https://github.com/rails-api/active_model_serializers/pull/1340) Add support for resource-level meta. (@beauby) Fixes: - [#1501](https://github.com/rails-api/active_model_serializers/pull/1501) Adds tests for SerializableResource::use_adapter?,doc typos (@domitian) - [#1488](https://github.com/rails-api/active_model_serializers/pull/1488) Require ActiveSupport's string inflections (@nate00) From 7b98cf3e36fcbc5467638f325aa4ecbd259bc3e1 Mon Sep 17 00:00:00 2001 From: Benjamin Fleischer Date: Tue, 9 Feb 2016 20:53:38 -0600 Subject: [PATCH 23/33] Update SimpleCov; remove compatibility patch Also, SimpleCov.start already called SimpleCov.pid = Process.pid So, no need for that --- .simplecov | 1 - Gemfile | 1 - active_model_serializers.gemspec | 1 + test/support/simplecov.rb | 6 ------ 4 files changed, 1 insertion(+), 8 deletions(-) delete mode 100644 test/support/simplecov.rb diff --git a/.simplecov b/.simplecov index 616df804..955a6060 100644 --- a/.simplecov +++ b/.simplecov @@ -19,7 +19,6 @@ ENV['FULL_BUILD'] ||= ENV['CI'] # rubocop:enable Style/DoubleNegation ## CONFIGURE SIMPLECOV -SimpleCov.pid = $$ # In case there's any forking SimpleCov.profiles.define 'app' do coverage_dir 'coverage' diff --git a/Gemfile b/Gemfile index 9a386356..9f9282b5 100644 --- a/Gemfile +++ b/Gemfile @@ -41,7 +41,6 @@ group :test do gem 'activerecord-jdbcsqlite3-adapter', platform: :jruby gem 'codeclimate-test-reporter', require: false - gem 'simplecov', '~> 0.10', require: false, group: :development end group :development, :test do diff --git a/active_model_serializers.gemspec b/active_model_serializers.gemspec index 2f5def5a..7febe809 100644 --- a/active_model_serializers.gemspec +++ b/active_model_serializers.gemspec @@ -51,6 +51,7 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'will_paginate', '~> 3.0', '>= 3.0.7' spec.add_development_dependency 'bundler', '~> 1.6' + spec.add_development_dependency 'simplecov', '~> 0.11' spec.add_development_dependency 'timecop', '~> 0.7' spec.add_development_dependency 'minitest-reporters' spec.add_development_dependency 'grape', ['>= 0.13', '< 1.0'] diff --git a/test/support/simplecov.rb b/test/support/simplecov.rb deleted file mode 100644 index 2a249972..00000000 --- a/test/support/simplecov.rb +++ /dev/null @@ -1,6 +0,0 @@ -# https://github.com/colszowka/simplecov/pull/400 -# https://github.com/ruby/ruby/blob/trunk/lib/English.rb -unless defined?(English) - # The exception object passed to +raise+. - alias $ERROR_INFO $! # rubocop:disable Style/SpecialGlobalVars -end From 2789cc5221478a357225811356d815f4004255fc Mon Sep 17 00:00:00 2001 From: Benjamin Fleischer Date: Tue, 9 Feb 2016 22:22:55 -0600 Subject: [PATCH 24/33] Test CachedSerializer#fragment_cached? --- CHANGELOG.md | 2 + test/serializers/cached_serializer_test.rb | 82 ++++++++++++++++++++++ 2 files changed, 84 insertions(+) create mode 100644 test/serializers/cached_serializer_test.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index b6a852b0..cd8b567d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ Features: relationship-level links and meta attributes. (@beauby) - [#1340](https://github.com/rails-api/active_model_serializers/pull/1340) Add support for resource-level meta. (@beauby) Fixes: +- [#1477](https://github.com/rails-api/active_model_serializers/pull/1477) Fix `fragment_cached?` + method to check if caching (@bdmac) - [#1501](https://github.com/rails-api/active_model_serializers/pull/1501) Adds tests for SerializableResource::use_adapter?,doc typos (@domitian) - [#1488](https://github.com/rails-api/active_model_serializers/pull/1488) Require ActiveSupport's string inflections (@nate00) Misc: diff --git a/test/serializers/cached_serializer_test.rb b/test/serializers/cached_serializer_test.rb new file mode 100644 index 00000000..6f31f9e2 --- /dev/null +++ b/test/serializers/cached_serializer_test.rb @@ -0,0 +1,82 @@ +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? + 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? + 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? + 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? + 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? + 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? + 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? + 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? + end + + 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 + end + end + end +end From d67f7da11402d9fd9dde4721b5859c02f346efbe Mon Sep 17 00:00:00 2001 From: Brian McManus Date: Fri, 22 Jan 2016 11:24:30 -0800 Subject: [PATCH 25/33] Preserve the serializer type when fragment caching We were not previously cloning the type setting into the dynamically generated cached/non-cached serializers for a given fragment-cached serializer. This led to the type generated for JsonApi having the wrong value when fragment caching is enabled by adding either :except or :only options to cache. This pulls the type setting from the fragment-cached serializer forward onto the dynamic caching classes so it is preserved in the output. --- .../serializer/adapter/fragment_cache.rb | 4 ++++ test/adapter/fragment_cache_test.rb | 14 +++++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/lib/active_model/serializer/adapter/fragment_cache.rb b/lib/active_model/serializer/adapter/fragment_cache.rb index 5c97a64a..02d56eaa 100644 --- a/lib/active_model/serializer/adapter/fragment_cache.rb +++ b/lib/active_model/serializer/adapter/fragment_cache.rb @@ -94,6 +94,10 @@ module ActiveModel 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) diff --git a/test/adapter/fragment_cache_test.rb b/test/adapter/fragment_cache_test.rb index 0ad78eb5..aded06d4 100644 --- a/test/adapter/fragment_cache_test.rb +++ b/test/adapter/fragment_cache_test.rb @@ -3,6 +3,14 @@ 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 + + belongs_to :author + end + def setup super @spam = Spam::UnrelatedLink.new(id: 'spam-id-1') @@ -31,8 +39,12 @@ module ActiveModel } 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 end end end end - From 527c2aed98a9b8687726b691620334f4c4e0bb9e Mon Sep 17 00:00:00 2001 From: Benjamin Fleischer Date: Tue, 9 Feb 2016 22:37:56 -0600 Subject: [PATCH 26/33] Update changelog [ci skip] For https://github.com/rails-api/active_model_serializers/pull/1458 --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cd8b567d..f1d25517 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,8 +7,10 @@ Features: relationship-level links and meta attributes. (@beauby) - [#1340](https://github.com/rails-api/active_model_serializers/pull/1340) Add support for resource-level meta. (@beauby) Fixes: +- [#1458](https://github.com/rails-api/active_model_serializers/pull/1458) Preserve the serializer + type when fragment caching. (@bdmac) - [#1477](https://github.com/rails-api/active_model_serializers/pull/1477) Fix `fragment_cached?` - method to check if caching (@bdmac) + method to check if caching. (@bdmac) - [#1501](https://github.com/rails-api/active_model_serializers/pull/1501) Adds tests for SerializableResource::use_adapter?,doc typos (@domitian) - [#1488](https://github.com/rails-api/active_model_serializers/pull/1488) Require ActiveSupport's string inflections (@nate00) Misc: From 2c4193851b3a2e3120cbb3c61fa45577bd5c8ed7 Mon Sep 17 00:00:00 2001 From: Yohan Robert Date: Wed, 10 Feb 2016 01:02:48 +0100 Subject: [PATCH 27/33] Follow up to #1454 PR #1454 was merged with some missing fixes. All these fixes are addressed by this commit: - Rename ActiveModel::Serializer::Adapter::JsonApi::Association to ActiveModel::Serializer::Adapter::JsonApi::Relationship - Move ActiveModel::Serializer::Adapter:: JsonApi::Relationship and ActiveModel::Serializer::Adapter::JsonApi::ResourceIdentifier to ActiveModel::Serializer::Adapter::JsonApi::ApiObjects module - Add unit test for ActiveModel::Serializer::Adapter::JsonApi::Relationship - Add unit test for ActiveModel::Serializer::Adapter::JsonApi::ResourceIdentifier --- CHANGELOG.md | 5 + .../serializer/adapter/json_api.rb | 14 +- .../adapter/json_api/api_objects.rb | 13 ++ .../json_api/api_objects/relationship.rb | 52 ++++++ .../api_objects/resource_identifier.rb | 39 ++++ .../adapter/json_api/association.rb | 48 ----- .../adapter/json_api/resource_identifier.rb | 41 ----- .../json_api/api_objects/relationship_test.rb | 168 ++++++++++++++++++ .../api_objects/resource_identifier_test.rb | 88 +++++++++ 9 files changed, 372 insertions(+), 96 deletions(-) create mode 100644 lib/active_model/serializer/adapter/json_api/api_objects.rb create mode 100644 lib/active_model/serializer/adapter/json_api/api_objects/relationship.rb create mode 100644 lib/active_model/serializer/adapter/json_api/api_objects/resource_identifier.rb delete mode 100644 lib/active_model/serializer/adapter/json_api/association.rb delete mode 100644 lib/active_model/serializer/adapter/json_api/resource_identifier.rb create mode 100644 test/adapter/json_api/api_objects/relationship_test.rb create mode 100644 test/adapter/json_api/api_objects/resource_identifier_test.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index b6a852b0..ab451b60 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,14 +1,19 @@ ## 0.10.x Breaking changes: + Features: +- [#1504](https://github.com/rails-api/active_model_serializers/pull/1504) Adds the changes missing from #1454 + and add more tests for resource identifier and relationship objects. (@groyoh) - [#1018](https://github.com/rails-api/active_model_serializers/pull/1018) Add more tests and docs for top-level links. (@leandrocp) - [#1454](https://github.com/rails-api/active_model_serializers/pull/1454) Add support for relationship-level links and meta attributes. (@beauby) - [#1340](https://github.com/rails-api/active_model_serializers/pull/1340) Add support for resource-level meta. (@beauby) + Fixes: - [#1501](https://github.com/rails-api/active_model_serializers/pull/1501) Adds tests for SerializableResource::use_adapter?,doc typos (@domitian) - [#1488](https://github.com/rails-api/active_model_serializers/pull/1488) Require ActiveSupport's string inflections (@nate00) + Misc: ### v0.10.0.rc4 (2016/01/27 11:00 +00:00) diff --git a/lib/active_model/serializer/adapter/json_api.rb b/lib/active_model/serializer/adapter/json_api.rb index a4157193..cfe4e04d 100644 --- a/lib/active_model/serializer/adapter/json_api.rb +++ b/lib/active_model/serializer/adapter/json_api.rb @@ -6,10 +6,9 @@ module ActiveModel autoload :PaginationLinks autoload :FragmentCache autoload :Link - autoload :Association - autoload :ResourceIdentifier 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. @@ -100,7 +99,7 @@ module ActiveModel end def process_resource(serializer, primary) - resource_identifier = JsonApi::ResourceIdentifier.new(serializer).as_json + resource_identifier = ApiObjects::ResourceIdentifier.new(serializer).as_json return false unless @resource_identifiers.add?(resource_identifier) resource_object = resource_object_for(serializer) @@ -136,7 +135,7 @@ module ActiveModel def resource_object_for(serializer) resource_object = cache_check(serializer) do - resource_object = JsonApi::ResourceIdentifier.new(serializer).as_json + resource_object = ApiObjects::ResourceIdentifier.new(serializer).as_json requested_fields = fieldset && fieldset.fields_for(resource_object[:type]) attributes = attributes_for(serializer, requested_fields) @@ -160,12 +159,13 @@ module ActiveModel 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] = JsonApi::Association.new(serializer, + hash[association.key] = ApiObjects::Relationship.new( + serializer, association.serializer, association.options, association.links, - association.meta) - .as_json + association.meta + ).as_json end end diff --git a/lib/active_model/serializer/adapter/json_api/api_objects.rb b/lib/active_model/serializer/adapter/json_api/api_objects.rb new file mode 100644 index 00000000..bad3173c --- /dev/null +++ b/lib/active_model/serializer/adapter/json_api/api_objects.rb @@ -0,0 +1,13 @@ +module ActiveModel + class Serializer + module Adapter + class JsonApi + module ApiObjects + extend ActiveSupport::Autoload + autoload :Relationship + autoload :ResourceIdentifier + 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 new file mode 100644 index 00000000..d1ebc1b9 --- /dev/null +++ b/lib/active_model/serializer/adapter/json_api/api_objects/relationship.rb @@ -0,0 +1,52 @@ +module ActiveModel + class Serializer + module Adapter + class JsonApi + module ApiObjects + class Relationship + def initialize(parent_serializer, serializer, options = {}, links = {}, meta = nil) + @object = parent_serializer.object + @scope = parent_serializer.scope + + @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 + end + @meta = meta.respond_to?(:call) ? parent_serializer.instance_eval(&meta) : meta + end + + def as_json + hash = {} + hash[:data] = data if options[:include_data] + links = self.links + hash[:links] = links if links.any? + meta = self.meta + hash[:meta] = meta if meta + + hash + end + + protected + + attr_reader :object, :scope, :data, :options, :links, :meta + + private + + def data_for(serializer, options) + if serializer.respond_to?(:each) + serializer.map { |s| ResourceIdentifier.new(s).as_json } + else + if options[:virtual_value] + options[:virtual_value] + elsif serializer && serializer.object + ResourceIdentifier.new(serializer).as_json + end + end + end + end + end + end + end + end +end diff --git a/lib/active_model/serializer/adapter/json_api/api_objects/resource_identifier.rb b/lib/active_model/serializer/adapter/json_api/api_objects/resource_identifier.rb new file mode 100644 index 00000000..058f0603 --- /dev/null +++ b/lib/active_model/serializer/adapter/json_api/api_objects/resource_identifier.rb @@ -0,0 +1,39 @@ +module ActiveModel + class Serializer + module Adapter + class JsonApi + module ApiObjects + class ResourceIdentifier + def initialize(serializer) + @id = id_for(serializer) + @type = type_for(serializer) + end + + def as_json + { id: id, type: type } + end + + protected + + attr_reader :id, :type + + private + + def type_for(serializer) + return serializer._type if serializer._type + if ActiveModelSerializers.config.jsonapi_resource_type == :singular + serializer.object.class.model_name.singular + else + serializer.object.class.model_name.plural + end + end + + def id_for(serializer) + serializer.read_attribute_for_serialization(:id).to_s + end + end + end + end + end + end +end diff --git a/lib/active_model/serializer/adapter/json_api/association.rb b/lib/active_model/serializer/adapter/json_api/association.rb deleted file mode 100644 index b6cfc70d..00000000 --- a/lib/active_model/serializer/adapter/json_api/association.rb +++ /dev/null @@ -1,48 +0,0 @@ -module ActiveModel - class Serializer - module Adapter - class JsonApi - class Association - def initialize(parent_serializer, serializer, options, links, meta) - @object = parent_serializer.object - @scope = parent_serializer.scope - - @options = options - @data = data_for(serializer, options) - @links = links - .map { |key, value| { key => Link.new(parent_serializer, value).as_json } } - .reduce({}, :merge) - @meta = meta.respond_to?(:call) ? parent_serializer.instance_eval(&meta) : meta - end - - def as_json - hash = {} - hash[:data] = @data if @options[:include_data] - hash[:links] = @links if @links.any? - hash[:meta] = @meta if @meta - - hash - end - - protected - - attr_reader :object, :scope - - private - - def data_for(serializer, options) - if serializer.respond_to?(:each) - serializer.map { |s| ResourceIdentifier.new(s).as_json } - else - if options[:virtual_value] - options[:virtual_value] - elsif serializer && serializer.object - ResourceIdentifier.new(serializer).as_json - end - end - end - end - end - end - end -end diff --git a/lib/active_model/serializer/adapter/json_api/resource_identifier.rb b/lib/active_model/serializer/adapter/json_api/resource_identifier.rb deleted file mode 100644 index 99bff298..00000000 --- a/lib/active_model/serializer/adapter/json_api/resource_identifier.rb +++ /dev/null @@ -1,41 +0,0 @@ -module ActiveModel - class Serializer - module Adapter - class JsonApi - class ResourceIdentifier - def initialize(serializer) - @id = id_for(serializer) - @type = type_for(serializer) - end - - def as_json - { id: @id.to_s, type: @type } - end - - protected - - attr_reader :object, :scope - - private - - def type_for(serializer) - return serializer._type if serializer._type - if ActiveModelSerializers.config.jsonapi_resource_type == :singular - serializer.object.class.model_name.singular - else - serializer.object.class.model_name.plural - end - end - - def id_for(serializer) - if serializer.respond_to?(:id) - serializer.id - else - serializer.object.id - end - end - end - end - end - end -end diff --git a/test/adapter/json_api/api_objects/relationship_test.rb b/test/adapter/json_api/api_objects/relationship_test.rb new file mode 100644 index 00000000..5564400e --- /dev/null +++ b/test/adapter/json_api/api_objects/relationship_test.rb @@ -0,0 +1,168 @@ +require 'test_helper' + +module ActiveModel + class Serializer + module Adapter + class JsonApi + module ApiObjects + class RelationshipTest < ActiveSupport::TestCase + def setup + @blog = Blog.new(id: 1) + @author = Author.new(id: 1, name: 'Steve K.', blog: @blog) + @serializer = BlogSerializer.new(@blog) + ActionController::Base.cache_store.clear + end + + def test_relationship_with_data + expected = { + data: { + id: '1', + type: 'blogs' + } + } + test_relationship(expected, options: { include_data: true }) + end + + def test_relationship_with_nil_model + @serializer = BlogSerializer.new(nil) + expected = { data: nil } + test_relationship(expected, options: { include_data: true }) + end + + def test_relationship_with_nil_serializer + @serializer = nil + expected = { data: nil } + test_relationship(expected, options: { include_data: true }) + end + + def test_relationship_with_data_array + posts = [Post.new(id: 1), Post.new(id: 2)] + @serializer = ActiveModel::Serializer::ArraySerializer.new(posts) + @author.posts = posts + @author.blog = nil + expected = { + data: [ + { + id: '1', + type: 'posts' + }, + { + id: '2', + type: 'posts' + } + ] + } + test_relationship(expected, options: { include_data: true }) + end + + def test_relationship_data_not_included + test_relationship({}, options: { include_data: false }) + end + + def test_relationship_simple_link + links = { self: 'a link' } + test_relationship({ links: { self: 'a link' } }, links: links) + end + + def test_relationship_many_links + links = { + self: 'a link', + related: 'another link' + } + expected = { + links: { + self: 'a link', + related: 'another link' + } + } + test_relationship(expected, links: links) + end + + def test_relationship_block_link + links = { self: proc { "#{object.id}" } } + expected = { links: { self: "#{@blog.id}" } } + test_relationship(expected, links: links) + end + + def test_relationship_block_link_with_meta + links = { + self: proc do + href "#{object.id}" + meta(id: object.id) + end + } + expected = { + links: { + self: { + href: "#{@blog.id}", + meta: { id: @blog.id } + } + } + } + test_relationship(expected, links: links) + end + + def test_relationship_simple_meta + meta = { id: '1' } + expected = { meta: meta } + test_relationship(expected, meta: meta) + end + + def test_relationship_block_meta + meta = proc do + { id: object.id } + end + expected = { + meta: { + id: @blog.id + } + } + test_relationship(expected, meta: meta) + end + + def test_relationship_with_everything + links = { + self: 'a link', + related: proc do + href "#{object.id}" + meta object.id + end + + } + meta = proc do + { id: object.id } + end + expected = { + data: { + id: '1', + type: 'blogs' + }, + links: { + self: 'a link', + related: { + href: '1', meta: 1 + } + }, + meta: { + id: @blog.id + } + } + test_relationship(expected, meta: meta, options: { include_data: true }, links: links) + end + + private + + def test_relationship(expected, params = {}) + options = params.fetch(:options, {}) + links = params.fetch(:links, {}) + meta = params[:meta] + parent_serializer = AuthorSerializer.new(@author) + relationship = Relationship.new(parent_serializer, @serializer, options, links, meta) + assert_equal(expected, relationship.as_json) + end + end + end + end + end + end +end diff --git a/test/adapter/json_api/api_objects/resource_identifier_test.rb b/test/adapter/json_api/api_objects/resource_identifier_test.rb new file mode 100644 index 00000000..a40f0707 --- /dev/null +++ b/test/adapter/json_api/api_objects/resource_identifier_test.rb @@ -0,0 +1,88 @@ +require 'test_helper' + +module ActiveModel + class Serializer + module Adapter + class JsonApi + module ApiObjects + class ResourceIdentifierTest < ActiveSupport::TestCase + class WithDefinedTypeSerializer < Serializer + type 'with_defined_type' + end + + class WithDefinedIdSerializer < Serializer + def id + 'special_id' + end + end + + class FragmentedSerializer < Serializer; end + + def setup + @model = Author.new(id: 1, name: 'Steve K.') + ActionController::Base.cache_store.clear + end + + def test_defined_type + test_type(WithDefinedTypeSerializer, 'with_defined_type') + end + + def test_singular_type + test_type_inflection(AuthorSerializer, 'author', :singular) + end + + def test_plural_type + test_type_inflection(AuthorSerializer, 'authors', :plural) + end + + def test_id_defined_on_object + test_id(AuthorSerializer, @model.id.to_s) + end + + def test_id_defined_on_serializer + test_id(WithDefinedIdSerializer, 'special_id') + end + + def test_id_defined_on_fragmented + FragmentedSerializer.fragmented(WithDefinedIdSerializer.new(@author)) + test_id(FragmentedSerializer, 'special_id') + end + + private + + def test_type_inflection(serializer_class, expected_type, inflection) + original_inflection = ActiveModelSerializers.config.jsonapi_resource_type + ActiveModelSerializers.config.jsonapi_resource_type = inflection + test_type(serializer_class, expected_type) + ActiveModelSerializers.config.jsonapi_resource_type = original_inflection + end + + def test_type(serializer_class, expected_type) + serializer = serializer_class.new(@model) + resource_identifier = ResourceIdentifier.new(serializer) + expected = { + id: @model.id.to_s, + type: expected_type + } + + assert_equal(expected, resource_identifier.as_json) + end + + def test_id(serializer_class, id) + serializer = serializer_class.new(@model) + resource_identifier = ResourceIdentifier.new(serializer) + inflection = ActiveModelSerializers.config.jsonapi_resource_type + type = @model.class.model_name.send(inflection) + expected = { + id: id, + type: type + } + + assert_equal(expected, resource_identifier.as_json) + end + end + end + end + end + end +end From b1fd43303cb10cc636e06ccddf1a76a18981c70b Mon Sep 17 00:00:00 2001 From: Yohan Robert Date: Wed, 10 Feb 2016 21:00:53 +0100 Subject: [PATCH 28/33] Fix relationship behavior when using block When using the relationships DSL with a block e.g. has_one :bio do link :self, "some_link" end the "data" field would be rendered with a nil value even though the bio is not nil. This happened because the block return value was set to nil but used as a value for the "data" field. --- lib/active_model/serializer/reflection.rb | 13 +- test/adapter/json_api/links_test.rb | 29 ---- test/adapter/json_api/relationship_test.rb | 173 +++++++++++++++++++++ 3 files changed, 182 insertions(+), 33 deletions(-) create mode 100644 test/adapter/json_api/relationship_test.rb diff --git a/lib/active_model/serializer/reflection.rb b/lib/active_model/serializer/reflection.rb index 89fa4074..d7378e60 100644 --- a/lib/active_model/serializer/reflection.rb +++ b/lib/active_model/serializer/reflection.rb @@ -42,17 +42,17 @@ module ActiveModel def link(name, value = nil, &block) @_links[name] = block || value - nil + :nil end def meta(value = nil, &block) @_meta = block || value - nil + :nil end def include_data(value = true) @_include_data = value - nil + :nil end def value(serializer) @@ -60,7 +60,12 @@ module ActiveModel @scope = serializer.scope if block - instance_eval(&block) + block_value = instance_eval(&block) + if block_value == :nil + serializer.read_attribute_for_serialization(name) + else + block_value + end else serializer.read_attribute_for_serialization(name) end diff --git a/test/adapter/json_api/links_test.rb b/test/adapter/json_api/links_test.rb index 81dde4a3..43e37dd7 100644 --- a/test/adapter/json_api/links_test.rb +++ b/test/adapter/json_api/links_test.rb @@ -17,18 +17,6 @@ module ActiveModel link :yet_another do "//example.com/resource/#{object.id}" end - - has_many :posts do - link :self do - href '//example.com/link_author/relationships/posts' - meta stuff: 'value' - end - link :related do - href '//example.com/link_author/posts' - meta count: object.posts.count - end - include_data false - end end def setup @@ -91,23 +79,6 @@ module ActiveModel } assert_equal(expected, hash[:data][:links]) end - - def test_relationship_links - hash = serializable(@author, adapter: :json_api).serializable_hash - expected = { - links: { - self: { - href: '//example.com/link_author/relationships/posts', - meta: { stuff: 'value' } - }, - related: { - href: '//example.com/link_author/posts', - meta: { count: 1 } - } - } - } - assert_equal(expected, hash[:data][:relationships][:posts]) - end end end end diff --git a/test/adapter/json_api/relationship_test.rb b/test/adapter/json_api/relationship_test.rb new file mode 100644 index 00000000..110fbec4 --- /dev/null +++ b/test/adapter/json_api/relationship_test.rb @@ -0,0 +1,173 @@ +require 'test_helper' + +module ActiveModel + class Serializer + module Adapter + class JsonApi + class RelationshipTest < ActiveSupport::TestCase + RelationshipAuthor = Class.new(::Model) + class RelationshipAuthorSerializer < ActiveModel::Serializer + has_one :bio do + link :self, '//example.com/link_author/relationships/bio' + end + + has_one :profile do + link :related do + "//example.com/profiles/#{object.profile.id}" + end + end + + has_many :locations do + link :related do + ids = object.locations.map!(&:id).join(',') + href "//example.com/locations/#{ids}" + end + end + + has_many :posts do + link :related do + ids = object.posts.map!(&:id).join(',') + href "//example.com/posts/#{ids}" + meta ids: ids + end + end + + has_many :roles do + meta count: object.posts.count + end + + has_one :blog do + link :self, '//example.com/link_author/relationships/blog' + include_data false + end + + belongs_to :reviewer do + meta name: 'Dan Brown' + include_data true + end + + has_many :likes do + link :related do + ids = object.likes.map!(&:id).join(',') + href "//example.com/likes/#{ids}" + meta ids: ids + end + meta liked: object.likes.any? + end + end + + def setup + @post = Post.new(id: 1337, comments: [], author: nil) + @blog = Blog.new(id: 1337, name: 'extra') + @bio = Bio.new(id: 1337) + @like = Like.new(id: 1337) + @role = Role.new(id: 1337) + @profile = Profile.new(id: 1337) + @location = Location.new(id: 1337) + @reviewer = Author.new(id: 1337) + @author = RelationshipAuthor.new( + id: 1337, + posts: [@post], + blog: @blog, + reviewer: @reviewer, + bio: @bio, + likes: [@like], + roles: [@role], + locations: [@location], + profile: @profile + ) + end + + def test_relationship_simple_link + hash = serializable(@author, adapter: :json_api).serializable_hash + expected = { + data: { + id: '1337', + type: 'bios' + }, + links: { + self: '//example.com/link_author/relationships/bio' + } + } + assert_equal(expected, hash[:data][:relationships][:bio]) + end + + def test_relationship_block_link + hash = serializable(@author, adapter: :json_api).serializable_hash + expected = { + data: { id: '1337', type: 'profiles' }, + links: { related: '//example.com/profiles/1337' } + } + assert_equal(expected, hash[:data][:relationships][:profile]) + end + + def test_relationship_block_link_href + hash = serializable(@author, adapter: :json_api).serializable_hash + expected = { + data: [{ id: '1337', type: 'locations' }], + links: { + related: { href: '//example.com/locations/1337' } + } + } + assert_equal(expected, hash[:data][:relationships][:locations]) + end + + def test_relationship_block_link_meta + hash = serializable(@author, adapter: :json_api).serializable_hash + expected = { + data: [{ id: '1337', type: 'posts' }], + links: { + related: { + href: '//example.com/posts/1337', + meta: { ids: '1337' } + } + } + } + assert_equal(expected, hash[:data][:relationships][:posts]) + end + + def test_relationship_meta + hash = serializable(@author, adapter: :json_api).serializable_hash + expected = { + data: [{ id: '1337', type: 'roles' }], + meta: { count: 1 } + } + assert_equal(expected, hash[:data][:relationships][:roles]) + end + + def test_relationship_not_including_data + hash = serializable(@author, adapter: :json_api).serializable_hash + expected = { + links: { self: '//example.com/link_author/relationships/blog' } + } + assert_equal(expected, hash[:data][:relationships][:blog]) + end + + def test_relationship_including_data_explicit + hash = serializable(@author, adapter: :json_api).serializable_hash + expected = { + data: { id: '1337', type: 'authors' }, + meta: { name: 'Dan Brown' } + } + assert_equal(expected, hash[:data][:relationships][:reviewer]) + end + + def test_relationship_with_everything + hash = serializable(@author, adapter: :json_api).serializable_hash + expected = { + data: [{ id: '1337', type: 'likes' }], + links: { + related: { + href: '//example.com/likes/1337', + meta: { ids: '1337' } + } + }, + meta: { liked: true } + } + assert_equal(expected, hash[:data][:relationships][:likes]) + end + end + end + end + end +end From f1b3fe6a37721fde0e35ccac016f22ea6418055e Mon Sep 17 00:00:00 2001 From: Benjamin Fleischer Date: Thu, 11 Feb 2016 00:51:06 -0600 Subject: [PATCH 29/33] Fix rubocop config `undefined method '[]' for nil:NilClass` --- Rakefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rakefile b/Rakefile index 4106d987..ef76e7a9 100644 --- a/Rakefile +++ b/Rakefile @@ -29,7 +29,7 @@ else Rake::Task[:rubocop].clear if Rake::Task.task_defined?(:rubocop) desc 'Execute rubocop' RuboCop::RakeTask.new(:rubocop) do |task| - task.options = ['--rails', '--display-cop-names', '--display-style-guide'] + task.options = ['--display-cop-names', '--display-style-guide'] task.fail_on_error = true end end From b45f7b4ffea36b44fdf7be075295ad9db6bea1b0 Mon Sep 17 00:00:00 2001 From: Benjamin Fleischer Date: Thu, 11 Feb 2016 00:53:11 -0600 Subject: [PATCH 30/33] Add changelog for https://github.com/rails-api/active_model_serializers/pull/1372 --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f1d25517..29f766c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ Breaking changes: Features: +- [#1372](https://github.com/rails-api/active_model_serializers/pull/1372) Support + cache_store.read_multi. (@LcpMarvel) - [#1018](https://github.com/rails-api/active_model_serializers/pull/1018) Add more tests and docs for top-level links. (@leandrocp) - [#1454](https://github.com/rails-api/active_model_serializers/pull/1454) Add support for relationship-level links and meta attributes. (@beauby) From 3cbc0541c1b9fd529320e497d8adb5807e474674 Mon Sep 17 00:00:00 2001 From: Yohan Robert Date: Wed, 10 Feb 2016 21:38:30 +0100 Subject: [PATCH 31/33] Fix bug displaying nil for relationship link href When using the relationship link with a block, calling "meta" without "href", e.g. has_one :bio do link :related do meta id: 1 end end results in in a nil "href", e.g. { links: { posts: { related: { href: nil, meta: { id: 1 } } } } }. According to JSONAPI, we should be able to use meta without href (http://jsonapi.org/format/#document-links). --- CHANGELOG.md | 2 + .../serializer/adapter/json_api/link.rb | 5 +- test/adapter/json_api/relationship_test.rb | 61 ++++++++++++------- 3 files changed, 45 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index de59d5df..f237bb24 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ Features: - [#1340](https://github.com/rails-api/active_model_serializers/pull/1340) Add support for resource-level meta. (@beauby) Fixes: +- [#1516](https://github.com/rails-api/active_model_serializers/pull/1501) No longer return a nil href when only + adding meta to a relationship link. (@groyoh) - [#1458](https://github.com/rails-api/active_model_serializers/pull/1458) Preserve the serializer type when fragment caching. (@bdmac) - [#1477](https://github.com/rails-api/active_model_serializers/pull/1477) Fix `fragment_cached?` diff --git a/lib/active_model/serializer/adapter/json_api/link.rb b/lib/active_model/serializer/adapter/json_api/link.rb index bed230c3..f8f7e7ee 100644 --- a/lib/active_model/serializer/adapter/json_api/link.rb +++ b/lib/active_model/serializer/adapter/json_api/link.rb @@ -28,8 +28,9 @@ module ActiveModel def as_json return @value if @value - hash = { href: @href } - hash.merge!(meta: @meta) if @meta + hash = {} + hash[:href] = @href if @href + hash[:meta] = @meta if @meta hash end diff --git a/test/adapter/json_api/relationship_test.rb b/test/adapter/json_api/relationship_test.rb index 110fbec4..b612a980 100644 --- a/test/adapter/json_api/relationship_test.rb +++ b/test/adapter/json_api/relationship_test.rb @@ -19,19 +19,25 @@ module ActiveModel has_many :locations do link :related do - ids = object.locations.map!(&:id).join(',') + ids = object.locations.map(&:id).join(',') href "//example.com/locations/#{ids}" end end has_many :posts do link :related do - ids = object.posts.map!(&:id).join(',') + ids = object.posts.map(&:id).join(',') href "//example.com/posts/#{ids}" meta ids: ids end end + has_many :comments do + link :self do + meta ids: [1] + end + end + has_many :roles do meta count: object.posts.count end @@ -48,7 +54,7 @@ module ActiveModel has_many :likes do link :related do - ids = object.likes.map!(&:id).join(',') + ids = object.likes.map(&:id).join(',') href "//example.com/likes/#{ids}" meta ids: ids end @@ -65,6 +71,7 @@ module ActiveModel @profile = Profile.new(id: 1337) @location = Location.new(id: 1337) @reviewer = Author.new(id: 1337) + @comment = Comment.new(id: 1337) @author = RelationshipAuthor.new( id: 1337, posts: [@post], @@ -74,12 +81,12 @@ module ActiveModel likes: [@like], roles: [@role], locations: [@location], - profile: @profile + profile: @profile, + comments: [@comment] ) end def test_relationship_simple_link - hash = serializable(@author, adapter: :json_api).serializable_hash expected = { data: { id: '1337', @@ -89,31 +96,28 @@ module ActiveModel self: '//example.com/link_author/relationships/bio' } } - assert_equal(expected, hash[:data][:relationships][:bio]) + assert_relationship(:bio, expected) end def test_relationship_block_link - hash = serializable(@author, adapter: :json_api).serializable_hash expected = { data: { id: '1337', type: 'profiles' }, links: { related: '//example.com/profiles/1337' } } - assert_equal(expected, hash[:data][:relationships][:profile]) + assert_relationship(:profile, expected) end def test_relationship_block_link_href - hash = serializable(@author, adapter: :json_api).serializable_hash expected = { data: [{ id: '1337', type: 'locations' }], links: { related: { href: '//example.com/locations/1337' } } } - assert_equal(expected, hash[:data][:relationships][:locations]) + assert_relationship(:locations, expected) end - def test_relationship_block_link_meta - hash = serializable(@author, adapter: :json_api).serializable_hash + def test_relationship_block_link_href_and_meta expected = { data: [{ id: '1337', type: 'posts' }], links: { @@ -123,37 +127,45 @@ module ActiveModel } } } - assert_equal(expected, hash[:data][:relationships][:posts]) + assert_relationship(:posts, expected) + end + + def test_relationship_block_link_meta + expected = { + data: [{ id: '1337', type: 'comments' }], + links: { + self: { + meta: { ids: [1] } + } + } + } + assert_relationship(:comments, expected) end def test_relationship_meta - hash = serializable(@author, adapter: :json_api).serializable_hash expected = { data: [{ id: '1337', type: 'roles' }], meta: { count: 1 } } - assert_equal(expected, hash[:data][:relationships][:roles]) + assert_relationship(:roles, expected) end def test_relationship_not_including_data - hash = serializable(@author, adapter: :json_api).serializable_hash expected = { links: { self: '//example.com/link_author/relationships/blog' } } - assert_equal(expected, hash[:data][:relationships][:blog]) + assert_relationship(:blog, expected) end def test_relationship_including_data_explicit - hash = serializable(@author, adapter: :json_api).serializable_hash expected = { data: { id: '1337', type: 'authors' }, meta: { name: 'Dan Brown' } } - assert_equal(expected, hash[:data][:relationships][:reviewer]) + assert_relationship(:reviewer, expected) end def test_relationship_with_everything - hash = serializable(@author, adapter: :json_api).serializable_hash expected = { data: [{ id: '1337', type: 'likes' }], links: { @@ -164,7 +176,14 @@ module ActiveModel }, meta: { liked: true } } - assert_equal(expected, hash[:data][:relationships][:likes]) + assert_relationship(:likes, expected) + end + + private + + def assert_relationship(relationship_name, expected) + hash = serializable(@author, adapter: :json_api).serializable_hash + assert_equal(expected, hash[:data][:relationships][relationship_name]) end end end From c2eace34684d5e61ceb8b25e344c9c96e8d4e92c Mon Sep 17 00:00:00 2001 From: Yohan Robert Date: Fri, 19 Feb 2016 10:13:54 +0100 Subject: [PATCH 32/33] Fix deprecation warning Fix the "Calling deprecated ArraySerializer... Please use CollectionSerializer" warning that appeared when running the specs. This happened because on of the test called ArraySerializer instead of CollectionSerializer. --- test/adapter/json_api/api_objects/relationship_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/adapter/json_api/api_objects/relationship_test.rb b/test/adapter/json_api/api_objects/relationship_test.rb index 5564400e..26577bc9 100644 --- a/test/adapter/json_api/api_objects/relationship_test.rb +++ b/test/adapter/json_api/api_objects/relationship_test.rb @@ -37,7 +37,7 @@ module ActiveModel def test_relationship_with_data_array posts = [Post.new(id: 1), Post.new(id: 2)] - @serializer = ActiveModel::Serializer::ArraySerializer.new(posts) + @serializer = ActiveModel::Serializer::CollectionSerializer.new(posts) @author.posts = posts @author.blog = nil expected = { From 727d7631ae8e1a316b62dfa77d67b2c92e6f39aa Mon Sep 17 00:00:00 2001 From: Yohan Robert Date: Sat, 13 Feb 2016 14:08:40 +0100 Subject: [PATCH 33/33] Add symbol support for ActiveModel::Serializer.type method The ActiveModel::Serializer.type method now accepts symbol as paremeter: class AuthorSerializer < ActiveModel::Serializer type :profile end The test file for the type was also refactored. --- CHANGELOG.md | 2 + docs/general/serializers.md | 20 +++++- lib/active_model/serializer/type.rb | 2 +- .../json_api/resource_type_config_test.rb | 71 ------------------- test/adapter/json_api/type_test.rb | 61 ++++++++++++++++ 5 files changed, 83 insertions(+), 73 deletions(-) delete mode 100644 test/adapter/json_api/resource_type_config_test.rb create mode 100644 test/adapter/json_api/type_test.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index f237bb24..86086f1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ Breaking changes: Features: +- [#1515](https://github.com/rails-api/active_model_serializers/pull/1515) Adds support for symbols to the + `ActiveModel::Serializer.type` method. (@groyoh) - [#1504](https://github.com/rails-api/active_model_serializers/pull/1504) Adds the changes missing from #1454 and add more tests for resource identifier and relationship objects. Fix association block with link returning `data: nil`.(@groyoh) diff --git a/docs/general/serializers.md b/docs/general/serializers.md index 4014cfe2..65ccaa1a 100644 --- a/docs/general/serializers.md +++ b/docs/general/serializers.md @@ -107,12 +107,30 @@ end #### ::type -e.g. +The `::type` method defines the JSONAPI [type](http://jsonapi.org/format/#document-resource-object-identification) that will be rendered for this serializer. +It either takes a `String` or `Symbol` as parameter. +Note: This method is useful only when using the `:json_api` adapter. + +Examples: ```ruby class UserProfileSerializer < ActiveModel::Serializer type 'profile' end +class AuthorProfileSerializer < ActiveModel::Serializer + type :profile +end +``` + +With the `:json_api` adapter, the previous serializers would be rendered as: + +``` json +{ + "data": { + "id": "1", + "type": "profile" + } +} ``` #### ::link diff --git a/lib/active_model/serializer/type.rb b/lib/active_model/serializer/type.rb index 563cb694..c37c9af8 100644 --- a/lib/active_model/serializer/type.rb +++ b/lib/active_model/serializer/type.rb @@ -17,7 +17,7 @@ module ActiveModel # class AdminAuthorSerializer < ActiveModel::Serializer # type 'authors' def type(type) - self._type = type + self._type = type && type.to_s end end end diff --git a/test/adapter/json_api/resource_type_config_test.rb b/test/adapter/json_api/resource_type_config_test.rb deleted file mode 100644 index d4301c75..00000000 --- a/test/adapter/json_api/resource_type_config_test.rb +++ /dev/null @@ -1,71 +0,0 @@ -require 'test_helper' - -module ActiveModel - class Serializer - module Adapter - class JsonApi - class ResourceTypeConfigTest < ActiveSupport::TestCase - class ProfileTypeSerializer < ActiveModel::Serializer - attributes :name - type 'profile' - end - - def setup - @author = Author.new(id: 1, name: 'Steve K.') - @author.bio = nil - @author.roles = [] - @blog = Blog.new(id: 23, name: 'AMS Blog') - @post = Post.new(id: 42, title: 'New Post', body: 'Body') - @anonymous_post = Post.new(id: 43, title: 'Hello!!', body: 'Hello, world!!') - @comment = Comment.new(id: 1, body: 'ZOMG A COMMENT') - @post.comments = [@comment] - @post.blog = @blog - @anonymous_post.comments = [] - @anonymous_post.blog = nil - @comment.post = @post - @comment.author = nil - @post.author = @author - @anonymous_post.author = nil - @blog = Blog.new(id: 1, name: 'My Blog!!') - @blog.writer = @author - @blog.articles = [@post, @anonymous_post] - @author.posts = [] - end - - def with_jsonapi_resource_type type - old_type = ActiveModelSerializers.config.jsonapi_resource_type - ActiveModelSerializers.config.jsonapi_resource_type = type - yield - ensure - ActiveModelSerializers.config.jsonapi_resource_type = old_type - end - - def test_config_plural - with_jsonapi_resource_type :plural do - hash = serializable(@comment, adapter: :json_api).serializable_hash - assert_equal('comments', hash[:data][:type]) - end - end - - def test_config_singular - with_jsonapi_resource_type :singular do - hash = serializable(@comment, adapter: :json_api).serializable_hash - assert_equal('comment', hash[:data][:type]) - end - end - - def test_explicit_type_value - hash = serializable(@author, serializer: ProfileTypeSerializer, adapter: :json_api).serializable_hash - assert_equal('profile', hash.fetch(:data).fetch(:type)) - end - - private - - def serializable(resource, options = {}) - ActiveModel::SerializableResource.new(resource, options) - end - end - end - end - end -end diff --git a/test/adapter/json_api/type_test.rb b/test/adapter/json_api/type_test.rb new file mode 100644 index 00000000..d034957e --- /dev/null +++ b/test/adapter/json_api/type_test.rb @@ -0,0 +1,61 @@ +require 'test_helper' + +module ActiveModel + class Serializer + module Adapter + class JsonApi + class TypeTest < ActiveSupport::TestCase + class StringTypeSerializer < ActiveModel::Serializer + attribute :name + type 'profile' + end + + class SymbolTypeSerializer < ActiveModel::Serializer + attribute :name + type :profile + end + + setup do + @author = Author.new(id: 1, name: 'Steve K.') + end + + def test_config_plural + with_jsonapi_resource_type :plural do + assert_type(@author, 'authors') + end + end + + def test_config_singular + with_jsonapi_resource_type :singular do + assert_type(@author, 'author') + end + end + + def test_explicit_string_type_value + assert_type(@author, 'profile', serializer: StringTypeSerializer) + end + + def test_explicit_symbol_type_value + assert_type(@author, 'profile', serializer: SymbolTypeSerializer) + end + + private + + def assert_type(resource, expected_type, opts = {}) + opts = opts.reverse_merge(adapter: :json_api) + hash = serializable(resource, opts).serializable_hash + assert_equal(expected_type, hash.fetch(:data).fetch(:type)) + end + + def with_jsonapi_resource_type inflection + old_inflection = ActiveModelSerializers.config.jsonapi_resource_type + ActiveModelSerializers.config.jsonapi_resource_type = inflection + yield + ensure + ActiveModelSerializers.config.jsonapi_resource_type = old_inflection + end + end + end + end + end +end