http-api-tools 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +18 -0
  3. data/.rspec +3 -0
  4. data/Gemfile +6 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +485 -0
  7. data/Rakefile +4 -0
  8. data/http-api-tools.gemspec +29 -0
  9. data/lib/hat/base_json_serializer.rb +107 -0
  10. data/lib/hat/expanded_relation_includes.rb +77 -0
  11. data/lib/hat/identity_map.rb +42 -0
  12. data/lib/hat/json_serializer_dsl.rb +62 -0
  13. data/lib/hat/model/acts_like_active_model.rb +16 -0
  14. data/lib/hat/model/attributes.rb +159 -0
  15. data/lib/hat/model/has_many_array.rb +47 -0
  16. data/lib/hat/model/transformers/date_time_transformer.rb +31 -0
  17. data/lib/hat/model/transformers/registry.rb +55 -0
  18. data/lib/hat/model.rb +2 -0
  19. data/lib/hat/nesting/json_serializer.rb +45 -0
  20. data/lib/hat/nesting/relation_loader.rb +89 -0
  21. data/lib/hat/relation_includes.rb +140 -0
  22. data/lib/hat/serializer_registry.rb +27 -0
  23. data/lib/hat/sideloading/json_deserializer.rb +121 -0
  24. data/lib/hat/sideloading/json_deserializer_mapping.rb +27 -0
  25. data/lib/hat/sideloading/json_serializer.rb +125 -0
  26. data/lib/hat/sideloading/relation_sideloader.rb +79 -0
  27. data/lib/hat/sideloading/sideload_map.rb +54 -0
  28. data/lib/hat/type_key_resolver.rb +27 -0
  29. data/lib/hat/version.rb +3 -0
  30. data/lib/hat.rb +9 -0
  31. data/reports/empty.png +0 -0
  32. data/reports/minus.png +0 -0
  33. data/reports/plus.png +0 -0
  34. data/spec/hat/expanded_relation_includes_spec.rb +32 -0
  35. data/spec/hat/identity_map_spec.rb +31 -0
  36. data/spec/hat/model/attributes_spec.rb +170 -0
  37. data/spec/hat/model/has_many_array_spec.rb +48 -0
  38. data/spec/hat/model/transformers/date_time_transformer_spec.rb +36 -0
  39. data/spec/hat/model/transformers/registry_spec.rb +53 -0
  40. data/spec/hat/nesting/json_serializer_spec.rb +173 -0
  41. data/spec/hat/relation_includes_spec.rb +185 -0
  42. data/spec/hat/sideloading/json_deserializer_spec.rb +93 -0
  43. data/spec/hat/sideloading/json_serializer_performance_spec.rb +51 -0
  44. data/spec/hat/sideloading/json_serializer_spec.rb +185 -0
  45. data/spec/hat/sideloading/sideload_map_spec.rb +59 -0
  46. data/spec/hat/support/company_deserializer_mapping.rb +11 -0
  47. data/spec/hat/support/person_deserializer_mapping.rb +9 -0
  48. data/spec/hat/support/spec_models.rb +89 -0
  49. data/spec/hat/support/spec_nesting_serializers.rb +41 -0
  50. data/spec/hat/support/spec_sideloading_serializers.rb +41 -0
  51. data/spec/hat/type_key_resolver_spec.rb +19 -0
  52. data/spec/spec_helper.rb +8 -0
  53. metadata +214 -0
