smooth_operator 0.4.4 → 1.2.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 (68) hide show
  1. checksums.yaml +9 -9
  2. data/.gitignore +2 -1
  3. data/.rspec +4 -0
  4. data/Gemfile +13 -0
  5. data/README.md +258 -10
  6. data/console.rb +44 -0
  7. data/lib/smooth_operator/array_with_meta_data.rb +31 -0
  8. data/lib/smooth_operator/attribute_assignment.rb +102 -0
  9. data/lib/smooth_operator/attribute_methods.rb +87 -0
  10. data/lib/smooth_operator/attributes/base.rb +107 -0
  11. data/lib/smooth_operator/attributes/dirty.rb +29 -0
  12. data/lib/smooth_operator/attributes/normal.rb +15 -0
  13. data/lib/smooth_operator/delegation.rb +60 -0
  14. data/lib/smooth_operator/finder_methods.rb +43 -0
  15. data/lib/smooth_operator/helpers.rb +79 -0
  16. data/lib/smooth_operator/model_schema.rb +81 -0
  17. data/lib/smooth_operator/open_struct.rb +37 -0
  18. data/lib/smooth_operator/operator.rb +145 -0
  19. data/lib/smooth_operator/operators/faraday.rb +75 -0
  20. data/lib/smooth_operator/operators/typhoeus.rb +77 -0
  21. data/lib/smooth_operator/persistence.rb +144 -0
  22. data/lib/smooth_operator/relation/array_relation.rb +13 -0
  23. data/lib/smooth_operator/relation/association_reflection.rb +75 -0
  24. data/lib/smooth_operator/relation/associations.rb +75 -0
  25. data/lib/smooth_operator/relation/reflection.rb +41 -0
  26. data/lib/smooth_operator/relation/single_relation.rb +14 -0
  27. data/lib/smooth_operator/remote_call/base.rb +80 -0
  28. data/lib/smooth_operator/remote_call/errors/connection_failed.rb +20 -0
  29. data/lib/smooth_operator/remote_call/errors/timeout.rb +20 -0
  30. data/lib/smooth_operator/remote_call/faraday.rb +19 -0
  31. data/lib/smooth_operator/remote_call/typhoeus.rb +19 -0
  32. data/lib/smooth_operator/serialization.rb +79 -0
  33. data/lib/smooth_operator/translation.rb +27 -0
  34. data/lib/smooth_operator/validations.rb +15 -0
  35. data/lib/smooth_operator/version.rb +1 -1
  36. data/lib/smooth_operator.rb +26 -5
  37. data/smooth_operator.gemspec +12 -3
  38. data/spec/factories/user_factory.rb +34 -0
  39. data/spec/require_helper.rb +11 -0
  40. data/spec/smooth_operator/attribute_assignment_spec.rb +351 -0
  41. data/spec/smooth_operator/attributes_dirty_spec.rb +53 -0
  42. data/spec/smooth_operator/delegation_spec.rb +139 -0
  43. data/spec/smooth_operator/finder_methods_spec.rb +105 -0
  44. data/spec/smooth_operator/model_schema_spec.rb +31 -0
  45. data/spec/smooth_operator/operator_spec.rb +46 -0
  46. data/spec/smooth_operator/persistence_spec.rb +424 -0
  47. data/spec/smooth_operator/remote_call_spec.rb +320 -0
  48. data/spec/smooth_operator/serialization_spec.rb +80 -0
  49. data/spec/smooth_operator/validations_spec.rb +42 -0
  50. data/spec/spec_helper.rb +25 -0
  51. data/spec/support/helpers/persistence_helper.rb +38 -0
  52. data/spec/support/localhost_server.rb +97 -0
  53. data/spec/support/models/address.rb +14 -0
  54. data/spec/support/models/comment.rb +3 -0
  55. data/spec/support/models/post.rb +13 -0
  56. data/spec/support/models/user.rb +41 -0
  57. data/spec/support/models/user_with_address_and_posts.rb +89 -0
  58. data/spec/support/test_server.rb +165 -0
  59. metadata +108 -18
  60. data/lib/smooth_operator/base.rb +0 -30
  61. data/lib/smooth_operator/core.rb +0 -218
  62. data/lib/smooth_operator/http_handlers/typhoeus/base.rb +0 -58
  63. data/lib/smooth_operator/http_handlers/typhoeus/orm.rb +0 -34
  64. data/lib/smooth_operator/http_handlers/typhoeus/remote_call.rb +0 -28
  65. data/lib/smooth_operator/operator/base.rb +0 -43
  66. data/lib/smooth_operator/operator/exceptions.rb +0 -64
  67. data/lib/smooth_operator/operator/orm.rb +0 -118
  68. data/lib/smooth_operator/operator/remote_call.rb +0 -84
