mongoid-history 0.4.1 → 0.4.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,120 @@
1
+ require 'spec_helper'
2
+
3
+ describe Mongoid::History::Tracker do
4
+ before :all do
5
+ class RealState
6
+ include Mongoid::Document
7
+ include Mongoid::History::Trackable
8
+
9
+ field :name, type: String
10
+ belongs_to :user
11
+ embeds_one :address, class_name: 'Contact', as: :contactable
12
+ embeds_one :embone, as: :embedable
13
+
14
+ track_history on: :all, # track title and body fields only, default is :all
15
+ modifier_field: :modifier, # adds "referenced_in :modifier" to track who made the change, default is :modifier
16
+ version_field: :version, # adds "field :version, :type => Integer" to track current version, default is :version
17
+ track_create: true, # track document creation, default is false
18
+ track_update: true, # track document updates, default is true
19
+ track_destroy: false # track document destruction, default is false
20
+ end
21
+
22
+ class Company
23
+ include Mongoid::Document
24
+ include Mongoid::History::Trackable
25
+
26
+ field :name
27
+ belongs_to :user
28
+ embeds_one :address, class_name: 'Contact', as: :contactable
29
+ embeds_one :second_address, class_name: 'Contact', as: :contactable
30
+ embeds_one :embone, as: :embedable
31
+
32
+ track_history on: :all, # track title and body fields only, default is :all
33
+ modifier_field: :modifier, # adds "referenced_in :modifier" to track who made the change, default is :modifier
34
+ version_field: :version, # adds "field :version, :type => Integer" to track current version, default is :version
35
+ track_create: true, # track document creation, default is false
36
+ track_update: true, # track document updates, default is true
37
+ track_destroy: false # track document destruction, default is false
38
+ end
39
+
40
+ class Embone
41
+ include Mongoid::Document
42
+ include Mongoid::History::Trackable
43
+
44
+ field :name
45
+ embedded_in :embedable, polymorphic: true
46
+
47
+ track_history on: :all, # track title and body fields only, default is :all
48
+ modifier_field: :modifier, # adds "referenced_in :modifier" to track who made the change, default is :modifier
49
+ version_field: :version, # adds "field :version, :type => Integer" to track current version, default is :version
50
+ track_create: true, # track document creation, default is false
51
+ track_update: true, # track document updates, default is true
52
+ track_destroy: false, # track document destruction, default is false
53
+ scope: :embedable
54
+ end
55
+
56
+ class Contact
57
+ include Mongoid::Document
58
+ include Mongoid::History::Trackable
59
+
60
+ field :address
61
+ field :city
62
+ field :state
63
+ embedded_in :contactable, polymorphic: true
64
+
65
+ track_history on: :all, # track title and body fields only, default is :all
66
+ modifier_field: :modifier, # adds "referenced_in :modifier" to track who made the change, default is :modifier
67
+ version_field: :version, # adds "field :version, :type => Integer" to track current version, default is :version
68
+ track_create: true, # track document creation, default is false
69
+ track_update: true, # track document updates, default is true
70
+ track_destroy: false, # track document destruction, default is false
71
+ scope: [:real_state, :company]
72
+ end
73
+
74
+ class User
75
+ include Mongoid::Document
76
+ has_many :companies, dependent: :destroy
77
+ has_many :real_states, dependent: :destroy
78
+ end
79
+ end
80
+
81
+ it "tracks history for nested embedded documents with polymorphic relations" do
82
+ user = User.new
83
+ user.save!
84
+
85
+ real_state = user.real_states.build(name: 'rs_name')
86
+ real_state.save!
87
+ real_state.build_address(address: "Main Street #123", city: "Highland Park", state: 'IL').save!
88
+ expect(real_state.history_tracks.count).to eq(2)
89
+
90
+ real_state.reload
91
+ real_state.address.update_attribute(:address, 'Second Street')
92
+ expect(real_state.history_tracks.count).to eq(3)
93
+ expect(real_state.history_tracks.last.action).to eq('update')
94
+
95
+ real_state.build_embone(name: 'Lorem ipsum').save!
96
+ expect(real_state.history_tracks.count).to eq(4)
97
+ expect(real_state.history_tracks.last.action).to eq('create')
98
+ expect(real_state.history_tracks.last.association_chain.last['name']).to eq('embone')
99
+
100
+ company = user.companies.build(name: 'co_name')
101
+ company.save!
102
+ company.build_address(address: "Main Street #456", city: "Evanston", state: 'IL').save!
103
+ expect(company.history_tracks.count).to eq(2)
104
+
105
+ company.reload
106
+ company.address.update_attribute(:address, 'Second Street')
107
+ expect(company.history_tracks.count).to eq(3)
108
+ expect(company.history_tracks.last.action).to eq('update')
109
+
110
+ company.build_second_address(address: "Main Street #789", city: "Highland Park", state: 'IL').save!
111
+ expect(company.history_tracks.count).to eq(4)
112
+ expect(company.history_tracks.last.action).to eq('create')
113
+ expect(company.history_tracks.last.association_chain.last['name']).to eq('second_address')
114
+
115
+ company.build_embone(name: 'Lorem ipsum').save!
116
+ expect(company.history_tracks.count).to eq(5)
117
+ expect(company.history_tracks.last.action).to eq('create')
118
+ expect(company.history_tracks.last.association_chain.last['name']).to eq('embone')
119
+ end
120
+ end
@@ -1,788 +1,883 @@
1
- require 'spec_helper'
2
-
3
- describe Mongoid::History do
4
- before :all do
5
- class Post
6
- include Mongoid::Document
7
- include Mongoid::Timestamps
8
- include Mongoid::History::Trackable
9
-
10
- field :title
11
- field :body
12
- field :rating
13
- field :views, type: Integer
14
-
15
- embeds_many :comments, store_as: :coms
16
- embeds_one :section, store_as: :sec
17
- embeds_many :tags, cascade_callbacks: true
18
-
19
- accepts_nested_attributes_for :tags, allow_destroy: true
20
-
21
- track_history on: [:title, :body], track_destroy: true
22
- end
23
-
24
- class Comment
25
- include Mongoid::Document
26
- include Mongoid::Timestamps
27
- include Mongoid::History::Trackable
28
-
29
- field :t, as: :title
30
- field :body
31
- embedded_in :commentable, polymorphic: true
32
- track_history on: [:title, :body], scope: :post, track_create: true, track_destroy: true
33
- end
34
-
35
- class Section
36
- include Mongoid::Document
37
- include Mongoid::Timestamps
38
- include Mongoid::History::Trackable
39
-
40
- field :t, as: :title
41
- embedded_in :post
42
- track_history on: [:title], scope: :post, track_create: true, track_destroy: true
43
- end
44
-
45
- class User
46
- include Mongoid::Document
47
- include Mongoid::Timestamps
48
- include Mongoid::History::Trackable
49
-
50
- field :n, as: :name
51
- field :em, as: :email
52
- field :phone
53
- field :address
54
- field :city
55
- field :country
56
- field :aliases, type: Array
57
- track_history except: [:email, :updated_at]
58
- end
59
-
60
- class Tag
61
- include Mongoid::Document
62
- # include Mongoid::Timestamps (see: https://github.com/mongoid/mongoid/issues/3078)
63
- include Mongoid::History::Trackable
64
-
65
- belongs_to :updated_by, class_name: "User"
66
-
67
- field :title
68
- track_history on: [:title], scope: :post, track_create: true, track_destroy: true, modifier_field: :updated_by
69
- end
70
-
71
- class Foo < Comment
72
- end
73
-
74
- @persisted_history_options = Mongoid::History.trackable_class_options
75
- end
76
-
77
- before(:each) { Mongoid::History.trackable_class_options = @persisted_history_options }
78
- let(:user) { User.create(name: "Aaron", email: "aaron@randomemail.com", aliases: ['bob'], country: 'Canada', city: 'Toronto', address: '21 Jump Street') }
79
- let(:another_user) { User.create(name: "Another Guy", email: "anotherguy@randomemail.com") }
80
- let(:post) { Post.create(title: "Test", body: "Post", modifier: user, views: 100) }
81
- let(:comment) { post.comments.create(title: "test", body: "comment", modifier: user) }
82
- let(:tag) { Tag.create(title: "test") }
83
-
84
- describe "track" do
85
- describe "on creation" do
86
- it "should have one history track in comment" do
87
- comment.history_tracks.count.should == 1
88
- end
89
-
90
- it "should assign title and body on modified" do
91
- comment.history_tracks.first.modified.should == { 't' => "test", 'body' => "comment" }
92
- end
93
-
94
- it "should not assign title and body on original" do
95
- comment.history_tracks.first.original.should == {}
96
- end
97
-
98
- it "should assign modifier" do
99
- comment.history_tracks.first.modifier.should == user
100
- end
101
-
102
- it "should assign version" do
103
- comment.history_tracks.first.version.should == 1
104
- end
105
-
106
- it "should assign scope" do
107
- comment.history_tracks.first.scope.should == "post"
108
- end
109
-
110
- it "should assign method" do
111
- comment.history_tracks.first.action.should == "create"
112
- end
113
-
114
- it "should assign association_chain" do
115
- expected = [
116
- { 'id' => post.id, 'name' => "Post" },
117
- { 'id' => comment.id, 'name' => "coms" }
118
- ]
119
- comment.history_tracks.first.association_chain.should == expected
120
- end
121
- end
122
-
123
- describe "on destruction" do
124
- it "should have two history track records in post" do
125
- lambda {
126
- post.destroy
127
- }.should change(Tracker, :count).by(1)
128
- end
129
-
130
- it "should assign destroy on track record" do
131
- post.destroy
132
- post.history_tracks.last.action.should == "destroy"
133
- end
134
-
135
- it "should return affected attributes from track record" do
136
- post.destroy
137
- post.history_tracks.last.affected["title"].should == "Test"
138
- end
139
- end
140
-
141
- describe "on update non-embedded" do
142
- it "should create a history track if changed attributes match tracked attributes" do
143
- lambda {
144
- post.update_attributes(title: "Another Test")
145
- }.should change(Tracker, :count).by(1)
146
- end
147
-
148
- it "should not create a history track if changed attributes do not match tracked attributes" do
149
- lambda {
150
- post.update_attributes(rating: "untracked")
151
- }.should change(Tracker, :count).by(0)
152
- end
153
-
154
- it "should assign modified fields" do
155
- post.update_attributes(title: "Another Test")
156
- post.history_tracks.last.modified.should == {
157
- "title" => "Another Test"
158
- }
159
- end
160
-
161
- it "should assign method field" do
162
- post.update_attributes(title: "Another Test")
163
- post.history_tracks.last.action.should == "update"
164
- end
165
-
166
- it "should assign original fields" do
167
- post.update_attributes(title: "Another Test")
168
- post.history_tracks.last.original.should == {
169
- "title" => "Test"
170
- }
171
- end
172
-
173
- it "should assign modifier" do
174
- post.update_attributes(title: "Another Test")
175
- post.history_tracks.first.modifier.should == user
176
- end
177
-
178
- it "should assign version on history tracks" do
179
- post.update_attributes(title: "Another Test")
180
- post.history_tracks.first.version.should == 1
181
- end
182
-
183
- it "should assign version on post" do
184
- post.update_attributes(title: "Another Test")
185
- post.version.should == 1
186
- end
187
-
188
- it "should assign scope" do
189
- post.update_attributes(title: "Another Test")
190
- post.history_tracks.first.scope.should == "post"
191
- end
192
-
193
- it "should assign association_chain" do
194
- post.update_attributes(title: "Another Test")
195
- post.history_tracks.last.association_chain.should == [{ 'id' => post.id, 'name' => "Post" }]
196
- end
197
-
198
- it "should exclude defined options" do
199
- name = user.name
200
- user.update_attributes(name: "Aaron2", email: "aaronsnewemail@randomemail.com")
201
- user.history_tracks.first.original.keys.should == ["n"]
202
- user.history_tracks.first.original["n"].should == name
203
- user.history_tracks.first.modified.keys.should == ["n"]
204
- user.history_tracks.first.modified["n"].should == user.name
205
- end
206
-
207
- it "should undo field changes" do
208
- name = user.name
209
- user.update_attributes(name: "Aaron2", email: "aaronsnewemail@randomemail.com")
210
- user.history_tracks.first.undo! nil
211
- user.reload.name.should == name
212
- end
213
-
214
- it "should undo non-existing field changes" do
215
- post = Post.create(modifier: user, views: 100)
216
- post.reload.title.should be_nil
217
- post.update_attributes(title: "Aaron2")
218
- post.reload.title.should == "Aaron2"
219
- post.history_tracks.first.undo! nil
220
- post.reload.title.should be_nil
221
- end
222
-
223
- it "should track array changes" do
224
- aliases = user.aliases
225
- user.update_attributes(aliases: ['bob', 'joe'])
226
- user.history_tracks.first.original["aliases"].should == aliases
227
- user.history_tracks.first.modified["aliases"].should == user.aliases
228
- end
229
-
230
- it "should undo array changes" do
231
- aliases = user.aliases
232
- user.update_attributes(aliases: ['bob', 'joe'])
233
- user.history_tracks.first.undo! nil
234
- user.reload.aliases.should == aliases
235
- end
236
- end
237
-
238
- describe "#tracked_changes" do
239
- context "create action" do
240
- subject { tag.history_tracks.first.tracked_changes }
241
- it "consider all fields values as :to" do
242
- subject[:title].should == { to: "test" }.with_indifferent_access
243
- end
244
- end
245
- context "destroy action" do
246
- subject {
247
- tag.destroy
248
- tag.history_tracks.last.tracked_changes
249
- }
250
- it "consider all fields values as :from" do
251
- subject[:title].should == { from: "test" }.with_indifferent_access
252
- end
253
- end
254
- context "update action" do
255
- subject { user.history_tracks.first.tracked_changes }
256
- before do
257
- user.update_attributes(name: "Aaron2", email: nil, country: '', city: nil, phone: '867-5309', aliases: ['', 'bill', 'james'])
258
- end
259
- it { should be_a HashWithIndifferentAccess }
260
- it "should track changed field" do
261
- subject[:n].should == { from: "Aaron", to: "Aaron2" }.with_indifferent_access
262
- end
263
- it "should track added field" do
264
- subject[:phone].should == { to: "867-5309" }.with_indifferent_access
265
- end
266
- it "should track removed field" do
267
- subject[:city].should == { from: "Toronto" }.with_indifferent_access
268
- end
269
- it "should not consider blank as removed" do
270
- subject[:country].should == { from: "Canada", to: '' }.with_indifferent_access
271
- end
272
- it "should track changed array field" do
273
- subject[:aliases].should == { from: ["bob"], to: ["", "bill", "james"] }.with_indifferent_access
274
- end
275
- it "should not track unmodified field" do
276
- subject[:address].should be_nil
277
- end
278
- it "should not track untracked fields" do
279
- subject[:email].should be_nil
280
- end
281
- end
282
- end
283
-
284
- describe "#tracked_edits" do
285
- context "create action" do
286
- subject { tag.history_tracks.first.tracked_edits }
287
- it "consider all edits as ;add" do
288
- subject[:add].should == { title: "test" }.with_indifferent_access
289
- end
290
- end
291
- context "destroy action" do
292
- subject {
293
- tag.destroy
294
- tag.history_tracks.last.tracked_edits
295
- }
296
- it "consider all edits as ;remove" do
297
- subject[:remove].should == { title: "test" }.with_indifferent_access
298
- end
299
- end
300
- context "update action" do
301
- subject { user.history_tracks.first.tracked_edits }
302
- before do
303
- user.update_attributes(name: "Aaron2", email: nil, country: '', city: nil, phone: '867-5309', aliases: ['', 'bill', 'james'])
304
- end
305
- it { should be_a HashWithIndifferentAccess }
306
- it "should track changed field" do
307
- subject[:modify].should == { n: { from: "Aaron", to: "Aaron2" } }.with_indifferent_access
308
- end
309
- it "should track added field" do
310
- subject[:add].should == { phone: "867-5309" }.with_indifferent_access
311
- end
312
- it "should track removed field and consider blank as removed" do
313
- subject[:remove].should == { city: "Toronto", country: "Canada" }.with_indifferent_access
314
- end
315
- it "should track changed array field" do
316
- subject[:array].should == { aliases: { remove: ["bob"], add: ["", "bill", "james"] } }.with_indifferent_access
317
- end
318
- it "should not track unmodified field" do
319
- %w(add modify remove array).each do |edit|
320
- subject[edit][:address].should be_nil
321
- end
322
- end
323
- it "should not track untracked fields" do
324
- %w(add modify remove array).each do |edit|
325
- subject[edit][:email].should be_nil
326
- end
327
- end
328
- end
329
- context "with empty values" do
330
- subject { Tracker.new }
331
- it "should skip empty values" do
332
- subject.stub(:tracked_changes) { { name: { to: '', from: [] }, city: { to: 'Toronto', from: '' } } }
333
- subject.tracked_edits.should == { add: { city: "Toronto" } }.with_indifferent_access
334
- end
335
- end
336
- end
337
-
338
- describe "on update non-embedded twice" do
339
- it "should assign version on post" do
340
- post.update_attributes(title: "Test2")
341
- post.update_attributes(title: "Test3")
342
- post.version.should == 2
343
- end
344
-
345
- it "should create a history track if changed attributes match tracked attributes" do
346
- lambda {
347
- post.update_attributes(title: "Test2")
348
- post.update_attributes(title: "Test3")
349
- }.should change(Tracker, :count).by(2)
350
- end
351
-
352
- it "should create a history track of version 2" do
353
- post.update_attributes(title: "Test2")
354
- post.update_attributes(title: "Test3")
355
- post.history_tracks.where(version: 2).first.should_not be_nil
356
- end
357
-
358
- it "should assign modified fields" do
359
- post.update_attributes(title: "Test2")
360
- post.update_attributes(title: "Test3")
361
- post.history_tracks.where(version: 2).first.modified.should == {
362
- "title" => "Test3"
363
- }
364
- end
365
-
366
- it "should assign original fields" do
367
- post.update_attributes(title: "Test2")
368
- post.update_attributes(title: "Test3")
369
- post.history_tracks.where(version: 2).first.original.should == {
370
- "title" => "Test2"
371
- }
372
- end
373
-
374
- it "should assign modifier" do
375
- post.update_attributes(title: "Another Test", modifier: another_user)
376
- post.history_tracks.last.modifier.should == another_user
377
- end
378
- end
379
-
380
- describe "on update embedded 1..N (embeds_many)" do
381
- it "should assign version on comment" do
382
- comment.update_attributes(title: "Test2")
383
- comment.version.should == 2 # first track generated on creation
384
- end
385
-
386
- it "should create a history track of version 2" do
387
- comment.update_attributes(title: "Test2")
388
- comment.history_tracks.where(version: 2).first.should_not be_nil
389
- end
390
-
391
- it "should assign modified fields" do
392
- comment.update_attributes(t: "Test2")
393
- comment.history_tracks.where(version: 2).first.modified.should == {
394
- "t" => "Test2"
395
- }
396
- end
397
-
398
- it "should assign original fields" do
399
- comment.update_attributes(title: "Test2")
400
- comment.history_tracks.where(version: 2).first.original.should == {
401
- "t" => "test"
402
- }
403
- end
404
-
405
- it "should be possible to undo from parent" do
406
- comment.update_attributes(title: "Test 2")
407
- user
408
- post.history_tracks.last.undo!(user)
409
- comment.reload
410
- comment.title.should == "test"
411
- end
412
-
413
- it "should assign modifier" do
414
- post.update_attributes(title: "Another Test", modifier: another_user)
415
- post.history_tracks.last.modifier.should == another_user
416
- end
417
- end
418
-
419
- describe "on update embedded 1..1 (embeds_one)" do
420
- let(:section) { Section.new(title: 'Technology') }
421
-
422
- before(:each) do
423
- post.section = section
424
- post.save!
425
- post.reload
426
- post.section
427
- end
428
-
429
- it "should assign version on create section" do
430
- section.version.should == 1
431
- end
432
-
433
- it "should assign version on section" do
434
- section.update_attributes(title: 'Technology 2')
435
- section.version.should == 2 # first track generated on creation
436
- end
437
-
438
- it "should create a history track of version 2" do
439
- section.update_attributes(title: 'Technology 2')
440
- section.history_tracks.where(version: 2).first.should_not be_nil
441
- end
442
-
443
- it "should assign modified fields" do
444
- section.update_attributes(title: 'Technology 2')
445
- section.history_tracks.where(version: 2).first.modified.should == {
446
- "t" => "Technology 2"
447
- }
448
- end
449
-
450
- it "should assign original fields" do
451
- section.update_attributes(title: 'Technology 2')
452
- section.history_tracks.where(version: 2).first.original.should == {
453
- "t" => "Technology"
454
- }
455
- end
456
-
457
- it "should be possible to undo from parent" do
458
- section.update_attributes(title: 'Technology 2')
459
- post.history_tracks.last.undo!(user)
460
- section.reload
461
- section.title.should == "Technology"
462
- end
463
-
464
- it "should assign modifier" do
465
- section.update_attributes(title: "Business", modifier: another_user)
466
- post.history_tracks.last.modifier.should == another_user
467
- end
468
- end
469
-
470
- describe "on destroy embedded" do
471
- it "should be possible to re-create destroyed embedded" do
472
- comment.destroy
473
- comment.history_tracks.last.undo!(user)
474
- post.reload
475
- post.comments.first.title.should == "test"
476
- end
477
-
478
- it "should be possible to re-create destroyed embedded from parent" do
479
- comment.destroy
480
- post.history_tracks.last.undo!(user)
481
- post.reload
482
- post.comments.first.title.should == "test"
483
- end
484
-
485
- it "should be possible to destroy after re-create embedded from parent" do
486
- comment.destroy
487
- post.history_tracks.last.undo!(user)
488
- post.history_tracks.last.undo!(user)
489
- post.reload
490
- post.comments.count.should == 0
491
- end
492
-
493
- it "should be possible to create with redo after undo create embedded from parent" do
494
- comment # initialize
495
- post.comments.create!(title: "The second one")
496
- track = post.history_tracks.last
497
- track.undo!(user)
498
- track.redo!(user)
499
- post.reload
500
- post.comments.count.should == 2
501
- end
502
- end
503
-
504
- describe "embedded with cascading callbacks" do
505
-
506
- let(:tag_foo) { post.tags.create(title: "foo", updated_by: user) }
507
- let(:tag_bar) { post.tags.create(title: "bar") }
508
-
509
- # it "should have cascaded the creation callbacks and set timestamps" do
510
- # tag_foo; tag_bar # initialize
511
- # tag_foo.created_at.should_not be_nil
512
- # tag_foo.updated_at.should_not be_nil
513
- # end
514
-
515
- it "should allow an update through the parent model" do
516
- update_hash = { "post" => { "tags_attributes" => { "1234" => { "id" => tag_bar.id, "title" => "baz" } } } }
517
- post.update_attributes(update_hash["post"])
518
- post.tags.last.title.should == "baz"
519
- end
520
-
521
- it "should be possible to destroy through parent model using canoncial _destroy macro" do
522
- tag_foo
523
- tag_bar # initialize
524
- post.tags.count.should == 2
525
- update_hash = { "post" => { "tags_attributes" => { "1234" => { "id" => tag_bar.id, "title" => "baz", "_destroy" => "true" } } } }
526
- post.update_attributes(update_hash["post"])
527
- post.tags.count.should == 1
528
- post.history_tracks.to_a.last.action.should == "destroy"
529
- end
530
-
531
- it "should write relationship name for association_chain hiearchy instead of class name when using _destroy macro" do
532
- update_hash = { "tags_attributes" => { "1234" => { "id" => tag_foo.id, "_destroy" => "1" } } }
533
- post.update_attributes(update_hash)
534
-
535
- # historically this would have evaluated to 'Tags' and an error would be thrown
536
- # on any call that walked up the association_chain, e.g. 'trackable'
537
- tag_foo.history_tracks.last.association_chain.last["name"].should == "tags"
538
- lambda { tag_foo.history_tracks.last.trackable }.should_not raise_error
539
- end
540
- end
541
-
542
- describe "non-embedded" do
543
- it "should undo changes" do
544
- post.update_attributes(title: "Test2")
545
- post.history_tracks.where(version: 1).last.undo!(user)
546
- post.reload
547
- post.title.should == "Test"
548
- end
549
-
550
- it "should undo destruction" do
551
- post.destroy
552
- post.history_tracks.where(version: 1).last.undo!(user)
553
- Post.find(post.id).title.should == "Test"
554
- end
555
-
556
- it "should create a new history track after undo" do
557
- comment # initialize
558
- post.update_attributes(title: "Test2")
559
- post.history_tracks.last.undo!(user)
560
- post.reload
561
- post.history_tracks.count.should == 3
562
- end
563
-
564
- it "should assign user as the modifier of the newly created history track" do
565
- post.update_attributes(title: "Test2")
566
- post.history_tracks.where(version: 1).last.undo!(user)
567
- post.reload
568
- post.history_tracks.where(version: 2).last.modifier.should == user
569
- end
570
-
571
- it "should stay the same after undo and redo" do
572
- post.update_attributes(title: "Test2")
573
- track = post.history_tracks.last
574
- track.undo!(user)
575
- track.redo!(user)
576
- post2 = Post.where(_id: post.id).first
577
-
578
- post.title.should == post2.title
579
- end
580
-
581
- it "should be destroyed after undo and redo" do
582
- post.destroy
583
- track = post.history_tracks.where(version: 1).last
584
- track.undo!(user)
585
- track.redo!(user)
586
- Post.where(_id: post.id).first.should be_nil
587
- end
588
- end
589
-
590
- describe "embedded" do
591
- it "should undo changes" do
592
- comment.update_attributes(title: "Test2")
593
- comment.history_tracks.where(version: 2).first.undo!(user)
594
- comment.reload
595
- comment.title.should == "test"
596
- end
597
-
598
- it "should create a new history track after undo" do
599
- comment.update_attributes(title: "Test2")
600
- comment.history_tracks.where(version: 2).first.undo!(user)
601
- comment.reload
602
- comment.history_tracks.count.should == 3
603
- end
604
-
605
- it "should assign user as the modifier of the newly created history track" do
606
- comment.update_attributes(title: "Test2")
607
- comment.history_tracks.where(version: 2).first.undo!(user)
608
- comment.reload
609
- comment.history_tracks.where(version: 3).first.modifier.should == user
610
- end
611
-
612
- it "should stay the same after undo and redo" do
613
- comment.update_attributes(title: "Test2")
614
- track = comment.history_tracks.where(version: 2).first
615
- track.undo!(user)
616
- track.redo!(user)
617
- comment.reload
618
- comment.title.should == "Test2"
619
- end
620
- end
621
-
622
- describe "trackables" do
623
- before :each do
624
- comment.update_attributes(title: "Test2") # version == 2
625
- comment.update_attributes(title: "Test3") # version == 3
626
- comment.update_attributes(title: "Test4") # version == 4
627
- end
628
-
629
- describe "undo" do
630
- it "should recognize :from, :to options" do
631
- comment.undo! user, from: 4, to: 2
632
- comment.title.should == "test"
633
- end
634
-
635
- it "should recognize parameter as version number" do
636
- comment.undo! user, 3
637
- comment.title.should == "Test2"
638
- end
639
-
640
- it "should undo last version when no parameter is specified" do
641
- comment.undo! user
642
- comment.title.should == "Test3"
643
- end
644
-
645
- it "should recognize :last options" do
646
- comment.undo! user, last: 2
647
- comment.title.should == "Test2"
648
- end
649
-
650
- context "protected attributes" do
651
- before :each do
652
- Comment.attr_accessible(nil)
653
- end
654
-
655
- after :each do
656
- Comment.attr_protected(nil)
657
- end
658
-
659
- it "should undo last version when no parameter is specified on protected attributes" do
660
- comment.undo! user
661
- comment.title.should == "Test3"
662
- end
663
-
664
- it "should recognize :last options on model with protected attributes" do
665
- comment.undo! user, last: 2
666
- comment.title.should == "Test2"
667
- end
668
- end
669
- end
670
-
671
- describe "redo" do
672
- before :each do
673
- comment.update_attributes(title: "Test5")
674
- end
675
-
676
- it "should recognize :from, :to options" do
677
- comment.redo! user, from: 2, to: 4
678
- comment.title.should == "Test4"
679
- end
680
-
681
- it "should recognize parameter as version number" do
682
- comment.redo! user, 2
683
- comment.title.should == "Test2"
684
- end
685
-
686
- it "should redo last version when no parameter is specified" do
687
- comment.redo! user
688
- comment.title.should == "Test5"
689
- end
690
-
691
- it "should recognize :last options" do
692
- comment.redo! user, last: 1
693
- comment.title.should == "Test5"
694
- end
695
-
696
- context "protected attributes" do
697
- before :each do
698
- Comment.attr_accessible(nil)
699
- end
700
-
701
- after :each do
702
- Comment.attr_protected(nil)
703
- end
704
-
705
- it "should recognize parameter as version number" do
706
- comment.redo! user, 2
707
- comment.title.should == "Test2"
708
- end
709
-
710
- it "should recognize :from, :to options" do
711
- comment.redo! user, from: 2, to: 4
712
- comment.title.should == "Test4"
713
- end
714
- end
715
-
716
- end
717
- end
718
-
719
- describe "localized fields" do
720
- before :each do
721
- class Sausage
722
- include Mongoid::Document
723
- include Mongoid::History::Trackable
724
-
725
- field :flavour, localize: true
726
- track_history on: [:flavour], track_destroy: true
727
- end
728
- end
729
- it "should correctly undo and redo" do
730
- if Sausage.respond_to?(:localized_fields)
731
- sausage = Sausage.create(flavour_translations: { 'en' => "Apple", 'nl' => 'Appel' })
732
- sausage.update_attributes(flavour: "Guinness")
733
-
734
- track = sausage.history_tracks.last
735
-
736
- track.undo! user
737
- sausage.reload.flavour.should == "Apple"
738
-
739
- track.redo! user
740
- sausage.reload.flavour.should == "Guinness"
741
-
742
- sausage.destroy
743
- sausage.history_tracks.last.action.should == "destroy"
744
- sausage.history_tracks.last.undo! user
745
- sausage.reload.flavour.should == "Guinness"
746
- end
747
- end
748
- end
749
-
750
- describe "embedded with a polymorphic trackable" do
751
- let(:foo) { Foo.new(title: 'a title', body: 'a body') }
752
- before :each do
753
- post.comments << foo
754
- post.save
755
- end
756
- it "should assign interface name in association chain" do
757
- foo.update_attribute(:body, 'a changed body')
758
- expected_root = { "name" => "Post", "id" => post.id }
759
- expected_node = { "name" => "coms", "id" => foo.id }
760
- foo.history_tracks.first.association_chain.should == [expected_root, expected_node]
761
- end
762
- end
763
-
764
- describe "#trackable_parent_class" do
765
- context "a non-embedded model" do
766
- it "should return the trackable parent class" do
767
- tag.history_tracks.first.trackable_parent_class.should == Tag
768
- end
769
- it "should return the parent class even if the trackable is deleted" do
770
- tracker = tag.history_tracks.first
771
- tag.destroy
772
- tracker.trackable_parent_class.should == Tag
773
- end
774
- end
775
- context "an embedded model" do
776
- it "should return the trackable parent class" do
777
- comment.update_attributes(title: "Foo")
778
- comment.history_tracks.first.trackable_parent_class.should == Post
779
- end
780
- it "should return the parent class even if the trackable is deleted" do
781
- tracker = comment.history_tracks.first
782
- comment.destroy
783
- tracker.trackable_parent_class.should == Post
784
- end
785
- end
786
- end
787
- end
788
- end
1
+ require 'spec_helper'
2
+
3
+ describe Mongoid::History do
4
+ before :all do
5
+ class Post
6
+ include Mongoid::Document
7
+ include Mongoid::Timestamps
8
+ include Mongoid::History::Trackable
9
+
10
+ field :title
11
+ field :body
12
+ field :rating
13
+ field :views, type: Integer
14
+
15
+ embeds_many :comments, store_as: :coms
16
+ embeds_one :section, store_as: :sec
17
+ embeds_many :tags, cascade_callbacks: true
18
+
19
+ accepts_nested_attributes_for :tags, allow_destroy: true
20
+
21
+ track_history on: [:title, :body], track_destroy: true
22
+ end
23
+
24
+ class Comment
25
+ include Mongoid::Document
26
+ include Mongoid::Timestamps
27
+ include Mongoid::History::Trackable
28
+
29
+ field :t, as: :title
30
+ field :body
31
+ embedded_in :commentable, polymorphic: true
32
+ track_history on: [:title, :body], scope: :post, track_create: true, track_destroy: true
33
+ end
34
+
35
+ class Section
36
+ include Mongoid::Document
37
+ include Mongoid::Timestamps
38
+ include Mongoid::History::Trackable
39
+
40
+ field :t, as: :title
41
+ embedded_in :post
42
+ track_history on: [:title], scope: :post, track_create: true, track_destroy: true
43
+ end
44
+
45
+ class User
46
+ include Mongoid::Document
47
+ include Mongoid::Timestamps
48
+ include Mongoid::History::Trackable
49
+
50
+ field :n, as: :name
51
+ field :em, as: :email
52
+ field :phone
53
+ field :address
54
+ field :city
55
+ field :country
56
+ field :aliases, type: Array
57
+ track_history except: [:email, :updated_at]
58
+ end
59
+
60
+ class Tag
61
+ include Mongoid::Document
62
+ # include Mongoid::Timestamps (see: https://github.com/mongoid/mongoid/issues/3078)
63
+ include Mongoid::History::Trackable
64
+
65
+ belongs_to :updated_by, class_name: "User"
66
+
67
+ field :title
68
+ track_history on: [:title], scope: :post, track_create: true, track_destroy: true, modifier_field: :updated_by
69
+ end
70
+
71
+ class Foo < Comment
72
+ end
73
+
74
+ @persisted_history_options = Mongoid::History.trackable_class_options
75
+ end
76
+
77
+ before(:each) { Mongoid::History.trackable_class_options = @persisted_history_options }
78
+ let(:user) { User.create(name: "Aaron", email: "aaron@randomemail.com", aliases: ['bob'], country: 'Canada', city: 'Toronto', address: '21 Jump Street') }
79
+ let(:another_user) { User.create(name: "Another Guy", email: "anotherguy@randomemail.com") }
80
+ let(:post) { Post.create(title: "Test", body: "Post", modifier: user, views: 100) }
81
+ let(:comment) { post.comments.create(title: "test", body: "comment", modifier: user) }
82
+ let(:tag) { Tag.create(title: "test") }
83
+
84
+ describe "track" do
85
+ describe "on creation" do
86
+ it "should have one history track in comment" do
87
+ comment.history_tracks.count.should == 1
88
+ end
89
+
90
+ it "should assign title and body on modified" do
91
+ comment.history_tracks.first.modified.should == { 't' => "test", 'body' => "comment" }
92
+ end
93
+
94
+ it "should not assign title and body on original" do
95
+ comment.history_tracks.first.original.should == {}
96
+ end
97
+
98
+ it "should assign modifier" do
99
+ comment.history_tracks.first.modifier.should == user
100
+ end
101
+
102
+ it "should assign version" do
103
+ comment.history_tracks.first.version.should == 1
104
+ end
105
+
106
+ it "should assign scope" do
107
+ comment.history_tracks.first.scope.should == "post"
108
+ end
109
+
110
+ it "should assign method" do
111
+ comment.history_tracks.first.action.should == "create"
112
+ end
113
+
114
+ it "should assign association_chain" do
115
+ expected = [
116
+ { 'id' => post.id, 'name' => "Post" },
117
+ { 'id' => comment.id, 'name' => "coms" }
118
+ ]
119
+ comment.history_tracks.first.association_chain.should == expected
120
+ end
121
+ end
122
+
123
+ describe "on destruction" do
124
+ it "should have two history track records in post" do
125
+ lambda {
126
+ post.destroy
127
+ }.should change(Tracker, :count).by(1)
128
+ end
129
+
130
+ it "should assign destroy on track record" do
131
+ post.destroy
132
+ post.history_tracks.last.action.should == "destroy"
133
+ end
134
+
135
+ it "should return affected attributes from track record" do
136
+ post.destroy
137
+ post.history_tracks.last.affected["title"].should == "Test"
138
+ end
139
+ end
140
+
141
+ describe "on update non-embedded" do
142
+ it "should create a history track if changed attributes match tracked attributes" do
143
+ lambda {
144
+ post.update_attributes(title: "Another Test")
145
+ }.should change(Tracker, :count).by(1)
146
+ end
147
+
148
+ it "should not create a history track if changed attributes do not match tracked attributes" do
149
+ lambda {
150
+ post.update_attributes(rating: "untracked")
151
+ }.should change(Tracker, :count).by(0)
152
+ end
153
+
154
+ it "should assign modified fields" do
155
+ post.update_attributes(title: "Another Test")
156
+ post.history_tracks.last.modified.should == {
157
+ "title" => "Another Test"
158
+ }
159
+ end
160
+
161
+ it "should assign method field" do
162
+ post.update_attributes(title: "Another Test")
163
+ post.history_tracks.last.action.should == "update"
164
+ end
165
+
166
+ it "should assign original fields" do
167
+ post.update_attributes(title: "Another Test")
168
+ post.history_tracks.last.original.should == {
169
+ "title" => "Test"
170
+ }
171
+ end
172
+
173
+ it "should assign modifier" do
174
+ post.update_attributes(title: "Another Test")
175
+ post.history_tracks.first.modifier.should == user
176
+ end
177
+
178
+ it "should assign version on history tracks" do
179
+ post.update_attributes(title: "Another Test")
180
+ post.history_tracks.first.version.should == 1
181
+ end
182
+
183
+ it "should assign version on post" do
184
+ post.update_attributes(title: "Another Test")
185
+ post.version.should == 1
186
+ end
187
+
188
+ it "should assign scope" do
189
+ post.update_attributes(title: "Another Test")
190
+ post.history_tracks.first.scope.should == "post"
191
+ end
192
+
193
+ it "should assign association_chain" do
194
+ post.update_attributes(title: "Another Test")
195
+ post.history_tracks.last.association_chain.should == [{ 'id' => post.id, 'name' => "Post" }]
196
+ end
197
+
198
+ it "should exclude defined options" do
199
+ name = user.name
200
+ user.update_attributes(name: "Aaron2", email: "aaronsnewemail@randomemail.com")
201
+ user.history_tracks.first.original.keys.should == ["n"]
202
+ user.history_tracks.first.original["n"].should == name
203
+ user.history_tracks.first.modified.keys.should == ["n"]
204
+ user.history_tracks.first.modified["n"].should == user.name
205
+ end
206
+
207
+ it "should undo field changes" do
208
+ name = user.name
209
+ user.update_attributes(name: "Aaron2", email: "aaronsnewemail@randomemail.com")
210
+ user.history_tracks.first.undo! nil
211
+ user.reload.name.should == name
212
+ end
213
+
214
+ it "should undo non-existing field changes" do
215
+ post = Post.create(modifier: user, views: 100)
216
+ post.reload.title.should be_nil
217
+ post.update_attributes(title: "Aaron2")
218
+ post.reload.title.should == "Aaron2"
219
+ post.history_tracks.first.undo! nil
220
+ post.reload.title.should be_nil
221
+ end
222
+
223
+ it "should track array changes" do
224
+ aliases = user.aliases
225
+ user.update_attributes(aliases: ['bob', 'joe'])
226
+ user.history_tracks.first.original["aliases"].should == aliases
227
+ user.history_tracks.first.modified["aliases"].should == user.aliases
228
+ end
229
+
230
+ it "should undo array changes" do
231
+ aliases = user.aliases
232
+ user.update_attributes(aliases: ['bob', 'joe'])
233
+ user.history_tracks.first.undo! nil
234
+ user.reload.aliases.should == aliases
235
+ end
236
+ end
237
+
238
+ describe "#tracked_changes" do
239
+ context "create action" do
240
+ subject { tag.history_tracks.first.tracked_changes }
241
+ it "consider all fields values as :to" do
242
+ subject[:title].should == { to: "test" }.with_indifferent_access
243
+ end
244
+ end
245
+ context "destroy action" do
246
+ subject {
247
+ tag.destroy
248
+ tag.history_tracks.last.tracked_changes
249
+ }
250
+ it "consider all fields values as :from" do
251
+ subject[:title].should == { from: "test" }.with_indifferent_access
252
+ end
253
+ end
254
+ context "update action" do
255
+ subject { user.history_tracks.first.tracked_changes }
256
+ before do
257
+ user.update_attributes(name: "Aaron2", email: nil, country: '', city: nil, phone: '867-5309', aliases: ['', 'bill', 'james'])
258
+ end
259
+ it { should be_a HashWithIndifferentAccess }
260
+ it "should track changed field" do
261
+ subject[:n].should == { from: "Aaron", to: "Aaron2" }.with_indifferent_access
262
+ end
263
+ it "should track added field" do
264
+ subject[:phone].should == { to: "867-5309" }.with_indifferent_access
265
+ end
266
+ it "should track removed field" do
267
+ subject[:city].should == { from: "Toronto" }.with_indifferent_access
268
+ end
269
+ it "should not consider blank as removed" do
270
+ subject[:country].should == { from: "Canada", to: '' }.with_indifferent_access
271
+ end
272
+ it "should track changed array field" do
273
+ subject[:aliases].should == { from: ["bob"], to: ["", "bill", "james"] }.with_indifferent_access
274
+ end
275
+ it "should not track unmodified field" do
276
+ subject[:address].should be_nil
277
+ end
278
+ it "should not track untracked fields" do
279
+ subject[:email].should be_nil
280
+ end
281
+ end
282
+ end
283
+
284
+ describe "#tracked_edits" do
285
+ context "create action" do
286
+ subject { tag.history_tracks.first.tracked_edits }
287
+ it "consider all edits as ;add" do
288
+ subject[:add].should == { title: "test" }.with_indifferent_access
289
+ end
290
+ end
291
+ context "destroy action" do
292
+ subject {
293
+ tag.destroy
294
+ tag.history_tracks.last.tracked_edits
295
+ }
296
+ it "consider all edits as ;remove" do
297
+ subject[:remove].should == { title: "test" }.with_indifferent_access
298
+ end
299
+ end
300
+ context "update action" do
301
+ subject { user.history_tracks.first.tracked_edits }
302
+ before do
303
+ user.update_attributes(name: "Aaron2", email: nil, country: '', city: nil, phone: '867-5309', aliases: ['', 'bill', 'james'])
304
+ end
305
+ it { should be_a HashWithIndifferentAccess }
306
+ it "should track changed field" do
307
+ subject[:modify].should == { n: { from: "Aaron", to: "Aaron2" } }.with_indifferent_access
308
+ end
309
+ it "should track added field" do
310
+ subject[:add].should == { phone: "867-5309" }.with_indifferent_access
311
+ end
312
+ it "should track removed field and consider blank as removed" do
313
+ subject[:remove].should == { city: "Toronto", country: "Canada" }.with_indifferent_access
314
+ end
315
+ it "should track changed array field" do
316
+ subject[:array].should == { aliases: { remove: ["bob"], add: ["", "bill", "james"] } }.with_indifferent_access
317
+ end
318
+ it "should not track unmodified field" do
319
+ %w(add modify remove array).each do |edit|
320
+ subject[edit][:address].should be_nil
321
+ end
322
+ end
323
+ it "should not track untracked fields" do
324
+ %w(add modify remove array).each do |edit|
325
+ subject[edit][:email].should be_nil
326
+ end
327
+ end
328
+ end
329
+ context "with empty values" do
330
+ subject { Tracker.new }
331
+ it "should skip empty values" do
332
+ subject.stub(:tracked_changes) { { name: { to: '', from: [] }, city: { to: 'Toronto', from: '' } } }
333
+ subject.tracked_edits.should == { add: { city: "Toronto" } }.with_indifferent_access
334
+ end
335
+ end
336
+ end
337
+
338
+ describe "on update non-embedded twice" do
339
+ it "should assign version on post" do
340
+ post.update_attributes(title: "Test2")
341
+ post.update_attributes(title: "Test3")
342
+ post.version.should == 2
343
+ end
344
+
345
+ it "should create a history track if changed attributes match tracked attributes" do
346
+ lambda {
347
+ post.update_attributes(title: "Test2")
348
+ post.update_attributes(title: "Test3")
349
+ }.should change(Tracker, :count).by(2)
350
+ end
351
+
352
+ it "should create a history track of version 2" do
353
+ post.update_attributes(title: "Test2")
354
+ post.update_attributes(title: "Test3")
355
+ post.history_tracks.where(version: 2).first.should_not be_nil
356
+ end
357
+
358
+ it "should assign modified fields" do
359
+ post.update_attributes(title: "Test2")
360
+ post.update_attributes(title: "Test3")
361
+ post.history_tracks.where(version: 2).first.modified.should == {
362
+ "title" => "Test3"
363
+ }
364
+ end
365
+
366
+ it "should assign original fields" do
367
+ post.update_attributes(title: "Test2")
368
+ post.update_attributes(title: "Test3")
369
+ post.history_tracks.where(version: 2).first.original.should == {
370
+ "title" => "Test2"
371
+ }
372
+ end
373
+
374
+ it "should assign modifier" do
375
+ post.update_attributes(title: "Another Test", modifier: another_user)
376
+ post.history_tracks.last.modifier.should == another_user
377
+ end
378
+ end
379
+
380
+ describe "on update embedded 1..N (embeds_many)" do
381
+ it "should assign version on comment" do
382
+ comment.update_attributes(title: "Test2")
383
+ comment.version.should == 2 # first track generated on creation
384
+ end
385
+
386
+ it "should create a history track of version 2" do
387
+ comment.update_attributes(title: "Test2")
388
+ comment.history_tracks.where(version: 2).first.should_not be_nil
389
+ end
390
+
391
+ it "should assign modified fields" do
392
+ comment.update_attributes(t: "Test2")
393
+ comment.history_tracks.where(version: 2).first.modified.should == {
394
+ "t" => "Test2"
395
+ }
396
+ end
397
+
398
+ it "should assign original fields" do
399
+ comment.update_attributes(title: "Test2")
400
+ comment.history_tracks.where(version: 2).first.original.should == {
401
+ "t" => "test"
402
+ }
403
+ end
404
+
405
+ it "should be possible to undo from parent" do
406
+ comment.update_attributes(title: "Test 2")
407
+ user
408
+ post.history_tracks.last.undo!(user)
409
+ comment.reload
410
+ comment.title.should == "test"
411
+ end
412
+
413
+ it "should assign modifier" do
414
+ post.update_attributes(title: "Another Test", modifier: another_user)
415
+ post.history_tracks.last.modifier.should == another_user
416
+ end
417
+ end
418
+
419
+ describe "on update embedded 1..1 (embeds_one)" do
420
+ let(:section) { Section.new(title: 'Technology') }
421
+
422
+ before(:each) do
423
+ post.section = section
424
+ post.save!
425
+ post.reload
426
+ post.section
427
+ end
428
+
429
+ it "should assign version on create section" do
430
+ section.version.should == 1
431
+ end
432
+
433
+ it "should assign version on section" do
434
+ section.update_attributes(title: 'Technology 2')
435
+ section.version.should == 2 # first track generated on creation
436
+ end
437
+
438
+ it "should create a history track of version 2" do
439
+ section.update_attributes(title: 'Technology 2')
440
+ section.history_tracks.where(version: 2).first.should_not be_nil
441
+ end
442
+
443
+ it "should assign modified fields" do
444
+ section.update_attributes(title: 'Technology 2')
445
+ section.history_tracks.where(version: 2).first.modified.should == {
446
+ "t" => "Technology 2"
447
+ }
448
+ end
449
+
450
+ it "should assign original fields" do
451
+ section.update_attributes(title: 'Technology 2')
452
+ section.history_tracks.where(version: 2).first.original.should == {
453
+ "t" => "Technology"
454
+ }
455
+ end
456
+
457
+ it "should be possible to undo from parent" do
458
+ section.update_attributes(title: 'Technology 2')
459
+ post.history_tracks.last.undo!(user)
460
+ section.reload
461
+ section.title.should == "Technology"
462
+ end
463
+
464
+ it "should assign modifier" do
465
+ section.update_attributes(title: "Business", modifier: another_user)
466
+ post.history_tracks.last.modifier.should == another_user
467
+ end
468
+ end
469
+
470
+ describe "on destroy embedded" do
471
+ it "should be possible to re-create destroyed embedded" do
472
+ comment.destroy
473
+ comment.history_tracks.last.undo!(user)
474
+ post.reload
475
+ post.comments.first.title.should == "test"
476
+ end
477
+
478
+ it "should be possible to re-create destroyed embedded from parent" do
479
+ comment.destroy
480
+ post.history_tracks.last.undo!(user)
481
+ post.reload
482
+ post.comments.first.title.should == "test"
483
+ end
484
+
485
+ it "should be possible to destroy after re-create embedded from parent" do
486
+ comment.destroy
487
+ post.history_tracks.last.undo!(user)
488
+ post.history_tracks.last.undo!(user)
489
+ post.reload
490
+ post.comments.count.should == 0
491
+ end
492
+
493
+ it "should be possible to create with redo after undo create embedded from parent" do
494
+ comment # initialize
495
+ post.comments.create!(title: "The second one")
496
+ track = post.history_tracks.last
497
+ track.undo!(user)
498
+ track.redo!(user)
499
+ post.reload
500
+ post.comments.count.should == 2
501
+ end
502
+ end
503
+
504
+ describe "embedded with cascading callbacks" do
505
+
506
+ let(:tag_foo) { post.tags.create(title: "foo", updated_by: user) }
507
+ let(:tag_bar) { post.tags.create(title: "bar") }
508
+
509
+ # it "should have cascaded the creation callbacks and set timestamps" do
510
+ # tag_foo; tag_bar # initialize
511
+ # tag_foo.created_at.should_not be_nil
512
+ # tag_foo.updated_at.should_not be_nil
513
+ # end
514
+
515
+ it "should allow an update through the parent model" do
516
+ update_hash = { "post" => { "tags_attributes" => { "1234" => { "id" => tag_bar.id, "title" => "baz" } } } }
517
+ post.update_attributes(update_hash["post"])
518
+ post.tags.last.title.should == "baz"
519
+ end
520
+
521
+ it "should be possible to destroy through parent model using canoncial _destroy macro" do
522
+ tag_foo
523
+ tag_bar # initialize
524
+ post.tags.count.should == 2
525
+ update_hash = { "post" => { "tags_attributes" => { "1234" => { "id" => tag_bar.id, "title" => "baz", "_destroy" => "true" } } } }
526
+ post.update_attributes(update_hash["post"])
527
+ post.tags.count.should == 1
528
+ post.history_tracks.to_a.last.action.should == "destroy"
529
+ end
530
+
531
+ it "should write relationship name for association_chain hiearchy instead of class name when using _destroy macro" do
532
+ update_hash = { "tags_attributes" => { "1234" => { "id" => tag_foo.id, "_destroy" => "1" } } }
533
+ post.update_attributes(update_hash)
534
+
535
+ # historically this would have evaluated to 'Tags' and an error would be thrown
536
+ # on any call that walked up the association_chain, e.g. 'trackable'
537
+ tag_foo.history_tracks.last.association_chain.last["name"].should == "tags"
538
+ lambda { tag_foo.history_tracks.last.trackable }.should_not raise_error
539
+ end
540
+ end
541
+
542
+ describe "non-embedded" do
543
+ it "should undo changes" do
544
+ post.update_attributes(title: "Test2")
545
+ post.history_tracks.where(version: 1).last.undo!(user)
546
+ post.reload
547
+ post.title.should == "Test"
548
+ end
549
+
550
+ it "should undo destruction" do
551
+ post.destroy
552
+ post.history_tracks.where(version: 1).last.undo!(user)
553
+ Post.find(post.id).title.should == "Test"
554
+ end
555
+
556
+ it "should create a new history track after undo" do
557
+ comment # initialize
558
+ post.update_attributes(title: "Test2")
559
+ post.history_tracks.last.undo!(user)
560
+ post.reload
561
+ post.history_tracks.count.should == 3
562
+ end
563
+
564
+ it "should assign user as the modifier of the newly created history track" do
565
+ post.update_attributes(title: "Test2")
566
+ post.history_tracks.where(version: 1).last.undo!(user)
567
+ post.reload
568
+ post.history_tracks.where(version: 2).last.modifier.should == user
569
+ end
570
+
571
+ it "should stay the same after undo and redo" do
572
+ post.update_attributes(title: "Test2")
573
+ track = post.history_tracks.last
574
+ track.undo!(user)
575
+ track.redo!(user)
576
+ post2 = Post.where(_id: post.id).first
577
+
578
+ post.title.should == post2.title
579
+ end
580
+
581
+ it "should be destroyed after undo and redo" do
582
+ post.destroy
583
+ track = post.history_tracks.where(version: 1).last
584
+ track.undo!(user)
585
+ track.redo!(user)
586
+ Post.where(_id: post.id).first.should be_nil
587
+ end
588
+ end
589
+
590
+ describe "embedded" do
591
+ it "should undo changes" do
592
+ comment.update_attributes(title: "Test2")
593
+ comment.history_tracks.where(version: 2).first.undo!(user)
594
+ comment.reload
595
+ comment.title.should == "test"
596
+ end
597
+
598
+ it "should create a new history track after undo" do
599
+ comment.update_attributes(title: "Test2")
600
+ comment.history_tracks.where(version: 2).first.undo!(user)
601
+ comment.reload
602
+ comment.history_tracks.count.should == 3
603
+ end
604
+
605
+ it "should assign user as the modifier of the newly created history track" do
606
+ comment.update_attributes(title: "Test2")
607
+ comment.history_tracks.where(version: 2).first.undo!(user)
608
+ comment.reload
609
+ comment.history_tracks.where(version: 3).first.modifier.should == user
610
+ end
611
+
612
+ it "should stay the same after undo and redo" do
613
+ comment.update_attributes(title: "Test2")
614
+ track = comment.history_tracks.where(version: 2).first
615
+ track.undo!(user)
616
+ track.redo!(user)
617
+ comment.reload
618
+ comment.title.should == "Test2"
619
+ end
620
+ end
621
+
622
+ describe "trackables" do
623
+ before :each do
624
+ comment.update_attributes(title: "Test2") # version == 2
625
+ comment.update_attributes(title: "Test3") # version == 3
626
+ comment.update_attributes(title: "Test4") # version == 4
627
+ end
628
+
629
+ describe "undo" do
630
+ it "should recognize :from, :to options" do
631
+ comment.undo! user, from: 4, to: 2
632
+ comment.title.should == "test"
633
+ end
634
+
635
+ it "should recognize parameter as version number" do
636
+ comment.undo! user, 3
637
+ comment.title.should == "Test2"
638
+ end
639
+
640
+ it "should undo last version when no parameter is specified" do
641
+ comment.undo! user
642
+ comment.title.should == "Test3"
643
+ end
644
+
645
+ it "should recognize :last options" do
646
+ comment.undo! user, last: 2
647
+ comment.title.should == "Test2"
648
+ end
649
+
650
+ if Mongoid::History.mongoid3?
651
+ context "protected attributes" do
652
+ before :each do
653
+ Comment.attr_accessible(nil)
654
+ end
655
+
656
+ after :each do
657
+ Comment.attr_protected(nil)
658
+ end
659
+
660
+ it "should undo last version when no parameter is specified on protected attributes" do
661
+ comment.undo! user
662
+ comment.title.should == "Test3"
663
+ end
664
+
665
+ it "should recognize :last options on model with protected attributes" do
666
+ comment.undo! user, last: 2
667
+ comment.title.should == "Test2"
668
+ end
669
+ end
670
+ end
671
+ end
672
+
673
+ describe "redo" do
674
+ before :each do
675
+ comment.update_attributes(title: "Test5")
676
+ end
677
+
678
+ it "should recognize :from, :to options" do
679
+ comment.redo! user, from: 2, to: 4
680
+ comment.title.should == "Test4"
681
+ end
682
+
683
+ it "should recognize parameter as version number" do
684
+ comment.redo! user, 2
685
+ comment.title.should == "Test2"
686
+ end
687
+
688
+ it "should redo last version when no parameter is specified" do
689
+ comment.redo! user
690
+ comment.title.should == "Test5"
691
+ end
692
+
693
+ it "should recognize :last options" do
694
+ comment.redo! user, last: 1
695
+ comment.title.should == "Test5"
696
+ end
697
+
698
+ if Mongoid::History.mongoid3?
699
+ context "protected attributes" do
700
+ before :each do
701
+ Comment.attr_accessible(nil)
702
+ end
703
+
704
+ after :each do
705
+ Comment.attr_protected(nil)
706
+ end
707
+
708
+ it "should recognize parameter as version number" do
709
+ comment.redo! user, 2
710
+ comment.title.should == "Test2"
711
+ end
712
+
713
+ it "should recognize :from, :to options" do
714
+ comment.redo! user, from: 2, to: 4
715
+ comment.title.should == "Test4"
716
+ end
717
+ end
718
+ end
719
+
720
+ end
721
+ end
722
+
723
+ describe "localized fields" do
724
+ before :each do
725
+ class Sausage
726
+ include Mongoid::Document
727
+ include Mongoid::History::Trackable
728
+
729
+ field :flavour, localize: true
730
+ track_history on: [:flavour], track_destroy: true
731
+ end
732
+ end
733
+ it "should correctly undo and redo" do
734
+ if Sausage.respond_to?(:localized_fields)
735
+ sausage = Sausage.create(flavour_translations: { 'en' => "Apple", 'nl' => 'Appel' })
736
+ sausage.update_attributes(flavour: "Guinness")
737
+
738
+ track = sausage.history_tracks.last
739
+
740
+ track.undo! user
741
+ sausage.reload.flavour.should == "Apple"
742
+
743
+ track.redo! user
744
+ sausage.reload.flavour.should == "Guinness"
745
+
746
+ sausage.destroy
747
+ sausage.history_tracks.last.action.should == "destroy"
748
+ sausage.history_tracks.last.undo! user
749
+ sausage.reload.flavour.should == "Guinness"
750
+ end
751
+ end
752
+ end
753
+
754
+ describe "embedded with a polymorphic trackable" do
755
+ let(:foo) { Foo.new(title: 'a title', body: 'a body') }
756
+ before :each do
757
+ post.comments << foo
758
+ post.save
759
+ end
760
+ it "should assign interface name in association chain" do
761
+ foo.update_attribute(:body, 'a changed body')
762
+ expected_root = { "name" => "Post", "id" => post.id }
763
+ expected_node = { "name" => "coms", "id" => foo.id }
764
+ foo.history_tracks.first.association_chain.should == [expected_root, expected_node]
765
+ end
766
+ end
767
+
768
+ describe "#trackable_parent_class" do
769
+ context "a non-embedded model" do
770
+ it "should return the trackable parent class" do
771
+ tag.history_tracks.first.trackable_parent_class.should == Tag
772
+ end
773
+ it "should return the parent class even if the trackable is deleted" do
774
+ tracker = tag.history_tracks.first
775
+ tag.destroy
776
+ tracker.trackable_parent_class.should == Tag
777
+ end
778
+ end
779
+ context "an embedded model" do
780
+ it "should return the trackable parent class" do
781
+ comment.update_attributes(title: "Foo")
782
+ comment.history_tracks.first.trackable_parent_class.should == Post
783
+ end
784
+ it "should return the parent class even if the trackable is deleted" do
785
+ tracker = comment.history_tracks.first
786
+ comment.destroy
787
+ tracker.trackable_parent_class.should == Post
788
+ end
789
+ end
790
+ end
791
+
792
+ describe "when default scope is present" do
793
+ before do
794
+ class Post
795
+ default_scope -> { where(title: nil) }
796
+ end
797
+ class Comment
798
+ default_scope -> { where(title: nil) }
799
+ end
800
+ class User
801
+ default_scope -> { where(name: nil) }
802
+ end
803
+ class Tag
804
+ default_scope -> { where(title: nil) }
805
+ end
806
+ end
807
+
808
+ describe "post" do
809
+
810
+ it "should correctly undo and redo" do
811
+ post.update_attributes(title: 'a new title')
812
+ track = post.history_tracks.last
813
+ track.undo! user
814
+ post.reload.title.should == 'Test'
815
+ track.redo! user
816
+ post.reload.title.should == 'a new title'
817
+ end
818
+
819
+ it "should stay the same after undo and redo" do
820
+ post.update_attributes(title: 'testing')
821
+ track = post.history_tracks.last
822
+ track.undo! user
823
+ track.redo! user
824
+ post.reload.title.should == 'testing'
825
+ end
826
+ end
827
+ describe "comment" do
828
+ it "should correctly undo and redo" do
829
+ comment.update_attributes(title: 'a new title')
830
+ track = comment.history_tracks.last
831
+ track.undo! user
832
+ comment.reload.title.should == 'test'
833
+ track.redo! user
834
+ comment.reload.title.should == 'a new title'
835
+ end
836
+
837
+ it "should stay the same after undo and redo" do
838
+ comment.update_attributes(title: 'testing')
839
+ track = comment.history_tracks.last
840
+ track.undo! user
841
+ track.redo! user
842
+ comment.reload.title.should == 'testing'
843
+ end
844
+ end
845
+ describe "user" do
846
+ it "should correctly undo and redo" do
847
+ user.update_attributes(name: 'a new name')
848
+ track = user.history_tracks.last
849
+ track.undo! user
850
+ user.reload.name.should == 'Aaron'
851
+ track.redo! user
852
+ user.reload.name.should == 'a new name'
853
+ end
854
+
855
+ it "should stay the same after undo and redo" do
856
+ user.update_attributes(name: 'testing')
857
+ track = user.history_tracks.last
858
+ track.undo! user
859
+ track.redo! user
860
+ user.reload.name.should == 'testing'
861
+ end
862
+ end
863
+ describe "tag" do
864
+ it "should correctly undo and redo" do
865
+ tag.update_attributes(title: 'a new title')
866
+ track = tag.history_tracks.last
867
+ track.undo! user
868
+ tag.reload.title.should == 'test'
869
+ track.redo! user
870
+ tag.reload.title.should == 'a new title'
871
+ end
872
+
873
+ it "should stay the same after undo and redo" do
874
+ tag.update_attributes(title: 'testing')
875
+ track = tag.history_tracks.last
876
+ track.undo! user
877
+ track.redo! user
878
+ tag.reload.title.should == 'testing'
879
+ end
880
+ end
881
+ end
882
+ end
883
+ end