mirror of
https://github.com/ditkrg/active_model_serializers.git
synced 2026-01-22 22:06:50 +00:00
Add include_data :if_sideloaded (#1931)
For JSONAPI, `include_data` currently means, "should we populate the 'data'" key for this relationship. Current options are true/false. This adds the `:if_sideloaded` option. This means "only populate the 'data' key when we are sideloading this relationship." This is because 'data' is often only relevant to sideloading, and causes a database hit. Addresses https://github.com/rails-api/active_model_serializers/issues/1555
This commit is contained in:
parent
6ed499f38e
commit
2145540795
@ -17,6 +17,7 @@ Features:
|
|||||||
- Added `jsonapi_namespace_separator` config option.
|
- Added `jsonapi_namespace_separator` config option.
|
||||||
- [#1889](https://github.com/rails-api/active_model_serializers/pull/1889) Support key transformation for Attributes adapter (@iancanderson, @danbee)
|
- [#1889](https://github.com/rails-api/active_model_serializers/pull/1889) Support key transformation for Attributes adapter (@iancanderson, @danbee)
|
||||||
- [#1917](https://github.com/rails-api/active_model_serializers/pull/1917) Add `jsonapi_pagination_links_enabled` configuration option (@richmolj)
|
- [#1917](https://github.com/rails-api/active_model_serializers/pull/1917) Add `jsonapi_pagination_links_enabled` configuration option (@richmolj)
|
||||||
|
- [#1797](https://github.com/rails-api/active_model_serializers/pull/1797) Only include 'relationships' when sideloading (@richmolj)
|
||||||
|
|
||||||
Fixes:
|
Fixes:
|
||||||
|
|
||||||
|
|||||||
@ -83,7 +83,8 @@ module ActiveModel
|
|||||||
# +default_include_directive+ config value when not provided)
|
# +default_include_directive+ config value when not provided)
|
||||||
# @return [Enumerator<Association>]
|
# @return [Enumerator<Association>]
|
||||||
#
|
#
|
||||||
def associations(include_directive = ActiveModelSerializers.default_include_directive)
|
def associations(include_directive = ActiveModelSerializers.default_include_directive, include_slice = nil)
|
||||||
|
include_slice ||= include_directive
|
||||||
return unless object
|
return unless object
|
||||||
|
|
||||||
Enumerator.new do |y|
|
Enumerator.new do |y|
|
||||||
@ -91,7 +92,8 @@ module ActiveModel
|
|||||||
next if reflection.excluded?(self)
|
next if reflection.excluded?(self)
|
||||||
key = reflection.options.fetch(:key, reflection.name)
|
key = reflection.options.fetch(:key, reflection.name)
|
||||||
next unless include_directive.key?(key)
|
next unless include_directive.key?(key)
|
||||||
y.yield reflection.build_association(self, instance_options)
|
|
||||||
|
y.yield reflection.build_association(self, instance_options, include_slice)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -30,6 +30,7 @@ module ActiveModel
|
|||||||
# Make JSON API top-level jsonapi member opt-in
|
# Make JSON API top-level jsonapi member opt-in
|
||||||
# ref: http://jsonapi.org/format/#document-top-level
|
# ref: http://jsonapi.org/format/#document-top-level
|
||||||
config.jsonapi_include_toplevel_object = false
|
config.jsonapi_include_toplevel_object = false
|
||||||
|
config.include_data_default = true
|
||||||
|
|
||||||
config.schema_path = 'test/support/schemas'
|
config.schema_path = 'test/support/schemas'
|
||||||
end
|
end
|
||||||
|
|||||||
@ -37,7 +37,7 @@ module ActiveModel
|
|||||||
def initialize(*)
|
def initialize(*)
|
||||||
super
|
super
|
||||||
@_links = {}
|
@_links = {}
|
||||||
@_include_data = true
|
@_include_data = Serializer.config.include_data_default
|
||||||
@_meta = nil
|
@_meta = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -69,17 +69,15 @@ module ActiveModel
|
|||||||
# Blog.find(object.blog_id)
|
# Blog.find(object.blog_id)
|
||||||
# end
|
# end
|
||||||
# end
|
# end
|
||||||
def value(serializer)
|
def value(serializer, include_slice)
|
||||||
@object = serializer.object
|
@object = serializer.object
|
||||||
@scope = serializer.scope
|
@scope = serializer.scope
|
||||||
|
|
||||||
if block
|
block_value = instance_exec(serializer, &block) if block
|
||||||
block_value = instance_exec(serializer, &block)
|
return unless include_data?(include_slice)
|
||||||
if block_value != :nil
|
|
||||||
block_value
|
if block && block_value != :nil
|
||||||
elsif @_include_data
|
block_value
|
||||||
serializer.read_attribute_for_serialization(name)
|
|
||||||
end
|
|
||||||
else
|
else
|
||||||
serializer.read_attribute_for_serialization(name)
|
serializer.read_attribute_for_serialization(name)
|
||||||
end
|
end
|
||||||
@ -106,11 +104,11 @@ module ActiveModel
|
|||||||
#
|
#
|
||||||
# @api private
|
# @api private
|
||||||
#
|
#
|
||||||
def build_association(parent_serializer, parent_serializer_options)
|
def build_association(parent_serializer, parent_serializer_options, include_slice = {})
|
||||||
association_value = value(parent_serializer)
|
association_value = value(parent_serializer, include_slice)
|
||||||
reflection_options = options.dup
|
reflection_options = options.dup
|
||||||
serializer_class = parent_serializer.class.serializer_for(association_value, reflection_options)
|
serializer_class = parent_serializer.class.serializer_for(association_value, reflection_options)
|
||||||
reflection_options[:include_data] = @_include_data
|
reflection_options[:include_data] = include_data?(include_slice)
|
||||||
reflection_options[:links] = @_links
|
reflection_options[:links] = @_links
|
||||||
reflection_options[:meta] = @_meta
|
reflection_options[:meta] = @_meta
|
||||||
|
|
||||||
@ -137,6 +135,14 @@ module ActiveModel
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def include_data?(include_slice)
|
||||||
|
if @_include_data == :if_sideloaded
|
||||||
|
include_slice.key?(name)
|
||||||
|
else
|
||||||
|
@_include_data
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def serializer_options(parent_serializer, parent_serializer_options, reflection_options)
|
def serializer_options(parent_serializer, parent_serializer_options, reflection_options)
|
||||||
serializer = reflection_options.fetch(:serializer, nil)
|
serializer = reflection_options.fetch(:serializer, nil)
|
||||||
|
|
||||||
|
|||||||
@ -235,17 +235,17 @@ module ActiveModelSerializers
|
|||||||
@primary = []
|
@primary = []
|
||||||
@included = []
|
@included = []
|
||||||
@resource_identifiers = Set.new
|
@resource_identifiers = Set.new
|
||||||
serializers.each { |serializer| process_resource(serializer, true) }
|
serializers.each { |serializer| process_resource(serializer, true, @include_directive) }
|
||||||
serializers.each { |serializer| process_relationships(serializer, @include_directive) }
|
serializers.each { |serializer| process_relationships(serializer, @include_directive) }
|
||||||
|
|
||||||
[@primary, @included]
|
[@primary, @included]
|
||||||
end
|
end
|
||||||
|
|
||||||
def process_resource(serializer, primary)
|
def process_resource(serializer, primary, include_slice = {})
|
||||||
resource_identifier = ResourceIdentifier.new(serializer, instance_options).as_json
|
resource_identifier = ResourceIdentifier.new(serializer, instance_options).as_json
|
||||||
return false unless @resource_identifiers.add?(resource_identifier)
|
return false unless @resource_identifiers.add?(resource_identifier)
|
||||||
|
|
||||||
resource_object = resource_object_for(serializer)
|
resource_object = resource_object_for(serializer, include_slice)
|
||||||
if primary
|
if primary
|
||||||
@primary << resource_object
|
@primary << resource_object
|
||||||
else
|
else
|
||||||
@ -255,21 +255,21 @@ module ActiveModelSerializers
|
|||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
def process_relationships(serializer, include_directive)
|
def process_relationships(serializer, include_slice)
|
||||||
serializer.associations(include_directive).each do |association|
|
serializer.associations(include_slice).each do |association|
|
||||||
process_relationship(association.serializer, include_directive[association.key])
|
process_relationship(association.serializer, include_slice[association.key])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def process_relationship(serializer, include_directive)
|
def process_relationship(serializer, include_slice)
|
||||||
if serializer.respond_to?(:each)
|
if serializer.respond_to?(:each)
|
||||||
serializer.each { |s| process_relationship(s, include_directive) }
|
serializer.each { |s| process_relationship(s, include_slice) }
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
return unless serializer && serializer.object
|
return unless serializer && serializer.object
|
||||||
return unless process_resource(serializer, false)
|
return unless process_resource(serializer, false, include_slice)
|
||||||
|
|
||||||
process_relationships(serializer, include_directive)
|
process_relationships(serializer, include_slice)
|
||||||
end
|
end
|
||||||
|
|
||||||
# {http://jsonapi.org/format/#document-resource-object-attributes Document Resource Object Attributes}
|
# {http://jsonapi.org/format/#document-resource-object-attributes Document Resource Object Attributes}
|
||||||
@ -293,7 +293,7 @@ module ActiveModelSerializers
|
|||||||
end
|
end
|
||||||
|
|
||||||
# {http://jsonapi.org/format/#document-resource-objects Document Resource Objects}
|
# {http://jsonapi.org/format/#document-resource-objects Document Resource Objects}
|
||||||
def resource_object_for(serializer)
|
def resource_object_for(serializer, include_slice = {})
|
||||||
resource_object = serializer.fetch(self) do
|
resource_object = serializer.fetch(self) do
|
||||||
resource_object = ResourceIdentifier.new(serializer, instance_options).as_json
|
resource_object = ResourceIdentifier.new(serializer, instance_options).as_json
|
||||||
|
|
||||||
@ -304,7 +304,7 @@ module ActiveModelSerializers
|
|||||||
end
|
end
|
||||||
|
|
||||||
requested_associations = fieldset.fields_for(resource_object[:type]) || '*'
|
requested_associations = fieldset.fields_for(resource_object[:type]) || '*'
|
||||||
relationships = relationships_for(serializer, requested_associations)
|
relationships = relationships_for(serializer, requested_associations, include_slice)
|
||||||
resource_object[:relationships] = relationships if relationships.any?
|
resource_object[:relationships] = relationships if relationships.any?
|
||||||
|
|
||||||
links = links_for(serializer)
|
links = links_for(serializer)
|
||||||
@ -432,12 +432,12 @@ module ActiveModelSerializers
|
|||||||
# id: 'required-id',
|
# id: 'required-id',
|
||||||
# meta: meta
|
# meta: meta
|
||||||
# }.reject! {|_,v| v.nil? }
|
# }.reject! {|_,v| v.nil? }
|
||||||
def relationships_for(serializer, requested_associations)
|
def relationships_for(serializer, requested_associations, include_slice)
|
||||||
include_directive = JSONAPI::IncludeDirective.new(
|
include_directive = JSONAPI::IncludeDirective.new(
|
||||||
requested_associations,
|
requested_associations,
|
||||||
allow_wildcard: true
|
allow_wildcard: true
|
||||||
)
|
)
|
||||||
serializer.associations(include_directive).each_with_object({}) do |association, hash|
|
serializer.associations(include_directive, include_slice).each_with_object({}) do |association, hash|
|
||||||
hash[association.key] = Relationship.new(serializer, instance_options, association).as_json
|
hash[association.key] = Relationship.new(serializer, instance_options, association).as_json
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
166
test/adapter/json_api/include_data_if_sideloaded_test.rb
Normal file
166
test/adapter/json_api/include_data_if_sideloaded_test.rb
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
require 'test_helper'
|
||||||
|
|
||||||
|
module ActiveModel
|
||||||
|
class Serializer
|
||||||
|
module Adapter
|
||||||
|
class JsonApi
|
||||||
|
class IncludeParamTest < ActiveSupport::TestCase
|
||||||
|
IncludeParamAuthor = Class.new(::Model)
|
||||||
|
|
||||||
|
class CustomCommentLoader
|
||||||
|
def all
|
||||||
|
[{ foo: 'bar' }]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class TagSerializer < ActiveModel::Serializer
|
||||||
|
attributes :id, :name
|
||||||
|
end
|
||||||
|
|
||||||
|
class IncludeParamAuthorSerializer < ActiveModel::Serializer
|
||||||
|
class_attribute :comment_loader
|
||||||
|
|
||||||
|
has_many :tags, serializer: TagSerializer do
|
||||||
|
link :self, '//example.com/link_author/relationships/tags'
|
||||||
|
include_data :if_sideloaded
|
||||||
|
end
|
||||||
|
|
||||||
|
has_many :unlinked_tags, serializer: TagSerializer do
|
||||||
|
include_data :if_sideloaded
|
||||||
|
end
|
||||||
|
|
||||||
|
has_many :posts, serializer: PostWithTagsSerializer do
|
||||||
|
include_data :if_sideloaded
|
||||||
|
end
|
||||||
|
has_many :locations do
|
||||||
|
include_data :if_sideloaded
|
||||||
|
end
|
||||||
|
has_many :comments do
|
||||||
|
include_data :if_sideloaded
|
||||||
|
IncludeParamAuthorSerializer.comment_loader.all
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def setup
|
||||||
|
IncludeParamAuthorSerializer.comment_loader = Class.new(CustomCommentLoader).new
|
||||||
|
@tag = Tag.new(id: 1337, name: 'mytag')
|
||||||
|
@author = IncludeParamAuthor.new(
|
||||||
|
id: 1337,
|
||||||
|
tags: [@tag]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_relationship_not_loaded_when_not_included
|
||||||
|
expected = {
|
||||||
|
links: {
|
||||||
|
self: '//example.com/link_author/relationships/tags'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@author.define_singleton_method(:read_attribute_for_serialization) do |attr|
|
||||||
|
fail 'should not be called' if attr == :tags
|
||||||
|
super(attr)
|
||||||
|
end
|
||||||
|
|
||||||
|
assert_relationship(:tags, expected)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_relationship_included
|
||||||
|
expected = {
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
id: '1337',
|
||||||
|
type: 'tags'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
links: {
|
||||||
|
self: '//example.com/link_author/relationships/tags'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_relationship(:tags, expected, include: :tags)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_sideloads_included
|
||||||
|
expected = [
|
||||||
|
{
|
||||||
|
id: '1337',
|
||||||
|
type: 'tags',
|
||||||
|
attributes: { name: 'mytag' }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
hash = result(include: :tags)
|
||||||
|
assert_equal(expected, hash[:included])
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_nested_relationship
|
||||||
|
expected = {
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
id: '1337',
|
||||||
|
type: 'tags'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
links: {
|
||||||
|
self: '//example.com/link_author/relationships/tags'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expected_no_data = {
|
||||||
|
links: {
|
||||||
|
self: '//example.com/link_author/relationships/tags'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_relationship(:tags, expected, include: [:tags, { posts: :tags }])
|
||||||
|
|
||||||
|
@author.define_singleton_method(:read_attribute_for_serialization) do |attr|
|
||||||
|
fail 'should not be called' if attr == :tags
|
||||||
|
super(attr)
|
||||||
|
end
|
||||||
|
|
||||||
|
assert_relationship(:tags, expected_no_data, include: { posts: :tags })
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_include_params_with_no_block
|
||||||
|
@author.define_singleton_method(:read_attribute_for_serialization) do |attr|
|
||||||
|
fail 'should not be called' if attr == :locations
|
||||||
|
super(attr)
|
||||||
|
end
|
||||||
|
|
||||||
|
expected = { meta: {} }
|
||||||
|
|
||||||
|
assert_relationship(:locations, expected)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_block_relationship
|
||||||
|
expected = {
|
||||||
|
data: [
|
||||||
|
{ 'foo' => 'bar' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_relationship(:comments, expected, include: [:comments])
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_node_not_included_when_no_link
|
||||||
|
expected = nil
|
||||||
|
assert_relationship(:unlinked_tags, expected)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def result(opts)
|
||||||
|
opts = { adapter: :json_api }.merge(opts)
|
||||||
|
serializable(@author, opts).serializable_hash
|
||||||
|
end
|
||||||
|
|
||||||
|
def assert_relationship(relationship_name, expected, opts = {})
|
||||||
|
hash = result(opts)
|
||||||
|
assert_equal(expected, hash[:data][:relationships][relationship_name])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
Loading…
Reference in New Issue
Block a user