Skip to content

Improve jsonapi include #1159

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions lib/active_model/serializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,26 @@ def attributes(options = {})
end
end

# Transforms an inclusion hash into an array of corresponding existing associations,
# and corresponding inclusion hashes.
# @param [Hash] includes
# @return [Array] an array of pairs [association, include_hash] for matching associations
def expand_includes(includes = {})
if includes.size == 1 && includes.each_key.first == :*
associations.map { |assoc| [assoc, includes.each_value.first] }
elsif includes.size == 1 && includes.each_key.first == :**
associations.map { |assoc| [assoc, { :** => nil }] }
else
expanded_associations = includes.map do |inc|
association = associations.find { |assoc| assoc.key == inc.first }
[association, inc.second] if association
end
expanded_associations.delete(nil)

expanded_associations
end
end

private # rubocop:disable Lint/UselessAccessModifier

ActiveModelSerializers.silence_warnings do
Expand Down
37 changes: 15 additions & 22 deletions lib/active_model/serializer/adapter/json.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,38 +2,31 @@ class ActiveModel::Serializer::Adapter::Json < ActiveModel::Serializer::Adapter
extend ActiveSupport::Autoload
autoload :FragmentCache

def initialize(serializer, options = {})
super
@included = ActiveModel::Serializer::Utils.include_args_to_hash(instance_options[:include] || '*')
end

def serializable_hash(options = nil)
options ||= {}
if serializer.respond_to?(:each)
result = serializer.map { |s| FlattenJson.new(s).serializable_hash(options) }
result = serializer.map { |s| FlattenJson.new(s, include: @included).serializable_hash(options) }
else
hash = {}

core = cache_check(serializer) do
serializer.attributes(options)
end

serializer.associations.each do |association|
serializer = association.serializer
association_options = association.options

if serializer.respond_to?(:each)
array_serializer = serializer
hash[association.key] = array_serializer.map do |item|
cache_check(item) do
item.attributes(association_options)
end
included_associations = serializer.expand_includes(@included)
included_associations.each do |association, assoc_includes|
hash[association.key] =
if association.options[:virtual_value]
association.options[:virtual_value]
elsif association.serializer && association.serializer.object
FlattenJson.new(association.serializer, association.options.merge(include: assoc_includes))
.serializable_hash(options)
end
else
hash[association.key] =
if serializer && serializer.object
cache_check(serializer) do
serializer.attributes(options)
end
elsif association_options[:virtual_value]
association_options[:virtual_value]
end
end
end
result = core.merge hash
end
Expand All @@ -42,6 +35,6 @@ def serializable_hash(options = nil)
end

def fragment_cache(cached_hash, non_cached_hash)
ActiveModel::Serializer::Adapter::Json::FragmentCache.new().fragment_cache(cached_hash, non_cached_hash)
ActiveModel::Serializer::Adapter::Json::FragmentCache.new.fragment_cache(cached_hash, non_cached_hash)
end
end
31 changes: 15 additions & 16 deletions lib/active_model/serializer/adapter/json_api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -124,33 +124,32 @@ def relationships_for(serializer)
end

def included_for(serializer)
included.flat_map { |inc|
association = serializer.associations.find { |assoc| assoc.key == inc.first }
_included_for(association.serializer, inc.second) if association
}.uniq
included = []
included_associations = serializer.expand_includes(@included)
included_associations.each do |association, assoc_includes|
_included_for(included, association.serializer, assoc_includes)
end

included
end

def _included_for(serializer, includes)
def _included_for(included, serializer, includes)
if serializer.respond_to?(:each)
serializer.flat_map { |s| _included_for(s, includes) }.uniq
serializer.each { |s| _included_for(included, s, includes) }
else
return [] unless serializer && serializer.object
return unless serializer && serializer.object

primary_data = primary_data_for(serializer, instance_options)
relationships = relationships_for(serializer)
primary_data[:relationships] = relationships if relationships.any?

included = [primary_data]
return if included.include?(primary_data)
included.push(primary_data)

includes.each do |inc|
association = serializer.associations.find { |assoc| assoc.key == inc.first }
if association
included.concat(_included_for(association.serializer, inc.second))
included.uniq!
end
included_associations = serializer.expand_includes(includes)
included_associations.each do |association, assoc_includes|
_included_for(included, association.serializer, assoc_includes)
end

included
end
end

Expand Down
6 changes: 3 additions & 3 deletions test/action_controller/json_api/linked_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,9 @@ def render_resource_with_nested_include
render json: @post, include: [comments: [:author]], adapter: :json_api
end

def render_resource_with_nested_has_many_include
def render_resource_with_nested_has_many_include_wildcard
setup_post
render json: @post, include: 'author.roles', adapter: :json_api
render json: @post, include: 'author.*', adapter: :json_api
end

def render_resource_with_missing_nested_has_many_include
Expand Down Expand Up @@ -96,7 +96,7 @@ def test_render_resource_with_include
end

def test_render_resource_with_nested_has_many_include
get :render_resource_with_nested_has_many_include
get :render_resource_with_nested_has_many_include_wildcard
response = JSON.parse(@response.body)
expected_linked = [
{
Expand Down