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,384 @@
1
+ # encoding: utf-8
2
+
3
+ require File.join(File.dirname(__FILE__), "../spec_helper.rb")
4
+
5
+ describe Him::Model::Attributes do
6
+ context "mapping data to Ruby objects" do
7
+ before { spawn_model "Foo::User" }
8
+
9
+ it "handles new resource" do
10
+ @new_user = Foo::User.new(fullname: "Tobias Fünke")
11
+ expect(@new_user.new?).to be_truthy
12
+ expect(@new_user.fullname).to eq("Tobias Fünke")
13
+ end
14
+
15
+ it "handles new resource with block" do
16
+ @new_user = Foo::User.new do |user|
17
+ user.fullname = "Tobias Fünke"
18
+ end
19
+ expect(@new_user.new?).to be_truthy
20
+ expect(@new_user.fullname).to eq("Tobias Fünke")
21
+ end
22
+
23
+ it "accepts new resource with strings as hash keys" do
24
+ @new_user = Foo::User.new("fullname" => "Tobias Fünke")
25
+ expect(@new_user.fullname).to eq("Tobias Fünke")
26
+ end
27
+
28
+ it "handles method missing for getter" do
29
+ @new_user = Foo::User.new(fullname: "Mayonegg")
30
+ expect { @new_user.unknown_method_for_a_user }.to raise_error(NoMethodError)
31
+ expect { @new_user.fullname }.not_to raise_error
32
+ end
33
+
34
+ it "handles method missing for setter" do
35
+ @new_user = Foo::User.new
36
+ expect { @new_user.fullname = "Tobias Fünke" }.not_to raise_error
37
+ end
38
+
39
+ it "handles method missing for query" do
40
+ @new_user = Foo::User.new
41
+ expect { @new_user.fullname? }.not_to raise_error
42
+ end
43
+
44
+ it "handles respond_to for getter" do
45
+ @new_user = Foo::User.new(fullname: "Mayonegg")
46
+ expect(@new_user).not_to respond_to(:unknown_method_for_a_user)
47
+ expect(@new_user).to respond_to(:fullname)
48
+ end
49
+
50
+ it "handles respond_to for setter" do
51
+ @new_user = Foo::User.new
52
+ expect(@new_user).to respond_to(:fullname=)
53
+ end
54
+
55
+ it "handles respond_to for query" do
56
+ @new_user = Foo::User.new
57
+ expect(@new_user).to respond_to(:fullname?)
58
+ end
59
+
60
+ it "handles has_attribute? for getter" do
61
+ @new_user = Foo::User.new(fullname: "Mayonegg")
62
+ expect(@new_user).not_to have_attribute(:unknown_method_for_a_user)
63
+ expect(@new_user).to have_attribute(:fullname)
64
+ end
65
+
66
+ it "handles get_attribute for getter" do
67
+ @new_user = Foo::User.new(fullname: "Mayonegg")
68
+ expect(@new_user.get_attribute(:unknown_method_for_a_user)).to be_nil
69
+ expect(@new_user.get_attribute(:fullname)).to eq("Mayonegg")
70
+ end
71
+
72
+ it "handles get_attribute for getter with dash" do
73
+ @new_user = Foo::User.new(:'life-span' => "3 years")
74
+ expect(@new_user.get_attribute(:unknown_method_for_a_user)).to be_nil
75
+ expect(@new_user.get_attribute(:'life-span')).to eq("3 years")
76
+ end
77
+
78
+ it "handles attribute_changed_in_place?" do
79
+ @new_user = Foo::User.new
80
+ @new_user.fullname = "Schmoo"
81
+ expect(@new_user.attribute_changed_in_place?(:fullname)).to be_truthy
82
+ end
83
+ end
84
+
85
+ context "assigning new resource data" do
86
+ before do
87
+ spawn_model "Foo::User"
88
+ @user = Foo::User.new(active: false)
89
+ end
90
+
91
+ it "handles data update through #assign_attributes" do
92
+ @user.assign_attributes active: true
93
+ expect(@user).to be_active
94
+ end
95
+
96
+ it "expects to receive hash" do
97
+ expect { @user.assign_attributes(1) }.to raise_error(ArgumentError, 'When assigning attributes, you must pass a hash as an argument.')
98
+ end
99
+ end
100
+
101
+ context "checking resource equality" do
102
+ before do
103
+ Him::API.setup url: "https://api.example.com" do |builder|
104
+ builder.use Him::Middleware::FirstLevelParseJSON
105
+ builder.use Faraday::Request::UrlEncoded
106
+ builder.adapter :test do |stub|
107
+ stub.get("/users/1") { [200, {}, { id: 1, fullname: "Lindsay Fünke" }.to_json] }
108
+ stub.get("/users/2") { [200, {}, { id: 1, fullname: "Tobias Fünke" }.to_json] }
109
+ stub.get("/admins/1") { [200, {}, { id: 1, fullname: "Lindsay Fünke" }.to_json] }
110
+ end
111
+ end
112
+
113
+ spawn_model "Foo::User"
114
+ spawn_model "Foo::Admin"
115
+ end
116
+
117
+ let(:user) { Foo::User.find(1) }
118
+
119
+ it "returns true for the exact same object" do
120
+ expect(user).to eq(user)
121
+ end
122
+
123
+ it "returns true for the same resource via find" do
124
+ expect(user).to eq(Foo::User.find(1))
125
+ end
126
+
127
+ it "returns true for the same class with identical data" do
128
+ expect(user).to eq(Foo::User.new(id: 1, fullname: "Lindsay Fünke"))
129
+ end
130
+
131
+ it "returns true for a different resource with the same data" do
132
+ expect(user).to eq(Foo::Admin.find(1))
133
+ end
134
+
135
+ it "returns false for the same class with different data" do
136
+ expect(user).not_to eq(Foo::User.new(id: 2, fullname: "Tobias Fünke"))
137
+ end
138
+
139
+ it "returns false for a non-resource with the same data" do
140
+ fake_user = double(data: { id: 1, fullname: "Lindsay Fünke" })
141
+ expect(user).not_to eq(fake_user)
142
+ end
143
+
144
+ it "delegates eql? to ==" do
145
+ other = Object.new
146
+ expect(user).to receive(:==).with(other).and_return(true)
147
+ expect(user.eql?(other)).to be_truthy
148
+ end
149
+
150
+ it "treats equal resources as equal for Array#uniq" do
151
+ user2 = Foo::User.find(1)
152
+ expect([user, user2].uniq).to eq([user])
153
+ end
154
+
155
+ it "treats equal resources as equal for hash keys" do
156
+ Foo::User.find(1)
157
+ hash = { user => true }
158
+ hash[Foo::User.find(1)] = false
159
+ expect(hash.size).to eq(1)
160
+ expect(hash).to eq(user => false)
161
+ end
162
+ end
163
+
164
+ context "handling metadata and errors" do
165
+ before do
166
+ Him::API.setup url: "https://api.example.com" do |builder|
167
+ builder.use Him::Middleware::FirstLevelParseJSON
168
+ builder.adapter :test do |stub|
169
+ stub.post("/users") { [200, {}, { id: 1, fullname: "Tobias Fünke" }.to_json] }
170
+ end
171
+ end
172
+
173
+ spawn_model "Foo::User" do
174
+ store_response_errors :errors
175
+ store_metadata :my_data
176
+ end
177
+
178
+ @user = Foo::User.new(_errors: %w[Foo Bar], _metadata: { secret: true })
179
+ end
180
+
181
+ it "should return response_errors stored in the method provided by `store_response_errors`" do
182
+ expect(@user.errors).to eq(%w[Foo Bar])
183
+ end
184
+
185
+ it "should remove the default method for errors" do
186
+ expect { @user.response_errors }.to raise_error(NoMethodError)
187
+ end
188
+
189
+ it "should return metadata stored in the method provided by `store_metadata`" do
190
+ expect(@user.my_data).to eq(secret: true)
191
+ end
192
+
193
+ it "should remove the default method for metadata" do
194
+ expect { @user.metadata }.to raise_error(NoMethodError)
195
+ end
196
+
197
+ it "should work with #save" do
198
+ @user.assign_attributes(fullname: "Tobias Fünke")
199
+ @user.save
200
+ expect { @user.metadata }.to raise_error(NoMethodError)
201
+ expect(@user.my_data).to be_empty
202
+ expect(@user.errors).to be_empty
203
+ end
204
+ end
205
+
206
+ context "overwriting default attribute methods" do
207
+ context "for getter method" do
208
+ before do
209
+ Him::API.setup url: "https://api.example.com" do |builder|
210
+ builder.use Him::Middleware::FirstLevelParseJSON
211
+ builder.adapter :test do |stub|
212
+ stub.get("/users/1") { [200, {}, { id: 1, fullname: "Tobias Fünke", document: { url: "http://example.com" } }.to_json] }
213
+ end
214
+ end
215
+
216
+ spawn_model "Foo::User" do
217
+ def document
218
+ attributes[:document][:url]
219
+ end
220
+ end
221
+ end
222
+
223
+ it "bypasses Her's method" do
224
+ @user = Foo::User.find(1)
225
+ expect(@user.document).to eq("http://example.com")
226
+
227
+ @user = Foo::User.find(1)
228
+ expect(@user.document).to eq("http://example.com")
229
+ end
230
+ end
231
+
232
+ context "for setter method" do
233
+ before do
234
+ Him::API.setup url: "https://api.example.com" do |builder|
235
+ builder.use Him::Middleware::FirstLevelParseJSON
236
+ builder.adapter :test do |stub|
237
+ stub.get("/users/1") { [200, {}, { id: 1, fullname: "Tobias Fünke", document: { url: "http://example.com" } }.to_json] }
238
+ end
239
+ end
240
+
241
+ spawn_model "Foo::User" do
242
+ def document=(document)
243
+ @_her_attributes[:document] = document[:url]
244
+ end
245
+ end
246
+ end
247
+
248
+ it "bypasses Her's method" do
249
+ @user = Foo::User.find(1)
250
+ expect(@user.document).to eq("http://example.com")
251
+
252
+ @user = Foo::User.find(1)
253
+ expect(@user.document).to eq("http://example.com")
254
+ end
255
+
256
+ it "exposes the method to respond_to? and respond_to_without_missing?" do
257
+ @user = Foo::User.find(1)
258
+ expect(@user.respond_to?(:document=)).to be_truthy
259
+ expect(@user.respond_to_without_missing?(:document=)).to be_truthy
260
+ end
261
+
262
+ it "exposes a non-existent method to respond_to? but not respond_to_without_missing?" do
263
+ @user = Foo::User.find(1)
264
+ expect(@user.respond_to?(:nonexistent=)).to be_truthy
265
+ expect(@user.respond_to_without_missing?(:nonexistent=)).to be_falsey
266
+ end
267
+ end
268
+
269
+ context "for predicate method" do
270
+ before do
271
+ Him::API.setup url: "https://api.example.com" do |builder|
272
+ builder.use Him::Middleware::FirstLevelParseJSON
273
+ builder.adapter :test do |stub|
274
+ stub.get("/users/1") { [200, {}, { id: 1, fullname: "Lindsay Fünke", document: { url: nil } }.to_json] }
275
+ stub.get("/users/2") { [200, {}, { id: 1, fullname: "Tobias Fünke", document: { url: "http://example.com" } }.to_json] }
276
+ end
277
+ end
278
+
279
+ spawn_model "Foo::User" do
280
+ def document?
281
+ document[:url].present?
282
+ end
283
+ end
284
+ end
285
+
286
+ it "byoasses Her's method" do
287
+ @user = Foo::User.find(1)
288
+ expect(@user.document?).to be_falsey
289
+
290
+ @user = Foo::User.find(1)
291
+ expect(@user.document?).to be_falsey
292
+
293
+ @user = Foo::User.find(2)
294
+ expect(@user.document?).to be_truthy
295
+ end
296
+ end
297
+ end
298
+
299
+ context "attributes class method" do
300
+ before do
301
+ spawn_model "Foo::User" do
302
+ attributes :fullname, :document
303
+ end
304
+ end
305
+
306
+ context "instance" do
307
+ subject { Foo::User.new }
308
+
309
+ it { is_expected.to respond_to(:fullname) }
310
+ it { is_expected.to respond_to(:fullname=) }
311
+ it { is_expected.to respond_to(:fullname?) }
312
+ end
313
+
314
+ it "defines setter that affects attributes" do
315
+ user = Foo::User.new
316
+ user.fullname = "Tobias Fünke"
317
+ expect(user.attributes[:fullname]).to eq("Tobias Fünke")
318
+ end
319
+
320
+ it "defines getter that reads attributes" do
321
+ user = Foo::User.new
322
+ user.assign_attributes(fullname: "Tobias Fünke")
323
+ expect(user.fullname).to eq("Tobias Fünke")
324
+ end
325
+
326
+ it "defines predicate that reads attributes" do
327
+ user = Foo::User.new
328
+ expect(user.fullname?).to be_falsey
329
+ user.assign_attributes(fullname: "Tobias Fünke")
330
+ expect(user.fullname?).to be_truthy
331
+ end
332
+
333
+ it "does not define a getter for attributes matching reserved words" do
334
+ user = Foo::User.new
335
+ user.assign_attributes(class: "klass")
336
+ expect(user.class.name).to eq("Foo::User")
337
+ end
338
+
339
+ it "does not crash when data includes an 'attributes' key" do
340
+ user = Foo::User.new(attributes: { foo: "bar" })
341
+ expect(user.attributes).to be_a(Hash)
342
+ expect(user.attributes[:attributes]).to eq("foo" => "bar")
343
+ end
344
+
345
+ context "when attribute methods are already defined" do
346
+ before do
347
+ class AbstractUser
348
+
349
+ def fullname
350
+ raise NotImplementedError
351
+ end
352
+
353
+ def fullname=(value)
354
+ raise NotImplementedError
355
+ end
356
+
357
+ def fullname?
358
+ raise NotImplementedError
359
+ end
360
+ end
361
+ @spawned_models << :AbstractUser
362
+
363
+ spawn_model "Foo::User", super_class: AbstractUser do
364
+ attributes :fullname
365
+ end
366
+ end
367
+
368
+ it "overrides getter method" do
369
+ user = Foo::User.new
370
+ expect { user.fullname }.to_not raise_error(NotImplementedError)
371
+ end
372
+
373
+ it "overrides setter method" do
374
+ user = Foo::User.new
375
+ expect { user.fullname = "foo" }.to_not raise_error(NotImplementedError)
376
+ end
377
+
378
+ it "overrides predicate method" do
379
+ user = Foo::User.new
380
+ expect { user.fullname? }.to_not raise_error(NotImplementedError)
381
+ end
382
+ end
383
+ end
384
+ end
@@ -0,0 +1,194 @@
1
+ # encoding: utf-8
2
+
3
+ require File.join(File.dirname(__FILE__), "../spec_helper.rb")
4
+
5
+ describe "Him::Model and ActiveModel::Callbacks" do
6
+ before do
7
+ Him::API.setup url: "https://api.example.com" do |builder|
8
+ builder.use Him::Middleware::FirstLevelParseJSON
9
+ end
10
+ end
11
+
12
+ context :before_save do
13
+ subject { User.create(name: "Tobias Funke") }
14
+ before do
15
+ Him::API.default_api.connection.adapter :test do |stub|
16
+ stub.post("/users") { |env| [200, {}, { id: 1, name: env[:body][:name] }.to_json] }
17
+ stub.put("/users/1") { |env| [200, {}, { id: 1, name: env[:body][:name] }.to_json] }
18
+ end
19
+ end
20
+
21
+ context "when using a symbol callback" do
22
+ before do
23
+ spawn_model "User" do
24
+ before_save :alter_name
25
+ def alter_name
26
+ name.upcase!
27
+ end
28
+ end
29
+ end
30
+
31
+ describe "#name" do
32
+ subject { super().name }
33
+ it { is_expected.to eq("TOBIAS FUNKE") }
34
+ end
35
+ end
36
+
37
+ context "when using a block callback" do
38
+ before do
39
+ spawn_model "User" do
40
+ before_save -> { name.upcase! }
41
+ end
42
+ end
43
+
44
+ describe "#name" do
45
+ subject { super().name }
46
+ it { is_expected.to eq("TOBIAS FUNKE") }
47
+ end
48
+ end
49
+
50
+ context "when changing a value of an existing resource in a callback" do
51
+ before do
52
+ spawn_model "User" do
53
+ before_save :alter_name
54
+ def alter_name
55
+ self.name = "Lumberjack" if persisted?
56
+ end
57
+ end
58
+ end
59
+
60
+ it "should call the server with the changed value" do
61
+ expect(subject.name).to eq("Tobias Funke")
62
+ subject.save
63
+ expect(subject.name).to eq("Lumberjack")
64
+ end
65
+ end
66
+ end
67
+
68
+ context :before_create do
69
+ subject { User.create(name: "Tobias Funke") }
70
+ before do
71
+ Him::API.default_api.connection.adapter :test do |stub|
72
+ stub.post("/users") { |env| [200, {}, { id: 1, name: env[:body][:name] }.to_json] }
73
+ end
74
+ end
75
+
76
+ context "when using a symbol callback" do
77
+ before do
78
+ spawn_model "User" do
79
+ before_create :alter_name
80
+ def alter_name
81
+ name.upcase!
82
+ end
83
+ end
84
+ end
85
+
86
+ describe "#name" do
87
+ subject { super().name }
88
+ it { is_expected.to eq("TOBIAS FUNKE") }
89
+ end
90
+ end
91
+
92
+ context "when using a block callback" do
93
+ before do
94
+ spawn_model "User" do
95
+ before_create -> { name.upcase! }
96
+ end
97
+ end
98
+
99
+ describe "#name" do
100
+ subject { super().name }
101
+ it { is_expected.to eq("TOBIAS FUNKE") }
102
+ end
103
+ end
104
+ end
105
+
106
+ context :after_find do
107
+ subject { User.find(1) }
108
+ before do
109
+ Him::API.default_api.connection.adapter :test do |stub|
110
+ stub.get("/users/1") { [200, {}, { id: 1, name: "Tobias Funke" }.to_json] }
111
+ end
112
+ end
113
+
114
+ context "when using a symbol callback" do
115
+ before do
116
+ spawn_model "User" do
117
+ after_find :alter_name
118
+ def alter_name
119
+ name.upcase!
120
+ end
121
+ end
122
+ end
123
+
124
+ describe "#name" do
125
+ subject { super().name }
126
+ it { is_expected.to eq("TOBIAS FUNKE") }
127
+ end
128
+ end
129
+
130
+ context "callback invocation count" do
131
+ before do
132
+ spawn_model "User" do
133
+ attr_accessor :find_count
134
+ after_find :track_find
135
+ def track_find
136
+ self.find_count ||= 0
137
+ self.find_count += 1
138
+ end
139
+ end
140
+ end
141
+
142
+ it "calls after_find exactly once" do
143
+ user = User.find(1)
144
+ expect(user.find_count).to eq(1)
145
+ end
146
+ end
147
+
148
+ context "when using a block callback" do
149
+ before do
150
+ spawn_model "User" do
151
+ after_find -> { name.upcase! }
152
+ end
153
+ end
154
+
155
+ describe "#name" do
156
+ subject { super().name }
157
+ it { is_expected.to eq("TOBIAS FUNKE") }
158
+ end
159
+ end
160
+ end
161
+
162
+ context :after_initialize do
163
+ subject { User.new(name: "Tobias Funke") }
164
+
165
+ context "when using a symbol callback" do
166
+ before do
167
+ spawn_model "User" do
168
+ after_initialize :alter_name
169
+ def alter_name
170
+ name.upcase!
171
+ end
172
+ end
173
+ end
174
+
175
+ describe "#name" do
176
+ subject { super().name }
177
+ it { is_expected.to eq("TOBIAS FUNKE") }
178
+ end
179
+ end
180
+
181
+ context "when using a block callback" do
182
+ before do
183
+ spawn_model "User" do
184
+ after_initialize -> { name.upcase! }
185
+ end
186
+ end
187
+
188
+ describe "#name" do
189
+ subject { super().name }
190
+ it { is_expected.to eq("TOBIAS FUNKE") }
191
+ end
192
+ end
193
+ end
194
+ end
@@ -0,0 +1,133 @@
1
+ # encoding: utf-8
2
+
3
+ require File.join(File.dirname(__FILE__), "../spec_helper.rb")
4
+
5
+ describe "Him::Model and ActiveModel::Dirty" do
6
+ context "checking dirty attributes" do
7
+ before do
8
+ Him::API.setup url: "https://api.example.com" do |builder|
9
+ builder.use Him::Middleware::FirstLevelParseJSON
10
+ builder.use Faraday::Request::UrlEncoded
11
+ builder.adapter :test do |stub|
12
+ stub.get("/users") { [200, {}, [{ id: 1, fullname: "Lindsay Fünke" }, { id: 2, fullname: "Maeby Fünke" }].to_json] }
13
+ stub.get("/users/1") { [200, {}, { id: 1, fullname: "Lindsay Fünke" }.to_json] }
14
+ stub.get("/users/2") { [200, {}, { id: 2, fullname: "Maeby Fünke" }.to_json] }
15
+ stub.get("/users/3") { [200, {}, { user_id: 3, fullname: "Maeby Fünke" }.to_json] }
16
+ stub.put("/users/1") { [200, {}, { id: 1, fullname: "Tobias Fünke" }.to_json] }
17
+ stub.put("/users/2") { [400, {}, { errors: ["Email cannot be blank"] }.to_json] }
18
+ stub.post("/users") { [200, {}, { id: 1, fullname: "Tobias Fünke" }.to_json] }
19
+ stub.get("/users/1/posts") { [200, {}, [{ id: 1, user_id: 1, body: "Hello" }].to_json] }
20
+ stub.get("/users/1/posts/1") { [200, {}, { id: 1, user_id: 1, body: "Hello" }.to_json] }
21
+ end
22
+ end
23
+
24
+ spawn_model "Foo::Post" do
25
+ belongs_to :user
26
+ attributes :body
27
+ end
28
+ spawn_model "Foo::User" do
29
+ has_many :posts
30
+ attributes :fullname, :email
31
+ end
32
+ spawn_model "Dynamic::User" do
33
+ primary_key :user_id
34
+ end
35
+ end
36
+
37
+ context "for existing resource" do
38
+ let(:user) { Foo::User.find(1) }
39
+ it "has no changes" do
40
+ expect(user.changes).to be_empty
41
+ expect(user).not_to be_changed
42
+ end
43
+ context "with successful save" do
44
+ it "tracks dirty attributes" do
45
+ user.fullname = "Tobias Fünke"
46
+ expect(user.fullname_changed?).to be_truthy
47
+ expect(user.email_changed?).to be_falsey
48
+ expect(user).to be_changed
49
+ user.save
50
+ expect(user).not_to be_changed
51
+ end
52
+
53
+ it "tracks only changed dirty attributes" do
54
+ user.fullname = user.fullname
55
+ expect(user.fullname_changed?).to be_falsey
56
+ end
57
+
58
+ it "tracks previous changes" do
59
+ user.fullname = "Tobias Fünke"
60
+ user.save
61
+ expect(user.previous_changes).to eq("fullname" => ["Lindsay Fünke", "Tobias Fünke"])
62
+ end
63
+
64
+ it "tracks dirty attribute for mass assign for dynamic created attributes" do
65
+ user = Dynamic::User.find(3)
66
+ user.assign_attributes(fullname: "New Fullname")
67
+ expect(user.fullname_changed?).to be_truthy
68
+ expect(user).to be_changed
69
+ expect(user.changes.length).to eq(1)
70
+ end
71
+ end
72
+
73
+ context "with erroneous save" do
74
+ it "tracks dirty attributes" do
75
+ user = Foo::User.find(2)
76
+ user.fullname = "Tobias Fünke"
77
+ expect(user.fullname_changed?).to be_truthy
78
+ expect(user.email_changed?).to be_falsey
79
+ expect(user).to be_changed
80
+ user.save
81
+ expect(user).to be_changed
82
+ end
83
+ end
84
+ end
85
+
86
+ context "for an existing resource from an association" do
87
+ let(:post) { Foo::User.find(1).posts.find(1) }
88
+ it "has no changes" do
89
+ expect(post.changes).to be_empty
90
+ expect(post).to_not be_changed
91
+ end
92
+ end
93
+
94
+ context "for an existing resource from an association collection" do
95
+ let(:post) { Foo::User.find(1).posts.first }
96
+ it "has no changes" do
97
+ expect(post.changes).to be_empty
98
+ expect(post).to_not be_changed
99
+ end
100
+ end
101
+
102
+ context "for resources from a collection" do
103
+ let(:users) { Foo::User.all.fetch }
104
+ it "has no changes" do
105
+ users.each do |user|
106
+ expect(user.changes).to be_empty
107
+ expect(user).to_not be_changed
108
+ end
109
+ end
110
+ end
111
+
112
+ context "for new resource" do
113
+ let(:user) { Foo::User.new(fullname: "Lindsay Fünke") }
114
+ it "has changes" do
115
+ expect(user).to be_changed
116
+ end
117
+ it "tracks dirty attributes" do
118
+ user.fullname = "Tobias Fünke"
119
+ expect(user.fullname_changed?).to be_truthy
120
+ expect(user).to be_changed
121
+ user.save
122
+ expect(user).not_to be_changed
123
+ end
124
+ end
125
+
126
+ context "for a new resource from an association" do
127
+ let(:post) { Foo::User.find(1).posts.build }
128
+ it "has changes" do
129
+ expect(post).to be_changed
130
+ end
131
+ end
132
+ end
133
+ end