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