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,354 @@
1
+ # encoding: utf-8
2
+
3
+ require File.join(File.dirname(__FILE__), "../spec_helper.rb")
4
+
5
+ describe Restorm::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
+ end
78
+
79
+ context "assigning new resource data" do
80
+ before do
81
+ spawn_model "Foo::User"
82
+ @user = Foo::User.new(active: false)
83
+ end
84
+
85
+ it "handles data update through #assign_attributes" do
86
+ @user.assign_attributes active: true
87
+ expect(@user).to be_active
88
+ end
89
+
90
+ it "expects to receive hash" do
91
+ expect { @user.assign_attributes(1) }.to raise_error(ArgumentError, 'When assigning attributes, you must pass a hash as an argument.')
92
+ end
93
+ end
94
+
95
+ context "checking resource equality" do
96
+ before do
97
+ Restorm::API.setup url: "https://api.example.com" do |builder|
98
+ builder.use Restorm::Middleware::FirstLevelParseJSON
99
+ builder.use Faraday::Request::UrlEncoded
100
+ builder.adapter :test do |stub|
101
+ stub.get("/users/1") { [200, {}, { id: 1, fullname: "Lindsay Fünke" }.to_json] }
102
+ stub.get("/users/2") { [200, {}, { id: 1, fullname: "Tobias Fünke" }.to_json] }
103
+ stub.get("/admins/1") { [200, {}, { id: 1, fullname: "Lindsay Fünke" }.to_json] }
104
+ end
105
+ end
106
+
107
+ spawn_model "Foo::User"
108
+ spawn_model "Foo::Admin"
109
+ end
110
+
111
+ let(:user) { Foo::User.find(1) }
112
+
113
+ it "returns true for the exact same object" do
114
+ expect(user).to eq(user)
115
+ end
116
+
117
+ it "returns true for the same resource via find" do
118
+ expect(user).to eq(Foo::User.find(1))
119
+ end
120
+
121
+ it "returns true for the same class with identical data" do
122
+ expect(user).to eq(Foo::User.new(id: 1, fullname: "Lindsay Fünke"))
123
+ end
124
+
125
+ it "returns true for a different resource with the same data" do
126
+ expect(user).to eq(Foo::Admin.find(1))
127
+ end
128
+
129
+ it "returns false for the same class with different data" do
130
+ expect(user).not_to eq(Foo::User.new(id: 2, fullname: "Tobias Fünke"))
131
+ end
132
+
133
+ it "returns false for a non-resource with the same data" do
134
+ fake_user = double(data: { id: 1, fullname: "Lindsay Fünke" })
135
+ expect(user).not_to eq(fake_user)
136
+ end
137
+
138
+ it "delegates eql? to ==" do
139
+ other = Object.new
140
+ expect(user).to receive(:==).with(other).and_return(true)
141
+ expect(user.eql?(other)).to be_truthy
142
+ end
143
+
144
+ it "treats equal resources as equal for Array#uniq" do
145
+ user2 = Foo::User.find(1)
146
+ expect([user, user2].uniq).to eq([user])
147
+ end
148
+
149
+ it "treats equal resources as equal for hash keys" do
150
+ Foo::User.find(1)
151
+ hash = { user => true }
152
+ hash[Foo::User.find(1)] = false
153
+ expect(hash.size).to eq(1)
154
+ expect(hash).to eq(user => false)
155
+ end
156
+ end
157
+
158
+ context "handling metadata and errors" do
159
+ before do
160
+ Restorm::API.setup url: "https://api.example.com" do |builder|
161
+ builder.use Restorm::Middleware::FirstLevelParseJSON
162
+ builder.adapter :test do |stub|
163
+ stub.post("/users") { [200, {}, { id: 1, fullname: "Tobias Fünke" }.to_json] }
164
+ end
165
+ end
166
+
167
+ spawn_model "Foo::User" do
168
+ store_response_errors :errors
169
+ store_metadata :my_data
170
+ end
171
+
172
+ @user = Foo::User.new(_errors: %w[Foo Bar], _metadata: { secret: true })
173
+ end
174
+
175
+ it "should return response_errors stored in the method provided by `store_response_errors`" do
176
+ expect(@user.errors).to eq(%w[Foo Bar])
177
+ end
178
+
179
+ it "should remove the default method for errors" do
180
+ expect { @user.response_errors }.to raise_error(NoMethodError)
181
+ end
182
+
183
+ it "should return metadata stored in the method provided by `store_metadata`" do
184
+ expect(@user.my_data).to eq(secret: true)
185
+ end
186
+
187
+ it "should remove the default method for metadata" do
188
+ expect { @user.metadata }.to raise_error(NoMethodError)
189
+ end
190
+
191
+ it "should work with #save" do
192
+ @user.assign_attributes(fullname: "Tobias Fünke")
193
+ @user.save
194
+ expect { @user.metadata }.to raise_error(NoMethodError)
195
+ expect(@user.my_data).to be_empty
196
+ expect(@user.errors).to be_empty
197
+ end
198
+ end
199
+
200
+ context "overwriting default attribute methods" do
201
+ context "for getter method" do
202
+ before do
203
+ Restorm::API.setup url: "https://api.example.com" do |builder|
204
+ builder.use Restorm::Middleware::FirstLevelParseJSON
205
+ builder.adapter :test do |stub|
206
+ stub.get("/users/1") { [200, {}, { id: 1, fullname: "Tobias Fünke", document: { url: "http://example.com" } }.to_json] }
207
+ end
208
+ end
209
+
210
+ spawn_model "Foo::User" do
211
+ def document
212
+ attributes[:document][:url]
213
+ end
214
+ end
215
+ end
216
+
217
+ it "bypasses Restorm's method" do
218
+ @user = Foo::User.find(1)
219
+ expect(@user.document).to eq("http://example.com")
220
+
221
+ @user = Foo::User.find(1)
222
+ expect(@user.document).to eq("http://example.com")
223
+ end
224
+ end
225
+
226
+ context "for setter method" do
227
+ before do
228
+ Restorm::API.setup url: "https://api.example.com" do |builder|
229
+ builder.use Restorm::Middleware::FirstLevelParseJSON
230
+ builder.adapter :test do |stub|
231
+ stub.get("/users/1") { [200, {}, { id: 1, fullname: "Tobias Fünke", document: { url: "http://example.com" } }.to_json] }
232
+ end
233
+ end
234
+
235
+ spawn_model "Foo::User" do
236
+ def document=(document)
237
+ @_restorm_attributes[:document] = document[:url]
238
+ end
239
+ end
240
+ end
241
+
242
+ it "bypasses Restorm's method" do
243
+ @user = Foo::User.find(1)
244
+ expect(@user.document).to eq("http://example.com")
245
+
246
+ @user = Foo::User.find(1)
247
+ expect(@user.document).to eq("http://example.com")
248
+ end
249
+ end
250
+
251
+ context "for predicate method" do
252
+ before do
253
+ Restorm::API.setup url: "https://api.example.com" do |builder|
254
+ builder.use Restorm::Middleware::FirstLevelParseJSON
255
+ builder.adapter :test do |stub|
256
+ stub.get("/users/1") { [200, {}, { id: 1, fullname: "Lindsay Fünke", document: { url: nil } }.to_json] }
257
+ stub.get("/users/2") { [200, {}, { id: 1, fullname: "Tobias Fünke", document: { url: "http://example.com" } }.to_json] }
258
+ end
259
+ end
260
+
261
+ spawn_model "Foo::User" do
262
+ def document?
263
+ document[:url].present?
264
+ end
265
+ end
266
+ end
267
+
268
+ it "byoasses Restorm's method" do
269
+ @user = Foo::User.find(1)
270
+ expect(@user.document?).to be_falsey
271
+
272
+ @user = Foo::User.find(1)
273
+ expect(@user.document?).to be_falsey
274
+
275
+ @user = Foo::User.find(2)
276
+ expect(@user.document?).to be_truthy
277
+ end
278
+ end
279
+ end
280
+
281
+ context "attributes class method" do
282
+ before do
283
+ spawn_model "Foo::User" do
284
+ attributes :fullname, :document
285
+ end
286
+ end
287
+
288
+ context "instance" do
289
+ subject { Foo::User.new }
290
+
291
+ it { is_expected.to respond_to(:fullname) }
292
+ it { is_expected.to respond_to(:fullname=) }
293
+ it { is_expected.to respond_to(:fullname?) }
294
+ end
295
+
296
+ it "defines setter that affects attributes" do
297
+ user = Foo::User.new
298
+ user.fullname = "Tobias Fünke"
299
+ expect(user.attributes[:fullname]).to eq("Tobias Fünke")
300
+ end
301
+
302
+ it "defines getter that reads attributes" do
303
+ user = Foo::User.new
304
+ user.assign_attributes(fullname: "Tobias Fünke")
305
+ expect(user.fullname).to eq("Tobias Fünke")
306
+ end
307
+
308
+ it "defines predicate that reads attributes" do
309
+ user = Foo::User.new
310
+ expect(user.fullname?).to be_falsey
311
+ user.assign_attributes(fullname: "Tobias Fünke")
312
+ expect(user.fullname?).to be_truthy
313
+ end
314
+
315
+ context "when attribute methods are already defined" do
316
+ before do
317
+ class AbstractUser
318
+
319
+ def fullname
320
+ raise NotImplementedError
321
+ end
322
+
323
+ def fullname=(value)
324
+ raise NotImplementedError
325
+ end
326
+
327
+ def fullname?
328
+ raise NotImplementedError
329
+ end
330
+ end
331
+ @spawned_models << :AbstractUser
332
+
333
+ spawn_model "Foo::User", super_class: AbstractUser do
334
+ attributes :fullname
335
+ end
336
+ end
337
+
338
+ it "overrides getter method" do
339
+ user = Foo::User.new
340
+ expect { user.fullname }.to_not raise_error(NotImplementedError)
341
+ end
342
+
343
+ it "overrides setter method" do
344
+ user = Foo::User.new
345
+ expect { user.fullname = "foo" }.to_not raise_error(NotImplementedError)
346
+ end
347
+
348
+ it "overrides predicate method" do
349
+ user = Foo::User.new
350
+ expect { user.fullname? }.to_not raise_error(NotImplementedError)
351
+ end
352
+ end
353
+ end
354
+ end
@@ -0,0 +1,176 @@
1
+ # encoding: utf-8
2
+
3
+ require File.join(File.dirname(__FILE__), "../spec_helper.rb")
4
+
5
+ describe "Restorm::Model and ActiveModel::Callbacks" do
6
+ before do
7
+ Restorm::API.setup url: "https://api.example.com" do |builder|
8
+ builder.use Restorm::Middleware::FirstLevelParseJSON
9
+ end
10
+ end
11
+
12
+ context :before_save do
13
+ subject { User.create(name: "Tobias Funke") }
14
+ before do
15
+ Restorm::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
+ Restorm::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
+ Restorm::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 "when using a block callback" do
131
+ before do
132
+ spawn_model "User" do
133
+ after_find -> { name.upcase! }
134
+ end
135
+ end
136
+
137
+ describe "#name" do
138
+ subject { super().name }
139
+ it { is_expected.to eq("TOBIAS FUNKE") }
140
+ end
141
+ end
142
+ end
143
+
144
+ context :after_initialize do
145
+ subject { User.new(name: "Tobias Funke") }
146
+
147
+ context "when using a symbol callback" do
148
+ before do
149
+ spawn_model "User" do
150
+ after_initialize :alter_name
151
+ def alter_name
152
+ name.upcase!
153
+ end
154
+ end
155
+ end
156
+
157
+ describe "#name" do
158
+ subject { super().name }
159
+ it { is_expected.to eq("TOBIAS FUNKE") }
160
+ end
161
+ end
162
+
163
+ context "when using a block callback" do
164
+ before do
165
+ spawn_model "User" do
166
+ after_initialize -> { name.upcase! }
167
+ end
168
+ end
169
+
170
+ describe "#name" do
171
+ subject { super().name }
172
+ it { is_expected.to eq("TOBIAS FUNKE") }
173
+ end
174
+ end
175
+ end
176
+ end
@@ -0,0 +1,133 @@
1
+ # encoding: utf-8
2
+
3
+ require File.join(File.dirname(__FILE__), "../spec_helper.rb")
4
+
5
+ describe "Restorm::Model and ActiveModel::Dirty" do
6
+ context "checking dirty attributes" do
7
+ before do
8
+ Restorm::API.setup url: "https://api.example.com" do |builder|
9
+ builder.use Restorm::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