mongoid-history 0.4.0 → 0.4.1

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