@@ -0,0 +1,424 @@
1
+ require "spec_helper"
2
+
3
+ shared_examples_for "successful persistent remote call" do
4
+ it "it should populate 'internal_data' with the server's response" do
5
+ execute_method
6
+ expect(subject.server_response).to be true
7
+ end
8
+
9
+ it "it should populate 'last_remote_call' with the remote_call used on this transaction" do
10
+ expect(subject.last_remote_call).to be_nil unless method_to_execute.to_s =~ /create/
11
+ execute_method
12
+ expect(subject.last_remote_call).to be_a_kind_of(SmoothOperator::RemoteCall::Base)
13
+ end
14
+ end
15
+
16
+ shared_examples_for "persistent remote call" do
17
+ it "should send the extra params set by .query_string method" do
18
+ execute_method
19
+ expect(subject.last_remote_call.parsed_response['query_params']).to be true
20
+ end
21
+
22
+ context "when the response is positive" do
23
+ let(:method_arguments) { ['', { status: 200 }] }
24
+
25
+ it "it should return true" do
26
+ execute_method
27
+ expect(subject.last_remote_call.ok?).to be true
28
+ expect(subject.last_remote_call.status).to be true
29
+ end
30
+
31
+ it "it should assert the subject's persistence" do
32
+ execute_method
33
+ expect(subject.persisted?).to be(persistence_state[200])
34
+ end
35
+
36
+ it_behaves_like "successful persistent remote call"
37
+ end
38
+
39
+ context "when the response is unprocessed_entity" do
40
+ let(:method_arguments) { ['', { status: 422 }] }
41
+
42
+ it "it should return false" do
43
+ execute_method
44
+ expect(subject.last_remote_call.not_processed?).to be true
45
+ expect(subject.last_remote_call.status).to be false
46
+ end
47
+
48
+ it "it should assert the subject's persistence" do
49
+ execute_method
50
+ expect(subject.persisted?).to be(persistence_state[422])
51
+ end
52
+
53
+ it_behaves_like "successful persistent remote call"
54
+ end
55
+
56
+ context "when the response is client side error" do
57
+ let(:method_arguments) { ['', { status: 404 }] }
58
+
59
+ it "it should return nil" do
60
+ execute_method
61
+ expect(subject.last_remote_call.client_error?).to be true
62
+ expect(subject.last_remote_call.error?).to be true
63
+ expect(subject.last_remote_call.status).to be nil
64
+ end
65
+
66
+ it "it should assert the subject's persistence" do
67
+ execute_method
68
+ expect(subject.persisted?).to be(persistence_state[500])
69
+ end
70
+ end
71
+
72
+ context "when the there is a connection error ou http 500" do
73
+ let(:method_arguments) { ['', { status: 500 }] }
74
+
75
+ it "it should return nil" do
76
+ execute_method
77
+ expect(subject.last_remote_call.server_error?).to be true
78
+ expect(subject.last_remote_call.error?).to be true
79
+ expect(subject.last_remote_call.status).to be_nil
80
+ end
81
+
82
+ it "it should NOT alter 'internal_data'" do
83
+ execute_method
84
+ expect(subject.respond_to?(:server_response)).to be(false)
85
+ end
86
+
87
+ it "it should populate 'last_remote_call' with the remote_call used on this transaction" do
88
+ expect(subject.last_remote_call).to be_nil unless method_to_execute.to_s =~ /create/
89
+ execute_method
90
+ expect(subject.last_remote_call).to be_a_kind_of(SmoothOperator::RemoteCall::Base)
91
+ end
92
+
93
+ it "it should assert the subject's persistence" do
94
+ execute_method
95
+ expect(subject.persisted?).to be(persistence_state[500])
96
+ end
97
+ end
98
+ end
99
+
100
+ shared_examples_for "save method" do
101
+ it "it should make a http call with the contents of 'internal_data'" do
102
+ execute_method
103
+ expect(subject.last_remote_call.parsed_response['internal_data_match']).to be true
104
+ end
105
+ end
106
+
107
+ shared_examples_for "a method that calls the #make_the_call method" do
108
+ let(:method_arguments) { ['/custom_path', { "user" => { "first_name" => "nhoJ" }, "extra_params" => 'extra_params' }, { option1: 'option1', option2: 'option2', http_verb: :get, serializable_options: { only: [:first_name] } }] }
109
+
110
+ it "it should pass all arguments to #make_the_call" do
111
+ _method_arguments = method_arguments.dup
112
+ _method_arguments[0] = SmoothOperator::Helpers.remove_initial_slash(_method_arguments[0])
113
+
114
+ expect(subject.class).to receive(:make_the_call).with(:get, *_method_arguments)
115
+
116
+ execute_method
117
+ end
118
+
119
+ it "it should pass all arguments to .make_the_call" do
120
+ _method_arguments = method_arguments.dup
121
+ _method_arguments[0] = SmoothOperator::Helpers.remove_initial_slash(_method_arguments[0])
122
+
123
+ expect(subject.class).to receive(:make_the_call).with(:get, *_method_arguments)
124
+
125
+ execute_method
126
+ end
127
+ end
128
+
129
+
130
+ describe SmoothOperator::Persistence, helpers: :persistence do
131
+
132
+ describe "#reload" do
133
+ subject { new_user }
134
+ let(:method_to_execute) { :reload }
135
+
136
+ context "before calling #reload" do
137
+ it "#has_data_from_server and #from_server should return false" do
138
+ expect(subject.from_server).to be_falsey
139
+ expect(subject.has_data_from_server).to be_falsey
140
+ end
141
+ end
142
+
143
+ context "when subject doesn't has an id" do
144
+ it "it should raise 'UnknownPath'" do
145
+ expect{ subject.reload }.to raise_error 'UnknownPath'
146
+ end
147
+ end
148
+
149
+ context "when subject has an id" do
150
+ before do
151
+ subject.id = 1
152
+ subject.reload
153
+ end
154
+
155
+ it "it should fetch server data" do
156
+ expect(subject.attributes).to eq(attributes_for(:user_with_address_and_posts))
157
+ end
158
+
159
+ it "#has_data_from_server and #from_server should return true" do
160
+ expect(subject.from_server).to be true
161
+ expect(subject.has_data_from_server).to be true
162
+ end
163
+ end
164
+
165
+ context "when calling #reload on a nested object" do
166
+
167
+ context "without the option 'ignore_parent: true'" do
168
+ before do
169
+ subject.id = 1
170
+ subject.reload
171
+ subject.posts.first.reload
172
+ end
173
+
174
+ it "it should fetch server data, with NESTED REST url" do
175
+ expect(subject.posts.first.attributes).to eq({ id: 1, body: 'from_nested_url' })
176
+ end
177
+ end
178
+
179
+ context "with the option 'ignore_parent: true'" do
180
+ before do
181
+ subject.id = 1
182
+ subject.reload
183
+ subject.posts.first.reload(nil, nil, { ignore_parent: true })
184
+ end
185
+
186
+ it "it should fetch server data, with REST url" do
187
+ expect(subject.posts.first.attributes).to eq({ id: 1, body: 'from_resource_url' })
188
+ end
189
+ end
190
+
191
+ end
192
+
193
+ it_behaves_like "a method that calls the #make_the_call method"
194
+ end
195
+
196
+ describe ".create" do
197
+
198
+ subject { created_subject }
199
+ let(:method_arguments) { [] }
200
+
201
+ context "when attributes DON'T contain an ID" do
202
+ let(:method_to_execute) { :create_without_id }
203
+ let(:persistence_state) { { 200 => true, 422 => false, 500 => false } }
204
+
205
+ it_behaves_like "persistent remote call"
206
+
207
+ it "it should make a post http call" do
208
+ execute_method
209
+ expect(subject.last_remote_call.parsed_response['http_verb']).to eq('post')
210
+ end
211
+
212
+ it_behaves_like "save method"
213
+ end
214
+
215
+ context "when attributes contain an ID" do
216
+ let(:method_to_execute) { :create_with_id }
217
+ let(:persistence_state) { { 200 => true, 422 => true, 500 => true } }
218
+
219
+ it_behaves_like "persistent remote call"
220
+
221
+ it "it should make a post http call" do
222
+ execute_method
223
+ expect(subject.last_remote_call.parsed_response['http_verb']).to eq('put')
224
+ end
225
+
226
+ it_behaves_like "save method"
227
+ end
228
+
229
+ end
230
+
231
+ describe "#new_record?" do
232
+ context "when initializing an instance without an id" do
233
+ subject { new_user }
234
+
235
+ it "it should return true" do
236
+ expect(subject.new_record?).to be(true)
237
+ end
238
+ end
239
+
240
+ context "when initializing an instance with an id" do
241
+ subject { existing_user }
242
+
243
+ it "it should return false" do
244
+ expect(subject.new_record?).to be(false)
245
+ end
246
+ end
247
+ end
248
+
249
+ describe "#destroyed?" do
250
+ context "before executing #destroy" do
251
+ subject { existing_user }
252
+
253
+ it "it should return false" do
254
+ expect(subject.destroyed?).to be_falsey
255
+ end
256
+ end
257
+
258
+ context "after a successful execution of #destroy" do
259
+ subject { existing_user }
260
+ before { subject.destroy('', { status: 200 }) }
261
+
262
+ it "it should return true" do
263
+ expect(subject.destroyed?).to be(true)
264
+ end
265
+ end
266
+
267
+ context "after a failed execution of #destroy" do
268
+ subject { existing_user }
269
+ before { subject.destroy('', { status: 422 }) }
270
+
271
+ it "it should return false" do
272
+ expect(subject.destroyed?).to be(false)
273
+ end
274
+ end
275
+
276
+ end
277
+
278
+ describe "#persisted?" do
279
+ context "when initializing an instance without an id" do
280
+ subject { new_user }
281
+
282
+ it "it should return false" do
283
+ expect(subject.persisted?).to be(false)
284
+ end
285
+ end
286
+
287
+ context "when initializing an instance with an id" do
288
+
289
+ context "before destroying the instance" do
290
+ subject { existing_user }
291
+
292
+ it "it should return true" do
293
+ expect(subject.persisted?).to be(true)
294
+ end
295
+ end
296
+
297
+ context "after a successful #destroy" do
298
+ subject { existing_user }
299
+ before { subject.destroy('', { status: 200 }) }
300
+
301
+ it "it should return false" do
302
+ expect(subject.persisted?).to be(false)
303
+ end
304
+ end
305
+
306
+ context "after a failed #destroy" do
307
+ subject { existing_user }
308
+ before { subject.destroy('', { status: 422 }) }
309
+
310
+ it "it should return true" do
311
+ expect(subject.persisted?).to be(true)
312
+ end
313
+ end
314
+ end
315
+ end
316
+
317
+ describe "#save" do
318
+
319
+ let(:method_to_execute) { :save }
320
+ let(:method_arguments) { [] }
321
+
322
+ context "when an instance is NOT persisted" do
323
+ subject { new_user }
324
+ let(:persistence_state) { { 200 => true, 422 => false, 500 => false } }
325
+
326
+ it "it should make a post http call" do
327
+ execute_method
328
+ expect(subject.last_remote_call.parsed_response['http_verb']).to eq('post')
329
+ end
330
+
331
+ it_behaves_like "save method"
332
+
333
+ it_behaves_like "persistent remote call"
334
+
335
+ it_behaves_like "a method that calls the #make_the_call method"
336
+ end
337
+
338
+ context "when an instance IS persisted" do
339
+ context "and it uses 'put' http verb to save" do
340
+ subject { existing_user }
341
+ let(:persistence_state) { { 200 => true, 422 => true, 500 => true } }
342
+
343
+ it "it should make a put http call" do
344
+ execute_method
345
+ expect(subject.last_remote_call.parsed_response['http_verb']).to eq('put')
346
+ end
347
+
348
+ it_behaves_like "save method"
349
+
350
+ it_behaves_like "persistent remote call"
351
+
352
+ it_behaves_like "a method that calls the #make_the_call method"
353
+ end
354
+
355
+ context "and it uses 'patch' http verb to save" do
356
+ subject { existing_user_with_patch }
357
+ let(:persistence_state) { { 200 => true, 422 => true, 500 => true } }
358
+
359
+ it "it should make a patch http call" do
360
+ execute_method
361
+ expect(subject.last_remote_call.parsed_response['http_verb']).to eq('patch')
362
+ end
363
+
364
+ it_behaves_like "save method"
365
+
366
+ it_behaves_like "persistent remote call"
367
+
368
+ it_behaves_like "a method that calls the #make_the_call method"
369
+ end
370
+ end
371
+ end
372
+
373
+ describe "#save!" do
374
+ subject { existing_user }
375
+
376
+ context "when #save return true" do
377
+ it "should return true" do
378
+ expect(subject.save!('', { status: 200 })).to be(true)
379
+ end
380
+ end
381
+
382
+ context "when #save return false or nil" do
383
+ it "should raise an exception" do
384
+ expect { subject.save!('', { status: 500 }) }.to raise_error
385
+ end
386
+ end
387
+ end
388
+
389
+ describe "#destroy" do
390
+
391
+ let(:method_to_execute) { :destroy }
392
+ let(:method_arguments) { [] }
393
+
394
+ context "when an instance is not persisted" do
395
+ subject { new_user }
396
+
397
+ it "it should return false" do
398
+ expect(execute_method).to be_falsey
399
+ end
400
+
401
+ it "it NOT should make a delete http call" do
402
+ expect(subject.last_remote_call).to be_nil
403
+ execute_method
404
+ expect(subject.last_remote_call).to be_nil
405
+ end
406
+ end
407
+
408
+ context "when an instance is persisted" do
409
+ subject { existing_user }
410
+ let(:persistence_state) { { 200 => false, 422 => true, 500 => true } }
411
+
412
+ it "it should make a delete http call" do
413
+ execute_method
414
+ expect(subject.last_remote_call.parsed_response['http_verb']).to eq('delete')
415
+ end
416
+
417
+ it_behaves_like "persistent remote call"
418
+
419
+ it_behaves_like "a method that calls the #make_the_call method"
420
+ end
421
+
422
+ end
423
+
424
+ end