Skip to content

Commit e53a32d

Browse files
authored
Merge pull request #832 from cerebris/complex_include_tests
Adds tests and fix for complex include missing relationships
2 parents 374b324 + c0e2f8d commit e53a32d

8 files changed

Lines changed: 185 additions & 15 deletions

File tree

lib/jsonapi/resource_serializer.rb

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ def process_primary(source, include_directives)
173173
source.each { |resource| process_primary(resource, include_directives) }
174174
else
175175
return {} if source.nil?
176-
add_included_object(object_hash(source, include_directives), true)
176+
add_resource(source, include_directives, true)
177177
end
178178
end
179179

@@ -285,9 +285,9 @@ def relationships_hash(source, fetchable_fields, include_directives = {})
285285
id = resource.id
286286
relationships_only = already_serialized?(relationship.type, id)
287287
if include_linkage && !relationships_only
288-
add_included_object(object_hash(resource, ia))
288+
add_resource(resource, ia)
289289
elsif include_linked_children || relationships_only
290-
relationships_hash(resource, ia)
290+
relationships_hash(resource, fetchable_fields, ia)
291291
end
292292
end
293293
end
@@ -311,12 +311,7 @@ def cached_relationships_hash(source, include_directives)
311311
end
312312

313313
source.preloaded_fragments[key].each do |id, f|
314-
unless already_serialized?(relationship.type, id)
315-
obj_hash = object_hash(f, ia)
316-
unless self_referential_and_already_in_source(f)
317-
add_included_object(obj_hash)
318-
end
319-
end
314+
add_resource(f, ia)
320315

321316
if h.has_key?(key)
322317
# The hash already has everything we need except the :data field
@@ -467,14 +462,29 @@ def set_primary(type, id)
467462
@included_objects[type][id][:primary] = true
468463
end
469464

470-
# Collects the hashes for all objects processed by the serializer
471-
def add_included_object(obj_hash, primary = false)
472-
is_cached = obj_hash.is_a?(JSONAPI::CachedResourceFragment)
473-
type = is_cached ? obj_hash.type : obj_hash['type']
474-
id = is_cached ? obj_hash.id : obj_hash['id']
465+
def add_resource(source, include_directives, primary = false)
466+
type = source.is_a?(JSONAPI::CachedResourceFragment) ? source.type : source.class._type
467+
id = source.id
475468

476469
@included_objects[type] ||= {}
477-
@included_objects[type][id] = { primary: primary, object_hash: obj_hash }
470+
existing = @included_objects[type][id]
471+
472+
if existing.nil?
473+
obj_hash = object_hash(source, include_directives)
474+
@included_objects[type][id] = {
475+
primary: primary,
476+
object_hash: obj_hash,
477+
includes: Set.new(include_directives[:include_related].keys)
478+
}
479+
else
480+
include_related = Set.new(include_directives[:include_related].keys)
481+
unless existing[:includes].superset?(include_related)
482+
obj_hash = object_hash(source, include_directives)
483+
@included_objects[type][id][:object_hash].deep_merge!(obj_hash)
484+
@included_objects[type][id][:includes].add(include_related)
485+
@included_objects[type][id][:primary] = existing[:primary] | primary
486+
end
487+
end
478488
end
479489

480490
def generate_link_builder(primary_resource_klass, options)

test/controllers/controller_test.rb

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3638,3 +3638,69 @@ def test_caching_with_join_to_resource_with_sql_fragment
36383638
assert_response :success
36393639
end
36403640
end
3641+
3642+
class Api::BoxesControllerTest < ActionController::TestCase
3643+
def test_complex_includes_base
3644+
assert_cacheable_get :index
3645+
assert_response :success
3646+
end
3647+
3648+
def test_complex_includes_two_level
3649+
assert_cacheable_get :index, params: {include: 'things,things.user'}
3650+
3651+
assert_response :success
3652+
3653+
# The test is hardcoded with the include order. This should be changed at some point since either thing could come first and still be valid
3654+
assert_equal '1', json_response['included'][0]['id']
3655+
assert_equal 'things', json_response['included'][0]['type']
3656+
assert_equal '1', json_response['included'][0]['relationships']['user']['data']['id']
3657+
assert_nil json_response['included'][0]['relationships']['things']['data']
3658+
3659+
assert_equal '2', json_response['included'][1]['id']
3660+
assert_equal 'things', json_response['included'][1]['type']
3661+
assert_equal '1', json_response['included'][1]['relationships']['user']['data']['id']
3662+
assert_nil json_response['included'][1]['relationships']['things']['data']
3663+
3664+
assert_equal '1', json_response['included'][2]['id']
3665+
assert_equal 'users', json_response['included'][2]['type']
3666+
assert_nil json_response['included'][2]['relationships']['things']['data']
3667+
end
3668+
3669+
def test_complex_includes_things_nested_things
3670+
assert_cacheable_get :index, params: {include: 'things,things.things'}
3671+
3672+
assert_response :success
3673+
3674+
# The test is hardcoded with the include order. This should be changed at some point since either thing could come first and still be valid
3675+
assert_equal '2', json_response['included'][0]['id']
3676+
assert_equal 'things', json_response['included'][0]['type']
3677+
assert_nil json_response['included'][0]['relationships']['user']['data']
3678+
assert_equal '1', json_response['included'][0]['relationships']['things']['data'][0]['id']
3679+
3680+
assert_equal '1', json_response['included'][1]['id']
3681+
assert_equal 'things', json_response['included'][1]['type']
3682+
assert_nil json_response['included'][1]['relationships']['user']['data']
3683+
assert_equal '2', json_response['included'][1]['relationships']['things']['data'][0]['id']
3684+
end
3685+
3686+
def test_complex_includes_nested_things_secondary_users
3687+
assert_cacheable_get :index, params: {include: 'things,things.user,things.things'}
3688+
3689+
assert_response :success
3690+
3691+
# The test is hardcoded with the include order. This should be changed at some point since either thing could come first and still be valid
3692+
assert_equal '1', json_response['included'][2]['id']
3693+
assert_equal 'users', json_response['included'][2]['type']
3694+
assert_nil json_response['included'][2]['relationships']['things']['data']
3695+
3696+
assert_equal '2', json_response['included'][0]['id']
3697+
assert_equal 'things', json_response['included'][0]['type']
3698+
assert_equal '1', json_response['included'][0]['relationships']['user']['data']['id']
3699+
assert_equal '1', json_response['included'][0]['relationships']['things']['data'][0]['id']
3700+
3701+
assert_equal '1', json_response['included'][1]['id']
3702+
assert_equal 'things', json_response['included'][1]['type']
3703+
assert_equal '1', json_response['included'][1]['relationships']['user']['data']['id']
3704+
assert_equal '2', json_response['included'][1]['relationships']['things']['data'][0]['id']
3705+
end
3706+
end

