http-api-tools 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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