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.
Files changed (76) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +4 -0
  3. data/.rspec +1 -0
  4. data/.rubocop.yml +31 -0
  5. data/.rubocop_todo.yml +232 -0
  6. data/.ruby-version +1 -0
  7. data/.travis.yml +55 -0
  8. data/.yardopts +2 -0
  9. data/CONTRIBUTING.md +26 -0
  10. data/Gemfile +10 -0
  11. data/HER_README.md +1065 -0
  12. data/LICENSE +7 -0
  13. data/README.md +7 -0
  14. data/Rakefile +11 -0
  15. data/UPGRADE.md +101 -0
  16. data/gemfiles/Gemfile.activemodel-4.2 +6 -0
  17. data/gemfiles/Gemfile.activemodel-5.0 +6 -0
  18. data/gemfiles/Gemfile.activemodel-5.1 +6 -0
  19. data/gemfiles/Gemfile.activemodel-5.2 +6 -0
  20. data/gemfiles/Gemfile.faraday-1.0 +6 -0
  21. data/lib/restorm/api.rb +121 -0
  22. data/lib/restorm/collection.rb +13 -0
  23. data/lib/restorm/errors.rb +29 -0
  24. data/lib/restorm/json_api/model.rb +42 -0
  25. data/lib/restorm/middleware/accept_json.rb +18 -0
  26. data/lib/restorm/middleware/first_level_parse_json.rb +37 -0
  27. data/lib/restorm/middleware/json_api_parser.rb +37 -0
  28. data/lib/restorm/middleware/parse_json.rb +22 -0
  29. data/lib/restorm/middleware/second_level_parse_json.rb +37 -0
  30. data/lib/restorm/middleware.rb +12 -0
  31. data/lib/restorm/model/associations/association.rb +128 -0
  32. data/lib/restorm/model/associations/association_proxy.rb +44 -0
  33. data/lib/restorm/model/associations/belongs_to_association.rb +95 -0
  34. data/lib/restorm/model/associations/has_many_association.rb +100 -0
  35. data/lib/restorm/model/associations/has_one_association.rb +79 -0
  36. data/lib/restorm/model/associations.rb +141 -0
  37. data/lib/restorm/model/attributes.rb +322 -0
  38. data/lib/restorm/model/base.rb +33 -0
  39. data/lib/restorm/model/deprecated_methods.rb +61 -0
  40. data/lib/restorm/model/http.rb +119 -0
  41. data/lib/restorm/model/introspection.rb +67 -0
  42. data/lib/restorm/model/nested_attributes.rb +45 -0
  43. data/lib/restorm/model/orm.rb +299 -0
  44. data/lib/restorm/model/parse.rb +223 -0
  45. data/lib/restorm/model/paths.rb +125 -0
  46. data/lib/restorm/model/relation.rb +209 -0
  47. data/lib/restorm/model.rb +75 -0
  48. data/lib/restorm/version.rb +3 -0
  49. data/lib/restorm.rb +19 -0
  50. data/restorm.gemspec +29 -0
  51. data/spec/api_spec.rb +120 -0
  52. data/spec/collection_spec.rb +41 -0
  53. data/spec/json_api/model_spec.rb +169 -0
  54. data/spec/middleware/accept_json_spec.rb +11 -0
  55. data/spec/middleware/first_level_parse_json_spec.rb +63 -0
  56. data/spec/middleware/json_api_parser_spec.rb +52 -0
  57. data/spec/middleware/second_level_parse_json_spec.rb +35 -0
  58. data/spec/model/associations/association_proxy_spec.rb +29 -0
  59. data/spec/model/associations_spec.rb +911 -0
  60. data/spec/model/attributes_spec.rb +354 -0
  61. data/spec/model/callbacks_spec.rb +176 -0
  62. data/spec/model/dirty_spec.rb +133 -0
  63. data/spec/model/http_spec.rb +201 -0
  64. data/spec/model/introspection_spec.rb +81 -0
  65. data/spec/model/nested_attributes_spec.rb +135 -0
  66. data/spec/model/orm_spec.rb +704 -0
  67. data/spec/model/parse_spec.rb +520 -0
  68. data/spec/model/paths_spec.rb +348 -0
  69. data/spec/model/relation_spec.rb +247 -0
  70. data/spec/model/validations_spec.rb +43 -0
  71. data/spec/model_spec.rb +45 -0
  72. data/spec/spec_helper.rb +25 -0
  73. data/spec/support/macros/her_macros.rb +17 -0
  74. data/spec/support/macros/model_macros.rb +36 -0
  75. data/spec/support/macros/request_macros.rb +27 -0
  76. metadata +203 -0
