restorm 1.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 +7 -0
- data/.gitignore +4 -0
- data/.rspec +1 -0
- data/.rubocop.yml +31 -0
- data/.rubocop_todo.yml +232 -0
- data/.ruby-version +1 -0
- data/.travis.yml +55 -0
- data/.yardopts +2 -0
- data/CONTRIBUTING.md +26 -0
- data/Gemfile +10 -0
- data/HER_README.md +1065 -0
- data/LICENSE +7 -0
- data/README.md +7 -0
- data/Rakefile +11 -0
- data/UPGRADE.md +101 -0
- data/gemfiles/Gemfile.activemodel-4.2 +6 -0
- data/gemfiles/Gemfile.activemodel-5.0 +6 -0
- data/gemfiles/Gemfile.activemodel-5.1 +6 -0
- data/gemfiles/Gemfile.activemodel-5.2 +6 -0
- data/gemfiles/Gemfile.faraday-1.0 +6 -0
- data/lib/restorm/api.rb +121 -0
- data/lib/restorm/collection.rb +13 -0
- data/lib/restorm/errors.rb +29 -0
- data/lib/restorm/json_api/model.rb +42 -0
- data/lib/restorm/middleware/accept_json.rb +18 -0
- data/lib/restorm/middleware/first_level_parse_json.rb +37 -0
- data/lib/restorm/middleware/json_api_parser.rb +37 -0
- data/lib/restorm/middleware/parse_json.rb +22 -0
- data/lib/restorm/middleware/second_level_parse_json.rb +37 -0
- data/lib/restorm/middleware.rb +12 -0
- data/lib/restorm/model/associations/association.rb +128 -0
- data/lib/restorm/model/associations/association_proxy.rb +44 -0
- data/lib/restorm/model/associations/belongs_to_association.rb +95 -0
- data/lib/restorm/model/associations/has_many_association.rb +100 -0
- data/lib/restorm/model/associations/has_one_association.rb +79 -0
- data/lib/restorm/model/associations.rb +141 -0
- data/lib/restorm/model/attributes.rb +322 -0
- data/lib/restorm/model/base.rb +33 -0
- data/lib/restorm/model/deprecated_methods.rb +61 -0
- data/lib/restorm/model/http.rb +119 -0
- data/lib/restorm/model/introspection.rb +67 -0
- data/lib/restorm/model/nested_attributes.rb +45 -0
- data/lib/restorm/model/orm.rb +299 -0
- data/lib/restorm/model/parse.rb +223 -0
- data/lib/restorm/model/paths.rb +125 -0
- data/lib/restorm/model/relation.rb +209 -0
- data/lib/restorm/model.rb +75 -0
- data/lib/restorm/version.rb +3 -0
- data/lib/restorm.rb +19 -0
- data/restorm.gemspec +29 -0
- data/spec/api_spec.rb +120 -0
- data/spec/collection_spec.rb +41 -0
- data/spec/json_api/model_spec.rb +169 -0
- data/spec/middleware/accept_json_spec.rb +11 -0
- data/spec/middleware/first_level_parse_json_spec.rb +63 -0
- data/spec/middleware/json_api_parser_spec.rb +52 -0
- data/spec/middleware/second_level_parse_json_spec.rb +35 -0
- data/spec/model/associations/association_proxy_spec.rb +29 -0
- data/spec/model/associations_spec.rb +911 -0
- data/spec/model/attributes_spec.rb +354 -0
- data/spec/model/callbacks_spec.rb +176 -0
- data/spec/model/dirty_spec.rb +133 -0
- data/spec/model/http_spec.rb +201 -0
- data/spec/model/introspection_spec.rb +81 -0
- data/spec/model/nested_attributes_spec.rb +135 -0
- data/spec/model/orm_spec.rb +704 -0
- data/spec/model/parse_spec.rb +520 -0
- data/spec/model/paths_spec.rb +348 -0
- data/spec/model/relation_spec.rb +247 -0
- data/spec/model/validations_spec.rb +43 -0
- data/spec/model_spec.rb +45 -0
- data/spec/spec_helper.rb +25 -0
- data/spec/support/macros/her_macros.rb +17 -0
- data/spec/support/macros/model_macros.rb +36 -0
- data/spec/support/macros/request_macros.rb +27 -0
- metadata +203 -0
@@ -0,0 +1,911 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require File.join(File.dirname(__FILE__), "../spec_helper.rb")
|
4
|
+
|
5
|
+
describe Restorm::Model::Associations do
|
6
|
+
context "setting associations without details" do
|
7
|
+
before { spawn_model "Foo::User" }
|
8
|
+
subject(:associations) { Foo::User.associations }
|
9
|
+
|
10
|
+
describe "has_many associations" do
|
11
|
+
subject { associations[:has_many] }
|
12
|
+
|
13
|
+
context "single" do
|
14
|
+
let(:comments_association) do
|
15
|
+
{
|
16
|
+
name: :comments,
|
17
|
+
data_key: :comments,
|
18
|
+
default: [],
|
19
|
+
class_name: "Comment",
|
20
|
+
path: "/comments",
|
21
|
+
inverse_of: nil
|
22
|
+
}
|
23
|
+
end
|
24
|
+
before { Foo::User.has_many :comments }
|
25
|
+
|
26
|
+
it { is_expected.to eql [comments_association] }
|
27
|
+
end
|
28
|
+
|
29
|
+
context "multiple" do
|
30
|
+
let(:comments_association) do
|
31
|
+
{
|
32
|
+
name: :comments,
|
33
|
+
data_key: :comments,
|
34
|
+
default: [],
|
35
|
+
class_name: "Comment",
|
36
|
+
path: "/comments",
|
37
|
+
inverse_of: nil
|
38
|
+
}
|
39
|
+
end
|
40
|
+
let(:posts_association) do
|
41
|
+
{
|
42
|
+
name: :posts,
|
43
|
+
data_key: :posts,
|
44
|
+
default: [],
|
45
|
+
class_name: "Post",
|
46
|
+
path: "/posts",
|
47
|
+
inverse_of: nil
|
48
|
+
}
|
49
|
+
end
|
50
|
+
before do
|
51
|
+
Foo::User.has_many :comments
|
52
|
+
Foo::User.has_many :posts
|
53
|
+
end
|
54
|
+
|
55
|
+
it { is_expected.to eql [comments_association, posts_association] }
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe "has_one associations" do
|
60
|
+
subject { associations[:has_one] }
|
61
|
+
|
62
|
+
context "single" do
|
63
|
+
let(:category_association) do
|
64
|
+
{
|
65
|
+
name: :category,
|
66
|
+
data_key: :category,
|
67
|
+
default: nil,
|
68
|
+
class_name: "Category",
|
69
|
+
path: "/category"
|
70
|
+
}
|
71
|
+
end
|
72
|
+
before { Foo::User.has_one :category }
|
73
|
+
|
74
|
+
it { is_expected.to eql [category_association] }
|
75
|
+
end
|
76
|
+
|
77
|
+
context "multiple" do
|
78
|
+
let(:category_association) do
|
79
|
+
{
|
80
|
+
name: :category,
|
81
|
+
data_key: :category,
|
82
|
+
default: nil,
|
83
|
+
class_name: "Category",
|
84
|
+
path: "/category"
|
85
|
+
}
|
86
|
+
end
|
87
|
+
let(:role_association) do
|
88
|
+
{
|
89
|
+
name: :role,
|
90
|
+
data_key: :role,
|
91
|
+
default: nil,
|
92
|
+
class_name: "Role",
|
93
|
+
path: "/role"
|
94
|
+
}
|
95
|
+
end
|
96
|
+
before do
|
97
|
+
Foo::User.has_one :category
|
98
|
+
Foo::User.has_one :role
|
99
|
+
end
|
100
|
+
|
101
|
+
it { is_expected.to eql [category_association, role_association] }
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
describe "belongs_to associations" do
|
106
|
+
subject { associations[:belongs_to] }
|
107
|
+
|
108
|
+
context "single" do
|
109
|
+
let(:organization_association) do
|
110
|
+
{
|
111
|
+
name: :organization,
|
112
|
+
data_key: :organization,
|
113
|
+
default: nil,
|
114
|
+
class_name: "Organization",
|
115
|
+
foreign_key: "organization_id"
|
116
|
+
}
|
117
|
+
end
|
118
|
+
before { Foo::User.belongs_to :organization }
|
119
|
+
|
120
|
+
it { is_expected.to eql [organization_association] }
|
121
|
+
end
|
122
|
+
|
123
|
+
context "specifying non-default path" do
|
124
|
+
let(:path) { 'my_special_path' }
|
125
|
+
let(:organization_association) do
|
126
|
+
{
|
127
|
+
name: :organization,
|
128
|
+
data_key: :organization,
|
129
|
+
default: nil,
|
130
|
+
class_name: "Organization",
|
131
|
+
foreign_key: "organization_id",
|
132
|
+
path: path
|
133
|
+
}
|
134
|
+
end
|
135
|
+
before { Foo::User.belongs_to :organization, path: path }
|
136
|
+
|
137
|
+
it { is_expected.to eql [organization_association] }
|
138
|
+
end
|
139
|
+
|
140
|
+
context "multiple" do
|
141
|
+
let(:organization_association) do
|
142
|
+
{
|
143
|
+
name: :organization,
|
144
|
+
data_key: :organization,
|
145
|
+
default: nil,
|
146
|
+
class_name: "Organization",
|
147
|
+
foreign_key: "organization_id"
|
148
|
+
}
|
149
|
+
end
|
150
|
+
let(:family_association) do
|
151
|
+
{
|
152
|
+
name: :family,
|
153
|
+
data_key: :family,
|
154
|
+
default: nil,
|
155
|
+
class_name: "Family",
|
156
|
+
foreign_key: "family_id"
|
157
|
+
}
|
158
|
+
end
|
159
|
+
before do
|
160
|
+
Foo::User.belongs_to :organization
|
161
|
+
Foo::User.belongs_to :family
|
162
|
+
end
|
163
|
+
|
164
|
+
it { is_expected.to eql [organization_association, family_association] }
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
context "setting associations with details" do
|
170
|
+
before { spawn_model "Foo::User" }
|
171
|
+
subject(:associations) { Foo::User.associations }
|
172
|
+
|
173
|
+
context "in base class" do
|
174
|
+
describe "has_many associations" do
|
175
|
+
subject { associations[:has_many] }
|
176
|
+
|
177
|
+
context "single" do
|
178
|
+
let(:comments_association) do
|
179
|
+
{
|
180
|
+
name: :comments,
|
181
|
+
data_key: :user_comments,
|
182
|
+
default: {},
|
183
|
+
class_name: "Post",
|
184
|
+
path: "/comments",
|
185
|
+
inverse_of: :admin
|
186
|
+
}
|
187
|
+
end
|
188
|
+
before do
|
189
|
+
Foo::User.has_many :comments, class_name: "Post",
|
190
|
+
inverse_of: :admin,
|
191
|
+
data_key: :user_comments,
|
192
|
+
default: {}
|
193
|
+
end
|
194
|
+
|
195
|
+
it { is_expected.to eql [comments_association] }
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
describe "has_one associations" do
|
200
|
+
subject { associations[:has_one] }
|
201
|
+
|
202
|
+
context "single" do
|
203
|
+
let(:category_association) do
|
204
|
+
{
|
205
|
+
name: :category,
|
206
|
+
data_key: :topic,
|
207
|
+
default: nil,
|
208
|
+
class_name: "Topic",
|
209
|
+
foreign_key: "topic_id",
|
210
|
+
path: "/category"
|
211
|
+
}
|
212
|
+
end
|
213
|
+
before do
|
214
|
+
Foo::User.has_one :category, class_name: "Topic",
|
215
|
+
foreign_key: "topic_id",
|
216
|
+
data_key: :topic, default: nil
|
217
|
+
end
|
218
|
+
|
219
|
+
it { is_expected.to eql [category_association] }
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
describe "belongs_to associations" do
|
224
|
+
subject { associations[:belongs_to] }
|
225
|
+
|
226
|
+
context "single" do
|
227
|
+
let(:organization_association) do
|
228
|
+
{
|
229
|
+
name: :organization,
|
230
|
+
data_key: :org,
|
231
|
+
default: true,
|
232
|
+
class_name: "Business",
|
233
|
+
foreign_key: "org_id"
|
234
|
+
}
|
235
|
+
end
|
236
|
+
before do
|
237
|
+
Foo::User.belongs_to :organization, class_name: "Business",
|
238
|
+
foreign_key: "org_id",
|
239
|
+
data_key: :org,
|
240
|
+
default: true
|
241
|
+
end
|
242
|
+
|
243
|
+
it { is_expected.to eql [organization_association] }
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
context "in parent class" do
|
249
|
+
before { Foo::User.has_many :comments, class_name: "Post" }
|
250
|
+
|
251
|
+
describe "associations accessor" do
|
252
|
+
subject(:associations) { Class.new(Foo::User).associations }
|
253
|
+
|
254
|
+
describe "#object_id" do
|
255
|
+
subject { associations.object_id }
|
256
|
+
it { is_expected.not_to eql Foo::User.associations.object_id }
|
257
|
+
end
|
258
|
+
|
259
|
+
describe "[:has_many]" do
|
260
|
+
subject { associations[:has_many] }
|
261
|
+
let(:association) do
|
262
|
+
{
|
263
|
+
name: :comments,
|
264
|
+
data_key: :comments,
|
265
|
+
default: [],
|
266
|
+
class_name: "Post",
|
267
|
+
path: "/comments",
|
268
|
+
inverse_of: nil
|
269
|
+
}
|
270
|
+
end
|
271
|
+
|
272
|
+
it { is_expected.to eql [association] }
|
273
|
+
end
|
274
|
+
end
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
context "handling associations without details" do
|
279
|
+
before do
|
280
|
+
spawn_model "Foo::User" do
|
281
|
+
has_many :comments, class_name: "Foo::Comment"
|
282
|
+
has_one :role, class_name: "Foo::Role"
|
283
|
+
belongs_to :organization, class_name: "Foo::Organization"
|
284
|
+
has_many :posts, inverse_of: :admin
|
285
|
+
end
|
286
|
+
|
287
|
+
spawn_model "Foo::Comment" do
|
288
|
+
belongs_to :user
|
289
|
+
parse_root_in_json true
|
290
|
+
end
|
291
|
+
|
292
|
+
spawn_model "Foo::Post" do
|
293
|
+
belongs_to :admin, class_name: "Foo::User"
|
294
|
+
end
|
295
|
+
|
296
|
+
spawn_model "Foo::Organization" do
|
297
|
+
parse_root_in_json true
|
298
|
+
end
|
299
|
+
|
300
|
+
spawn_model "Foo::Role"
|
301
|
+
end
|
302
|
+
|
303
|
+
context "with included data" do
|
304
|
+
before(:context) do
|
305
|
+
Restorm::API.setup url: "https://api.example.com" do |builder|
|
306
|
+
builder.use Restorm::Middleware::FirstLevelParseJSON
|
307
|
+
builder.use Faraday::Request::UrlEncoded
|
308
|
+
builder.adapter :test do |stub|
|
309
|
+
stub.get("/users/1") { [200, {}, { id: 1, name: "Tobias Fünke", comments: [{ comment: { id: 2, body: "Tobias, you blow hard!", user_id: 1 } }, { comment: { id: 3, body: "I wouldn't mind kissing that man between the cheeks, so to speak", user_id: 1 } }], role: { id: 1, body: "Admin" }, organization: { id: 1, name: "Bluth Company" }, organization_id: 1 }.to_json] }
|
310
|
+
stub.get("/users/1/comments") { [200, {}, [{ comment: { id: 4, body: "They're having a FIRESALE?" } }].to_json] }
|
311
|
+
stub.get("/users/1/role") { [200, {}, { id: 3, body: "User" }.to_json] }
|
312
|
+
stub.get("/users/1/posts") { [200, {}, [{ id: 1, body: "blogging stuff", admin_id: 1 }].to_json] }
|
313
|
+
stub.get("/organizations/1") { [200, {}, { organization: { id: 1, name: "Bluth Company Foo" } }.to_json] }
|
314
|
+
end
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
318
|
+
let(:user) { Foo::User.find(1) }
|
319
|
+
let(:user_params) { user.to_params }
|
320
|
+
|
321
|
+
it "maps an array of included data through has_many" do
|
322
|
+
expect(user.comments.first).to be_a(Foo::Comment)
|
323
|
+
expect(user.comments.length).to eq(2)
|
324
|
+
expect(user.comments.first.id).to eq(2)
|
325
|
+
expect(user.comments.first.body).to eq("Tobias, you blow hard!")
|
326
|
+
end
|
327
|
+
|
328
|
+
it "does not refetch the parents models data if they have been fetched before" do
|
329
|
+
expect(user.comments.first.user.object_id).to eq(user.object_id)
|
330
|
+
end
|
331
|
+
|
332
|
+
it "uses the given inverse_of key to set the parent model" do
|
333
|
+
expect(user.posts.first.admin.object_id).to eq(user.object_id)
|
334
|
+
end
|
335
|
+
|
336
|
+
it "fetches has_many data even if it was included, only if called with parameters" do
|
337
|
+
expect(user.comments.where(foo_id: 1).length).to eq(1)
|
338
|
+
end
|
339
|
+
|
340
|
+
it "maps an array of included data through has_one" do
|
341
|
+
expect(user.role).to be_a(Foo::Role)
|
342
|
+
expect(user.role.object_id).to eq(user.role.object_id)
|
343
|
+
expect(user.role.id).to eq(1)
|
344
|
+
expect(user.role.body).to eq("Admin")
|
345
|
+
end
|
346
|
+
|
347
|
+
it "fetches has_one data even if it was included, only if called with parameters" do
|
348
|
+
expect(user.role.where(foo_id: 2).id).to eq(3)
|
349
|
+
end
|
350
|
+
|
351
|
+
it "maps an array of included data through belongs_to" do
|
352
|
+
expect(user.organization).to be_a(Foo::Organization)
|
353
|
+
expect(user.organization.id).to eq(1)
|
354
|
+
expect(user.organization.name).to eq("Bluth Company")
|
355
|
+
end
|
356
|
+
|
357
|
+
it "fetches belongs_to data even if it was included, only if called with parameters" do
|
358
|
+
expect(user.organization.where(foo_id: 1).name).to eq("Bluth Company Foo")
|
359
|
+
end
|
360
|
+
|
361
|
+
it "includes has_many relationships in params by default" do
|
362
|
+
expect(user_params[:comments]).to be_kind_of(Array)
|
363
|
+
expect(user_params[:comments].length).to eq(2)
|
364
|
+
end
|
365
|
+
|
366
|
+
it "includes has_one relationship in params by default" do
|
367
|
+
expect(user_params[:role]).to be_kind_of(Hash)
|
368
|
+
expect(user_params[:role]).not_to be_empty
|
369
|
+
end
|
370
|
+
|
371
|
+
it "includes belongs_to relationship in params by default" do
|
372
|
+
expect(user_params[:organization]).to be_kind_of(Hash)
|
373
|
+
expect(user_params[:organization]).not_to be_empty
|
374
|
+
end
|
375
|
+
end
|
376
|
+
|
377
|
+
context "without included data" do
|
378
|
+
before(:context) do
|
379
|
+
Restorm::API.setup url: "https://api.example.com" do |builder|
|
380
|
+
builder.use Restorm::Middleware::FirstLevelParseJSON
|
381
|
+
builder.use Faraday::Request::UrlEncoded
|
382
|
+
builder.adapter :test do |stub|
|
383
|
+
stub.get("/users/2") { [200, {}, { id: 2, name: "Lindsay Fünke", organization_id: 2 }.to_json] }
|
384
|
+
stub.get("/users/2/comments") { [200, {}, [{ comment: { id: 4, body: "They're having a FIRESALE?" } }, { comment: { id: 5, body: "Is this the tiny town from Footloose?" } }].to_json] }
|
385
|
+
stub.get("/users/2/comments/5") { [200, {}, { comment: { id: 5, body: "Is this the tiny town from Footloose?" } }.to_json] }
|
386
|
+
stub.get("/users/2/role") { [200, {}, { id: 2, body: "User" }.to_json] }
|
387
|
+
stub.get("/organizations/2") do |env|
|
388
|
+
if env[:params]["admin"] == "true"
|
389
|
+
[200, {}, { organization: { id: 2, name: "Bluth Company (admin)" } }.to_json]
|
390
|
+
else
|
391
|
+
[200, {}, { organization: { id: 2, name: "Bluth Company" } }.to_json]
|
392
|
+
end
|
393
|
+
end
|
394
|
+
end
|
395
|
+
end
|
396
|
+
end
|
397
|
+
|
398
|
+
let(:user) { Foo::User.find(2) }
|
399
|
+
|
400
|
+
it "fetches data that was not included through has_many" do
|
401
|
+
expect(user.comments.first).to be_a(Foo::Comment)
|
402
|
+
expect(user.comments.length).to eq(2)
|
403
|
+
expect(user.comments.first.id).to eq(4)
|
404
|
+
expect(user.comments.first.body).to eq("They're having a FIRESALE?")
|
405
|
+
end
|
406
|
+
|
407
|
+
it "fetches data that was not included through has_many only once" do
|
408
|
+
expect(user.comments.first.object_id).to eq(user.comments.first.object_id)
|
409
|
+
end
|
410
|
+
|
411
|
+
it "fetches data that was cached through has_many if called with parameters" do
|
412
|
+
expect(user.comments.first.object_id).not_to eq(user.comments.where(foo_id: 1).first.object_id)
|
413
|
+
end
|
414
|
+
|
415
|
+
it "fetches data again after being reloaded" do
|
416
|
+
expect { user.comments.reload }.to change { user.comments.first.object_id }
|
417
|
+
end
|
418
|
+
|
419
|
+
it "fetches data that was not included through has_one" do
|
420
|
+
expect(user.role).to be_a(Foo::Role)
|
421
|
+
expect(user.role.id).to eq(2)
|
422
|
+
expect(user.role.body).to eq("User")
|
423
|
+
end
|
424
|
+
|
425
|
+
it "fetches data that was not included through belongs_to" do
|
426
|
+
expect(user.organization).to be_a(Foo::Organization)
|
427
|
+
expect(user.organization.id).to eq(2)
|
428
|
+
expect(user.organization.name).to eq("Bluth Company")
|
429
|
+
end
|
430
|
+
|
431
|
+
it "can tell if it has a association" do
|
432
|
+
expect(user.has_association?(:unknown_association)).to be false
|
433
|
+
expect(user.has_association?(:organization)).to be true
|
434
|
+
end
|
435
|
+
|
436
|
+
it "fetches the resource corresponding to a named association" do
|
437
|
+
expect(user.get_association(:unknown_association)).to be_nil
|
438
|
+
expect(user.get_association(:organization).name).to eq("Bluth Company")
|
439
|
+
end
|
440
|
+
|
441
|
+
it "pass query string parameters when additional arguments are passed" do
|
442
|
+
expect(user.organization.where(admin: true).name).to eq("Bluth Company (admin)")
|
443
|
+
expect(user.organization.name).to eq("Bluth Company")
|
444
|
+
end
|
445
|
+
|
446
|
+
it "fetches data with the specified id when calling find" do
|
447
|
+
comment = user.comments.find(5)
|
448
|
+
expect(comment).to be_a(Foo::Comment)
|
449
|
+
expect(comment.id).to eq(5)
|
450
|
+
end
|
451
|
+
|
452
|
+
it "'s associations responds to #empty?" do
|
453
|
+
expect(user.organization.respond_to?(:empty?)).to be_truthy
|
454
|
+
expect(user.organization).not_to be_empty
|
455
|
+
end
|
456
|
+
end
|
457
|
+
|
458
|
+
context "without included parent data" do
|
459
|
+
before(:context) do
|
460
|
+
Restorm::API.setup url: "https://api.example.com" do |builder|
|
461
|
+
builder.use Restorm::Middleware::FirstLevelParseJSON
|
462
|
+
builder.use Faraday::Request::UrlEncoded
|
463
|
+
builder.adapter :test do |stub|
|
464
|
+
stub.get("/users/1") { [200, {}, { id: 1, name: "Lindsay Fünke", organization_id: 2 }.to_json] }
|
465
|
+
end
|
466
|
+
end
|
467
|
+
end
|
468
|
+
|
469
|
+
let(:comment) { Foo::Comment.new(id: 7, user_id: 1) }
|
470
|
+
|
471
|
+
it "does fetch the parent models data only once" do
|
472
|
+
expect(comment.user.object_id).to eq(comment.user.object_id)
|
473
|
+
end
|
474
|
+
|
475
|
+
it "does fetch the parent models data that was cached if called with parameters" do
|
476
|
+
expect(comment.user.object_id).not_to eq(comment.user.where(a: 2).object_id)
|
477
|
+
end
|
478
|
+
end
|
479
|
+
|
480
|
+
context "when resource is new" do
|
481
|
+
let(:new_user) { Foo::User.new }
|
482
|
+
|
483
|
+
it "doesn't attempt to fetch association data" do
|
484
|
+
expect(new_user.comments).to eq([])
|
485
|
+
expect(new_user.role).to be_nil
|
486
|
+
expect(new_user.organization).to be_nil
|
487
|
+
end
|
488
|
+
end
|
489
|
+
|
490
|
+
context "when foreign_key is nil" do
|
491
|
+
before do
|
492
|
+
spawn_model "Foo::User" do
|
493
|
+
belongs_to :organization, class_name: "Foo::Organization"
|
494
|
+
end
|
495
|
+
|
496
|
+
spawn_model "Foo::Organization" do
|
497
|
+
parse_root_in_json true
|
498
|
+
end
|
499
|
+
end
|
500
|
+
|
501
|
+
let(:user) { Foo::User.new(organization_id: nil, name: "Katlin Fünke") }
|
502
|
+
|
503
|
+
it "returns nil" do
|
504
|
+
expect(user.organization).to be_nil
|
505
|
+
end
|
506
|
+
end
|
507
|
+
|
508
|
+
context "after" do
|
509
|
+
before(:context) do
|
510
|
+
Restorm::API.setup url: "https://api.example.com" do |builder|
|
511
|
+
builder.use Restorm::Middleware::FirstLevelParseJSON
|
512
|
+
builder.use Faraday::Request::UrlEncoded
|
513
|
+
builder.adapter :test do |stub|
|
514
|
+
stub.post("/users") { [200, {}, { id: 5, name: "Mr. Krabs", comments: [{ comment: { id: 99, body: "Rodríguez, nasibisibusi?", user_id: 5 } }], role: { id: 1, body: "Admin" }, organization: { id: 3, name: "Krusty Krab" }, organization_id: 3 }.to_json] }
|
515
|
+
stub.put("/users/5") { [200, {}, { id: 5, name: "Clancy Brown", comments: [{ comment: { id: 99, body: "Rodríguez, nasibisibusi?", user_id: 5 } }], role: { id: 1, body: "Admin" }, organization: { id: 3, name: "Krusty Krab" }, organization_id: 3 }.to_json] }
|
516
|
+
stub.delete("/users/5") { [200, {}, { id: 5, name: "Clancy Brown", comments: [{ comment: { id: 99, body: "Rodríguez, nasibisibusi?", user_id: 5 } }], role: { id: 1, body: "Admin" }, organization: { id: 3, name: "Krusty Krab" }, organization_id: 3 }.to_json] }
|
517
|
+
end
|
518
|
+
end
|
519
|
+
end
|
520
|
+
|
521
|
+
let(:user_after_create) { Foo::User.create }
|
522
|
+
let(:user_after_save_existing) { Foo::User.save_existing(5, name: "Clancy Brown") }
|
523
|
+
let(:user_after_destroy) { Foo::User.new(id: 5).destroy }
|
524
|
+
|
525
|
+
[:create, :save_existing, :destroy].each do |type|
|
526
|
+
context "after #{type}" do
|
527
|
+
let(:subject) { send("user_after_#{type}") }
|
528
|
+
|
529
|
+
it "maps an array of included data through has_many" do
|
530
|
+
expect(subject.comments.first).to be_a(Foo::Comment)
|
531
|
+
expect(subject.comments.length).to eq(1)
|
532
|
+
expect(subject.comments.first.id).to eq(99)
|
533
|
+
expect(subject.comments.first.body).to eq("Rodríguez, nasibisibusi?")
|
534
|
+
end
|
535
|
+
|
536
|
+
it "maps an array of included data through has_one" do
|
537
|
+
expect(subject.role).to be_a(Foo::Role)
|
538
|
+
expect(subject.role.id).to eq(1)
|
539
|
+
expect(subject.role.body).to eq("Admin")
|
540
|
+
end
|
541
|
+
end
|
542
|
+
end
|
543
|
+
end
|
544
|
+
end
|
545
|
+
|
546
|
+
context "handling associations with collection_path" do
|
547
|
+
before do
|
548
|
+
spawn_model "Foo::Organization" do
|
549
|
+
has_many :users
|
550
|
+
parse_root_in_json true
|
551
|
+
collection_path '/special/organizations'
|
552
|
+
end
|
553
|
+
spawn_model "Foo::User" do
|
554
|
+
belongs_to :organization
|
555
|
+
end
|
556
|
+
end
|
557
|
+
|
558
|
+
context "without included data" do
|
559
|
+
before(:context) do
|
560
|
+
Restorm::API.setup url: "https://api.example.com" do |builder|
|
561
|
+
builder.use Restorm::Middleware::FirstLevelParseJSON
|
562
|
+
builder.use Faraday::Request::UrlEncoded
|
563
|
+
builder.adapter :test do |stub|
|
564
|
+
stub.get("/users/2") { [200, {}, { id: 2, name: "Lindsay Fünke", organization_id: 2 }.to_json] }
|
565
|
+
stub.get("/special/organizations/2") { [200, {}, { organization: { id: 2, name: "Bluth Company" } }.to_json] }
|
566
|
+
end
|
567
|
+
end
|
568
|
+
end
|
569
|
+
|
570
|
+
let(:user) { Foo::User.find(2) }
|
571
|
+
|
572
|
+
it "fetches data that was not included through belongs_to" do
|
573
|
+
expect(user.organization).to be_a(Foo::Organization)
|
574
|
+
expect(user.organization.id).to eq(2)
|
575
|
+
expect(user.organization.name).to eq("Bluth Company")
|
576
|
+
end
|
577
|
+
end
|
578
|
+
end
|
579
|
+
|
580
|
+
context "handling associations with path_prefix" do
|
581
|
+
before do
|
582
|
+
spawn_model "Foo::Organization" do
|
583
|
+
has_many :users
|
584
|
+
parse_root_in_json true
|
585
|
+
end
|
586
|
+
spawn_model "Foo::User" do
|
587
|
+
belongs_to :organization
|
588
|
+
end
|
589
|
+
end
|
590
|
+
|
591
|
+
context "without included data" do
|
592
|
+
before(:context) do
|
593
|
+
Restorm::API.setup url: "https://api.example.com" do |builder|
|
594
|
+
builder.use Restorm::Middleware::FirstLevelParseJSON
|
595
|
+
builder.use Faraday::Request::UrlEncoded
|
596
|
+
builder.path_prefix = 'special'
|
597
|
+
builder.adapter :test do |stub|
|
598
|
+
stub.get("/special/users/2") { [200, {}, { id: 2, name: "Lindsay Fünke", organization_id: 2 }.to_json] }
|
599
|
+
stub.get("/special/organizations/2") { [200, {}, { organization: { id: 2, name: "Bluth Company" } }.to_json] }
|
600
|
+
end
|
601
|
+
end
|
602
|
+
end
|
603
|
+
|
604
|
+
let(:user) { Foo::User.find(2) }
|
605
|
+
|
606
|
+
it "fetches data that was not included through belongs_to" do
|
607
|
+
expect(user.organization).to be_a(Foo::Organization)
|
608
|
+
expect(user.organization.id).to eq(2)
|
609
|
+
expect(user.organization.name).to eq("Bluth Company")
|
610
|
+
end
|
611
|
+
end
|
612
|
+
end
|
613
|
+
|
614
|
+
context "handling associations with details in active_model_serializers format" do
|
615
|
+
before do
|
616
|
+
spawn_model "Foo::User" do
|
617
|
+
parse_root_in_json true, format: :active_model_serializers
|
618
|
+
has_many :comments, class_name: "Foo::Comment"
|
619
|
+
has_one :role, class_name: "Foo::Role"
|
620
|
+
belongs_to :organization, class_name: "Foo::Organization"
|
621
|
+
end
|
622
|
+
|
623
|
+
spawn_model "Foo::Role" do
|
624
|
+
belongs_to :user
|
625
|
+
parse_root_in_json true, format: :active_model_serializers
|
626
|
+
end
|
627
|
+
|
628
|
+
spawn_model "Foo::Comment" do
|
629
|
+
belongs_to :user
|
630
|
+
parse_root_in_json true, format: :active_model_serializers
|
631
|
+
end
|
632
|
+
|
633
|
+
spawn_model "Foo::Organization" do
|
634
|
+
parse_root_in_json true, format: :active_model_serializers
|
635
|
+
end
|
636
|
+
end
|
637
|
+
|
638
|
+
context "with included data" do
|
639
|
+
before(:context) do
|
640
|
+
Restorm::API.setup url: "https://api.example.com" do |builder|
|
641
|
+
builder.use Restorm::Middleware::FirstLevelParseJSON
|
642
|
+
builder.use Faraday::Request::UrlEncoded
|
643
|
+
builder.adapter :test do |stub|
|
644
|
+
stub.get("/users/1") { [200, {}, { user: { id: 1, name: "Tobias Fünke", comments: [{ id: 2, body: "Tobias, you blow hard!", user_id: 1 }, { id: 3, body: "I wouldn't mind kissing that man between the cheeks, so to speak", user_id: 1 }], role: { id: 1, body: "Admin" }, organization: { id: 1, name: "Bluth Company" }, organization_id: 1 } }.to_json] }
|
645
|
+
stub.get("/users/1/comments") { [200, {}, { comments: [{ id: 4, body: "They're having a FIRESALE?" }] }.to_json] }
|
646
|
+
stub.get("/organizations/1") { [200, {}, { organization: { id: 1, name: "Bluth Company Foo" } }.to_json] }
|
647
|
+
end
|
648
|
+
end
|
649
|
+
end
|
650
|
+
|
651
|
+
let(:user) { Foo::User.find(1) }
|
652
|
+
let(:user_params) { user.to_params }
|
653
|
+
|
654
|
+
it "maps an array of included data through has_many" do
|
655
|
+
expect(user.comments.first).to be_a(Foo::Comment)
|
656
|
+
expect(user.comments.length).to eq(2)
|
657
|
+
expect(user.comments.first.id).to eq(2)
|
658
|
+
expect(user.comments.first.body).to eq("Tobias, you blow hard!")
|
659
|
+
end
|
660
|
+
|
661
|
+
it "does not refetch the parents models data if they have been fetched before" do
|
662
|
+
expect(user.comments.first.user.object_id).to eq(user.object_id)
|
663
|
+
end
|
664
|
+
|
665
|
+
it "fetches has_many data even if it was included, only if called with parameters" do
|
666
|
+
expect(user.comments.where(foo_id: 1).length).to eq(1)
|
667
|
+
end
|
668
|
+
|
669
|
+
it "maps an array of included data through belongs_to" do
|
670
|
+
expect(user.organization).to be_a(Foo::Organization)
|
671
|
+
expect(user.organization.id).to eq(1)
|
672
|
+
expect(user.organization.name).to eq("Bluth Company")
|
673
|
+
end
|
674
|
+
|
675
|
+
it "fetches belongs_to data even if it was included, only if called with parameters" do
|
676
|
+
expect(user.organization.where(foo_id: 1).name).to eq("Bluth Company Foo")
|
677
|
+
end
|
678
|
+
|
679
|
+
it "includes has_many relationships in params by default" do
|
680
|
+
expect(user_params[:comments]).to be_kind_of(Array)
|
681
|
+
expect(user_params[:comments].length).to eq(2)
|
682
|
+
end
|
683
|
+
|
684
|
+
it "includes has_one relationships in params by default" do
|
685
|
+
expect(user_params[:role]).to be_kind_of(Hash)
|
686
|
+
expect(user_params[:role]).not_to be_empty
|
687
|
+
end
|
688
|
+
|
689
|
+
it "includes belongs_to relationship in params by default" do
|
690
|
+
expect(user_params[:organization]).to be_kind_of(Hash)
|
691
|
+
expect(user_params[:organization]).not_to be_empty
|
692
|
+
end
|
693
|
+
end
|
694
|
+
|
695
|
+
context "without included data" do
|
696
|
+
before(:context) do
|
697
|
+
Restorm::API.setup url: "https://api.example.com" do |builder|
|
698
|
+
builder.use Restorm::Middleware::FirstLevelParseJSON
|
699
|
+
builder.use Faraday::Request::UrlEncoded
|
700
|
+
builder.adapter :test do |stub|
|
701
|
+
stub.get("/users/2") { [200, {}, { user: { id: 2, name: "Lindsay Fünke", organization_id: 1 } }.to_json] }
|
702
|
+
stub.get("/users/2/comments") { [200, {}, { comments: [{ id: 4, body: "They're having a FIRESALE?" }, { id: 5, body: "Is this the tiny town from Footloose?" }] }.to_json] }
|
703
|
+
stub.get("/users/2/comments/5") { [200, {}, { comment: { id: 5, body: "Is this the tiny town from Footloose?" } }.to_json] }
|
704
|
+
stub.get("/organizations/1") { [200, {}, { organization: { id: 1, name: "Bluth Company Foo" } }.to_json] }
|
705
|
+
end
|
706
|
+
end
|
707
|
+
end
|
708
|
+
|
709
|
+
let(:user) { Foo::User.find(2) }
|
710
|
+
|
711
|
+
it "fetches data that was not included through has_many" do
|
712
|
+
expect(user.comments.first).to be_a(Foo::Comment)
|
713
|
+
expect(user.comments.length).to eq(2)
|
714
|
+
expect(user.comments.first.id).to eq(4)
|
715
|
+
expect(user.comments.first.body).to eq("They're having a FIRESALE?")
|
716
|
+
end
|
717
|
+
|
718
|
+
it "fetches data that was not included through belongs_to" do
|
719
|
+
expect(user.organization).to be_a(Foo::Organization)
|
720
|
+
expect(user.organization.id).to eq(1)
|
721
|
+
expect(user.organization.name).to eq("Bluth Company Foo")
|
722
|
+
end
|
723
|
+
|
724
|
+
it "fetches data with the specified id when calling find" do
|
725
|
+
comment = user.comments.find(5)
|
726
|
+
expect(comment).to be_a(Foo::Comment)
|
727
|
+
expect(comment.id).to eq(5)
|
728
|
+
end
|
729
|
+
end
|
730
|
+
end
|
731
|
+
|
732
|
+
context "handling associations with details" do
|
733
|
+
before do
|
734
|
+
spawn_model "Foo::User" do
|
735
|
+
belongs_to :company, path: "/organizations/:id", foreign_key: :organization_id, data_key: :organization
|
736
|
+
end
|
737
|
+
|
738
|
+
spawn_model "Foo::Company"
|
739
|
+
end
|
740
|
+
|
741
|
+
context "with included data" do
|
742
|
+
before(:context) do
|
743
|
+
Restorm::API.setup url: "https://api.example.com" do |builder|
|
744
|
+
builder.use Restorm::Middleware::FirstLevelParseJSON
|
745
|
+
builder.use Faraday::Request::UrlEncoded
|
746
|
+
builder.adapter :test do |stub|
|
747
|
+
stub.get("/users/1") { [200, {}, { id: 1, name: "Tobias Fünke", organization: { id: 1, name: "Bluth Company Inc." }, organization_id: 1 }.to_json] }
|
748
|
+
stub.get("/users/4") { [200, {}, { id: 1, name: "Tobias Fünke", organization: { id: 1, name: "Bluth Company Inc." } }.to_json] }
|
749
|
+
stub.get("/users/3") { [200, {}, { id: 2, name: "Lindsay Fünke", organization: nil }.to_json] }
|
750
|
+
end
|
751
|
+
end
|
752
|
+
end
|
753
|
+
|
754
|
+
let(:user) { Foo::User.find(1) }
|
755
|
+
|
756
|
+
it "maps an array of included data through belongs_to" do
|
757
|
+
expect(user.company).to be_a(Foo::Company)
|
758
|
+
expect(user.company.id).to eq(1)
|
759
|
+
expect(user.company.name).to eq("Bluth Company Inc.")
|
760
|
+
end
|
761
|
+
|
762
|
+
context "when included data is nil" do
|
763
|
+
let(:user) { Foo::User.find(3) }
|
764
|
+
|
765
|
+
it "does not map included data" do
|
766
|
+
expect(user.company).to be_nil
|
767
|
+
end
|
768
|
+
end
|
769
|
+
|
770
|
+
context "when included data has no foreign_key" do
|
771
|
+
let(:user) { Foo::User.find(4) }
|
772
|
+
|
773
|
+
it "maps included data anyway" do
|
774
|
+
expect(user.company.name).to eq("Bluth Company Inc.")
|
775
|
+
end
|
776
|
+
end
|
777
|
+
end
|
778
|
+
|
779
|
+
context "without included data" do
|
780
|
+
before(:context) do
|
781
|
+
Restorm::API.setup url: "https://api.example.com" do |builder|
|
782
|
+
builder.use Restorm::Middleware::FirstLevelParseJSON
|
783
|
+
builder.use Faraday::Request::UrlEncoded
|
784
|
+
builder.adapter :test do |stub|
|
785
|
+
stub.get("/users/2") { [200, {}, { id: 2, name: "Lindsay Fünke", organization_id: 1 }.to_json] }
|
786
|
+
stub.get("/organizations/1") { [200, {}, { id: 1, name: "Bluth Company" }.to_json] }
|
787
|
+
end
|
788
|
+
end
|
789
|
+
end
|
790
|
+
|
791
|
+
let(:user) { Foo::User.find(2) }
|
792
|
+
|
793
|
+
it "fetches data that was not included through belongs_to" do
|
794
|
+
expect(user.company).to be_a(Foo::Company)
|
795
|
+
expect(user.company.id).to eq(1)
|
796
|
+
expect(user.company.name).to eq("Bluth Company")
|
797
|
+
end
|
798
|
+
end
|
799
|
+
end
|
800
|
+
|
801
|
+
context "object returned by the association method" do
|
802
|
+
before do
|
803
|
+
spawn_model "Foo::Role" do
|
804
|
+
def present?
|
805
|
+
"of_course"
|
806
|
+
end
|
807
|
+
end
|
808
|
+
spawn_model "Foo::User" do
|
809
|
+
has_one :role
|
810
|
+
end
|
811
|
+
end
|
812
|
+
|
813
|
+
let(:associated_value) { Foo::Role.new }
|
814
|
+
let(:user_with_role) do
|
815
|
+
Foo::User.new.tap { |user| user.role = associated_value }
|
816
|
+
end
|
817
|
+
|
818
|
+
subject { user_with_role.role }
|
819
|
+
|
820
|
+
it "doesnt mask the object's basic methods" do
|
821
|
+
expect(subject.class).to eq(Foo::Role)
|
822
|
+
end
|
823
|
+
|
824
|
+
it "doesnt mask core methods like extend" do
|
825
|
+
committer = Module.new
|
826
|
+
subject.extend committer
|
827
|
+
expect(associated_value).to be_kind_of committer
|
828
|
+
end
|
829
|
+
|
830
|
+
it "can return the association object" do
|
831
|
+
expect(subject.association).to be_kind_of Restorm::Model::Associations::Association
|
832
|
+
end
|
833
|
+
|
834
|
+
it "still can call fetch via the association" do
|
835
|
+
expect(subject.association.fetch).to eq associated_value
|
836
|
+
end
|
837
|
+
|
838
|
+
it "calls missing methods on associated value" do
|
839
|
+
expect(subject.present?).to eq("of_course")
|
840
|
+
end
|
841
|
+
|
842
|
+
it "can use association methods like where" do
|
843
|
+
expect(subject.where(role: "committer").association
|
844
|
+
.params).to include :role
|
845
|
+
end
|
846
|
+
end
|
847
|
+
|
848
|
+
context "building and creating association data" do
|
849
|
+
before do
|
850
|
+
spawn_model "Foo::Comment"
|
851
|
+
spawn_model "Foo::User" do
|
852
|
+
has_many :comments
|
853
|
+
end
|
854
|
+
end
|
855
|
+
|
856
|
+
context "with #build" do
|
857
|
+
let(:comment) { Foo::User.new(id: 10).comments.build(body: "Hello!") }
|
858
|
+
|
859
|
+
it "takes the parent primary key" do
|
860
|
+
expect(comment.body).to eq("Hello!")
|
861
|
+
expect(comment.user_id).to eq(10)
|
862
|
+
end
|
863
|
+
end
|
864
|
+
|
865
|
+
context "with #create" do
|
866
|
+
let(:user) { Foo::User.find(10) }
|
867
|
+
let(:comment) { user.comments.create(body: "Hello!") }
|
868
|
+
|
869
|
+
before do
|
870
|
+
Restorm::API.setup url: "https://api.example.com" do |builder|
|
871
|
+
builder.use Restorm::Middleware::FirstLevelParseJSON
|
872
|
+
builder.use Faraday::Request::UrlEncoded
|
873
|
+
builder.adapter :test do |stub|
|
874
|
+
stub.get("/users/10") { [200, {}, { id: 10 }.to_json] }
|
875
|
+
stub.post("/comments") { |env| [200, {}, { id: 1, body: Faraday::Utils.parse_query(env[:body])["body"], user_id: Faraday::Utils.parse_query(env[:body])["user_id"].to_i }.to_json] }
|
876
|
+
end
|
877
|
+
end
|
878
|
+
|
879
|
+
Foo::User.use_api Restorm::API.default_api
|
880
|
+
Foo::Comment.use_api Restorm::API.default_api
|
881
|
+
end
|
882
|
+
|
883
|
+
it "takes the parent primary key and saves the resource" do
|
884
|
+
expect(comment.id).to eq(1)
|
885
|
+
expect(comment.body).to eq("Hello!")
|
886
|
+
expect(comment.user_id).to eq(10)
|
887
|
+
expect(user.comments).to eq([comment])
|
888
|
+
end
|
889
|
+
end
|
890
|
+
|
891
|
+
context "with #new" do
|
892
|
+
let(:user) { Foo::User.new(name: "vic", comments: [comment]) }
|
893
|
+
|
894
|
+
context "using hash attributes" do
|
895
|
+
let(:comment) { { text: "hello" } }
|
896
|
+
|
897
|
+
it "assigns nested models" do
|
898
|
+
expect(user.comments.first.text).to eq("hello")
|
899
|
+
end
|
900
|
+
end
|
901
|
+
|
902
|
+
context "using constructed objects" do
|
903
|
+
let(:comment) { Foo::Comment.new(text: "goodbye") }
|
904
|
+
|
905
|
+
it "assigns nested models" do
|
906
|
+
expect(user.comments.first.text).to eq("goodbye")
|
907
|
+
end
|
908
|
+
end
|
909
|
+
end
|
910
|
+
end
|
911
|
+
end
|