Skip to content

Commit aa5a000

Browse files
committed
Fix duplicate resources between data and included.
1 parent 0669901 commit aa5a000

File tree

3 files changed

+92
-15
lines changed

3 files changed

+92
-15
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ Features:
2020
- [#1050](https://github.com/rails-api/active_model_serializers/pull/1050) Add support for toplevel jsonapi member (@beauby, @bf4)
2121

2222
Fixes:
23+
- [#1239](https://github.com/rails-api/active_model_serializers/pull/1239) Fix duplicates in JSON API compound documents (@beauby)
2324
- [#1214](https://github.com/rails-api/active_model_serializers/pull/1214) retrieve the key from the reflection options when building associations (@NullVoxPopuli, @hut8)
2425

2526
Misc:

lib/active_model/serializer/adapter/json_api.rb

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -84,13 +84,15 @@ def serializable_hash_for_collection(options)
8484
serializer.each do |s|
8585
result = self.class.new(s, instance_options.merge(fieldset: fieldset)).serializable_hash(options)
8686
hash[:data] << result[:data]
87+
next unless result[:included]
8788

88-
if result[:included]
89-
hash[:included] ||= []
90-
hash[:included] |= result[:included]
91-
end
89+
hash[:included] ||= []
90+
hash[:included] |= result[:included]
9291
end
9392

93+
hash[:included].delete_if { |resource| Array(hash[:data]).include?(resource) } if hash[:included]
94+
hash.delete(:included) if hash[:included] && hash[:included].empty?
95+
9496
if serializer.paginated?
9597
hash[:links] ||= {}
9698
hash[:links].update(links_for(serializer, options))
@@ -102,9 +104,10 @@ def serializable_hash_for_collection(options)
102104
def serializable_hash_for_single_resource(options)
103105
primary_data = primary_data_for(serializer, options)
104106
relationships = relationships_for(serializer)
105-
included = included_resources(@include_tree)
107+
primary_data[:relationships] = relationships if relationships.any?
106108
hash = { data: primary_data }
107-
hash[:data][:relationships] = relationships if relationships.any?
109+
110+
included = included_resources(@include_tree, Array(primary_data))
108111
hash[:included] = included if included.any?
109112

110113
hash
@@ -171,31 +174,31 @@ def relationships_for(serializer)
171174
end
172175
end
173176

174-
def included_resources(include_tree)
177+
def included_resources(include_tree, primary_data)
175178
included = []
176179

177180
serializer.associations(include_tree).each do |association|
178-
add_included_resources_for(association.serializer, include_tree[association.key], included)
181+
add_included_resources_for(association.serializer, include_tree[association.key], primary_data, included)
179182
end
180183

181184
included
182185
end
183186

184-
def add_included_resources_for(serializer, include_tree, included)
187+
def add_included_resources_for(serializer, include_tree, primary_data, included)
185188
if serializer.respond_to?(:each)
186-
serializer.each { |s| add_included_resources_for(s, include_tree, included) }
189+
serializer.each { |s| add_included_resources_for(s, include_tree, primary_data, included) }
187190
else
188191
return unless serializer && serializer.object
189192

190-
primary_data = primary_data_for(serializer, instance_options)
193+
resource_object = primary_data_for(serializer, instance_options)
191194
relationships = relationships_for(serializer)
192-
primary_data[:relationships] = relationships if relationships.any?
195+
resource_object[:relationships] = relationships if relationships.any?
193196

194-
return if included.include?(primary_data)
195-
included.push(primary_data)
197+
return if included.include?(resource_object) || primary_data.include?(resource_object)
198+
included.push(resource_object)
196199

197200
serializer.associations(include_tree).each do |association|
198-
add_included_resources_for(association.serializer, include_tree[association.key], included)
201+
add_included_resources_for(association.serializer, include_tree[association.key], primary_data, included)
199202
end
200203
end
201204
end

test/adapter/json_api/linked_test.rb

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,79 @@ def test_nil_link_with_specified_serializer
277277
assert_equal expected, adapter.serializable_hash
278278
end
279279
end
280+
281+
class NoDuplicatesTest < Minitest::Test
282+
Post = Class.new(::Model)
283+
Author = Class.new(::Model)
284+
285+
class PostSerializer < ActiveModel::Serializer
286+
type 'posts'
287+
class AuthorSerializer < ActiveModel::Serializer
288+
type 'authors'
289+
has_many :posts, serializer: PostSerializer
290+
end
291+
belongs_to :author, serializer: AuthorSerializer
292+
end
293+
294+
def setup
295+
@author = Author.new(id: 1, posts: [], roles: [], bio: nil)
296+
@post1 = Post.new(id: 1, author: @author)
297+
@post2 = Post.new(id: 2, author: @author)
298+
@author.posts << @post1
299+
@author.posts << @post2
300+
end
301+
302+
def test_no_duplicates
303+
hash = ActiveModel::SerializableResource.new(@post1, adapter: :json_api,
304+
serializer: PostSerializer,
305+
include: '*.*')
306+
.serializable_hash
307+
expected = [
308+
{
309+
type: 'authors', id: '1',
310+
relationships: {
311+
posts: {
312+
data: [
313+
{ type: 'posts', id: '1' },
314+
{ type: 'posts', id: '2' }
315+
]
316+
}
317+
}
318+
},
319+
{
320+
type: 'posts', id: '2',
321+
relationships: {
322+
author: {
323+
data: { type: 'authors', id: '1' }
324+
}
325+
}
326+
}
327+
]
328+
assert_equal(expected, hash[:included])
329+
end
330+
331+
def test_no_duplicates_collection
332+
hash = ActiveModel::SerializableResource.new(
333+
[@post1, @post2], adapter: :json_api,
334+
each_serializer: PostSerializer,
335+
include: '*.*')
336+
.serializable_hash
337+
expected = [
338+
{
339+
type: 'authors', id: '1',
340+
relationships: {
341+
posts: {
342+
data: [
343+
{ type: 'posts', id: '1' },
344+
{ type: 'posts', id: '2' }
345+
]
346+
}
347+
}
348+
}
349+
]
350+
assert_equal(expected, hash[:included])
351+
end
352+
end
280353
end
281354
end
282355
end

0 commit comments

Comments
 (0)