mongoid-audit 0.0.1
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.
- data/.gitignore +17 -0
- data/.rspec +1 -0
- data/.travis.yml +4 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +191 -0
- data/Rakefile +1 -0
- data/config/mongoid.yml +8 -0
- data/lib/mongoid-audit.rb +24 -0
- data/lib/mongoid-audit/sweeper.rb +40 -0
- data/lib/mongoid-audit/trackable.rb +262 -0
- data/lib/mongoid-audit/tracker.rb +159 -0
- data/lib/mongoid-audit/version.rb +5 -0
- data/mongoid-audit.gemspec +27 -0
- data/spec/integration/integration_spec.rb +553 -0
- data/spec/spec_helper.rb +16 -0
- data/spec/support/database_cleaner.rb +9 -0
- data/spec/support/mongoid.rb +17 -0
- data/spec/trackable_spec.rb +121 -0
- data/spec/tracker_spec.rb +17 -0
- metadata +168 -0
@@ -0,0 +1,159 @@
|
|
1
|
+
module Mongoid::Audit
|
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::Audit.modifier_class_name
|
17
|
+
|
18
|
+
Mongoid::Audit.tracker_class_name = self.name.tableize.singularize.to_sym
|
19
|
+
|
20
|
+
# install model observer and action controller filter
|
21
|
+
Mongoid::Audit::Sweeper.send(:observe, Mongoid::Audit.tracker_class_name)
|
22
|
+
if defined?(ActionController) and defined?(ActionController::Base)
|
23
|
+
ActionController::Base.class_eval do
|
24
|
+
before_filter { |controller| Mongoid::Audit::Sweeper.instance.before(controller) }
|
25
|
+
after_filter { |controller| Mongoid::Audit::Sweeper.instance.after(controller) }
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def undo!(modifier)
|
31
|
+
if action.to_sym == :destroy
|
32
|
+
re_create
|
33
|
+
elsif action.to_sym == :create
|
34
|
+
re_destroy
|
35
|
+
else
|
36
|
+
trackable.update_attributes!(undo_attr(modifier))
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def redo!(modifier)
|
41
|
+
if action.to_sym == :destroy
|
42
|
+
re_destroy
|
43
|
+
elsif action.to_sym == :create
|
44
|
+
re_create
|
45
|
+
else
|
46
|
+
trackable.update_attributes!(redo_attr(modifier))
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def undo_attr(modifier)
|
51
|
+
undo_hash = affected.easy_unmerge(modified)
|
52
|
+
undo_hash.easy_merge!(original)
|
53
|
+
modifier_field = trackable.history_trackable_options[:modifier_field]
|
54
|
+
undo_hash[modifier_field] = modifier
|
55
|
+
undo_hash
|
56
|
+
end
|
57
|
+
|
58
|
+
def redo_attr(modifier)
|
59
|
+
redo_hash = affected.easy_unmerge(original)
|
60
|
+
redo_hash.easy_merge!(modified)
|
61
|
+
modifier_field = trackable.history_trackable_options[:modifier_field]
|
62
|
+
redo_hash[modifier_field] = modifier
|
63
|
+
redo_hash
|
64
|
+
end
|
65
|
+
|
66
|
+
def trackable_root
|
67
|
+
@trackable_root ||= trackable_parents_and_trackable.first
|
68
|
+
end
|
69
|
+
|
70
|
+
def trackable
|
71
|
+
@trackable ||= trackable_parents_and_trackable.last
|
72
|
+
end
|
73
|
+
|
74
|
+
def trackable_parents
|
75
|
+
@trackable_parents ||= trackable_parents_and_trackable[0, -1]
|
76
|
+
end
|
77
|
+
|
78
|
+
def trackable_parent
|
79
|
+
@trackable_parent ||= trackable_parents_and_trackable[-2]
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
def affected
|
84
|
+
@affected ||= (modified.keys | original.keys).inject({}){ |h,k| h[k] =
|
85
|
+
trackable ? trackable.attributes[k] : modified[k]; h}
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
def re_create
|
91
|
+
association_chain.length > 1 ? create_on_parent : create_standalone
|
92
|
+
end
|
93
|
+
|
94
|
+
def re_destroy
|
95
|
+
trackable.destroy
|
96
|
+
end
|
97
|
+
|
98
|
+
def create_standalone
|
99
|
+
class_name = association_chain.first["name"]
|
100
|
+
restored = class_name.constantize.new(modified)
|
101
|
+
restored.id = modified["_id"]
|
102
|
+
restored.save!
|
103
|
+
end
|
104
|
+
|
105
|
+
def create_on_parent
|
106
|
+
name = association_chain.last["name"]
|
107
|
+
if embeds_one?(trackable_parent, name)
|
108
|
+
trackable_parent.send("create_#{name}!", modified)
|
109
|
+
elsif embeds_many?(trackable_parent, name)
|
110
|
+
trackable_parent.send(name).create!(modified)
|
111
|
+
else
|
112
|
+
raise "This should never happen. Please report bug!"
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def trackable_parents_and_trackable
|
117
|
+
@trackable_parents_and_trackable ||= traverse_association_chain
|
118
|
+
end
|
119
|
+
|
120
|
+
def relation_of(doc, name)
|
121
|
+
meta = doc.reflect_on_association(name)
|
122
|
+
meta ? meta.relation : nil
|
123
|
+
end
|
124
|
+
|
125
|
+
def embeds_one?(doc, name)
|
126
|
+
relation_of(doc, name) == Mongoid::Relations::Embedded::One
|
127
|
+
end
|
128
|
+
|
129
|
+
def embeds_many?(doc, name)
|
130
|
+
relation_of(doc, name) == Mongoid::Relations::Embedded::Many
|
131
|
+
end
|
132
|
+
|
133
|
+
def traverse_association_chain
|
134
|
+
chain = association_chain.dup
|
135
|
+
doc = nil
|
136
|
+
documents = []
|
137
|
+
|
138
|
+
begin
|
139
|
+
node = chain.shift
|
140
|
+
name = node['name']
|
141
|
+
|
142
|
+
doc = if doc.nil?
|
143
|
+
# root association. First element of the association chain
|
144
|
+
klass = name.classify.constantize
|
145
|
+
klass.where(:_id => node['id']).first
|
146
|
+
elsif embeds_one?(doc, name)
|
147
|
+
doc.send(name)
|
148
|
+
elsif embeds_many?(doc, name)
|
149
|
+
doc.send(name).where(:_id => node['id']).first
|
150
|
+
else
|
151
|
+
raise "This should never happen. Please report bug."
|
152
|
+
end
|
153
|
+
documents << doc
|
154
|
+
end while( !chain.empty? )
|
155
|
+
documents
|
156
|
+
end
|
157
|
+
|
158
|
+
end
|
159
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'mongoid-audit/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "mongoid-audit"
|
8
|
+
gem.version = Mongoid::Audit::VERSION
|
9
|
+
gem.authors = ["Gleb Tv"]
|
10
|
+
gem.email = ["glebtv@gmail.com"]
|
11
|
+
gem.description = %q{Mongoid Audit}
|
12
|
+
gem.summary = %q{Easily track model change history and mantain audit log with mongoid}
|
13
|
+
gem.homepage = "https://github.com/rs-pro/mongoid-audit"
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.require_paths = ["lib"]
|
19
|
+
|
20
|
+
gem.add_runtime_dependency('easy_diff', ">= 0")
|
21
|
+
gem.add_runtime_dependency('mongoid', ">= 3.0.4")
|
22
|
+
|
23
|
+
gem.add_development_dependency('rspec', ["~> 2.12.0"])
|
24
|
+
gem.add_development_dependency('bundler', ['>= 1.0.0'])
|
25
|
+
gem.add_development_dependency('database_cleaner', [">= 0.8.0"])
|
26
|
+
gem.add_development_dependency('activesupport', '~> 3.2.11')
|
27
|
+
end
|
@@ -0,0 +1,553 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe Mongoid::Audit do
|
4
|
+
before :all do
|
5
|
+
class HistoryTracker
|
6
|
+
include Mongoid::Audit::Tracker
|
7
|
+
end
|
8
|
+
|
9
|
+
class Post
|
10
|
+
include Mongoid::Document
|
11
|
+
include Mongoid::Timestamps
|
12
|
+
include Mongoid::Audit::Trackable
|
13
|
+
|
14
|
+
field :title
|
15
|
+
field :body
|
16
|
+
field :rating
|
17
|
+
|
18
|
+
embeds_many :comments
|
19
|
+
embeds_one :section
|
20
|
+
embeds_many :tags
|
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::Audit::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::Audit::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::Audit::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::Audit::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
|