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.
- checksums.yaml +15 -0
- data/.gitignore +18 -0
- data/.rspec +3 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +22 -0
- data/README.md +485 -0
- data/Rakefile +4 -0
- data/http-api-tools.gemspec +29 -0
- data/lib/hat/base_json_serializer.rb +107 -0
- data/lib/hat/expanded_relation_includes.rb +77 -0
- data/lib/hat/identity_map.rb +42 -0
- data/lib/hat/json_serializer_dsl.rb +62 -0
- data/lib/hat/model/acts_like_active_model.rb +16 -0
- data/lib/hat/model/attributes.rb +159 -0
- data/lib/hat/model/has_many_array.rb +47 -0
- data/lib/hat/model/transformers/date_time_transformer.rb +31 -0
- data/lib/hat/model/transformers/registry.rb +55 -0
- data/lib/hat/model.rb +2 -0
- data/lib/hat/nesting/json_serializer.rb +45 -0
- data/lib/hat/nesting/relation_loader.rb +89 -0
- data/lib/hat/relation_includes.rb +140 -0
- data/lib/hat/serializer_registry.rb +27 -0
- data/lib/hat/sideloading/json_deserializer.rb +121 -0
- data/lib/hat/sideloading/json_deserializer_mapping.rb +27 -0
- data/lib/hat/sideloading/json_serializer.rb +125 -0
- data/lib/hat/sideloading/relation_sideloader.rb +79 -0
- data/lib/hat/sideloading/sideload_map.rb +54 -0
- data/lib/hat/type_key_resolver.rb +27 -0
- data/lib/hat/version.rb +3 -0
- data/lib/hat.rb +9 -0
- data/reports/empty.png +0 -0
- data/reports/minus.png +0 -0
- data/reports/plus.png +0 -0
- data/spec/hat/expanded_relation_includes_spec.rb +32 -0
- data/spec/hat/identity_map_spec.rb +31 -0
- data/spec/hat/model/attributes_spec.rb +170 -0
- data/spec/hat/model/has_many_array_spec.rb +48 -0
- data/spec/hat/model/transformers/date_time_transformer_spec.rb +36 -0
- data/spec/hat/model/transformers/registry_spec.rb +53 -0
- data/spec/hat/nesting/json_serializer_spec.rb +173 -0
- data/spec/hat/relation_includes_spec.rb +185 -0
- data/spec/hat/sideloading/json_deserializer_spec.rb +93 -0
- data/spec/hat/sideloading/json_serializer_performance_spec.rb +51 -0
- data/spec/hat/sideloading/json_serializer_spec.rb +185 -0
- data/spec/hat/sideloading/sideload_map_spec.rb +59 -0
- data/spec/hat/support/company_deserializer_mapping.rb +11 -0
- data/spec/hat/support/person_deserializer_mapping.rb +9 -0
- data/spec/hat/support/spec_models.rb +89 -0
- data/spec/hat/support/spec_nesting_serializers.rb +41 -0
- data/spec/hat/support/spec_sideloading_serializers.rb +41 -0
- data/spec/hat/type_key_resolver_spec.rb +19 -0
- data/spec/spec_helper.rb +8 -0
- 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
|