test/fixtures/active_record.rb

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,33 @@
264264
create_table :questionables, force: true do |t|
265265
t.timestamps null: false
266266
end
267+
268+
create_table :boxes, force: true do |t|
269+
t.string :name
270+
t.timestamps null: false
271+
end
272+
273+
create_table :things, force: true do |t|
274+
t.string :name
275+
t.references :user
276+
t.references :box
277+
278+
t.timestamps null: false
279+
end
280+
281+
create_table :users, force: true do |t|
282+
t.string :name
283+
t.timestamps null: false
284+
end
285+
286+
create_table :related_things, force: true do |t|
287+
t.string :name
288+
t.references :from, references: :thing
289+
t.references :to, references: :thing
290+
291+
t.timestamps null: false
292+
end
293+
267294
# special cases
268295
end
269296

@@ -555,6 +582,27 @@ class Make < ActiveRecord::Base
555582
class WebPage < ActiveRecord::Base
556583
end
557584

585+
class Box < ActiveRecord::Base
586+
has_many :things
587+
end
588+
589+
class User < ActiveRecord::Base
590+
has_many :things
591+
end
592+
593+
class Thing < ActiveRecord::Base
594+
belongs_to :box
595+
belongs_to :user
596+
597+
has_many :related_things, foreign_key: :from_id
598+
has_many :things, through: :related_things, source: :to
599+
end
600+
601+
class RelatedThing < ActiveRecord::Base
602+
belongs_to :from, class_name: Thing, foreign_key: :from_id
603+
belongs_to :to, class_name: Thing, foreign_key: :to_id
604+
end
605+
558606
module Api
559607
module V7
560608
class Client < Customer
@@ -823,6 +871,11 @@ class NumerosTelefoneController < JSONAPI::ResourceController
823871
end
824872
end
825873

874+
module Api
875+
class BoxesController < JSONAPI::ResourceController
876+
end
877+
end
878+
826879
### RESOURCES
827880
class BaseResource < JSONAPI::Resource
828881
abstract
@@ -1712,6 +1765,23 @@ def show
17121765
end
17131766
end
17141767

1768+
module Api
1769+
class BoxResource < JSONAPI::Resource
1770+
has_many :things
1771+
end
1772+
1773+
class ThingResource < JSONAPI::Resource
1774+
has_one :box
1775+
has_one :user
1776+
1777+
has_many :things
1778+
end
1779+
1780+
class UserResource < JSONAPI::Resource
1781+
has_many :things
1782+
end
1783+
end
1784+
17151785
### PORO Data - don't do this in a production app
17161786
$breed_data = BreedData.new
17171787
$breed_data.add(Breed.new(0, 'persian'))

test/fixtures/boxes.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
box_1:
2+
id: 1

test/fixtures/related_things.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
related_thing_1:
2+
id: 1
3+
from_id: 1
4+
to_id: 2
5+
6+
related_thing_2:
7+
id: 2
8+
from_id: 2
9+
to_id: 1

test/fixtures/things.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
thing_1:
2+
id: 1
3+
user_id: 1
4+
box_id: 1
5+
6+
thing_2:
7+
id: 2
8+
user_id: 1
9+
box_id: 1

test/fixtures/users.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
user_1:
2+
id: 1

test/test_helper.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,8 @@ class CatResource < JSONAPI::Resource
256256
jsonapi_resources :authors
257257

258258
namespace :api do
259+
jsonapi_resources :boxes
260+
259261
namespace :v1 do
260262
jsonapi_resources :people
261263
jsonapi_resources :comments

0 commit comments

Comments
 (0)