lhs 21.3.0 → 23.0.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 +4 -4
- data/README.md +72 -11
- data/lhs.gemspec +1 -1
- data/lib/lhs/concerns/record/chainable.rb +4 -4
- data/lib/lhs/concerns/record/request.rb +19 -25
- data/lib/lhs/concerns/record/update.rb +17 -0
- data/lib/lhs/record.rb +6 -3
- data/lib/lhs/version.rb +1 -1
- data/spec/auto_oauth_spec.rb +40 -0
- data/spec/dummy/app/controllers/automatic_authentication_controller.rb +7 -0
- data/spec/dummy/app/models/dummy_record_with_auto_oauth_provider.rb +6 -0
- data/spec/dummy/app/models/providers/internal_services.rb +7 -0
- data/spec/dummy/config/routes.rb +1 -0
- data/spec/item/destroy_spec.rb +1 -1
- data/spec/proxy/record_identification_spec.rb +1 -1
- data/spec/record/all_spec.rb +1 -1
- data/spec/record/endpoints_spec.rb +1 -1
- data/spec/record/handle_includes_errors_spec.rb +1 -1
- data/spec/record/has_many_spec.rb +1 -1
- data/spec/record/has_one_spec.rb +1 -1
- data/spec/record/includes_first_page_spec.rb +737 -0
- data/spec/record/includes_spec.rb +545 -579
- data/spec/record/includes_warning_spec.rb +1 -1
- data/spec/record/mapping_spec.rb +2 -2
- data/spec/record/references_spec.rb +1 -1
- data/spec/record/relation_caching_spec.rb +3 -3
- data/spec/record/update_spec.rb +62 -0
- metadata +13 -6
- data/spec/record/includes_all_spec.rb +0 -693
data/spec/dummy/config/routes.rb
CHANGED
@@ -6,6 +6,7 @@ Rails.application.routes.draw do
|
|
6
6
|
# Automatic Authentication
|
7
7
|
get 'automatic_authentication/oauth' => 'automatic_authentication#o_auth'
|
8
8
|
get 'automatic_authentication/oauth_with_multiple_providers' => 'automatic_authentication#o_auth_with_multiple_providers'
|
9
|
+
get 'automatic_authentication/oauth_with_provider' => 'automatic_authentication#o_auth_with_provider'
|
9
10
|
|
10
11
|
# Request Cycle Cache
|
11
12
|
get 'request_cycle_cache/simple' => 'request_cycle_cache#simple'
|
data/spec/item/destroy_spec.rb
CHANGED
@@ -56,7 +56,7 @@ describe LHS::Item do
|
|
56
56
|
.to_return(status: 200, body: data.to_json)
|
57
57
|
stub_request(:get, "#{datastore}/v2/restaurants/1")
|
58
58
|
.to_return(status: 200, body: { name: 'Casa Ferlin' }.to_json)
|
59
|
-
item = Record.
|
59
|
+
item = Record.includes_first_page(:restaurant).find(1)
|
60
60
|
item.destroy
|
61
61
|
end
|
62
62
|
end
|
@@ -27,7 +27,7 @@ describe LHS::Proxy do
|
|
27
27
|
.to_return(body: {
|
28
28
|
items: [{ review: 'Nice restaurant' }]
|
29
29
|
}.to_json)
|
30
|
-
result = Search.where(what: 'Blumen').
|
30
|
+
result = Search.where(what: 'Blumen').includes_first_page(place: :feedbacks)
|
31
31
|
expect(result.place.feedbacks).to be_kind_of Feedback
|
32
32
|
expect(result.place.feedbacks.first.review).to eq 'Nice restaurant'
|
33
33
|
end
|
data/spec/record/all_spec.rb
CHANGED
@@ -52,7 +52,7 @@ describe LHS::Record do
|
|
52
52
|
end
|
53
53
|
|
54
54
|
it 'works in combination with include and includes' do
|
55
|
-
records = Record.
|
55
|
+
records = Record.includes_first_page(:product).includes(:options).all(color: 'blue')
|
56
56
|
expect(records.length).to eq total
|
57
57
|
expect(first_page_request).to have_been_requested.times(1)
|
58
58
|
expect(second_page_request).to have_been_requested.times(1)
|
@@ -88,7 +88,7 @@ describe LHS::Record do
|
|
88
88
|
stub_request(:get, "#{datastore}/products/LBC")
|
89
89
|
.to_return(body: { name: 'Local Business Card' }.to_json)
|
90
90
|
expect(lambda {
|
91
|
-
Contract.
|
91
|
+
Contract.includes_first_page(:product).where(entry_id: '123').all.first
|
92
92
|
}).not_to raise_error # Multiple base endpoints found
|
93
93
|
end
|
94
94
|
end
|
@@ -25,7 +25,7 @@ describe LHS::Record do
|
|
25
25
|
|
26
26
|
it 'allows to pass error_handling for includes to LHC' do
|
27
27
|
handler = ->(_) { return { deleted: true } }
|
28
|
-
record = Record.
|
28
|
+
record = Record.includes_first_page(:other).references(other: { error_handler: { LHC::NotFound => handler } }).find(id: 1)
|
29
29
|
|
30
30
|
expect(record.other.deleted).to be(true)
|
31
31
|
end
|
@@ -112,7 +112,7 @@ describe LHS::Record do
|
|
112
112
|
end
|
113
113
|
|
114
114
|
it 'explicit association configuration overrules href class casting' do
|
115
|
-
place = Place.
|
115
|
+
place = Place.includes_first_page(:categories).find(1)
|
116
116
|
expect(place.categories.first).to be_kind_of NewCategory
|
117
117
|
expect(place.categories.first.name).to eq('Pizza')
|
118
118
|
end
|
data/spec/record/has_one_spec.rb
CHANGED
@@ -108,7 +108,7 @@ describe LHS::Record do
|
|
108
108
|
end
|
109
109
|
|
110
110
|
it 'explicit association configuration overrules href class casting' do
|
111
|
-
place = Place.
|
111
|
+
place = Place.includes_first_page(:category).find(1)
|
112
112
|
expect(place.category).to be_kind_of NewCategory
|
113
113
|
expect(place.category.name).to eq('Pizza')
|
114
114
|
end
|
@@ -0,0 +1,737 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rails_helper'
|
4
|
+
|
5
|
+
describe LHS::Record do
|
6
|
+
let(:datastore) { 'http://local.ch/v2' }
|
7
|
+
before { LHC.config.placeholder('datastore', datastore) }
|
8
|
+
|
9
|
+
let(:stub_campaign_request) do
|
10
|
+
stub_request(:get, "#{datastore}/content-ads/51dfc5690cf271c375c5a12d")
|
11
|
+
.to_return(body: {
|
12
|
+
'href' => "#{datastore}/content-ads/51dfc5690cf271c375c5a12d",
|
13
|
+
'entry' => { 'href' => "#{datastore}/local-entries/lakj35asdflkj1203va" },
|
14
|
+
'user' => { 'href' => "#{datastore}/users/lakj35asdflkj1203va" }
|
15
|
+
}.to_json)
|
16
|
+
end
|
17
|
+
|
18
|
+
let(:stub_entry_request) do
|
19
|
+
stub_request(:get, "#{datastore}/local-entries/lakj35asdflkj1203va")
|
20
|
+
.to_return(body: { 'name' => 'Casa Ferlin' }.to_json)
|
21
|
+
end
|
22
|
+
|
23
|
+
let(:stub_user_request) do
|
24
|
+
stub_request(:get, "#{datastore}/users/lakj35asdflkj1203va")
|
25
|
+
.to_return(body: { 'name' => 'Mario' }.to_json)
|
26
|
+
end
|
27
|
+
|
28
|
+
context 'singlelevel includes' do
|
29
|
+
before do
|
30
|
+
class LocalEntry < LHS::Record
|
31
|
+
endpoint '{+datastore}/local-entries'
|
32
|
+
endpoint '{+datastore}/local-entries/{id}'
|
33
|
+
end
|
34
|
+
class User < LHS::Record
|
35
|
+
endpoint '{+datastore}/users'
|
36
|
+
endpoint '{+datastore}/users/{id}'
|
37
|
+
end
|
38
|
+
class Favorite < LHS::Record
|
39
|
+
endpoint '{+datastore}/favorites'
|
40
|
+
endpoint '{+datastore}/favorites/{id}'
|
41
|
+
end
|
42
|
+
stub_request(:get, "#{datastore}/local-entries/1")
|
43
|
+
.to_return(body: { company_name: 'local.ch' }.to_json)
|
44
|
+
stub_request(:get, "#{datastore}/users/1")
|
45
|
+
.to_return(body: { name: 'Mario' }.to_json)
|
46
|
+
stub_request(:get, "#{datastore}/favorites/1")
|
47
|
+
.to_return(body: {
|
48
|
+
local_entry: { href: "#{datastore}/local-entries/1" },
|
49
|
+
user: { href: "#{datastore}/users/1" }
|
50
|
+
}.to_json)
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'includes a resource' do
|
54
|
+
favorite = Favorite.includes_first_page(:local_entry).find(1)
|
55
|
+
expect(favorite.local_entry.company_name).to eq 'local.ch'
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'duplicates a class' do
|
59
|
+
expect(Favorite.object_id).not_to eq(Favorite.includes_first_page(:local_entry).object_id)
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'includes a list of resources' do
|
63
|
+
favorite = Favorite.includes_first_page(:local_entry, :user).find(1)
|
64
|
+
expect(favorite.local_entry).to be_kind_of LocalEntry
|
65
|
+
expect(favorite.local_entry.company_name).to eq 'local.ch'
|
66
|
+
expect(favorite.user.name).to eq 'Mario'
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'includes an array of resources' do
|
70
|
+
favorite = Favorite.includes_first_page([:local_entry, :user]).find(1)
|
71
|
+
expect(favorite.local_entry.company_name).to eq 'local.ch'
|
72
|
+
expect(favorite.user.name).to eq 'Mario'
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
context 'multilevel includes' do
|
77
|
+
before do
|
78
|
+
class Feedback < LHS::Record
|
79
|
+
endpoint '{+datastore}/feedbacks'
|
80
|
+
endpoint '{+datastore}/feedbacks/{id}'
|
81
|
+
end
|
82
|
+
stub_campaign_request
|
83
|
+
stub_entry_request
|
84
|
+
stub_user_request
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'includes linked resources while fetching multiple resources from one service' do
|
88
|
+
stub_request(:get, "#{datastore}/feedbacks?has_reviews=true")
|
89
|
+
.to_return(status: 200, body: {
|
90
|
+
items: [
|
91
|
+
{
|
92
|
+
'href' => "#{datastore}/feedbacks/-Sc4_pYNpqfsudzhtivfkA",
|
93
|
+
'campaign' => { 'href' => "#{datastore}/content-ads/51dfc5690cf271c375c5a12d" }
|
94
|
+
}
|
95
|
+
]
|
96
|
+
}.to_json)
|
97
|
+
|
98
|
+
feedbacks = Feedback.includes_first_page(campaign: :entry).where(has_reviews: true)
|
99
|
+
expect(feedbacks.first.campaign.entry.name).to eq 'Casa Ferlin'
|
100
|
+
end
|
101
|
+
|
102
|
+
it 'includes linked resources while fetching a single resource from one service' do
|
103
|
+
stub_request(:get, "#{datastore}/feedbacks/123")
|
104
|
+
.to_return(status: 200, body: {
|
105
|
+
'href' => "#{datastore}/feedbacks/-Sc4_pYNpqfsudzhtivfkA",
|
106
|
+
'campaign' => { 'href' => "#{datastore}/content-ads/51dfc5690cf271c375c5a12d" }
|
107
|
+
}.to_json)
|
108
|
+
|
109
|
+
feedbacks = Feedback.includes_first_page(campaign: :entry).find(123)
|
110
|
+
expect(feedbacks.campaign.entry.name).to eq 'Casa Ferlin'
|
111
|
+
end
|
112
|
+
|
113
|
+
it 'includes linked resources with array while fetching a single resource from one service' do
|
114
|
+
stub_request(:get, "#{datastore}/feedbacks/123")
|
115
|
+
.to_return(status: 200, body: {
|
116
|
+
'href' => "#{datastore}/feedbacks/-Sc4_pYNpqfsudzhtivfkA",
|
117
|
+
'campaign' => { 'href' => "#{datastore}/content-ads/51dfc5690cf271c375c5a12d" }
|
118
|
+
}.to_json)
|
119
|
+
|
120
|
+
feedbacks = Feedback.includes_first_page(campaign: [:entry, :user]).find(123)
|
121
|
+
expect(feedbacks.campaign.entry.name).to eq 'Casa Ferlin'
|
122
|
+
expect(feedbacks.campaign.user.name).to eq 'Mario'
|
123
|
+
end
|
124
|
+
|
125
|
+
it 'includes list of linked resources while fetching a single resource from one service' do
|
126
|
+
stub_request(:get, "#{datastore}/feedbacks/123")
|
127
|
+
.to_return(status: 200, body: {
|
128
|
+
'href' => "#{datastore}/feedbacks/-Sc4_pYNpqfsudzhtivfkA",
|
129
|
+
'campaign' => { 'href' => "#{datastore}/content-ads/51dfc5690cf271c375c5a12d" },
|
130
|
+
'user' => { 'href' => "#{datastore}/users/lakj35asdflkj1203va" }
|
131
|
+
}.to_json)
|
132
|
+
|
133
|
+
feedbacks = Feedback.includes_first_page(:user, campaign: [:entry, :user]).find(123)
|
134
|
+
expect(feedbacks.campaign.entry.name).to eq 'Casa Ferlin'
|
135
|
+
expect(feedbacks.campaign.user.name).to eq 'Mario'
|
136
|
+
expect(feedbacks.user.name).to eq 'Mario'
|
137
|
+
end
|
138
|
+
|
139
|
+
context 'include objects from known services' do
|
140
|
+
let(:stub_feedback_request) do
|
141
|
+
stub_request(:get, "#{datastore}/feedbacks")
|
142
|
+
.to_return(status: 200, body: {
|
143
|
+
items: [
|
144
|
+
{
|
145
|
+
'href' => "#{datastore}/feedbacks/-Sc4_pYNpqfsudzhtivfkA",
|
146
|
+
'entry' => {
|
147
|
+
'href' => "#{datastore}/local-entries/lakj35asdflkj1203va"
|
148
|
+
}
|
149
|
+
}
|
150
|
+
]
|
151
|
+
}.to_json)
|
152
|
+
end
|
153
|
+
|
154
|
+
let(:interceptor) { spy('interceptor') }
|
155
|
+
|
156
|
+
before do
|
157
|
+
class Entry < LHS::Record
|
158
|
+
endpoint '{+datastore}/local-entries/{id}'
|
159
|
+
end
|
160
|
+
LHC.config.interceptors = [interceptor]
|
161
|
+
end
|
162
|
+
|
163
|
+
it 'uses interceptors for included links from known services' do
|
164
|
+
stub_feedback_request
|
165
|
+
stub_entry_request
|
166
|
+
expect(Feedback.includes_first_page(:entry).where.first.entry.name).to eq 'Casa Ferlin'
|
167
|
+
expect(interceptor).to have_received(:before_request).twice
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
context 'includes not present in response' do
|
172
|
+
before do
|
173
|
+
class Parent < LHS::Record
|
174
|
+
endpoint '{+datastore}/local-parents'
|
175
|
+
endpoint '{+datastore}/local-parents/{id}'
|
176
|
+
end
|
177
|
+
|
178
|
+
class OptionalChild < LHS::Record
|
179
|
+
endpoint '{+datastore}/local-children/{id}'
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
it 'handles missing but included fields in single object response' do
|
184
|
+
stub_request(:get, "#{datastore}/local-parents/1")
|
185
|
+
.to_return(status: 200, body: {
|
186
|
+
'href' => "#{datastore}/local-parents/1",
|
187
|
+
'name' => 'RspecName'
|
188
|
+
}.to_json)
|
189
|
+
|
190
|
+
parent = Parent.includes_first_page(:optional_children).find(1)
|
191
|
+
expect(parent).not_to be nil
|
192
|
+
expect(parent.name).to eq 'RspecName'
|
193
|
+
expect(parent.optional_children).to be nil
|
194
|
+
end
|
195
|
+
|
196
|
+
it 'handles missing but included fields in collection response' do
|
197
|
+
stub_request(:get, "#{datastore}/local-parents")
|
198
|
+
.to_return(status: 200, body: {
|
199
|
+
items: [
|
200
|
+
{
|
201
|
+
'href' => "#{datastore}/local-parents/1",
|
202
|
+
'name' => 'RspecParent'
|
203
|
+
}, {
|
204
|
+
'href' => "#{datastore}/local-parents/2",
|
205
|
+
'name' => 'RspecParent2',
|
206
|
+
'optional_child' => {
|
207
|
+
'href' => "#{datastore}/local-children/1"
|
208
|
+
}
|
209
|
+
}
|
210
|
+
]
|
211
|
+
}.to_json)
|
212
|
+
|
213
|
+
stub_request(:get, "#{datastore}/local-children/1")
|
214
|
+
.to_return(status: 200, body: {
|
215
|
+
href: "#{datastore}/local_children/1",
|
216
|
+
name: 'RspecOptionalChild1'
|
217
|
+
}.to_json)
|
218
|
+
|
219
|
+
child = Parent.includes_first_page(:optional_child).where[1].optional_child
|
220
|
+
expect(child).not_to be nil
|
221
|
+
expect(child.name).to eq 'RspecOptionalChild1'
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
context 'links pointing to nowhere' do
|
227
|
+
before do
|
228
|
+
class Feedback < LHS::Record
|
229
|
+
endpoint '{+datastore}/feedbacks'
|
230
|
+
endpoint '{+datastore}/feedbacks/{id}'
|
231
|
+
end
|
232
|
+
|
233
|
+
stub_request(:get, "#{datastore}/feedbacks/123")
|
234
|
+
.to_return(status: 200, body: {
|
235
|
+
'href' => "#{datastore}/feedbacks/-Sc4_pYNpqfsudzhtivfkA",
|
236
|
+
'campaign' => { 'href' => "#{datastore}/content-ads/51dfc5690cf271c375c5a12d" }
|
237
|
+
}.to_json)
|
238
|
+
|
239
|
+
stub_request(:get, "#{datastore}/content-ads/51dfc5690cf271c375c5a12d")
|
240
|
+
.to_return(status: 404)
|
241
|
+
end
|
242
|
+
|
243
|
+
it 'raises LHC::NotFound for links that cannot be included' do
|
244
|
+
expect(-> {
|
245
|
+
Feedback.includes_first_page(campaign: :entry).find(123)
|
246
|
+
}).to raise_error LHC::NotFound
|
247
|
+
end
|
248
|
+
|
249
|
+
it 'ignores LHC::NotFound for links that cannot be included if configured so with reference options' do
|
250
|
+
feedback = Feedback
|
251
|
+
.includes_first_page(campaign: :entry)
|
252
|
+
.references(campaign: { ignored_errors: [LHC::NotFound] })
|
253
|
+
.find(123)
|
254
|
+
expect(feedback.campaign._raw.keys.length).to eq 1
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
context 'modules' do
|
259
|
+
before do
|
260
|
+
module Services
|
261
|
+
class LocalEntry < LHS::Record
|
262
|
+
endpoint '{+datastore}/local-entries'
|
263
|
+
end
|
264
|
+
|
265
|
+
class Feedback < LHS::Record
|
266
|
+
endpoint '{+datastore}/feedbacks'
|
267
|
+
end
|
268
|
+
end
|
269
|
+
stub_request(:get, "http://local.ch/v2/feedbacks?id=123")
|
270
|
+
.to_return(body: [].to_json)
|
271
|
+
end
|
272
|
+
|
273
|
+
it 'works with modules' do
|
274
|
+
Services::Feedback.includes_first_page(campaign: :entry).find(123)
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
context 'arrays' do
|
279
|
+
before do
|
280
|
+
class Place < LHS::Record
|
281
|
+
endpoint '{+datastore}/place'
|
282
|
+
endpoint '{+datastore}/place/{id}'
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
let!(:place_request) do
|
287
|
+
stub_request(:get, "#{datastore}/place/1")
|
288
|
+
.to_return(body: {
|
289
|
+
'relations' => [
|
290
|
+
{ 'href' => "#{datastore}/place/relations/2" },
|
291
|
+
{ 'href' => "#{datastore}/place/relations/3" }
|
292
|
+
]
|
293
|
+
}.to_json)
|
294
|
+
end
|
295
|
+
|
296
|
+
let!(:relation_request_1) do
|
297
|
+
stub_request(:get, "#{datastore}/place/relations/2")
|
298
|
+
.to_return(body: { name: 'Category' }.to_json)
|
299
|
+
end
|
300
|
+
|
301
|
+
let!(:relation_request_2) do
|
302
|
+
stub_request(:get, "#{datastore}/place/relations/3")
|
303
|
+
.to_return(body: { name: 'ZeFrank' }.to_json)
|
304
|
+
end
|
305
|
+
|
306
|
+
it 'includes items of arrays' do
|
307
|
+
place = Place.includes_first_page(:relations).find(1)
|
308
|
+
expect(place.relations.first.name).to eq 'Category'
|
309
|
+
expect(place.relations[1].name).to eq 'ZeFrank'
|
310
|
+
end
|
311
|
+
|
312
|
+
context 'parallel with empty links' do
|
313
|
+
let!(:place_request_2) do
|
314
|
+
stub_request(:get, "#{datastore}/place/2")
|
315
|
+
.to_return(body: {
|
316
|
+
'relations' => []
|
317
|
+
}.to_json)
|
318
|
+
end
|
319
|
+
|
320
|
+
it 'loads places in parallel and merges included data properly' do
|
321
|
+
place = Place.includes_first_page(:relations).find(2, 1)
|
322
|
+
expect(place[0].relations.empty?).to be true
|
323
|
+
expect(place[1].relations[0].name).to eq 'Category'
|
324
|
+
expect(place[1].relations[1].name).to eq 'ZeFrank'
|
325
|
+
end
|
326
|
+
end
|
327
|
+
end
|
328
|
+
|
329
|
+
context 'empty collections' do
|
330
|
+
it 'skips including empty collections' do
|
331
|
+
class Place < LHS::Record
|
332
|
+
endpoint '{+datastore}/place'
|
333
|
+
endpoint '{+datastore}/place/{id}'
|
334
|
+
end
|
335
|
+
|
336
|
+
stub_request(:get, "#{datastore}/place/1")
|
337
|
+
.to_return(body: {
|
338
|
+
'available_products' => {
|
339
|
+
"url" => "#{datastore}/place/1/products",
|
340
|
+
"items" => []
|
341
|
+
}
|
342
|
+
}.to_json)
|
343
|
+
|
344
|
+
place = Place.includes_first_page(:available_products).find(1)
|
345
|
+
expect(place.available_products.empty?).to eq true
|
346
|
+
end
|
347
|
+
end
|
348
|
+
|
349
|
+
context 'extend items with arrays' do
|
350
|
+
it 'extends base items with arrays' do
|
351
|
+
class Place < LHS::Record
|
352
|
+
endpoint '{+datastore}/place'
|
353
|
+
endpoint '{+datastore}/place/{id}'
|
354
|
+
end
|
355
|
+
|
356
|
+
stub_request(:get, "#{datastore}/place/1")
|
357
|
+
.to_return(body: {
|
358
|
+
'contracts' => {
|
359
|
+
'items' => [{ 'href' => "#{datastore}/place/1/contacts/1" }]
|
360
|
+
}
|
361
|
+
}.to_json)
|
362
|
+
|
363
|
+
stub_request(:get, "#{datastore}/place/1/contacts/1")
|
364
|
+
.to_return(body: {
|
365
|
+
'products' => { 'href' => "#{datastore}/place/1/contacts/1/products" }
|
366
|
+
}.to_json)
|
367
|
+
|
368
|
+
place = Place.includes_first_page(:contracts).find(1)
|
369
|
+
expect(place.contracts.first.products.href).to eq "#{datastore}/place/1/contacts/1/products"
|
370
|
+
end
|
371
|
+
end
|
372
|
+
|
373
|
+
context 'unexpanded response when requesting the included collection' do
|
374
|
+
before do
|
375
|
+
class Customer < LHS::Record
|
376
|
+
endpoint '{+datastore}/customer/{id}'
|
377
|
+
end
|
378
|
+
end
|
379
|
+
|
380
|
+
let!(:customer_request) do
|
381
|
+
stub_request(:get, "#{datastore}/customer/1")
|
382
|
+
.to_return(body: {
|
383
|
+
places: {
|
384
|
+
href: "#{datastore}/places"
|
385
|
+
}
|
386
|
+
}.to_json)
|
387
|
+
end
|
388
|
+
|
389
|
+
let!(:places_request) do
|
390
|
+
stub_request(:get, "#{datastore}/places")
|
391
|
+
.to_return(body: {
|
392
|
+
items: [{ href: "#{datastore}/places/1" }]
|
393
|
+
}.to_json)
|
394
|
+
end
|
395
|
+
|
396
|
+
let!(:place_request) do
|
397
|
+
stub_request(:get, "#{datastore}/places/1")
|
398
|
+
.to_return(body: {
|
399
|
+
name: 'Casa Ferlin'
|
400
|
+
}.to_json)
|
401
|
+
end
|
402
|
+
|
403
|
+
it 'loads the collection and the single items, if not already expanded' do
|
404
|
+
place = Customer.includes_first_page(:places).find(1).places.first
|
405
|
+
assert_requested(place_request)
|
406
|
+
expect(place.name).to eq 'Casa Ferlin'
|
407
|
+
end
|
408
|
+
|
409
|
+
context 'forwarding options' do
|
410
|
+
let!(:places_request) do
|
411
|
+
stub_request(:get, "#{datastore}/places")
|
412
|
+
.with(headers: { 'Authorization' => 'Bearer 123' })
|
413
|
+
.to_return(
|
414
|
+
body: {
|
415
|
+
items: [{ href: "#{datastore}/places/1" }]
|
416
|
+
}.to_json
|
417
|
+
)
|
418
|
+
end
|
419
|
+
|
420
|
+
let!(:place_request) do
|
421
|
+
stub_request(:get, "#{datastore}/places/1")
|
422
|
+
.with(headers: { 'Authorization' => 'Bearer 123' })
|
423
|
+
.to_return(
|
424
|
+
body: {
|
425
|
+
name: 'Casa Ferlin'
|
426
|
+
}.to_json
|
427
|
+
)
|
428
|
+
end
|
429
|
+
|
430
|
+
it 'forwards options used to expand those unexpanded items' do
|
431
|
+
place = Customer
|
432
|
+
.includes_first_page(:places)
|
433
|
+
.references(places: { headers: { 'Authorization' => 'Bearer 123' } })
|
434
|
+
.find(1)
|
435
|
+
.places.first
|
436
|
+
assert_requested(place_request)
|
437
|
+
expect(place.name).to eq 'Casa Ferlin'
|
438
|
+
end
|
439
|
+
end
|
440
|
+
end
|
441
|
+
|
442
|
+
context 'includes with options' do
|
443
|
+
before do
|
444
|
+
class Customer < LHS::Record
|
445
|
+
endpoint '{+datastore}/customers/{id}'
|
446
|
+
endpoint '{+datastore}/customers'
|
447
|
+
end
|
448
|
+
|
449
|
+
class Place < LHS::Record
|
450
|
+
endpoint '{+datastore}/places'
|
451
|
+
end
|
452
|
+
|
453
|
+
stub_request(:get, "#{datastore}/places?forwarded_params=123")
|
454
|
+
.to_return(body: {
|
455
|
+
'items' => [{ id: 1 }]
|
456
|
+
}.to_json)
|
457
|
+
end
|
458
|
+
|
459
|
+
it 'forwards includes options to requests made for those includes' do
|
460
|
+
stub_request(:get, "#{datastore}/customers/1")
|
461
|
+
.to_return(body: {
|
462
|
+
'places' => {
|
463
|
+
'href' => "#{datastore}/places"
|
464
|
+
}
|
465
|
+
}.to_json)
|
466
|
+
customer = Customer
|
467
|
+
.includes_first_page(:places)
|
468
|
+
.references(places: { params: { forwarded_params: 123 } })
|
469
|
+
.find(1)
|
470
|
+
expect(customer.places.first.id).to eq 1
|
471
|
+
end
|
472
|
+
|
473
|
+
it 'is chain-able' do
|
474
|
+
stub_request(:get, "#{datastore}/customers?name=Steve")
|
475
|
+
.to_return(body: [
|
476
|
+
'places' => {
|
477
|
+
'href' => "#{datastore}/places"
|
478
|
+
}
|
479
|
+
].to_json)
|
480
|
+
customers = Customer
|
481
|
+
.where(name: 'Steve')
|
482
|
+
.references(places: { params: { forwarded_params: 123 } })
|
483
|
+
.includes_first_page(:places)
|
484
|
+
expect(customers.first.places.first.id).to eq 1
|
485
|
+
end
|
486
|
+
end
|
487
|
+
|
488
|
+
context 'more complex examples' do
|
489
|
+
before do
|
490
|
+
class Place < LHS::Record
|
491
|
+
endpoint 'http://datastore/places/{id}'
|
492
|
+
end
|
493
|
+
end
|
494
|
+
|
495
|
+
it 'forwards complex references' do
|
496
|
+
stub_request(:get, "http://datastore/places/123?limit=1&forwarded_params=for_place")
|
497
|
+
.to_return(body: {
|
498
|
+
'contracts' => {
|
499
|
+
'href' => "http://datastore/places/123/contracts"
|
500
|
+
}
|
501
|
+
}.to_json)
|
502
|
+
stub_request(:get, "http://datastore/places/123/contracts?forwarded_params=for_contracts")
|
503
|
+
.to_return(body: {
|
504
|
+
href: "http://datastore/places/123/contracts?forwarded_params=for_contracts",
|
505
|
+
items: [
|
506
|
+
{ product: { 'href' => "http://datastore/products/llo" } }
|
507
|
+
]
|
508
|
+
}.to_json)
|
509
|
+
stub_request(:get, "http://datastore/products/llo?forwarded_params=for_product")
|
510
|
+
.to_return(body: {
|
511
|
+
'href' => "http://datastore/products/llo",
|
512
|
+
'name' => 'Local Logo'
|
513
|
+
}.to_json)
|
514
|
+
place = Place
|
515
|
+
.options(params: { forwarded_params: 'for_place' })
|
516
|
+
.includes_first_page(contracts: :product)
|
517
|
+
.references(
|
518
|
+
contracts: {
|
519
|
+
params: { forwarded_params: 'for_contracts' },
|
520
|
+
product: { params: { forwarded_params: 'for_product' } }
|
521
|
+
}
|
522
|
+
)
|
523
|
+
.find_by(id: '123')
|
524
|
+
expect(
|
525
|
+
place.contracts.first.product.name
|
526
|
+
).to eq 'Local Logo'
|
527
|
+
end
|
528
|
+
|
529
|
+
it 'expands empty arrays' do
|
530
|
+
stub_request(:get, "http://datastore/places/123")
|
531
|
+
.to_return(body: {
|
532
|
+
'contracts' => {
|
533
|
+
'href' => "http://datastore/places/123/contracts"
|
534
|
+
}
|
535
|
+
}.to_json)
|
536
|
+
stub_request(:get, "http://datastore/places/123/contracts")
|
537
|
+
.to_return(body: {
|
538
|
+
href: "http://datastore/places/123/contracts",
|
539
|
+
items: []
|
540
|
+
}.to_json)
|
541
|
+
place = Place.includes_first_page(:contracts).find('123')
|
542
|
+
expect(place.contracts.collection?).to eq true
|
543
|
+
expect(
|
544
|
+
place.contracts.as_json
|
545
|
+
).to eq('href' => 'http://datastore/places/123/contracts', 'items' => [])
|
546
|
+
expect(place.contracts.to_a).to eq([])
|
547
|
+
end
|
548
|
+
end
|
549
|
+
|
550
|
+
context 'include and merge arrays when calling find in parallel' do
|
551
|
+
before do
|
552
|
+
class Place < LHS::Record
|
553
|
+
endpoint 'http://datastore/places/{id}'
|
554
|
+
end
|
555
|
+
stub_request(:get, 'http://datastore/places/1')
|
556
|
+
.to_return(body: {
|
557
|
+
category_relations: [{ href: 'http://datastore/category/1' }, { href: 'http://datastore/category/2' }]
|
558
|
+
}.to_json)
|
559
|
+
stub_request(:get, 'http://datastore/places/2')
|
560
|
+
.to_return(body: {
|
561
|
+
category_relations: [{ href: 'http://datastore/category/2' }, { href: 'http://datastore/category/1' }]
|
562
|
+
}.to_json)
|
563
|
+
stub_request(:get, "http://datastore/category/1").to_return(body: { name: 'Food' }.to_json)
|
564
|
+
stub_request(:get, "http://datastore/category/2").to_return(body: { name: 'Drinks' }.to_json)
|
565
|
+
end
|
566
|
+
|
567
|
+
it 'includes and merges linked resources in case of an array of links' do
|
568
|
+
places = Place
|
569
|
+
.includes_first_page(:category_relations)
|
570
|
+
.find(1, 2)
|
571
|
+
expect(places[0].category_relations[0].name).to eq 'Food'
|
572
|
+
expect(places[1].category_relations[0].name).to eq 'Drinks'
|
573
|
+
end
|
574
|
+
end
|
575
|
+
|
576
|
+
context 'single href with array response' do
|
577
|
+
it 'extends base items with arrays' do
|
578
|
+
class Sector < LHS::Record
|
579
|
+
endpoint '{+datastore}/sectors'
|
580
|
+
endpoint '{+datastore}/sectors/{id}'
|
581
|
+
end
|
582
|
+
|
583
|
+
stub_request(:get, "#{datastore}/sectors")
|
584
|
+
.with(query: hash_including(key: 'my_service'))
|
585
|
+
.to_return(body: [
|
586
|
+
{
|
587
|
+
href: "#{datastore}/sectors/1",
|
588
|
+
services: {
|
589
|
+
href: "#{datastore}/sectors/1/services"
|
590
|
+
},
|
591
|
+
keys: [
|
592
|
+
{
|
593
|
+
key: 'my_service',
|
594
|
+
language: 'de'
|
595
|
+
}
|
596
|
+
]
|
597
|
+
}
|
598
|
+
].to_json)
|
599
|
+
|
600
|
+
stub_request(:get, "#{datastore}/sectors/1/services")
|
601
|
+
.to_return(body: [
|
602
|
+
{
|
603
|
+
href: "#{datastore}/services/s1",
|
604
|
+
price_in_cents: 9900,
|
605
|
+
key: 'my_service_service_1'
|
606
|
+
},
|
607
|
+
{
|
608
|
+
href: "#{datastore}/services/s2",
|
609
|
+
price_in_cents: 19900,
|
610
|
+
key: 'my_service_service_2'
|
611
|
+
}
|
612
|
+
].to_json)
|
613
|
+
|
614
|
+
sector = Sector.includes_first_page(:services).find_by(key: 'my_service')
|
615
|
+
expect(sector.services.length).to eq 2
|
616
|
+
expect(sector.services.first.key).to eq 'my_service_service_1'
|
617
|
+
end
|
618
|
+
end
|
619
|
+
|
620
|
+
context 'include for POST/create' do
|
621
|
+
|
622
|
+
before do
|
623
|
+
class Record < LHS::Record
|
624
|
+
endpoint 'https://records'
|
625
|
+
end
|
626
|
+
stub_request(:post, 'https://records/')
|
627
|
+
.with(body: { color: 'blue' }.to_json)
|
628
|
+
.to_return(
|
629
|
+
body: {
|
630
|
+
color: 'blue',
|
631
|
+
alternative_categories: [
|
632
|
+
{ href: 'https://categories/blue' }
|
633
|
+
]
|
634
|
+
}.to_json
|
635
|
+
)
|
636
|
+
stub_request(:get, 'https://categories/blue')
|
637
|
+
.to_return(
|
638
|
+
body: {
|
639
|
+
name: 'blue'
|
640
|
+
}.to_json
|
641
|
+
)
|
642
|
+
end
|
643
|
+
|
644
|
+
it 'includes the resources from the post response' do
|
645
|
+
records = Record.includes_first_page(:alternative_categories).create(color: 'blue')
|
646
|
+
expect(records.alternative_categories.first.name).to eq 'blue'
|
647
|
+
end
|
648
|
+
end
|
649
|
+
|
650
|
+
context 'nested within another structure' do
|
651
|
+
before do
|
652
|
+
class Place < LHS::Record
|
653
|
+
endpoint 'https://places/{id}'
|
654
|
+
end
|
655
|
+
stub_request(:get, "https://places/1")
|
656
|
+
.to_return(body: {
|
657
|
+
customer: {
|
658
|
+
salesforce: {
|
659
|
+
href: 'https://salesforce/customers/1'
|
660
|
+
}
|
661
|
+
}
|
662
|
+
}.to_json)
|
663
|
+
end
|
664
|
+
|
665
|
+
let!(:nested_request) do
|
666
|
+
stub_request(:get, "https://salesforce/customers/1")
|
667
|
+
.to_return(body: {
|
668
|
+
name: 'Steve'
|
669
|
+
}.to_json)
|
670
|
+
end
|
671
|
+
|
672
|
+
it 'includes data that has been nested in an additional structure' do
|
673
|
+
place = Place.includes_first_page(customer: :salesforce).find(1)
|
674
|
+
expect(nested_request).to have_been_requested
|
675
|
+
expect(place.customer.salesforce.name).to eq 'Steve'
|
676
|
+
end
|
677
|
+
|
678
|
+
context 'included data has a configured record endpoint option' do
|
679
|
+
before do
|
680
|
+
class SalesforceCustomer < LHS::Record
|
681
|
+
endpoint 'https://salesforce/customers/{id}', headers: { 'Authorization': 'Bearer 123' }
|
682
|
+
end
|
683
|
+
end
|
684
|
+
|
685
|
+
let!(:nested_request) do
|
686
|
+
stub_request(:get, "https://salesforce/customers/1")
|
687
|
+
.with(headers: { 'Authorization' => 'Bearer 123' })
|
688
|
+
.to_return(body: {
|
689
|
+
name: 'Steve'
|
690
|
+
}.to_json)
|
691
|
+
end
|
692
|
+
|
693
|
+
it 'includes data that has been nested in an additional structure' do
|
694
|
+
place = Place.includes_first_page(customer: :salesforce).find(1)
|
695
|
+
expect(nested_request).to have_been_requested
|
696
|
+
expect(place.customer.salesforce.name).to eq 'Steve'
|
697
|
+
end
|
698
|
+
end
|
699
|
+
end
|
700
|
+
|
701
|
+
context 'include empty structures' do
|
702
|
+
before do
|
703
|
+
class Place < LHS::Record
|
704
|
+
endpoint 'https://places/{id}'
|
705
|
+
end
|
706
|
+
stub_request(:get, "https://places/1")
|
707
|
+
.to_return(body: {
|
708
|
+
id: '123'
|
709
|
+
}.to_json)
|
710
|
+
end
|
711
|
+
|
712
|
+
it 'skips includes when there is nothing and also does not raise an exception' do
|
713
|
+
expect(-> {
|
714
|
+
Place.includes_first_page(contracts: :product).find(1)
|
715
|
+
}).not_to raise_exception
|
716
|
+
end
|
717
|
+
end
|
718
|
+
|
719
|
+
context 'include partially empty structures' do
|
720
|
+
before do
|
721
|
+
class Place < LHS::Record
|
722
|
+
endpoint 'https://places/{id}'
|
723
|
+
end
|
724
|
+
stub_request(:get, "https://places/1")
|
725
|
+
.to_return(body: {
|
726
|
+
id: '123',
|
727
|
+
customer: {}
|
728
|
+
}.to_json)
|
729
|
+
end
|
730
|
+
|
731
|
+
it 'skips includes when there is nothing and also does not raise an exception' do
|
732
|
+
expect(-> {
|
733
|
+
Place.includes_first_page(customer: :salesforce).find(1)
|
734
|
+
}).not_to raise_exception
|
735
|
+
end
|
736
|
+
end
|
737
|
+
end
|