@@ -0,0 +1,348 @@
1
+ # encoding: utf-8
2
+
3
+ require File.join(File.dirname(__FILE__), "../spec_helper.rb")
4
+
5
+ describe Restorm::Model::Paths do
6
+ context "building request paths" do
7
+ context "simple model" do
8
+ before do
9
+ spawn_model "Foo::User"
10
+ end
11
+
12
+ describe "#request_path" do
13
+ it "builds paths with defaults" do
14
+ expect(Foo::User.new(id: "foo").request_path).to eq("users/foo")
15
+ expect(Foo::User.new(id: nil).request_path).to eq("users")
16
+ expect(Foo::User.new.request_path).to eq("users")
17
+ end
18
+
19
+ it "builds paths with custom collection path" do
20
+ Foo::User.collection_path "/utilisateurs"
21
+ expect(Foo::User.new(id: "foo").request_path).to eq("/utilisateurs/foo")
22
+ expect(Foo::User.new.request_path).to eq("/utilisateurs")
23
+ end
24
+
25
+ it "builds paths with custom relative collection path" do
26
+ Foo::User.collection_path "utilisateurs"
27
+ expect(Foo::User.new(id: "foo").request_path).to eq("utilisateurs/foo")
28
+ expect(Foo::User.new.request_path).to eq("utilisateurs")
29
+ end
30
+
31
+ it "builds paths with custom collection path with multiple variables" do
32
+ Foo::User.collection_path "/organizations/:organization_id/utilisateurs"
33
+
34
+ expect(Foo::User.new(id: "foo").request_path(_organization_id: "acme")).to eq("/organizations/acme/utilisateurs/foo")
35
+ expect(Foo::User.new.request_path(_organization_id: "acme")).to eq("/organizations/acme/utilisateurs")
36
+
37
+ expect(Foo::User.new(id: "foo", organization_id: "acme").request_path).to eq("/organizations/acme/utilisateurs/foo")
38
+ expect(Foo::User.new(organization_id: "acme").request_path).to eq("/organizations/acme/utilisateurs")
39
+ end
40
+
41
+ it "builds paths with custom relative collection path with multiple variables" do
42
+ Foo::User.collection_path "organizations/:organization_id/utilisateurs"
43
+
44
+ expect(Foo::User.new(id: "foo").request_path(_organization_id: "acme")).to eq("organizations/acme/utilisateurs/foo")
45
+ expect(Foo::User.new.request_path(_organization_id: "acme")).to eq("organizations/acme/utilisateurs")
46
+
47
+ expect(Foo::User.new(id: "foo", organization_id: "acme").request_path).to eq("organizations/acme/utilisateurs/foo")
48
+ expect(Foo::User.new(organization_id: "acme").request_path).to eq("organizations/acme/utilisateurs")
49
+ end
50
+
51
+ it "builds paths with custom item path" do
52
+ Foo::User.resource_path "/utilisateurs/:id"
53
+ expect(Foo::User.new(id: "foo").request_path).to eq("/utilisateurs/foo")
54
+ expect(Foo::User.new.request_path).to eq("users")
55
+ end
56
+
57
+ it "builds paths with custom relative item path" do
58
+ Foo::User.resource_path "utilisateurs/:id"
59
+ expect(Foo::User.new(id: "foo").request_path).to eq("utilisateurs/foo")
60
+ expect(Foo::User.new.request_path).to eq("users")
61
+ end
62
+
63
+ it "raises exceptions when building a path without required custom variables" do
64
+ Foo::User.collection_path "/organizations/:organization_id/utilisateurs"
65
+ expect { Foo::User.new(id: "foo").request_path }.to raise_error(Restorm::Errors::PathError, "Missing :_organization_id parameter to build the request path. Path is `/organizations/:organization_id/utilisateurs/:id`. Parameters are `{:id=>\"foo\"}`.")
66
+ end
67
+
68
+ it "escapes the variable values" do
69
+ Foo::User.collection_path "organizations/:organization_id/utilisateurs"
70
+ expect(Foo::User.new(id: "Привет").request_path(_organization_id: "лол")).to eq("organizations/%D0%BB%D0%BE%D0%BB/utilisateurs/%D0%9F%D1%80%D0%B8%D0%B2%D0%B5%D1%82")
71
+ expect(Foo::User.new(organization_id: "лол", id: "Привет").request_path).to eq("organizations/%D0%BB%D0%BE%D0%BB/utilisateurs/%D0%9F%D1%80%D0%B8%D0%B2%D0%B5%D1%82")
72
+ end
73
+ end
74
+ end
75
+
76
+ context "simple model with multiple words" do
77
+ before do
78
+ spawn_model "Foo::AdminUser"
79
+ end
80
+
81
+ describe "#request_path" do
82
+ it "builds paths with defaults" do
83
+ expect(Foo::AdminUser.new(id: "foo").request_path).to eq("admin_users/foo")
84
+ expect(Foo::AdminUser.new.request_path).to eq("admin_users")
85
+ end
86
+
87
+ it "builds paths with custom collection path" do
88
+ Foo::AdminUser.collection_path "/users"
89
+ expect(Foo::AdminUser.new(id: "foo").request_path).to eq("/users/foo")
90
+ expect(Foo::AdminUser.new.request_path).to eq("/users")
91
+ end
92
+
93
+ it "builds paths with custom relative collection path" do
94
+ Foo::AdminUser.collection_path "users"
95
+ expect(Foo::AdminUser.new(id: "foo").request_path).to eq("users/foo")
96
+ expect(Foo::AdminUser.new.request_path).to eq("users")
97
+ end
98
+
99
+ it "builds paths with custom collection path with multiple variables" do
100
+ Foo::AdminUser.collection_path "/organizations/:organization_id/users"
101
+ expect(Foo::AdminUser.new(id: "foo").request_path(_organization_id: "acme")).to eq("/organizations/acme/users/foo")
102
+ expect(Foo::AdminUser.new.request_path(_organization_id: "acme")).to eq("/organizations/acme/users")
103
+ end
104
+
105
+ it "builds paths with custom relative collection path with multiple variables" do
106
+ Foo::AdminUser.collection_path "organizations/:organization_id/users"
107
+ expect(Foo::AdminUser.new(id: "foo").request_path(_organization_id: "acme")).to eq("organizations/acme/users/foo")
108
+ expect(Foo::AdminUser.new.request_path(_organization_id: "acme")).to eq("organizations/acme/users")
109
+ end
110
+
111
+ it "builds paths with custom item path" do
112
+ Foo::AdminUser.resource_path "/users/:id"
113
+ expect(Foo::AdminUser.new(id: "foo").request_path).to eq("/users/foo")
114
+ expect(Foo::AdminUser.new.request_path).to eq("admin_users")
115
+ end
116
+
117
+ it "builds paths with custom relative item path" do
118
+ Foo::AdminUser.resource_path "users/:id"
119
+ expect(Foo::AdminUser.new(id: "foo").request_path).to eq("users/foo")
120
+ expect(Foo::AdminUser.new.request_path).to eq("admin_users")
121
+ end
122
+
123
+ it "raises exceptions when building a path without required custom variables" do
124
+ Foo::AdminUser.collection_path "/organizations/:organization_id/users"
125
+ expect { Foo::AdminUser.new(id: "foo").request_path }.to raise_error(Restorm::Errors::PathError, "Missing :_organization_id parameter to build the request path. Path is `/organizations/:organization_id/users/:id`. Parameters are `{:id=>\"foo\"}`.")
126
+ end
127
+
128
+ it "raises exceptions when building a relative path without required custom variables" do
129
+ Foo::AdminUser.collection_path "organizations/:organization_id/users"
130
+ expect { Foo::AdminUser.new(id: "foo").request_path }.to raise_error(Restorm::Errors::PathError, "Missing :_organization_id parameter to build the request path. Path is `organizations/:organization_id/users/:id`. Parameters are `{:id=>\"foo\"}`.")
131
+ end
132
+ end
133
+ end
134
+
135
+ context "children model" do
136
+ before do
137
+ Restorm::API.setup url: "https://api.example.com" do |builder|
138
+ builder.use Restorm::Middleware::FirstLevelParseJSON
139
+ builder.use Faraday::Request::UrlEncoded
140
+ builder.adapter :test do |stub|
141
+ stub.get("/users/foo") { [200, {}, { id: "foo" }.to_json] }
142
+ end
143
+ end
144
+
145
+ spawn_model("Foo::Model") { include_root_in_json true }
146
+
147
+ class User < Foo::Model; end
148
+ @spawned_models << :User
149
+ end
150
+
151
+ it "builds path using the children model name" do
152
+ expect(User.find("foo").id).to eq("foo")
153
+ expect(User.find("foo").id).to eq("foo")
154
+ end
155
+ end
156
+
157
+ context "nested model" do
158
+ before do
159
+ spawn_model "Foo::User"
160
+ end
161
+
162
+ describe "#request_path" do
163
+ it "builds paths with defaults" do
164
+ expect(Foo::User.new(id: "foo").request_path).to eq("users/foo")
165
+ expect(Foo::User.new.request_path).to eq("users")
166
+ end
167
+ end
168
+ end
169
+
170
+ context "custom primary key" do
171
+ before do
172
+ spawn_model "User" do
173
+ primary_key "UserId"
174
+ resource_path "users/:UserId"
175
+ end
176
+
177
+ spawn_model "Customer" do
178
+ primary_key :customer_id
179
+ resource_path "customers/:id"
180
+ end
181
+ end
182
+
183
+ describe "#request_path" do
184
+ it "uses the correct primary key attribute" do
185
+ expect(User.new(UserId: "foo").request_path).to eq("users/foo")
186
+ expect(User.new(id: "foo").request_path).to eq("users")
187
+ end
188
+
189
+ it "replaces :id with the appropriate primary key" do
190
+ expect(Customer.new(customer_id: "joe").request_path).to eq("customers/joe")
191
+ expect(Customer.new(id: "joe").request_path).to eq("customers")
192
+ end
193
+ end
194
+ end
195
+ end
196
+
197
+ context "making subdomain HTTP requests" do
198
+ before do
199
+ Restorm::API.setup url: "https://api.example.com/" do |builder|
200
+ builder.use Restorm::Middleware::FirstLevelParseJSON
201
+ builder.use Faraday::Request::UrlEncoded
202
+ builder.adapter :test do |stub|
203
+ stub.get("organizations/2/users") { [200, {}, [{ id: 1, fullname: "Tobias Fünke", organization_id: 2 }, { id: 2, fullname: "Lindsay Fünke", organization_id: 2 }].to_json] }
204
+ stub.post("organizations/2/users") { [200, {}, { id: 1, fullname: "Tobias Fünke", organization_id: 2 }.to_json] }
205
+ stub.put("organizations/2/users/1") { [200, {}, { id: 1, fullname: "Lindsay Fünke", organization_id: 2 }.to_json] }
206
+ stub.get("organizations/2/users/1") { [200, {}, { id: 1, fullname: "Tobias Fünke", organization_id: 2, active: true }.to_json] }
207
+ stub.delete("organizations/2/users/1") { [200, {}, { id: 1, fullname: "Lindsay Fünke", organization_id: 2, active: false }.to_json] }
208
+ end
209
+ end
210
+
211
+ spawn_model "Foo::User" do
212
+ collection_path "organizations/:organization_id/users"
213
+ end
214
+ end
215
+
216
+ describe "fetching a resource" do
217
+ it "maps a single resource to a Ruby object" do
218
+ @user = Foo::User.find(1, _organization_id: 2)
219
+ expect(@user.id).to eq(1)
220
+ expect(@user.fullname).to eq("Tobias Fünke")
221
+ end
222
+
223
+ it "maps a single resource using a scope to a Ruby object" do
224
+ Foo::User.scope :for_organization, ->(o) { where(organization_id: o) }
225
+ @user = Foo::User.for_organization(2).find(1)
226
+ expect(@user.id).to eq(1)
227
+ expect(@user.fullname).to eq("Tobias Fünke")
228
+ end
229
+ end
230
+
231
+ describe "fetching a collection" do
232
+ it "maps a collection of resources to an array of Ruby objects" do
233
+ @users = Foo::User.where(_organization_id: 2).all
234
+ expect(@users.length).to eq(2)
235
+ expect(@users.first.fullname).to eq("Tobias Fünke")
236
+ end
237
+ end
238
+
239
+ describe "handling new resource" do
240
+ it "handles new resource" do
241
+ @new_user = Foo::User.new(fullname: "Tobias Fünke", organization_id: 2)
242
+ expect(@new_user.new?).to be_truthy
243
+
244
+ @existing_user = Foo::User.find(1, _organization_id: 2)
245
+ expect(@existing_user.new?).to be_falsey
246
+ end
247
+ end
248
+
249
+ describe "creating resources" do
250
+ it "handle one-line resource creation" do
251
+ @user = Foo::User.create(fullname: "Tobias Fünke", organization_id: 2)
252
+ expect(@user.id).to eq(1)
253
+ expect(@user.fullname).to eq("Tobias Fünke")
254
+ end
255
+
256
+ it "handle resource creation through Model.new + #save" do
257
+ @user = Foo::User.new(fullname: "Tobias Fünke", organization_id: 2)
258
+ @user.save
259
+ expect(@user.fullname).to eq("Tobias Fünke")
260
+ end
261
+ end
262
+
263
+ context "updating resources" do
264
+ it "handle resource data update without saving it" do
265
+ @user = Foo::User.find(1, _organization_id: 2)
266
+ expect(@user.fullname).to eq("Tobias Fünke")
267
+ @user.fullname = "Kittie Sanchez"
268
+ expect(@user.fullname).to eq("Kittie Sanchez")
269
+ end
270
+
271
+ it "handle resource update through the .update class method" do
272
+ @user = Foo::User.save_existing(1, fullname: "Lindsay Fünke", organization_id: 2)
273
+ expect(@user.fullname).to eq("Lindsay Fünke")
274
+ end
275
+
276
+ it "handle resource update through #save on an existing resource" do
277
+ @user = Foo::User.find(1, _organization_id: 2)
278
+ @user.fullname = "Lindsay Fünke"
279
+ @user.save
280
+ expect(@user.fullname).to eq("Lindsay Fünke")
281
+ end
282
+ end
283
+
284
+ context "deleting resources" do
285
+ it "handle resource deletion through the .destroy class method" do
286
+ @user = Foo::User.destroy_existing(1, _organization_id: 2)
287
+ expect(@user.active).to be_falsey
288
+ end
289
+
290
+ it "handle resource deletion through #destroy on an existing resource" do
291
+ @user = Foo::User.find(1, _organization_id: 2)
292
+ @user.destroy
293
+ expect(@user.active).to be_falsey
294
+ end
295
+ end
296
+ end
297
+
298
+ context "making path HTTP requests" do
299
+ before do
300
+ Restorm::API.setup url: "https://example.com/api/" do |builder|
301
+ builder.use Restorm::Middleware::FirstLevelParseJSON
302
+ builder.use Faraday::Request::UrlEncoded
303
+ builder.adapter :test do |stub|
304
+ stub.get("/api/organizations/2/users") { [200, {}, [{ id: 1, fullname: "Tobias Fünke", organization_id: 2 }, { id: 2, fullname: "Lindsay Fünke", organization_id: 2 }].to_json] }
305
+ stub.get("/api/organizations/2/users/1") { [200, {}, { id: 1, fullname: "Tobias Fünke", organization_id: 2, active: true }.to_json] }
306
+ end
307
+ end
308
+
309
+ spawn_model "Foo::User" do
310
+ collection_path "organizations/:organization_id/users"
311
+ end
312
+ end
313
+
314
+ describe "fetching a resource" do
315
+ it "maps a single resource to a Ruby object" do
316
+ @user = Foo::User.find(1, _organization_id: 2)
317
+ expect(@user.id).to eq(1)
318
+ expect(@user.fullname).to eq("Tobias Fünke")
319
+ end
320
+ end
321
+
322
+ describe "fetching a collection" do
323
+ it "maps a collection of resources to an array of Ruby objects" do
324
+ @users = Foo::User.where(_organization_id: 2).all
325
+ expect(@users.length).to eq(2)
326
+ expect(@users.first.fullname).to eq("Tobias Fünke")
327
+ end
328
+ end
329
+
330
+ describe "fetching a resource with absolute path" do
331
+ it "maps a single resource to a Ruby object" do
332
+ Foo::User.resource_path "/api/" + Foo::User.resource_path
333
+ @user = Foo::User.find(1, _organization_id: 2)
334
+ expect(@user.id).to eq(1)
335
+ expect(@user.fullname).to eq("Tobias Fünke")
336
+ end
337
+ end
338
+
339
+ describe "fetching a collection with absolute path" do
340
+ it "maps a collection of resources to an array of Ruby objects" do
341
+ Foo::User.collection_path "/api/" + Foo::User.collection_path
342
+ @users = Foo::User.where(_organization_id: 2).all
343
+ expect(@users.length).to eq(2)
344
+ expect(@users.first.fullname).to eq("Tobias Fünke")
345
+ end
346
+ end
347
+ end
348
+ end
@@ -0,0 +1,247 @@
1
+ # encoding: utf-8
2
+
3
+ require File.join(File.dirname(__FILE__), "../spec_helper.rb")
4
+
5
+ describe Restorm::Model::Relation do
6
+ describe :where do
7
+ context "for base classes" do
8
+ before do
9
+ Restorm::API.setup url: "https://api.example.com" do |builder|
10
+ builder.use Restorm::Middleware::FirstLevelParseJSON
11
+ builder.adapter :test do |stub|
12
+ stub.get("/users?foo=1&bar=2") { ok! [{ id: 2, fullname: "Tobias Fünke" }] }
13
+ stub.get("/users?admin=1") { ok! [{ id: 1, fullname: "Tobias Fünke" }] }
14
+ stub.get("/users?id=3&foo=2") { ok! [{ id: 3, fullname: "Tobias Fünke" }] }
15
+
16
+ stub.get("/users") do
17
+ ok! [
18
+ { id: 1, fullname: "Tobias Fünke" },
19
+ { id: 2, fullname: "Lindsay Fünke" },
20
+ @created_user
21
+ ].compact
22
+ end
23
+
24
+ stub.post("/users") do
25
+ @created_user = { id: 3, fullname: "George Michael Bluth" }
26
+ ok! @created_user
27
+ end
28
+ end
29
+ end
30
+
31
+ spawn_model "Foo::User"
32
+ end
33
+
34
+ it "doesn't fetch the data immediatly" do
35
+ expect(Foo::User).to receive(:request).never
36
+ @users = Foo::User.where(admin: 1)
37
+ end
38
+
39
+ it "fetches the data and passes query parameters" do
40
+ expect(Foo::User).to receive(:request).once.and_call_original
41
+ @users = Foo::User.where(admin: 1)
42
+ expect(@users).to respond_to(:length)
43
+ expect(@users.size).to eql 1
44
+ end
45
+
46
+ it "fetches the data by parameters including primary_key" do
47
+ expect(Foo::User).to receive(:request).once.and_call_original
48
+ @users = Foo::User.where(id: 3, foo: 2)
49
+ expect(@users).to respond_to(:length)
50
+ expect(@users.size).to eql 1
51
+ end
52
+
53
+ it "chains multiple where statements" do
54
+ @user = Foo::User.where(foo: 1).where(bar: 2).first
55
+ expect(@user.id).to eq(2)
56
+ end
57
+
58
+ it "does not reuse relations" do
59
+ expect(Foo::User.all.size).to eql 2
60
+ expect(Foo::User.create(fullname: "George Michael Bluth").id).to eq(3)
61
+ expect(Foo::User.all.size).to eql 3
62
+ end
63
+ end
64
+
65
+ context "for parent class" do
66
+ before do
67
+ Restorm::API.setup url: "https://api.example.com" do |builder|
68
+ builder.use Restorm::Middleware::FirstLevelParseJSON
69
+ builder.adapter :test do |stub|
70
+ stub.get("/users?page=2") { ok! [{ id: 1, fullname: "Tobias Fünke" }, { id: 2, fullname: "Lindsay Fünke" }] }
71
+ end
72
+ end
73
+
74
+ spawn_model("Foo::Model") do
75
+ scope :page, ->(page) { where(page: page) }
76
+ end
77
+
78
+ class User < Foo::Model; end
79
+ @spawned_models << :User
80
+ end
81
+
82
+ it "propagates the scopes through its children" do
83
+ expect(User.page(2).length).to eq(2)
84
+ expect(User.scoped.page(2).length).to eq(2)
85
+ end
86
+ end
87
+ end
88
+
89
+ describe :create do
90
+ before do
91
+ Restorm::API.setup url: "https://api.example.com" do |builder|
92
+ builder.use Restorm::Middleware::FirstLevelParseJSON
93
+ builder.use Faraday::Request::UrlEncoded
94
+ builder.adapter :test do |stub|
95
+ stub.post("/users") { |env| ok! id: 1, fullname: params(env)[:fullname], email: params(env)[:email] }
96
+ end
97
+ end
98
+
99
+ spawn_model "Foo::User"
100
+ end
101
+
102
+ context "with a single where call" do
103
+ it "creates a resource and passes the query parameters" do
104
+ @user = Foo::User.where(fullname: "Tobias Fünke", email: "tobias@bluth.com").create
105
+ expect(@user.id).to eq(1)
106
+ expect(@user.fullname).to eq("Tobias Fünke")
107
+ expect(@user.email).to eq("tobias@bluth.com")
108
+ end
109
+ end
110
+
111
+ context "with multiple where calls" do
112
+ it "creates a resource and passes the query parameters" do
113
+ @user = Foo::User.where(fullname: "Tobias Fünke").create(email: "tobias@bluth.com")
114
+ expect(@user.id).to eq(1)
115
+ expect(@user.fullname).to eq("Tobias Fünke")
116
+ expect(@user.email).to eq("tobias@bluth.com")
117
+ end
118
+ end
119
+ end
120
+
121
+ describe :build do
122
+ before { spawn_model "Foo::User" }
123
+
124
+ it "handles new resource with build" do
125
+ @new_user = Foo::User.where(fullname: "Tobias Fünke").build
126
+ expect(@new_user.new?).to be_truthy
127
+ expect(@new_user.fullname).to eq("Tobias Fünke")
128
+ end
129
+ end
130
+
131
+ describe :scope do
132
+ before do
133
+ Restorm::API.setup url: "https://api.example.com" do |builder|
134
+ builder.use Restorm::Middleware::FirstLevelParseJSON
135
+ builder.adapter :test do |stub|
136
+ stub.get("/users?what=4&where=3") { ok! [{ id: 3, fullname: "Maeby Fünke" }] }
137
+ stub.get("/users?what=2") { ok! [{ id: 2, fullname: "Lindsay Fünke" }] }
138
+ stub.get("/users?where=6") { ok! [{ id: 4, fullname: "Tobias Fünke" }] }
139
+ stub.get('/bar/users') { ok! [] }
140
+ end
141
+ end
142
+
143
+ spawn_model "Foo::User" do
144
+ scope :foo, ->(v) { where(what: v) }
145
+ scope :bar, ->(v) { where(where: v) }
146
+ scope :baz, -> { bar(6) }
147
+ end
148
+
149
+ spawn_model "Bar::User" do
150
+ collection_path '/bar/users'
151
+ scope :baz, -> { where(where: 7) }
152
+ end
153
+ end
154
+
155
+ it "passes query parameters" do
156
+ @user = Foo::User.foo(2).first
157
+ expect(@user.id).to eq(2)
158
+ end
159
+
160
+ it "passes multiple query parameters" do
161
+ @user = Foo::User.foo(4).bar(3).first
162
+ expect(@user.id).to eq(3)
163
+ end
164
+
165
+ it "handles embedded scopes" do
166
+ @user = Foo::User.baz.first
167
+ expect(@user.id).to eq(4)
168
+ end
169
+
170
+ it "does not share scope with other models" do
171
+ expect(Bar::User.scoped).not_to respond_to(:foo, :bar)
172
+ expect(Foo::User.scoped.baz.params[:where]).to eq(6)
173
+ expect(Bar::User.scoped.baz.params[:where]).to eq(7)
174
+ end
175
+ end
176
+
177
+ describe :default_scope do
178
+ context "for new objects" do
179
+ before do
180
+ spawn_model "Foo::User" do
181
+ default_scope -> { where(active: true) }
182
+ default_scope -> { where(admin: true) }
183
+ end
184
+ end
185
+
186
+ it "should apply the scope to the attributes" do
187
+ expect(Foo::User.new).to be_active
188
+ expect(Foo::User.new).to be_admin
189
+ end
190
+ end
191
+
192
+ context "for fetched resources" do
193
+ before do
194
+ Restorm::API.setup url: "https://api.example.com" do |builder|
195
+ builder.use Restorm::Middleware::FirstLevelParseJSON
196
+ builder.use Faraday::Request::UrlEncoded
197
+ builder.adapter :test do |stub|
198
+ stub.post("/users") { |env| ok! id: 3, active: (params(env)[:active] == "true") }
199
+ end
200
+ end
201
+
202
+ spawn_model "Foo::User" do
203
+ default_scope -> { where(active: true) }
204
+ end
205
+ end
206
+
207
+ it("should apply the scope to the request") { expect(Foo::User.create).to be_active }
208
+ end
209
+
210
+ context "for fetched collections" do
211
+ before do
212
+ Restorm::API.setup url: "https://api.example.com" do |builder|
213
+ builder.use Restorm::Middleware::FirstLevelParseJSON
214
+ builder.use Faraday::Request::UrlEncoded
215
+ builder.adapter :test do |stub|
216
+ stub.get("/users?active=true") { |env| ok! [{ id: 3, active: (params(env)[:active] == "true") }] }
217
+ end
218
+ end
219
+
220
+ spawn_model "Foo::User" do
221
+ default_scope -> { where(active: true) }
222
+ end
223
+ end
224
+
225
+ it("should apply the scope to the request") { expect(Foo::User.all.first).to be_active }
226
+ end
227
+ end
228
+
229
+ describe :map do
230
+ before do
231
+ Restorm::API.setup url: "https://api.example.com" do |builder|
232
+ builder.use Restorm::Middleware::FirstLevelParseJSON
233
+ builder.adapter :test do |stub|
234
+ stub.get("/users") do
235
+ ok! [{ id: 1, fullname: "Tobias Fünke" }, { id: 2, fullname: "Lindsay Fünke" }]
236
+ end
237
+ end
238
+ end
239
+
240
+ spawn_model "Foo::User"
241
+ end
242
+
243
+ it "delegates the method to the fetched collection" do
244
+ expect(Foo::User.all.map(&:fullname)).to eq(["Tobias Fünke", "Lindsay Fünke"])
245
+ end
246
+ end
247
+ end
@@ -0,0 +1,43 @@
1
+ # encoding: utf-8
2
+
3
+ require File.join(File.dirname(__FILE__), "../spec_helper.rb")
4
+
5
+ describe "Restorm::Model and ActiveModel::Validations" do
6
+ context "validating attributes" do
7
+ before do
8
+ spawn_model "Foo::User" do
9
+ attributes :fullname, :email
10
+ validates_presence_of :fullname
11
+ validates_presence_of :email
12
+ end
13
+ end
14
+
15
+ it "validates attributes when calling #valid?" do
16
+ user = Foo::User.new
17
+ expect(user).not_to be_valid
18
+ expect(user.errors.full_messages).to include("Fullname can't be blank")
19
+ expect(user.errors.full_messages).to include("Email can't be blank")
20
+ user.fullname = "Tobias Fünke"
21
+ user.email = "tobias@bluthcompany.com"
22
+ expect(user).to be_valid
23
+ end
24
+ end
25
+
26
+ context "handling server errors" do
27
+ before do
28
+ spawn_model("Foo::Model") do
29
+ def errors
30
+ @response_errors
31
+ end
32
+ end
33
+
34
+ class User < Foo::Model; end
35
+ @spawned_models << :User
36
+ end
37
+
38
+ it "validates attributes when calling #valid?" do
39
+ user = User.new(_errors: ["Email cannot be blank"])
40
+ expect(user.errors).to include("Email cannot be blank")
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,45 @@
1
+ # encoding: utf-8
2
+
3
+ require "spec_helper"
4
+
5
+ describe Restorm::Model do
6
+ before do
7
+ Restorm::API.setup url: "https://api.example.com" do |connection|
8
+ connection.use Restorm::Middleware::FirstLevelParseJSON
9
+ connection.adapter :test do |stub|
10
+ stub.get("/users/1") { [200, {}, { id: 1, name: "Tobias Fünke" }.to_json] }
11
+ stub.get("/users/1/comments") { [200, {}, [{ id: 4, body: "They're having a FIRESALE?" }].to_json] }
12
+ end
13
+ end
14
+
15
+ spawn_model("Foo::User") { has_many :comments }
16
+ spawn_model("Foo::Comment")
17
+ end
18
+ subject { Foo::User.find(1) }
19
+
20
+ describe :has_key? do
21
+ it { is_expected.not_to have_key(:unknown_method_for_a_user) }
22
+ it { is_expected.not_to have_key(:unknown_method_for_a_user) }
23
+ it { is_expected.to have_key(:name) }
24
+ it { is_expected.to have_key(:comments) }
25
+ end
26
+
27
+ describe :serialization do
28
+ it "should be serialized without an error" do
29
+ expect { Marshal.dump(subject.comments) }.not_to raise_error
30
+ end
31
+
32
+ it "should correctly load serialized object" do
33
+ serialized_comments = Marshal.load(Marshal.dump(subject.comments))
34
+ expect(subject.comments.size).to eq(serialized_comments.size)
35
+ expect(subject.comments.first.id).to eq(serialized_comments.first.id)
36
+ expect(subject.comments.first.body).to eq(serialized_comments.first.body)
37
+ end
38
+ end
39
+
40
+ describe :[] do
41
+ it { is_expected.not_to have_key(:unknown_method_for_a_user) }
42
+ specify { expect(subject[:name]).to eq("Tobias Fünke") }
43
+ specify { expect(subject[:comments].first.body).to eq("They're having a FIRESALE?") }
44
+ end
45
+ end