him 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/ci.yml +40 -0
  3. data/.gitignore +6 -0
  4. data/.qlty/qlty.toml +57 -0
  5. data/.rspec +1 -0
  6. data/.ruby-version +1 -0
  7. data/.yardopts +2 -0
  8. data/CONTRIBUTING.md +26 -0
  9. data/Gemfile +2 -0
  10. data/LICENSE +8 -0
  11. data/README.md +1007 -0
  12. data/Rakefile +11 -0
  13. data/UPGRADE.md +101 -0
  14. data/gemfiles/Gemfile.activemodel-6.1 +6 -0
  15. data/gemfiles/Gemfile.activemodel-7.0 +6 -0
  16. data/gemfiles/Gemfile.activemodel-7.1 +6 -0
  17. data/gemfiles/Gemfile.activemodel-7.2 +6 -0
  18. data/gemfiles/Gemfile.activemodel-8.0 +6 -0
  19. data/him.gemspec +28 -0
  20. data/lib/him/api.rb +121 -0
  21. data/lib/him/collection.rb +21 -0
  22. data/lib/him/errors.rb +29 -0
  23. data/lib/him/json_api/model.rb +42 -0
  24. data/lib/him/middleware/accept_json.rb +18 -0
  25. data/lib/him/middleware/first_level_parse_json.rb +37 -0
  26. data/lib/him/middleware/json_api_parser.rb +65 -0
  27. data/lib/him/middleware/parse_json.rb +22 -0
  28. data/lib/him/middleware/second_level_parse_json.rb +37 -0
  29. data/lib/him/middleware.rb +12 -0
  30. data/lib/him/model/associations/association.rb +147 -0
  31. data/lib/him/model/associations/association_proxy.rb +47 -0
  32. data/lib/him/model/associations/belongs_to_association.rb +95 -0
  33. data/lib/him/model/associations/has_many_association.rb +113 -0
  34. data/lib/him/model/associations/has_one_association.rb +79 -0
  35. data/lib/him/model/associations.rb +141 -0
  36. data/lib/him/model/attributes.rb +337 -0
  37. data/lib/him/model/base.rb +33 -0
  38. data/lib/him/model/http.rb +113 -0
  39. data/lib/him/model/introspection.rb +77 -0
  40. data/lib/him/model/nested_attributes.rb +45 -0
  41. data/lib/him/model/orm.rb +306 -0
  42. data/lib/him/model/parse.rb +224 -0
  43. data/lib/him/model/paths.rb +125 -0
  44. data/lib/him/model/relation.rb +212 -0
  45. data/lib/him/model.rb +79 -0
  46. data/lib/him/version.rb +3 -0
  47. data/lib/him.rb +22 -0
  48. data/spec/api_spec.rb +120 -0
  49. data/spec/collection_spec.rb +70 -0
  50. data/spec/json_api/model_spec.rb +260 -0
  51. data/spec/middleware/accept_json_spec.rb +11 -0
  52. data/spec/middleware/first_level_parse_json_spec.rb +63 -0
  53. data/spec/middleware/json_api_parser_spec.rb +52 -0
  54. data/spec/middleware/second_level_parse_json_spec.rb +35 -0
  55. data/spec/model/associations/association_proxy_spec.rb +29 -0
  56. data/spec/model/associations_spec.rb +1010 -0
  57. data/spec/model/attributes_spec.rb +384 -0
  58. data/spec/model/callbacks_spec.rb +194 -0
  59. data/spec/model/dirty_spec.rb +133 -0
  60. data/spec/model/http_spec.rb +187 -0
  61. data/spec/model/introspection_spec.rb +110 -0
  62. data/spec/model/nested_attributes_spec.rb +135 -0
  63. data/spec/model/orm_spec.rb +717 -0
  64. data/spec/model/parse_spec.rb +619 -0
  65. data/spec/model/paths_spec.rb +348 -0
  66. data/spec/model/relation_spec.rb +255 -0
  67. data/spec/model/validations_spec.rb +45 -0
  68. data/spec/model_spec.rb +55 -0
  69. data/spec/spec_helper.rb +25 -0
  70. data/spec/support/extensions/array.rb +6 -0
  71. data/spec/support/extensions/hash.rb +6 -0
  72. data/spec/support/macros/her_macros.rb +17 -0
  73. data/spec/support/macros/model_macros.rb +36 -0
  74. data/spec/support/macros/request_macros.rb +27 -0
  75. metadata +201 -0