@@ -0,0 +1,170 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+ require 'hat/model/attributes'
5
+
6
+ module Hat
7
+ module Model
8
+
9
+ describe Attributes do
10
+
11
+ let(:test_model_class) do
12
+ Class.new do
13
+ include Attributes
14
+ attribute :name
15
+ attribute :dob, type: :date_time
16
+ attribute :tags, default: []
17
+ attribute :qualifications, default: { thing: 1 }
18
+ attribute :source, default: 'internal'
19
+ attribute :active
20
+ belongs_to :parent
21
+ has_many :children
22
+ end
23
+ end
24
+
25
+ describe "attribute definition" do
26
+
27
+ it "initializes attributes from constructor with coercions" do
28
+ test_model = test_model_class.new(name: "Moonunit", tags: ["Musician", "Guitarist"])
29
+ expect(test_model.name).to eq "Moonunit"
30
+ expect(test_model.tags).to eq ["Musician", "Guitarist"]
31
+ end
32
+
33
+ it "transforms date value when date_time type is defined" do
34
+ date_time_string = "2013-01-01T12:00:00.000Z"
35
+ test_model = test_model_class.new(dob: date_time_string)
36
+ expect(test_model.dob).to eq DateTime.parse(date_time_string)
37
+ end
38
+
39
+ it "sets default array if provided" do
40
+ expect(test_model_class.new.tags).to eq []
41
+ end
42
+
43
+ it "sets default hash if provided" do
44
+ expect(test_model_class.new.qualifications).to eq(thing: 1)
45
+ end
46
+
47
+ it "sets default primitive if provided" do
48
+ expect(test_model_class.new.source).to eq 'internal'
49
+ end
50
+
51
+ it "multiple objects with default array don't share the same default reference" do
52
+ a = test_model_class.new.tags
53
+ b = test_model_class.new.tags
54
+ expect(a).to_not be b
55
+ end
56
+
57
+ it "allows setting of values defined by attribute" do
58
+ test_model = test_model_class.new
59
+ test_model.name = 'New Name'
60
+ expect(test_model.name).to eql 'New Name'
61
+ end
62
+
63
+ it "prevents default values not catered to default_for" do
64
+ test_model_with_invalid_default = Class.new do
65
+ include Attributes
66
+ attribute :created_at, default: DateTime.now
67
+ end
68
+ expect{ test_model_with_invalid_default.new }.to raise_error
69
+
70
+ end
71
+
72
+ it 'sets a false value as false' do
73
+ test_model = test_model_class.new(active: false)
74
+ expect(test_model.active).to eq false
75
+ end
76
+
77
+ context "when given read_only as an option" do
78
+
79
+ let(:test_model_with_read_only_attribute_class) do
80
+ Class.new do
81
+ include Attributes
82
+ attribute :created_at, read_only: true
83
+ attribute :some_cool_value
84
+ end
85
+ end
86
+
87
+
88
+ let(:test_model) { test_model_with_read_only_attribute_class.new }
89
+
90
+ context "when setting the read-only variable" do
91
+ describe "initialize" do
92
+ it "sets the read only value" do
93
+ now = Time.now
94
+ test_object = test_model_with_read_only_attribute_class.new(created_at: now)
95
+ expect(test_object.created_at).to eq now
96
+ end
97
+
98
+ end
99
+
100
+ it "only allows reading of that attribute" do
101
+ expect{ test_model.created_at = Time.now }.to raise_error(NoMethodError)
102
+ expect(test_model.created_at).to be_nil
103
+ end
104
+ end
105
+
106
+ context "when setting the non read-only variable" do
107
+ it "allows reading and writing" do
108
+ value = 'value'
109
+ test_model.some_cool_value = value
110
+ expect(test_model.some_cool_value).to eql value
111
+ end
112
+ end
113
+
114
+ end
115
+ end
116
+
117
+ describe 'belongs_to' do
118
+
119
+ let(:test_model) { test_model_class.new }
120
+ let(:parent) { OpenStruct.new(id: 1) }
121
+
122
+ it 'creates accessor for attribute name' do
123
+ test_model.parent = parent
124
+ expect(test_model.parent).to eq parent
125
+ end
126
+
127
+ it 'creates accessor for attribute_id' do
128
+ test_model.parent_id = 1
129
+ expect(test_model.parent_id).to eq 1
130
+ end
131
+
132
+ it 'updates the id attribute when the belongs_to attribute is updated' do
133
+ test_model.parent = parent
134
+ expect(test_model.parent_id).to eq 1
135
+ end
136
+
137
+ end
138
+
139
+ describe 'has_many' do
140
+
141
+ let(:test_model) { test_model_class.new }
142
+ let(:child) { OpenStruct.new(id: 1) }
143
+
144
+ it 'creates accessor for attribute name' do
145
+ test_model.children = [child]
146
+ expect(test_model.children).to eq [child]
147
+ end
148
+
149
+ it 'creates accessor for attributes_id' do
150
+ test_model.child_ids = [1]
151
+ expect(test_model.child_ids).to eq [1]
152
+ end
153
+
154
+ it 'updates the ids attribute when the has_many attribute is updated' do
155
+ test_model.children = [OpenStruct.new(id: 3), OpenStruct.new(id: 4)]
156
+ expect(test_model.child_ids).to eq [3, 4]
157
+ end
158
+
159
+ end
160
+
161
+ describe "#errors" do
162
+ it "adds accessors for errors" do
163
+ test_model = test_model_class.new(errors: 'errors')
164
+ expect(test_model.errors).to eql "errors"
165
+ end
166
+ end
167
+
168
+ end
169
+ end
170
+ end
@@ -0,0 +1,48 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+ require 'hat/model/has_many_array'
5
+
6
+ module Hat
7
+ module Model
8
+ describe HasManyArray do
9
+
10
+ let(:has_many_attr_name) { 'things' }
11
+ let(:owner) { double('owner', has_many_changed: nil) }
12
+ let(:array) { HasManyArray.new([1], owner, has_many_attr_name) }
13
+
14
+ describe "observing mutation" do
15
+
16
+ before do
17
+ owner.should_receive(:has_many_changed).with(array, has_many_attr_name)
18
+ end
19
+
20
+ it "notifies on <<" do
21
+ array << 2
22
+ end
23
+
24
+ it "notifies on push" do
25
+ array.push(2)
26
+ end
27
+
28
+ it "notifies on delete" do
29
+ array.delete(1)
30
+ end
31
+
32
+ it "notifies on delete_at" do
33
+ array.delete_at(0)
34
+ end
35
+
36
+ it "notifies on clear" do
37
+ array.clear
38
+ end
39
+
40
+ it "notifies on shift" do
41
+ array.shift
42
+ end
43
+
44
+ end
45
+
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,36 @@
1
+ require 'spec_helper'
2
+ require 'hat/model/transformers/date_time_transformer'
3
+
4
+
5
+ module Hat
6
+ module Model
7
+ module Transformers
8
+
9
+ describe DateTimeTransformer do
10
+
11
+ let(:transformer) { DateTimeTransformer }
12
+
13
+ describe '.from_raw_value' do
14
+
15
+ it "transforms a date_time string" do
16
+ date_time_string = "2013-01-01T12:00:00.000Z"
17
+ expect(transformer.from_raw(date_time_string)).to eq DateTime.parse(date_time_string)
18
+ end
19
+
20
+ it "passes through date_time instances" do
21
+ now = DateTime.now
22
+ expect(transformer.from_raw(now)).to eql now
23
+ end
24
+
25
+ it "passes through nil" do
26
+ expect(transformer.from_raw(nil)).to eql nil
27
+ end
28
+
29
+ it "raises transform error on untransformable types" do
30
+ lambda { transformer.from_raw(11.11) }.should raise_error TransformError
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,53 @@
1
+ require 'spec_helper'
2
+ require 'hat/model/transformers/registry'
3
+
4
+
5
+ module Hat
6
+ module Model
7
+ module Transformers
8
+
9
+ describe Registry do
10
+
11
+ let(:registry) { Registry.instance }
12
+
13
+ describe 'registering and retrieving a transformer' do
14
+ it 'adds to the registry' do
15
+ registry.register(:foo, Object)
16
+ expect(registry.get(:foo)).to eq Object
17
+ end
18
+
19
+ it 'raises an exception if a transformer is registered more than once' do
20
+ registry.register(:xyz, Object)
21
+ lambda { registry.register(:xyz, Object) }.should raise_error
22
+ end
23
+ end
24
+
25
+ context 'with a registered transformer' do
26
+
27
+ it 'transforms a raw value with the transformer' do
28
+ date_string = '2000-10-10'
29
+ date_result = Date.today
30
+ registry.get(:date_time).should_receive(:from_raw).with(date_string).and_return(date_result)
31
+ expect(registry.from_raw(:date_time, date_string)).to eq date_result
32
+
33
+ end
34
+
35
+ end
36
+
37
+ context 'without a registered transformer' do
38
+ it 'passes through a raw value' do
39
+ raw_bar = 'bar'
40
+ expect(registry.get(:bar)).to be_nil
41
+ expect(registry.from_raw(:bar, raw_bar)).to eq raw_bar
42
+ end
43
+ end
44
+
45
+ describe 'pre-registered transformers' do
46
+ it 'has date_time transformer registered' do
47
+ expect(registry.get(:date_time)).to eq DateTimeTransformer
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,173 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+ require 'hat/nesting/json_serializer'
5
+
6
+ module Hat
7
+ module Nesting
8
+
9
+ describe JsonSerializer do
10
+
11
+ let(:company) { Company.new(id: 1, name: 'Hooroo') }
12
+ let(:person) { Person.new(id: 2, first_name: 'Rob', last_name: 'Monie') }
13
+ let(:skill) { Skill.new(id: 3, name: "JSON Serialization") }
14
+ let(:skill2) { Skill.new(id: 4, name: "JSON Serialization 2") }
15
+
16
+ before do
17
+ company.employees = [person]
18
+ person.employer = company
19
+ person.skills = [skill, skill2]
20
+ skill.person = person
21
+ skill2.person = person
22
+ end
23
+
24
+ describe "serialization of data" do
25
+ context "with a single top-level serializable object that has relationship names different to model class" do
26
+
27
+ context "without any includes" do
28
+
29
+ let(:serialized) { PersonSerializer.new(person).as_json.with_indifferent_access }
30
+ let(:serialized_person) { serialized[:people].first }
31
+
32
+ it "serializes basic attributes" do
33
+ expect(serialized_person[:id]).to eql person.id
34
+ expect(serialized_person[:first_name]).to eql person.first_name
35
+ expect(serialized_person[:last_name]).to eql person.last_name
36
+ end
37
+
38
+ it 'expect basic attributes with no value' do
39
+ expect(serialized_person.has_key?(:dob)).to be_true
40
+ end
41
+
42
+ it "serializes attributes defined as methods on the serializer" do
43
+ expect(serialized_person[:full_name]).to eql "#{person.first_name} #{person.last_name}"
44
+ end
45
+
46
+ it "serializes relationships as ids" do
47
+
48
+ expect(serialized_person[:employer_id]).to eql person.employer.id
49
+ expect(serialized_person[:skill_ids]).to eql person.skills.map(&:id)
50
+ end
51
+
52
+ it "doesn't serialize any relationships" do
53
+ expect(serialized[:companies]).to be_nil
54
+ expect(serialized[:skills]).to be_nil
55
+ end
56
+
57
+ end
58
+
59
+ context "with relations specified as includes" do
60
+
61
+ let(:serialized) do
62
+ PersonSerializer.new(person).includes(:employer, { skills: [:person] }).as_json.with_indifferent_access
63
+ end
64
+
65
+ let(:serialized_person) { serialized[:people].first }
66
+
67
+ it "serializes nested relationships" do
68
+ expect(serialized_person[:employer][:id]).to eql person.employer.id
69
+ expect(serialized_person[:skills].first[:id]).to eql person.skills.first.id
70
+ expect(serialized_person[:skills].first[:person][:id]).to eql person.skills.first.person.id
71
+ end
72
+
73
+ it "includes wildcard in includable when no explicit includables have been defined" do
74
+ expect(serialized[:meta][:includable]).to eq '*'
75
+ end
76
+
77
+ it "includes what was included in meta" do
78
+ expect(serialized[:meta][:included]).to eq 'employer,skills,skills.person'
79
+ end
80
+
81
+ end
82
+ end
83
+
84
+ context "with an array as the serializable object" do
85
+
86
+ let(:relation) do
87
+ [person, second_person]
88
+ end
89
+
90
+ let(:second_person) { Person.new(id: 5, first_name: 'Stu', last_name: 'Liston') }
91
+ let(:serialized) { JSON.parse(PersonSerializer.new(relation).to_json).with_indifferent_access }
92
+
93
+ before do
94
+ company.employees = [person, second_person]
95
+ person.employer = company
96
+ second_person.employer = company
97
+ person.skills = [skill]
98
+ second_person.skills = []
99
+ skill.person = person
100
+ end
101
+
102
+ it "serializes basic attributes of all items in the array" do
103
+ expect(serialized[:people][0][:id]).to eql person.id
104
+ expect(serialized[:people][0][:first_name]).to eql person.first_name
105
+ expect(serialized[:people][0][:last_name]).to eql person.last_name
106
+ expect(serialized[:people][0][:id]).to eql person.id
107
+
108
+ expect(serialized[:people][1][:id]).to eql second_person.id
109
+ expect(serialized[:people][1][:first_name]).to eql second_person.first_name
110
+ expect(serialized[:people][1][:last_name]).to eql second_person.last_name
111
+ expect(serialized[:people][1][:id]).to eql second_person.id
112
+ end
113
+
114
+ end
115
+
116
+ describe "meta data" do
117
+
118
+ let(:serializer) { PersonSerializer.new(person) }
119
+
120
+ it "adds root key" do
121
+ expect(serializer.as_json[:meta][:root_key]).to eql 'people'
122
+ end
123
+
124
+ it "adds type" do
125
+ expect(serializer.as_json[:meta][:type]).to eql 'person'
126
+ end
127
+
128
+ it "allows meta data to be added" do
129
+ serializer.meta(offset: 0, limit: 10)
130
+ expect(serializer.as_json[:meta][:offset]).to eql 0
131
+ expect(serializer.as_json[:meta][:limit]).to eql 10
132
+ end
133
+
134
+ end
135
+
136
+ describe "limiting nested data" do
137
+
138
+ class LimitedNestingPersonSerializer < PersonSerializer
139
+ includable :skills
140
+ end
141
+
142
+ let(:unlimited_serialized) { PersonSerializer.new(person).includes(:employer, { skills: [:person] }).as_json.with_indifferent_access }
143
+
144
+ let(:limited_serialized) do
145
+ LimitedNestingPersonSerializer.new(person).includes(:employer, { skills: [:person] }).as_json.with_indifferent_access
146
+ end
147
+
148
+ it "does not limit nesting if not limited in serializer" do
149
+ expect(unlimited_serialized[:people][0][:id]).to eq person.id
150
+ end
151
+
152
+ it "allows nesting of includable relations" do
153
+ expect(limited_serialized[:people][0][:skills].first[:name]).to eql person.skills.first.name
154
+ end
155
+
156
+ it "prevents nesting of non-includable relations" do
157
+ expect(limited_serialized[:skills]).to be_nil
158
+ end
159
+
160
+ it "includes what is includable in meta" do
161
+ expect(limited_serialized[:meta][:includable]).to eq 'skills'
162
+ end
163
+
164
+ it "includes what was included in meta" do
165
+ expect(limited_serialized[:meta][:included]).to eq 'skills'
166
+ expect(unlimited_serialized[:meta][:included]).to eq 'employer,skills,skills.person'
167
+ end
168
+
169
+ end
170
+ end
171
+ end
172
+ end
173
+ end
@@ -0,0 +1,185 @@
1
+ require 'spec_helper'
2
+ require 'hat/relation_includes'
3
+
4
+ module Hat
5
+ describe RelationIncludes do
6
+
7
+ context 'when constructed with no value' do
8
+ let(:includes) { RelationIncludes.new }
9
+
10
+ it 'is empty, not present and blank' do
11
+ expect(includes).to be_empty
12
+ expect(includes).to be_blank
13
+ expect(includes).to_not be_present
14
+ end
15
+ end
16
+
17
+ describe 'equality' do
18
+
19
+ it 'works as expected' do
20
+ one = [ :tags, { images: [:comments] }, { reviews: [:author] } ]
21
+ two = [ :tags, { images: [:comments] }, { reviews: [:author] } ]
22
+ expect(RelationIncludes.new(*one)).to eq RelationIncludes.new(*two)
23
+
24
+ one = [ { reviews: [:author] }, :tags, { images: [:comments] } ]
25
+ two = [ :tags, { images: [:comments] }, { reviews: [:author] } ]
26
+ expect(RelationIncludes.new(*one)).to eq RelationIncludes.new(*two)
27
+
28
+ one = [ :tags, { images: [:comments] }, { reviews: [:author] } ]
29
+ two = [ :tags, { images: [:comments] }, :reviews ]
30
+ expect(RelationIncludes.new(*one)).to_not eq RelationIncludes.new(*two)
31
+ end
32
+ end
33
+
34
+ describe '.from_string' do
35
+
36
+ let(:string) { 'a,a.b,a.b.c,b,c' }
37
+ let(:includes) { RelationIncludes.from_string(string) }
38
+
39
+ it 'creates single level includes' do
40
+ expect(includes).to include :b
41
+ expect(includes).to include :c
42
+ end
43
+
44
+ it 'creates nested includes' do
45
+ expect(includes).to include({ a: [{ b: [:c] }] })
46
+ end
47
+
48
+ it 'creates same structure when implicit parts of the path are removed' do
49
+ simplified_params = 'a.b.c,b,c'
50
+ simplified_includes = RelationIncludes.from_string(simplified_params)
51
+ expect(includes).to eq simplified_includes
52
+ end
53
+
54
+ context 'when a nil or empty string is provided' do
55
+
56
+ it 'returns a new includes' do
57
+ expect(RelationIncludes.from_string(nil)).to eq RelationIncludes.new
58
+ expect(RelationIncludes.from_string('')).to eq RelationIncludes.new
59
+ end
60
+ end
61
+ end
62
+
63
+ describe "#to_s" do
64
+
65
+ it "converts to dot-notation specified by the JSON API spec, sorted alphabetically" do
66
+ includes = RelationIncludes.new(:reviews, { images: [{ comments: [:author] }] }, :hashtags)
67
+ expect(includes.to_s).to eq 'hashtags,images,images.comments,images.comments.author,reviews'
68
+
69
+ includes = RelationIncludes.new(:hashtags, { images: [{ comments: [:author, :rating] }] }, :reviews)
70
+ expect(includes.to_s).to eq 'hashtags,images,images.comments,images.comments.author,images.comments.rating,reviews'
71
+ end
72
+ end
73
+
74
+ describe '#&' do
75
+
76
+ let(:relations) { [ :tags, { images: [:comments] }, { reviews: [:author] } ] }
77
+ let(:includes) { RelationIncludes.new(*relations) }
78
+
79
+ let(:scenarios) do
80
+ [
81
+ {
82
+ includes: [ { images: [:comments] } ],
83
+ other: [ { images: [:comments] } ],
84
+ expected: [ { images: [:comments] } ]
85
+ },
86
+ {
87
+ includes: [ { images: [:comments, :hashtags]} ],
88
+ other: [ { images: [:comments] } ],
89
+ expected: [ { images: [:comments] } ]
90
+ },
91
+ {
92
+ includes: [ { images: [:comments] } ],
93
+ other: [ { images: [:comments, :hashtags] } ],
94
+ expected: [ { images: [:comments] } ]
95
+ },
96
+ {
97
+ includes: [ :reviews, { images: [{ comments: [:author] }] }, :hashtags ],
98
+ other: [ :reviews, { images: [{ comments: [:author] }] }, :hashtags ],
99
+ expected: [ :reviews, { images: [{ comments: [:author] }] }, :hashtags ]
100
+ },
101
+ {
102
+ includes: [ :reviews, { images: [{ comments: [:author] }] } ],
103
+ other: [ :reviews, { images: [ :comments ]} ],
104
+ expected: [ :reviews, { images: [ :comments ]} ]
105
+ },
106
+ {
107
+ includes: [ :reviews, { images: [ :comments ]} ],
108
+ other: [ :reviews, { images: [{ comments: [:author] }] } ],
109
+ expected: [ :reviews, { images: [ :comments ]} ]
110
+ },
111
+ {
112
+ includes: [ :reviews, { images: [{ comments: [:author] }] } ],
113
+ other: [ :reviews, :images ],
114
+ expected: [ :reviews, :images ]
115
+ },
116
+ {
117
+ includes: [ :reviews, {images: [{ comments: [:author] }] } ],
118
+ other: [ :reviews, :images, :hashtags ],
119
+ expected: [ :reviews, :images ]
120
+ }
121
+ ]
122
+ end
123
+
124
+ it 'reuturns a new RelationIncludes as a deep intersection between two RelationIncludes' do
125
+ scenarios.each do |scenario|
126
+ includes = scenario[:includes]
127
+ other = scenario[:other]
128
+ expected = scenario[:expected]
129
+
130
+ intersection = RelationIncludes.new(*includes) & RelationIncludes.new(*other)
131
+ expect(intersection).to eq RelationIncludes.new(*expected)
132
+ end
133
+ end
134
+
135
+ end
136
+
137
+ describe "#includes_relation?" do
138
+
139
+ let(:includes) { RelationIncludes.new(:a, { b: [:c] }) }
140
+
141
+ it "includes correct relations when a symbol" do
142
+ expect(includes.includes_relation?(:a)).to be_true
143
+ end
144
+
145
+ it "includes relations when key of object" do
146
+ expect(includes.includes_relation?(:b)).to be_true
147
+ end
148
+
149
+ it "does not include unspecified relations" do
150
+ expect(includes.includes_relation?(:x)).to be_false
151
+ end
152
+
153
+ end
154
+
155
+ describe "#include" do
156
+
157
+ let(:includes) { RelationIncludes.new(:a, { b: [:c] }) }
158
+
159
+ it "includes new relations" do
160
+ includes.include([:y, :z])
161
+ expect(includes).to include :y
162
+ expect(includes).to include :z
163
+ end
164
+ end
165
+
166
+ describe "#find" do
167
+
168
+ let(:includes) { RelationIncludes.new(:a, { b: [:c] }) }
169
+
170
+ it "finds include by key" do
171
+ expect(includes.find(:b)).to eq({ b: [:c] })
172
+ end
173
+ end
174
+
175
+ describe "#nested_includes_for" do
176
+
177
+ let(:includes) { RelationIncludes.new(:a, { b: [:c] }) }
178
+
179
+ it "returns nested includes" do
180
+ expect(includes.nested_includes_for(:b)).to eq([:c])
181
+ end
182
+ end
183
+
184
+ end
185
+ end