mongoid-history-patched 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,154 @@
1
+ module Mongoid::History
2
+ module Tracker
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ include Mongoid::Document
7
+ include Mongoid::Timestamps
8
+ attr_writer :trackable
9
+
10
+ field :association_chain, :type => Array, :default => []
11
+ field :modified, :type => Hash
12
+ field :original, :type => Hash
13
+ field :version, :type => Integer
14
+ field :action, :type => String
15
+ field :scope, :type => String
16
+ belongs_to :modifier, :class_name => Mongoid::History.modifier_class_name
17
+
18
+ Mongoid::History.tracker_class_name = self.name.tableize.singularize.to_sym
19
+
20
+ if defined?(ActionController) and defined?(ActionController::Base)
21
+ ActionController::Base.class_eval do
22
+ around_filter Mongoid::History::Sweeper.instance
23
+ end
24
+ end
25
+ end
26
+
27
+ def undo!(modifier)
28
+ if action.to_sym == :destroy
29
+ re_create
30
+ elsif action.to_sym == :create
31
+ re_destroy
32
+ else
33
+ trackable.update_attributes!(undo_attr(modifier))
34
+ end
35
+ end
36
+
37
+ def redo!(modifier)
38
+ if action.to_sym == :destroy
39
+ re_destroy
40
+ elsif action.to_sym == :create
41
+ re_create
42
+ else
43
+ trackable.update_attributes!(redo_attr(modifier))
44
+ end
45
+ end
46
+
47
+ def undo_attr(modifier)
48
+ undo_hash = affected.easy_unmerge(modified)
49
+ undo_hash.easy_merge!(original)
50
+ modifier_field = trackable.history_trackable_options[:modifier_field]
51
+ undo_hash[modifier_field] = modifier
52
+ undo_hash
53
+ end
54
+
55
+ def redo_attr(modifier)
56
+ redo_hash = affected.easy_unmerge(original)
57
+ redo_hash.easy_merge!(modified)
58
+ modifier_field = trackable.history_trackable_options[:modifier_field]
59
+ redo_hash[modifier_field] = modifier
60
+ redo_hash
61
+ end
62
+
63
+ def trackable_root
64
+ @trackable_root ||= trackable_parents_and_trackable.first
65
+ end
66
+
67
+ def trackable
68
+ @trackable ||= trackable_parents_and_trackable.last
69
+ end
70
+
71
+ def trackable_parents
72
+ @trackable_parents ||= trackable_parents_and_trackable[0, -1]
73
+ end
74
+
75
+ def trackable_parent
76
+ @trackable_parent ||= trackable_parents_and_trackable[-2]
77
+ end
78
+
79
+ def affected
80
+ @affected ||= (modified.keys | original.keys).inject({}){ |h,k| h[k] =
81
+ trackable ? trackable.attributes[k] : modified[k]; h}
82
+ end
83
+
84
+ private
85
+
86
+ def re_create
87
+ association_chain.length > 1 ? create_on_parent : create_standalone
88
+ end
89
+
90
+ def re_destroy
91
+ trackable.destroy
92
+ end
93
+
94
+ def create_standalone
95
+ class_name = association_chain.first["name"]
96
+ restored = class_name.constantize.new(modified)
97
+ restored.save!
98
+ end
99
+
100
+ def create_on_parent
101
+ name = association_chain.last["name"]
102
+ if embeds_one?(trackable_parent, name)
103
+ trackable_parent.send("create_#{name}!", modified)
104
+ elsif embeds_many?(trackable_parent, name)
105
+ trackable_parent.send(name).create!(modified)
106
+ else
107
+ raise "This should never happen. Please report bug!"
108
+ end
109
+ end
110
+
111
+ def trackable_parents_and_trackable
112
+ @trackable_parents_and_trackable ||= traverse_association_chain
113
+ end
114
+
115
+ def relation_of(doc, name)
116
+ meta = doc.reflect_on_association(name)
117
+ meta ? meta.relation : nil
118
+ end
119
+
120
+ def embeds_one?(doc, name)
121
+ relation_of(doc, name) == Mongoid::Relations::Embedded::One
122
+ end
123
+
124
+ def embeds_many?(doc, name)
125
+ relation_of(doc, name) == Mongoid::Relations::Embedded::Many
126
+ end
127
+
128
+ def traverse_association_chain
129
+ chain = association_chain.dup
130
+ doc = nil
131
+ documents = []
132
+
133
+ begin
134
+ node = chain.shift
135
+ name = node['name']
136
+
137
+ doc = if doc.nil?
138
+ # root association. First element of the association chain
139
+ klass = name.classify.constantize
140
+ klass.where(:_id => node['id']).first
141
+ elsif embeds_one?(doc, name)
142
+ doc.send(name)
143
+ elsif embeds_many?(doc, name)
144
+ doc.send(name).where(:_id => node['id']).first
145
+ else
146
+ raise "This should never happen. Please report bug."
147
+ end
148
+ documents << doc
149
+ end while( !chain.empty? )
150
+ documents
151
+ end
152
+
153
+ end
154
+ end
@@ -0,0 +1,78 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "mongoid-history-patched"
8
+ s.version = "0.2.3"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Aaron Qian", "Justin Grimes"]
12
+ s.date = "2012-04-20"
13
+ s.description = "In frustration of Mongoid::Versioning, I created this plugin for tracking historical changes for any document, including embedded ones. It achieves this by storing all history tracks in a single collection that you define. (See Usage for more details) Embedded documents are referenced by storing an association path, which is an array of document_name and document_id fields starting from the top most parent document and down to the embedded document that should track history.\n\n This plugin implements multi-user undo, which allows users to undo any history change in any order. Undoing a document also creates a new history track. This is great for auditing and preventing vandalism, but it is probably not suitable for use cases such as a wiki."
14
+ s.email = ["aq1018@gmail.com", "justin.mgrimes@gmail.com"]
15
+ s.extra_rdoc_files = [
16
+ "LICENSE.txt",
17
+ "README.md"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ ".rspec",
22
+ ".travis.yml",
23
+ "Gemfile",
24
+ "LICENSE.txt",
25
+ "README.md",
26
+ "Rakefile",
27
+ "VERSION",
28
+ "lib/mongoid-history.rb",
29
+ "lib/mongoid/history.rb",
30
+ "lib/mongoid/history/sweeper.rb",
31
+ "lib/mongoid/history/trackable.rb",
32
+ "lib/mongoid/history/tracker.rb",
33
+ "mongoid-history.gemspec",
34
+ "spec/integration/integration_spec.rb",
35
+ "spec/spec_helper.rb",
36
+ "spec/trackable_spec.rb",
37
+ "spec/tracker_spec.rb"
38
+ ]
39
+ s.homepage = "http://github.com/aq1018/mongoid-history"
40
+ s.licenses = ["MIT"]
41
+ s.require_paths = ["lib"]
42
+ s.rubygems_version = "1.8.19"
43
+ s.summary = "history tracking, auditing, undo, redo for mongoid"
44
+
45
+ if s.respond_to? :specification_version then
46
+ s.specification_version = 3
47
+
48
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
49
+ s.add_runtime_dependency(%q<easy_diff>, [">= 0"])
50
+ s.add_runtime_dependency(%q<mongoid>, [">= 2.0.0"])
51
+ s.add_development_dependency(%q<bson_ext>, [">= 0"])
52
+ s.add_development_dependency(%q<rspec>, [">= 0"])
53
+ s.add_development_dependency(%q<yard>, [">= 0"])
54
+ s.add_development_dependency(%q<bundler>, [">= 1.0.0"])
55
+ s.add_development_dependency(%q<jeweler>, [">= 0"])
56
+ s.add_development_dependency(%q<database_cleaner>, [">= 0"])
57
+ else
58
+ s.add_dependency(%q<easy_diff>, [">= 0"])
59
+ s.add_dependency(%q<mongoid>, [">= 2.0.0"])
60
+ s.add_dependency(%q<bson_ext>, [">= 0"])
61
+ s.add_dependency(%q<rspec>, [">= 0"])
62
+ s.add_dependency(%q<yard>, [">= 0"])
63
+ s.add_dependency(%q<bundler>, [">= 1.0.0"])
64
+ s.add_dependency(%q<jeweler>, [">= 0"])
65
+ s.add_dependency(%q<database_cleaner>, [">= 0"])
66
+ end
67
+ else
68
+ s.add_dependency(%q<easy_diff>, [">= 0"])
69
+ s.add_dependency(%q<mongoid>, [">= 2.0.0"])
70
+ s.add_dependency(%q<bson_ext>, [">= 0"])
71
+ s.add_dependency(%q<rspec>, [">= 0"])
72
+ s.add_dependency(%q<yard>, [">= 0"])
73
+ s.add_dependency(%q<bundler>, [">= 1.0.0"])
74
+ s.add_dependency(%q<jeweler>, [">= 0"])
75
+ s.add_dependency(%q<database_cleaner>, [">= 0"])
76
+ end
77
+ end
78
+
@@ -0,0 +1,553 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe Mongoid::History do
4
+ before :all do
5
+ class HistoryTracker
6
+ include Mongoid::History::Tracker
7
+ end
8
+
9
+ class Post
10
+ include Mongoid::Document
11
+ include Mongoid::Timestamps
12
+ include Mongoid::History::Trackable
13
+
14
+ field :title
15
+ field :body
16
+ field :rating
17
+
18
+ embeds_many :comments
19
+ embeds_one :section
20
+ embeds_many :tags, :cascade_callbacks => true
21
+
22
+ accepts_nested_attributes_for :tags, :allow_destroy => true
23
+
24
+ track_history :on => [:title, :body], :track_destroy => true
25
+ end
26
+
27
+ class Comment
28
+ include Mongoid::Document
29
+ include Mongoid::Timestamps
30
+ include Mongoid::History::Trackable
31
+
32
+ field :title
33
+ field :body
34
+ embedded_in :post
35
+ track_history :on => [:title, :body], :scope => :post, :track_create => true, :track_destroy => true
36
+ end
37
+
38
+ class Section
39
+ include Mongoid::Document
40
+ include Mongoid::Timestamps
41
+ include Mongoid::History::Trackable
42
+
43
+ field :title
44
+ embedded_in :post
45
+ track_history :on => [:title], :scope => :post, :track_create => true, :track_destroy => true
46
+ end
47
+
48
+ class User
49
+ include Mongoid::Document
50
+ include Mongoid::Timestamps
51
+ include Mongoid::History::Trackable
52
+
53
+ field :email
54
+ field :name
55
+ track_history :except => [:email]
56
+ end
57
+
58
+ class Tag
59
+ include Mongoid::Document
60
+ include Mongoid::Timestamps
61
+ include Mongoid::History::Trackable
62
+
63
+ belongs_to :updated_by, :class_name => "User"
64
+
65
+ field :title
66
+ track_history :on => [:title], :scope => :post, :track_create => true, :track_destroy => true, :modifier_field => :updated_by
67
+ end
68
+ end
69
+
70
+ before :each do
71
+ @user = User.create(:name => "Aaron", :email => "aaron@randomemail.com")
72
+ @another_user = User.create(:name => "Another Guy", :email => "anotherguy@randomemail.com")
73
+ @post = Post.create(:title => "Test", :body => "Post", :modifier => @user, :views => 100)
74
+ @comment = @post.comments.create(:title => "test", :body => "comment", :modifier => @user)
75
+ end
76
+
77
+ describe "track" do
78
+ describe "on creation" do
79
+ it "should have one history track in comment" do
80
+ @comment.history_tracks.count.should == 1
81
+ end
82
+
83
+ it "should assign title and body on modified" do
84
+ @comment.history_tracks.first.modified.should == {'title' => "test", 'body' => "comment"}
85
+ end
86
+
87
+ it "should not assign title and body on original" do
88
+ @comment.history_tracks.first.original.should == {}
89
+ end
90
+
91
+ it "should assign modifier" do
92
+ @comment.history_tracks.first.modifier.should == @user
93
+ end
94
+
95
+ it "should assign version" do
96
+ @comment.history_tracks.first.version.should == 1
97
+ end
98
+
99
+ it "should assign scope" do
100
+ @comment.history_tracks.first.scope.should == "post"
101
+ end
102
+
103
+ it "should assign method" do
104
+ @comment.history_tracks.first.action.should == "create"
105
+ end
106
+
107
+ it "should assign association_chain" do
108
+ expected = [
109
+ {'id' => @post.id, 'name' => "Post"},
110
+ {'id' => @comment.id, 'name' => "comments"}
111
+ ]
112
+ @comment.history_tracks.first.association_chain.should == expected
113
+ end
114
+ end
115
+
116
+ describe "on destruction" do
117
+ it "should have two history track records in post" do
118
+ lambda {
119
+ @post.destroy
120
+ }.should change(HistoryTracker, :count).by(1)
121
+ end
122
+
123
+ it "should assign destroy on track record" do
124
+ @post.destroy
125
+ @post.history_tracks.last.action.should == "destroy"
126
+ end
127
+
128
+ it "should return affected attributes from track record" do
129
+ @post.destroy
130
+ @post.history_tracks.last.affected["title"].should == "Test"
131
+ end
132
+ end
133
+
134
+ describe "on update non-embedded" do
135
+ it "should create a history track if changed attributes match tracked attributes" do
136
+ lambda {
137
+ @post.update_attributes(:title => "Another Test")
138
+ }.should change(HistoryTracker, :count).by(1)
139
+ end
140
+
141
+ it "should not create a history track if changed attributes do not match tracked attributes" do
142
+ lambda {
143
+ @post.update_attributes(:rating => "untracked")
144
+ }.should change(HistoryTracker, :count).by(0)
145
+ end
146
+
147
+ it "should assign modified fields" do
148
+ @post.update_attributes(:title => "Another Test")
149
+ @post.history_tracks.last.modified.should == {
150
+ "title" => "Another Test"
151
+ }
152
+ end
153
+
154
+ it "should assign method field" do
155
+ @post.update_attributes(:title => "Another Test")
156
+ @post.history_tracks.last.action.should == "update"
157
+ end
158
+
159
+ it "should assign original fields" do
160
+ @post.update_attributes(:title => "Another Test")
161
+ @post.history_tracks.last.original.should == {
162
+ "title" => "Test"
163
+ }
164
+ end
165
+
166
+ it "should assign modifier" do
167
+ @post.update_attributes(:title => "Another Test")
168
+ @post.history_tracks.first.modifier.should == @user
169
+ end
170
+
171
+ it "should assign version on history tracks" do
172
+ @post.update_attributes(:title => "Another Test")
173
+ @post.history_tracks.first.version.should == 1
174
+ end
175
+
176
+ it "should assign version on post" do
177
+ @post.update_attributes(:title => "Another Test")
178
+ @post.version.should == 1
179
+ end
180
+
181
+ it "should assign scope" do
182
+ @post.update_attributes(:title => "Another Test")
183
+ @post.history_tracks.first.scope.should == "post"
184
+ end
185
+
186
+ it "should assign association_chain" do
187
+ @post.update_attributes(:title => "Another Test")
188
+ @post.history_tracks.last.association_chain.should == [{'id' => @post.id, 'name' => "Post"}]
189
+ end
190
+
191
+ it "should exclude defined options" do
192
+ @user.update_attributes(:name => "Aaron2", :email => "aaronsnewemail@randomemail.com")
193
+ @user.history_tracks.first.modified.keys.should include "name"
194
+ @user.history_tracks.first.modified.keys.should_not include "email"
195
+ end
196
+ end
197
+
198
+ describe "on update non-embedded twice" do
199
+ it "should assign version on post" do
200
+ @post.update_attributes(:title => "Test2")
201
+ @post.update_attributes(:title => "Test3")
202
+ @post.version.should == 2
203
+ end
204
+
205
+ it "should create a history track if changed attributes match tracked attributes" do
206
+ lambda {
207
+ @post.update_attributes(:title => "Test2")
208
+ @post.update_attributes(:title => "Test3")
209
+ }.should change(HistoryTracker, :count).by(2)
210
+ end
211
+
212
+ it "should create a history track of version 2" do
213
+ @post.update_attributes(:title => "Test2")
214
+ @post.update_attributes(:title => "Test3")
215
+ @post.history_tracks.where(:version => 2).first.should_not be_nil
216
+ end
217
+
218
+ it "should assign modified fields" do
219
+ @post.update_attributes(:title => "Test2")
220
+ @post.update_attributes(:title => "Test3")
221
+ @post.history_tracks.where(:version => 2).first.modified.should == {
222
+ "title" => "Test3"
223
+ }
224
+ end
225
+
226
+ it "should assign original fields" do
227
+ @post.update_attributes(:title => "Test2")
228
+ @post.update_attributes(:title => "Test3")
229
+ @post.history_tracks.where(:version => 2).first.original.should == {
230
+ "title" => "Test2"
231
+ }
232
+ end
233
+
234
+
235
+ it "should assign modifier" do
236
+ @post.update_attributes(:title => "Another Test", :modifier => @another_user)
237
+ @post.history_tracks.last.modifier.should == @another_user
238
+ end
239
+ end
240
+
241
+ describe "on update embedded 1..N (embeds_many)" do
242
+ it "should assign version on comment" do
243
+ @comment.update_attributes(:title => "Test2")
244
+ @comment.version.should == 2 # first track generated on creation
245
+ end
246
+
247
+ it "should create a history track of version 2" do
248
+ @comment.update_attributes(:title => "Test2")
249
+ @comment.history_tracks.where(:version => 2).first.should_not be_nil
250
+ end
251
+
252
+ it "should assign modified fields" do
253
+ @comment.update_attributes(:title => "Test2")
254
+ @comment.history_tracks.where(:version => 2).first.modified.should == {
255
+ "title" => "Test2"
256
+ }
257
+ end
258
+
259
+ it "should assign original fields" do
260
+ @comment.update_attributes(:title => "Test2")
261
+ @comment.history_tracks.where(:version => 2).first.original.should == {
262
+ "title" => "test"
263
+ }
264
+ end
265
+
266
+ it "should be possible to undo from parent" do
267
+ @comment.update_attributes(:title => "Test 2")
268
+ @post.history_tracks.last.undo!(@user)
269
+ @comment.reload
270
+ @comment.title.should == "test"
271
+ end
272
+
273
+ it "should assign modifier" do
274
+ @post.update_attributes(:title => "Another Test", :modifier => @another_user)
275
+ @post.history_tracks.last.modifier.should == @another_user
276
+ end
277
+ end
278
+
279
+ describe "on update embedded 1..1 (embeds_one)" do
280
+ before(:each) do
281
+ @section = Section.new(:title => 'Technology')
282
+ @post.section = @section
283
+ @post.save!
284
+ @post.reload
285
+ @section = @post.section
286
+ end
287
+
288
+ it "should assign version on create section" do
289
+ @section.version.should == 1
290
+ end
291
+
292
+ it "should assign version on section" do
293
+ @section.update_attributes(:title => 'Technology 2')
294
+ @section.version.should == 2 # first track generated on creation
295
+ end
296
+
297
+ it "should create a history track of version 2" do
298
+ @section.update_attributes(:title => 'Technology 2')
299
+ @section.history_tracks.where(:version => 2).first.should_not be_nil
300
+ end
301
+
302
+ it "should assign modified fields" do
303
+ @section.update_attributes(:title => 'Technology 2')
304
+ @section.history_tracks.where(:version => 2).first.modified.should == {
305
+ "title" => "Technology 2"
306
+ }
307
+ end
308
+
309
+ it "should assign original fields" do
310
+ @section.update_attributes(:title => 'Technology 2')
311
+ @section.history_tracks.where(:version => 2).first.original.should == {
312
+ "title" => "Technology"
313
+ }
314
+ end
315
+
316
+ it "should be possible to undo from parent" do
317
+ @section.update_attributes(:title => 'Technology 2')
318
+ @post.history_tracks.last.undo!(@user)
319
+ @section.reload
320
+ @section.title.should == "Technology"
321
+ end
322
+
323
+ it "should assign modifier" do
324
+ @section.update_attributes(:title => "Business", :modifier => @another_user)
325
+ @post.history_tracks.last.modifier.should == @another_user
326
+ end
327
+ end
328
+
329
+ describe "on destroy embedded" do
330
+ it "should be possible to re-create destroyed embedded" do
331
+ @comment.destroy
332
+ @comment.history_tracks.last.undo!(@user)
333
+ @post.reload
334
+ @post.comments.first.title.should == "test"
335
+ end
336
+
337
+ it "should be possible to re-create destroyed embedded from parent" do
338
+ @comment.destroy
339
+ @post.history_tracks.last.undo!(@user)
340
+ @post.reload
341
+ @post.comments.first.title.should == "test"
342
+ end
343
+
344
+ it "should be possible to destroy after re-create embedded from parent" do
345
+ @comment.destroy
346
+ @post.history_tracks.last.undo!(@user)
347
+ @post.history_tracks.last.undo!(@user)
348
+ @post.reload
349
+ @post.comments.count.should == 0
350
+ end
351
+
352
+ it "should be possible to create with redo after undo create embedded from parent" do
353
+ @post.comments.create!(:title => "The second one")
354
+ @track = @post.history_tracks.last
355
+ @track.undo!(@user)
356
+ @track.redo!(@user)
357
+ @post.reload
358
+ @post.comments.count.should == 2
359
+ end
360
+ end
361
+
362
+ describe "embedded with cascading callbacks" do
363
+ before(:each) do
364
+ Mongoid.instantiate_observers
365
+ Thread.current[:mongoid_history_sweeper_controller] = self
366
+ self.stub!(:current_user).and_return @user
367
+ @tag_foo = @post.tags.create(:title => "foo", :updated_by => @user)
368
+ @tag_bar = @post.tags.create(:title => "bar")
369
+ end
370
+
371
+ after(:each) do
372
+ Thread.current[:mongoid_history_sweeper_controller] = nil
373
+ end
374
+
375
+ it "should have cascaded the creation callbacks and set timestamps" do
376
+ @tag_foo.created_at.should_not be_nil
377
+ @tag_foo.updated_at.should_not be_nil
378
+ end
379
+
380
+ it "should allow an update through the parent model" do
381
+ update_hash = { "post" => { "tags_attributes" => { "1234" => { "id" => @tag_bar.id, "title" => "baz" } } } }
382
+ @post.update_attributes(update_hash["post"])
383
+ @post.tags.last.title.should == "baz"
384
+ end
385
+
386
+ it "should be possible to destroy through parent model using canoncial _destroy macro" do
387
+ @post.tags.count.should == 2
388
+ update_hash = { "post" => { "tags_attributes" => { "1234" => { "id" => @tag_bar.id, "title" => "baz", "_destroy" => "true"} } } }
389
+ @post.update_attributes(update_hash["post"])
390
+ @post.tags.count.should == 1
391
+ @post.history_tracks.last.action.should == "destroy"
392
+ end
393
+
394
+ it "should write relationship name for association_chain hiearchy instead of class name when using _destroy macro" do
395
+ update_hash = {"tags_attributes" => { "1234" => { "id" => @tag_foo.id, "_destroy" => "1"} } }
396
+ @post.update_attributes(update_hash)
397
+
398
+ # historically this would have evaluated to 'Tags' and an error would be thrown
399
+ # on any call that walked up the association_chain, e.g. 'trackable'
400
+ @tag_foo.history_tracks.last.association_chain.last["name"].should == "tags"
401
+ lambda{ @tag_foo.history_tracks.last.trackable }.should_not raise_error
402
+ end
403
+
404
+ it "should save modifier" do
405
+ @tag_foo.history_tracks.last.modifier.should eq @user
406
+ @tag_bar.history_tracks.last.modifier.should eq @user
407
+ end
408
+ end
409
+
410
+ describe "non-embedded" do
411
+ it "should undo changes" do
412
+ @post.update_attributes(:title => "Test2")
413
+ @post.history_tracks.where(:version => 1).last.undo!(@user)
414
+ @post.reload
415
+ @post.title.should == "Test"
416
+ end
417
+
418
+ it "should undo destruction" do
419
+ @post.destroy
420
+ @post.history_tracks.where(:version => 1).last.undo!(@user)
421
+ Post.find(@post.id).title.should == "Test"
422
+ end
423
+
424
+ it "should create a new history track after undo" do
425
+ @post.update_attributes(:title => "Test2")
426
+ @post.history_tracks.last.undo!(@user)
427
+ @post.reload
428
+ @post.history_tracks.count.should == 3
429
+ end
430
+
431
+ it "should assign @user as the modifier of the newly created history track" do
432
+ @post.update_attributes(:title => "Test2")
433
+ @post.history_tracks.where(:version => 1).last.undo!(@user)
434
+ @post.reload
435
+ @post.history_tracks.where(:version => 2).last.modifier.should == @user
436
+ end
437
+
438
+ it "should stay the same after undo and redo" do
439
+ @post.update_attributes(:title => "Test2")
440
+ @track = @post.history_tracks.last
441
+ @track.undo!(@user)
442
+ @track.redo!(@user)
443
+ @post2 = Post.where(:_id => @post.id).first
444
+
445
+ @post.title.should == @post2.title
446
+ end
447
+
448
+ it "should be destroyed after undo and redo" do
449
+ @post.destroy
450
+ @track = @post.history_tracks.where(:version => 1).last
451
+ @track.undo!(@user)
452
+ @track.redo!(@user)
453
+ Post.where(:_id => @post.id).first.should == nil
454
+ end
455
+ end
456
+
457
+ describe "embedded" do
458
+ it "should undo changes" do
459
+ @comment.update_attributes(:title => "Test2")
460
+ @comment.history_tracks.where(:version => 2).first.undo!(@user)
461
+ # reloading an embedded document === KAMIKAZE
462
+ # at least for the current release of mongoid...
463
+ @post.reload
464
+ @comment = @post.comments.first
465
+ @comment.title.should == "test"
466
+ end
467
+
468
+ it "should create a new history track after undo" do
469
+ @comment.update_attributes(:title => "Test2")
470
+ @comment.history_tracks.where(:version => 2).first.undo!(@user)
471
+ @post.reload
472
+ @comment = @post.comments.first
473
+ @comment.history_tracks.count.should == 3
474
+ end
475
+
476
+ it "should assign @user as the modifier of the newly created history track" do
477
+ @comment.update_attributes(:title => "Test2")
478
+ @comment.history_tracks.where(:version => 2).first.undo!(@user)
479
+ @post.reload
480
+ @comment = @post.comments.first
481
+ @comment.history_tracks.where(:version => 3).first.modifier.should == @user
482
+ end
483
+
484
+ it "should stay the same after undo and redo" do
485
+ @comment.update_attributes(:title => "Test2")
486
+ @track = @comment.history_tracks.where(:version => 2).first
487
+ @track.undo!(@user)
488
+ @track.redo!(@user)
489
+ @post2 = Post.where(:_id => @post.id).first
490
+ @comment2 = @post2.comments.first
491
+
492
+ @comment.title.should == @comment2.title
493
+ end
494
+ end
495
+
496
+ describe "trackables" do
497
+ before :each do
498
+ @comment.update_attributes(:title => "Test2") # version == 2
499
+ @comment.update_attributes(:title => "Test3") # version == 3
500
+ @comment.update_attributes(:title => "Test4") # version == 4
501
+ end
502
+
503
+ describe "undo" do
504
+ it "should recognize :from, :to options" do
505
+ @comment.undo! @user, :from => 4, :to => 2
506
+ @comment.title.should == "test"
507
+ end
508
+
509
+ it "should recognize parameter as version number" do
510
+ @comment.undo! @user, 3
511
+ @comment.title.should == "Test2"
512
+ end
513
+
514
+ it "should undo last version when no parameter is specified" do
515
+ @comment.undo! @user
516
+ @comment.title.should == "Test3"
517
+ end
518
+
519
+ it "should recognize :last options" do
520
+ @comment.undo! @user, :last => 2
521
+ @comment.title.should == "Test2"
522
+ end
523
+ end
524
+
525
+ describe "redo" do
526
+ before :each do
527
+ @comment.update_attributes(:title => "Test5")
528
+ end
529
+
530
+ it "should recognize :from, :to options" do
531
+ @comment.redo! @user, :from => 2, :to => 4
532
+ @comment.title.should == "Test4"
533
+ end
534
+
535
+ it "should recognize parameter as version number" do
536
+ @comment.redo! @user, 2
537
+ @comment.title.should == "Test2"
538
+ end
539
+
540
+ it "should redo last version when no parameter is specified" do
541
+ @comment.redo! @user
542
+ @comment.title.should == "Test5"
543
+ end
544
+
545
+ it "should recognize :last options" do
546
+ @comment.redo! @user, :last => 1
547
+ @comment.title.should == "Test5"
548
+ end
549
+
550
+ end
551
+ end
552
+ end
553
+ end