@@ -0,0 +1,348 @@
1
+ # encoding: utf-8
2
+
3
+ require File.join(File.dirname(__FILE__), "../spec_helper.rb")
4
+
5
+ describe Him::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(Him::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(Him::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(Him::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
+ Him::API.setup url: "https://api.example.com" do |builder|
138
+ builder.use Him::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
+ Him::API.setup url: "https://api.example.com/" do |builder|
200
+ builder.use Him::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
+ Him::API.setup url: "https://example.com/api/" do |builder|
301
+ builder.use Him::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,255 @@
1
+ # encoding: utf-8
2
+
3
+ require File.join(File.dirname(__FILE__), "../spec_helper.rb")
4
+
5
+ describe Him::Model::Relation do
6
+ describe :where do
7
+ context "for base classes" do
8
+ before do
9
+ Him::API.setup url: "https://api.example.com" do |builder|
10
+ builder.use Him::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 immediately" 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
+
64
+ it "returns an empty collection without fetching when [] is given" do
65
+ expect(Foo::User).not_to receive(:request)
66
+ users = Foo::User.where(fullname: [])
67
+ expect(users).to respond_to(:length)
68
+ expect(users.size).to eql 0
69
+ end
70
+ end
71
+
72
+ context "for parent class" do
73
+ before do
74
+ Him::API.setup url: "https://api.example.com" do |builder|
75
+ builder.use Him::Middleware::FirstLevelParseJSON
76
+ builder.adapter :test do |stub|
77
+ stub.get("/users?page=2") { ok! [{ id: 1, fullname: "Tobias Fünke" }, { id: 2, fullname: "Lindsay Fünke" }] }
78
+ end
79
+ end
80
+
81
+ spawn_model("Foo::Model") do
82
+ scope :page, ->(page) { where(page: page) }
83
+ end
84
+
85
+ class User < Foo::Model; end
86
+ @spawned_models << :User
87
+ end
88
+
89
+ it "propagates the scopes through its children" do
90
+ expect(User.page(2).length).to eq(2)
91
+ expect(User.scoped.page(2).length).to eq(2)
92
+ end
93
+ end
94
+ end
95
+
96
+ describe :create do
97
+ before do
98
+ Him::API.setup url: "https://api.example.com" do |builder|
99
+ builder.use Him::Middleware::FirstLevelParseJSON
100
+ builder.use Faraday::Request::UrlEncoded
101
+ builder.adapter :test do |stub|
102
+ stub.post("/users") { |env| ok! id: 1, fullname: params(env)[:fullname], email: params(env)[:email] }
103
+ end
104
+ end
105
+
106
+ spawn_model "Foo::User"
107
+ end
108
+
109
+ context "with a single where call" do
110
+ it "creates a resource and passes the query parameters" do
111
+ @user = Foo::User.where(fullname: "Tobias Fünke", email: "tobias@bluth.com").create
112
+ expect(@user.id).to eq(1)
113
+ expect(@user.fullname).to eq("Tobias Fünke")
114
+ expect(@user.email).to eq("tobias@bluth.com")
115
+ end
116
+ end
117
+
118
+ context "with multiple where calls" do
119
+ it "creates a resource and passes the query parameters" do
120
+ @user = Foo::User.where(fullname: "Tobias Fünke").create(email: "tobias@bluth.com")
121
+ expect(@user.id).to eq(1)
122
+ expect(@user.fullname).to eq("Tobias Fünke")
123
+ expect(@user.email).to eq("tobias@bluth.com")
124
+ end
125
+ end
126
+ end
127
+
128
+ describe :build do
129
+ before { spawn_model "Foo::User" }
130
+
131
+ it "handles new resource with build" do
132
+ @new_user = Foo::User.where(fullname: "Tobias Fünke").build
133
+ expect(@new_user.new?).to be_truthy
134
+ expect(@new_user.fullname).to eq("Tobias Fünke")
135
+ end
136
+ end
137
+
138
+ describe :scope do
139
+ before do
140
+ Him::API.setup url: "https://api.example.com" do |builder|
141
+ builder.use Him::Middleware::FirstLevelParseJSON
142
+ builder.adapter :test do |stub|
143
+ stub.get("/users?what=4&where=3") { ok! [{ id: 3, fullname: "Maeby Fünke" }] }
144
+ stub.get("/users?what=2") { ok! [{ id: 2, fullname: "Lindsay Fünke" }] }
145
+ stub.get("/users?where=6") { ok! [{ id: 4, fullname: "Tobias Fünke" }] }
146
+ stub.get('/bar/users') { ok! [] }
147
+ end
148
+ end
149
+
150
+ spawn_model "Foo::User" do
151
+ scope :foo, ->(v) { where(what: v) }
152
+ scope :bar, ->(v) { where(where: v) }
153
+ scope :baz, -> { bar(6) }
154
+ end
155
+
156
+ spawn_model "Bar::User" do
157
+ collection_path '/bar/users'
158
+ scope :baz, -> { where(where: 7) }
159
+ end
160
+ end
161
+
162
+ it "passes query parameters" do
163
+ @user = Foo::User.foo(2).first
164
+ expect(@user.id).to eq(2)
165
+ end
166
+
167
+ it "passes multiple query parameters" do
168
+ @user = Foo::User.foo(4).bar(3).first
169
+ expect(@user.id).to eq(3)
170
+ end
171
+
172
+ it "handles embedded scopes" do
173
+ @user = Foo::User.baz.first
174
+ expect(@user.id).to eq(4)
175
+ end
176
+
177
+ it "does not share scope with other models" do
178
+ expect(Bar::User.scoped).not_to respond_to(:foo, :bar)
179
+ expect(Foo::User.scoped.baz.params[:where]).to eq(6)
180
+ expect(Bar::User.scoped.baz.params[:where]).to eq(7)
181
+ end
182
+
183
+ end
184
+
185
+ describe :default_scope do
186
+ context "for new objects" do
187
+ before do
188
+ spawn_model "Foo::User" do
189
+ default_scope -> { where(active: true) }
190
+ default_scope -> { where(admin: true) }
191
+ end
192
+ end
193
+
194
+ it "should apply the scope to the attributes" do
195
+ expect(Foo::User.new).to be_active
196
+ expect(Foo::User.new).to be_admin
197
+ end
198
+ end
199
+
200
+ context "for fetched resources" do
201
+ before do
202
+ Him::API.setup url: "https://api.example.com" do |builder|
203
+ builder.use Him::Middleware::FirstLevelParseJSON
204
+ builder.use Faraday::Request::UrlEncoded
205
+ builder.adapter :test do |stub|
206
+ stub.post("/users") { |env| ok! id: 3, active: (params(env)[:active] == "true") }
207
+ end
208
+ end
209
+
210
+ spawn_model "Foo::User" do
211
+ default_scope -> { where(active: true) }
212
+ end
213
+ end
214
+
215
+ it("should apply the scope to the request") { expect(Foo::User.create).to be_active }
216
+ end
217
+
218
+ context "for fetched collections" do
219
+ before do
220
+ Him::API.setup url: "https://api.example.com" do |builder|
221
+ builder.use Him::Middleware::FirstLevelParseJSON
222
+ builder.use Faraday::Request::UrlEncoded
223
+ builder.adapter :test do |stub|
224
+ stub.get("/users?active=true") { |env| ok! [{ id: 3, active: (params(env)[:active] == "true") }] }
225
+ end
226
+ end
227
+
228
+ spawn_model "Foo::User" do
229
+ default_scope -> { where(active: true) }
230
+ end
231
+ end
232
+
233
+ it("should apply the scope to the request") { expect(Foo::User.all.first).to be_active }
234
+ end
235
+ end
236
+
237
+ describe :map do
238
+ before do
239
+ Him::API.setup url: "https://api.example.com" do |builder|
240
+ builder.use Him::Middleware::FirstLevelParseJSON
241
+ builder.adapter :test do |stub|
242
+ stub.get("/users") do
243
+ ok! [{ id: 1, fullname: "Tobias Fünke" }, { id: 2, fullname: "Lindsay Fünke" }]
244
+ end
245
+ end
246
+ end
247
+
248
+ spawn_model "Foo::User"
249
+ end
250
+
251
+ it "delegates the method to the fetched collection" do
252
+ expect(Foo::User.all.map(&:fullname)).to eq(["Tobias Fünke", "Lindsay Fünke"])
253
+ end
254
+ end
255
+ end
@@ -0,0 +1,45 @@
1
+ # encoding: utf-8
2
+
3
+ require File.join(File.dirname(__FILE__), "../spec_helper.rb")
4
+
5
+ describe "Him::Model and ActiveModel::Validations" do
6
+ context "validating attributes" do
7
+ before do
8
+ spawn_model "Foo::User" do
9
+ attributes :fullname, :email, :age
10
+ validates_presence_of :fullname
11
+ validates_presence_of :email
12
+ validates_numericality_of :age
13
+ end
14
+ end
15
+
16
+ it "validates attributes when calling #valid?" do
17
+ user = Foo::User.new
18
+ expect(user).not_to be_valid
19
+ expect(user.errors.full_messages).to include("Fullname can't be blank")
20
+ expect(user.errors.full_messages).to include("Email can't be blank")
21
+ user.fullname = "Tobias Fünke"
22
+ user.email = "tobias@bluthcompany.com"
23
+ user.age = 35
24
+ expect(user).to be_valid
25
+ end
26
+ end
27
+
28
+ context "handling server errors" do
29
+ before do
30
+ spawn_model("Foo::Model") do
31
+ def errors
32
+ @response_errors
33
+ end
34
+ end
35
+
36
+ class User < Foo::Model; end
37
+ @spawned_models << :User
38
+ end
39
+
40
+ it "validates attributes when calling #valid?" do
41
+ user = User.new(_errors: ["Email cannot be blank"])
42
+ expect(user.errors).to include("Email cannot be blank")
43
+ end
44
+ end
45
+ end