mongoid-history 0.3.3 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -82,13 +82,77 @@ module Mongoid::History
82
82
  @trackable_parent ||= trackable_parents_and_trackable[-2]
83
83
  end
84
84
 
85
+ # Outputs a :from, :to hash for each affected field. Intentionally excludes fields
86
+ # which are not tracked, even if there are tracked values for such fields
87
+ # present in the database.
88
+ #
89
+ # @return [ HashWithIndifferentAccess ] a change set in the format:
90
+ # { field_1: {to: new_val}, field_2: {from: old_val, to: new_val} }
91
+ def tracked_changes
92
+ @tracked_changes ||= (modified.keys | original.keys).inject(HashWithIndifferentAccess.new) do |h,k|
93
+ h[k] = {from: original[k], to: modified[k]}.delete_if{|k,v| v.nil?}
94
+ h
95
+ end.delete_if{|k,v| v.blank? || !trackable_parent_class.tracked_field?(k)}
96
+ end
97
+
98
+ # Outputs summary of edit actions performed: :add, :modify, :remove, or :array.
99
+ # Does deep comparison of arrays. Useful for creating human-readable representations
100
+ # of the history tracker. Considers changing a value to 'blank' to be a removal.
101
+ #
102
+ # @return [ HashWithIndifferentAccess ] a change set in the format:
103
+ # { add: { field_1: new_val, ... },
104
+ # modify: { field_2: {from: old_val, to: new_val}, ... },
105
+ # remove: { field_3: old_val },
106
+ # array: { field_4: {add: ['foo', 'bar'], remove: ['baz']} } }
107
+ def tracked_edits
108
+ @tracked_edits ||= tracked_changes.inject(HashWithIndifferentAccess.new) do |h,(k,v)|
109
+ unless v[:from].blank? && v[:to].blank?
110
+ if v[:from].blank?
111
+ h[:add] ||={}
112
+ h[:add][k] = v[:to]
113
+ elsif v[:to].blank?
114
+ h[:remove] ||={}
115
+ h[:remove][k] = v[:from]
116
+ else
117
+ if v[:from].is_a?(Array) && v[:to].is_a?(Array)
118
+ h[:array] ||={}
119
+ old_values = v[:from] - v[:to]
120
+ new_values = v[:to] - v[:from]
121
+ h[:array][k] = {add: new_values, remove: old_values}.delete_if{|k,v| v.blank?}
122
+ else
123
+ h[:modify] ||={}
124
+ h[:modify][k] = v
125
+ end
126
+ end
127
+ end
128
+ h
129
+ end
130
+ end
85
131
 
132
+ # Similar to #tracked_changes, but contains only a single value for each
133
+ # affected field:
134
+ # - :create and :update return the modified values
135
+ # - :destroy returns original values
136
+ # Included for legacy compatibility.
137
+ #
138
+ # @deprecated
139
+ #
140
+ # @return [ HashWithIndifferentAccess ] a change set in the format:
141
+ # { field_1: value, field_2: value }
86
142
  def affected
87
- @affected ||= (modified.keys | original.keys).inject({}){ |h,k| h[k] =
88
- trackable ? trackable.attributes[k] : modified[k]; h}
143
+ target = action.to_sym == :destroy ? :from : :to
144
+ @affected ||= tracked_changes.inject(HashWithIndifferentAccess.new){|h,(k,v)| h[k] = v[target]; h}
89
145
  end
90
146
 
91
- private
147
+ # Returns the class of the trackable, irrespective of whether the trackable object
148
+ # has been destroyed.
149
+ #
150
+ # @return [ Class ] the class of the trackable
151
+ def trackable_parent_class
152
+ association_chain.first["name"].constantize
153
+ end
154
+
155
+ private
92
156
 
93
157
  def re_create
94
158
  association_chain.length > 1 ? create_on_parent : create_standalone
@@ -99,18 +163,17 @@ private
99
163
  end
100
164
 
101
165
  def create_standalone
102
- class_name = association_chain.first["name"]
103
- restored = class_name.constantize.new(localize_keys(modified))
104
- restored.id = modified["_id"]
166
+ restored = trackable_parent_class.new(localize_keys(original))
167
+ restored.id = original["_id"]
105
168
  restored.save!
106
169
  end
107
170
 
108
171
  def create_on_parent
109
172
  name = association_chain.last["name"]
110
- if embeds_one?(trackable_parent, name)
111
- trackable_parent.send("create_#{name}!", localize_keys(modified))
112
- elsif embeds_many?(trackable_parent, name)
113
- trackable_parent.send(name).create!(localize_keys(modified))
173
+ if trackable_parent.class.embeds_one?(name)
174
+ trackable_parent.create_embedded(name, localize_keys(original))
175
+ elsif trackable_parent.class.embeds_many?(name)
176
+ trackable_parent.get_embedded(name).create!(localize_keys(original))
114
177
  else
115
178
  raise "This should never happen. Please report bug!"
116
179
  end
@@ -120,19 +183,6 @@ private
120
183
  @trackable_parents_and_trackable ||= traverse_association_chain
121
184
  end
122
185
 
123
- def relation_of(doc, name)
124
- meta = doc.reflect_on_association(name)
125
- meta ? meta.relation : nil
126
- end
127
-
128
- def embeds_one?(doc, name)
129
- relation_of(doc, name) == Mongoid::Relations::Embedded::One
130
- end
131
-
132
- def embeds_many?(doc, name)
133
- relation_of(doc, name) == Mongoid::Relations::Embedded::Many
134
- end
135
-
136
186
  def traverse_association_chain
137
187
  chain = association_chain.dup
138
188
  doc = nil
@@ -146,10 +196,10 @@ private
146
196
  # root association. First element of the association chain
147
197
  klass = name.classify.constantize
148
198
  klass.where(:_id => node['id']).first
149
- elsif embeds_one?(doc, name)
150
- doc.send(name)
151
- elsif embeds_many?(doc, name)
152
- doc.send(name).where(:_id => node['id']).first
199
+ elsif doc.class.embeds_one?(name)
200
+ doc.get_embedded(name)
201
+ elsif doc.class.embeds_many?(name)
202
+ doc.get_embedded(name).where(:_id => node['id']).first
153
203
  else
154
204
  raise "This should never happen. Please report bug."
155
205
  end
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "mongoid-history"
8
- s.version = "0.3.3"
8
+ s.version = "0.4.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Aaron Qian", "Justin Grimes"]
12
- s.date = "2013-04-01"
12
+ s.date = "2013-07-12"
13
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
14
  s.email = ["aq1018@gmail.com", "justin.mgrimes@gmail.com"]
15
15
  s.extra_rdoc_files = [
@@ -26,7 +26,6 @@ Gem::Specification.new do |s|
26
26
  "README.md",
27
27
  "Rakefile",
28
28
  "VERSION",
29
- "config/mongoid.yml",
30
29
  "lib/mongoid-history.rb",
31
30
  "lib/mongoid/history.rb",
32
31
  "lib/mongoid/history/sweeper.rb",
@@ -37,7 +36,6 @@ Gem::Specification.new do |s|
37
36
  "spec/integration/multi_relation_spec.rb",
38
37
  "spec/integration/nested_embedded_documents_spec.rb",
39
38
  "spec/spec_helper.rb",
40
- "spec/support/database_cleaner.rb",
41
39
  "spec/support/mongoid.rb",
42
40
  "spec/support/mongoid_history.rb",
43
41
  "spec/trackable_spec.rb",
@@ -1,7 +1,7 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Mongoid::History do
4
- before :each do
4
+ before :all do
5
5
  class Post
6
6
  include Mongoid::Document
7
7
  include Mongoid::Timestamps
@@ -11,9 +11,9 @@ describe Mongoid::History do
11
11
  field :body
12
12
  field :rating
13
13
 
14
- embeds_many :comments
15
- embeds_one :section
16
- embeds_many :tags
14
+ embeds_many :comments, store_as: :coms
15
+ embeds_one :section, store_as: :sec
16
+ embeds_many :tags, :cascade_callbacks => true
17
17
 
18
18
  accepts_nested_attributes_for :tags, :allow_destroy => true
19
19
 
@@ -25,9 +25,9 @@ describe Mongoid::History do
25
25
  include Mongoid::Timestamps
26
26
  include Mongoid::History::Trackable
27
27
 
28
- field :title
28
+ field :t, as: :title
29
29
  field :body
30
- embedded_in :post
30
+ embedded_in :commentable, polymorphic: true
31
31
  track_history :on => [:title, :body], :scope => :post, :track_create => true, :track_destroy => true
32
32
  end
33
33
 
@@ -36,7 +36,7 @@ describe Mongoid::History do
36
36
  include Mongoid::Timestamps
37
37
  include Mongoid::History::Trackable
38
38
 
39
- field :title
39
+ field :t, as: :title
40
40
  embedded_in :post
41
41
  track_history :on => [:title], :scope => :post, :track_create => true, :track_destroy => true
42
42
  end
@@ -46,15 +46,19 @@ describe Mongoid::History do
46
46
  include Mongoid::Timestamps
47
47
  include Mongoid::History::Trackable
48
48
 
49
- field :email
50
- field :name
49
+ field :n, as: :name
50
+ field :em, as: :email
51
+ field :phone
52
+ field :address
53
+ field :city
54
+ field :country
51
55
  field :aliases, :type => Array
52
- track_history :except => [:email]
56
+ track_history :except => [:email, :updated_at]
53
57
  end
54
58
 
55
59
  class Tag
56
60
  include Mongoid::Document
57
- include Mongoid::Timestamps
61
+ # include Mongoid::Timestamps (see: https://github.com/mongoid/mongoid/issues/3078)
58
62
  include Mongoid::History::Trackable
59
63
 
60
64
  belongs_to :updated_by, :class_name => "User"
@@ -63,144 +67,151 @@ describe Mongoid::History do
63
67
  track_history :on => [:title], :scope => :post, :track_create => true, :track_destroy => true, :modifier_field => :updated_by
64
68
  end
65
69
 
66
- @user = User.create(:name => "Aaron", :email => "aaron@randomemail.com", :aliases => [ 'bob' ])
67
- @another_user = User.create(:name => "Another Guy", :email => "anotherguy@randomemail.com")
68
- @post = Post.create(:title => "Test", :body => "Post", :modifier => @user, :views => 100)
69
- @comment = @post.comments.create(:title => "test", :body => "comment", :modifier => @user)
70
+ class Foo < Comment
71
+ end
72
+
73
+ @persisted_history_options = Mongoid::History.trackable_class_options
70
74
  end
71
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
+
72
83
  describe "track" do
73
84
  describe "on creation" do
74
85
  it "should have one history track in comment" do
75
- @comment.history_tracks.count.should == 1
86
+ comment.history_tracks.count.should == 1
76
87
  end
77
88
 
78
89
  it "should assign title and body on modified" do
79
- @comment.history_tracks.first.modified.should == {'title' => "test", 'body' => "comment"}
90
+ comment.history_tracks.first.modified.should == {'t' => "test", 'body' => "comment"}
80
91
  end
81
92
 
82
93
  it "should not assign title and body on original" do
83
- @comment.history_tracks.first.original.should == {}
94
+ comment.history_tracks.first.original.should == {}
84
95
  end
85
96
 
86
97
  it "should assign modifier" do
87
- @comment.history_tracks.first.modifier.should == @user
98
+ comment.history_tracks.first.modifier.should == user
88
99
  end
89
100
 
90
101
  it "should assign version" do
91
- @comment.history_tracks.first.version.should == 1
102
+ comment.history_tracks.first.version.should == 1
92
103
  end
93
104
 
94
105
  it "should assign scope" do
95
- @comment.history_tracks.first.scope.should == "post"
106
+ comment.history_tracks.first.scope.should == "post"
96
107
  end
97
108
 
98
109
  it "should assign method" do
99
- @comment.history_tracks.first.action.should == "create"
110
+ comment.history_tracks.first.action.should == "create"
100
111
  end
101
112
 
102
113
  it "should assign association_chain" do
103
114
  expected = [
104
- {'id' => @post.id, 'name' => "Post"},
105
- {'id' => @comment.id, 'name' => "comments"}
115
+ {'id' => post.id, 'name' => "Post"},
116
+ {'id' => comment.id, 'name' => "coms"}
106
117
  ]
107
- @comment.history_tracks.first.association_chain.should == expected
118
+ comment.history_tracks.first.association_chain.should == expected
108
119
  end
109
120
  end
110
121
 
111
122
  describe "on destruction" do
112
123
  it "should have two history track records in post" do
113
124
  lambda {
114
- @post.destroy
125
+ post.destroy
115
126
  }.should change(Tracker, :count).by(1)
116
127
  end
117
128
 
118
129
  it "should assign destroy on track record" do
119
- @post.destroy
120
- @post.history_tracks.last.action.should == "destroy"
130
+ post.destroy
131
+ post.history_tracks.last.action.should == "destroy"
121
132
  end
122
133
 
123
134
  it "should return affected attributes from track record" do
124
- @post.destroy
125
- @post.history_tracks.last.affected["title"].should == "Test"
135
+ post.destroy
136
+ post.history_tracks.last.affected["title"].should == "Test"
126
137
  end
127
138
  end
128
139
 
129
140
  describe "on update non-embedded" do
130
141
  it "should create a history track if changed attributes match tracked attributes" do
131
142
  lambda {
132
- @post.update_attributes(:title => "Another Test")
143
+ post.update_attributes(:title => "Another Test")
133
144
  }.should change(Tracker, :count).by(1)
134
145
  end
135
146
 
136
147
  it "should not create a history track if changed attributes do not match tracked attributes" do
137
148
  lambda {
138
- @post.update_attributes(:rating => "untracked")
149
+ post.update_attributes(:rating => "untracked")
139
150
  }.should change(Tracker, :count).by(0)
140
151
  end
141
152
 
142
153
  it "should assign modified fields" do
143
- @post.update_attributes(:title => "Another Test")
144
- @post.history_tracks.last.modified.should == {
154
+ post.update_attributes(:title => "Another Test")
155
+ post.history_tracks.last.modified.should == {
145
156
  "title" => "Another Test"
146
157
  }
147
158
  end
148
159
 
149
160
  it "should assign method field" do
150
- @post.update_attributes(:title => "Another Test")
151
- @post.history_tracks.last.action.should == "update"
161
+ post.update_attributes(:title => "Another Test")
162
+ post.history_tracks.last.action.should == "update"
152
163
  end
153
164
 
154
165
  it "should assign original fields" do
155
- @post.update_attributes(:title => "Another Test")
156
- @post.history_tracks.last.original.should == {
166
+ post.update_attributes(:title => "Another Test")
167
+ post.history_tracks.last.original.should == {
157
168
  "title" => "Test"
158
169
  }
159
170
  end
160
171
 
161
172
  it "should assign modifier" do
162
- @post.update_attributes(:title => "Another Test")
163
- @post.history_tracks.first.modifier.should == @user
173
+ post.update_attributes(:title => "Another Test")
174
+ post.history_tracks.first.modifier.should == user
164
175
  end
165
176
 
166
177
  it "should assign version on history tracks" do
167
- @post.update_attributes(:title => "Another Test")
168
- @post.history_tracks.first.version.should == 1
178
+ post.update_attributes(:title => "Another Test")
179
+ post.history_tracks.first.version.should == 1
169
180
  end
170
181
 
171
182
  it "should assign version on post" do
172
- @post.update_attributes(:title => "Another Test")
173
- @post.version.should == 1
183
+ post.update_attributes(:title => "Another Test")
184
+ post.version.should == 1
174
185
  end
175
186
 
176
187
  it "should assign scope" do
177
- @post.update_attributes(:title => "Another Test")
178
- @post.history_tracks.first.scope.should == "post"
188
+ post.update_attributes(:title => "Another Test")
189
+ post.history_tracks.first.scope.should == "post"
179
190
  end
180
191
 
181
192
  it "should assign association_chain" do
182
- @post.update_attributes(:title => "Another Test")
183
- @post.history_tracks.last.association_chain.should == [{'id' => @post.id, 'name' => "Post"}]
193
+ post.update_attributes(:title => "Another Test")
194
+ post.history_tracks.last.association_chain.should == [{'id' => post.id, 'name' => "Post"}]
184
195
  end
185
196
 
186
197
  it "should exclude defined options" do
187
- name = @user.name
188
- @user.update_attributes(:name => "Aaron2", :email => "aaronsnewemail@randomemail.com")
189
- @user.history_tracks.first.original.keys.should == [ "name", "updated_at" ]
190
- @user.history_tracks.first.original["name"].should == name
191
- @user.history_tracks.first.modified.keys.should == [ "name", "updated_at" ]
192
- @user.history_tracks.first.modified["name"].should == @user.name
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
193
204
  end
194
205
 
195
206
  it "should undo field changes" do
196
- name = @user.name
197
- @user.update_attributes(:name => "Aaron2", :email => "aaronsnewemail@randomemail.com")
198
- @user.history_tracks.first.undo! nil
199
- @user.reload.name.should == name
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
200
211
  end
201
212
 
202
213
  it "should undo non-existing field changes" do
203
- post = Post.create(:modifier => @user, :views => 100)
214
+ post = Post.create(:modifier => user, :views => 100)
204
215
  post.reload.title.should == nil
205
216
  post.update_attributes(:title => "Aaron2")
206
217
  post.reload.title.should == "Aaron2"
@@ -209,368 +220,463 @@ describe Mongoid::History do
209
220
  end
210
221
 
211
222
  it "should track array changes" do
212
- aliases = @user.aliases
213
- @user.update_attributes(:aliases => [ 'bob', 'joe' ])
214
- @user.history_tracks.first.original["aliases"].should == aliases
215
- @user.history_tracks.first.modified["aliases"].should == @user.aliases
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
216
227
  end
217
228
 
218
229
  it "should undo array changes" do
219
- aliases = @user.aliases
220
- @user.update_attributes(:aliases => [ 'bob', 'joe' ])
221
- @user.history_tracks.first.undo! nil
222
- @user.reload.aliases.should == aliases
230
+ aliases = user.aliases
231
+ user.update_attributes(:aliases => [ 'bob', 'joe' ])
232
+ user.history_tracks.first.undo! nil
233
+ user.reload.aliases.should == aliases
223
234
  end
235
+ end
224
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
225
329
  end
226
330
 
227
331
  describe "on update non-embedded twice" do
228
332
  it "should assign version on post" do
229
- @post.update_attributes(:title => "Test2")
230
- @post.update_attributes(:title => "Test3")
231
- @post.version.should == 2
333
+ post.update_attributes(:title => "Test2")
334
+ post.update_attributes(:title => "Test3")
335
+ post.version.should == 2
232
336
  end
233
337
 
234
338
  it "should create a history track if changed attributes match tracked attributes" do
235
339
  lambda {
236
- @post.update_attributes(:title => "Test2")
237
- @post.update_attributes(:title => "Test3")
340
+ post.update_attributes(:title => "Test2")
341
+ post.update_attributes(:title => "Test3")
238
342
  }.should change(Tracker, :count).by(2)
239
343
  end
240
344
 
241
345
  it "should create a history track of version 2" do
242
- @post.update_attributes(:title => "Test2")
243
- @post.update_attributes(:title => "Test3")
244
- @post.history_tracks.where(:version => 2).first.should_not be_nil
346
+ post.update_attributes(:title => "Test2")
347
+ post.update_attributes(:title => "Test3")
348
+ post.history_tracks.where(:version => 2).first.should_not be_nil
245
349
  end
246
350
 
247
351
  it "should assign modified fields" do
248
- @post.update_attributes(:title => "Test2")
249
- @post.update_attributes(:title => "Test3")
250
- @post.history_tracks.where(:version => 2).first.modified.should == {
352
+ post.update_attributes(:title => "Test2")
353
+ post.update_attributes(:title => "Test3")
354
+ post.history_tracks.where(:version => 2).first.modified.should == {
251
355
  "title" => "Test3"
252
356
  }
253
357
  end
254
358
 
255
359
  it "should assign original fields" do
256
- @post.update_attributes(:title => "Test2")
257
- @post.update_attributes(:title => "Test3")
258
- @post.history_tracks.where(:version => 2).first.original.should == {
360
+ post.update_attributes(:title => "Test2")
361
+ post.update_attributes(:title => "Test3")
362
+ post.history_tracks.where(:version => 2).first.original.should == {
259
363
  "title" => "Test2"
260
364
  }
261
365
  end
262
366
 
263
367
 
264
368
  it "should assign modifier" do
265
- @post.update_attributes(:title => "Another Test", :modifier => @another_user)
266
- @post.history_tracks.last.modifier.should == @another_user
369
+ post.update_attributes(:title => "Another Test", :modifier => another_user)
370
+ post.history_tracks.last.modifier.should == another_user
267
371
  end
268
372
  end
269
373
 
270
374
  describe "on update embedded 1..N (embeds_many)" do
271
375
  it "should assign version on comment" do
272
- @comment.update_attributes(:title => "Test2")
273
- @comment.version.should == 2 # first track generated on creation
376
+ comment.update_attributes(:title => "Test2")
377
+ comment.version.should == 2 # first track generated on creation
274
378
  end
275
379
 
276
380
  it "should create a history track of version 2" do
277
- @comment.update_attributes(:title => "Test2")
278
- @comment.history_tracks.where(:version => 2).first.should_not be_nil
381
+ comment.update_attributes(:title => "Test2")
382
+ comment.history_tracks.where(:version => 2).first.should_not be_nil
279
383
  end
280
384
 
281
385
  it "should assign modified fields" do
282
- @comment.update_attributes(:title => "Test2")
283
- @comment.history_tracks.where(:version => 2).first.modified.should == {
284
- "title" => "Test2"
386
+ comment.update_attributes(:t => "Test2")
387
+ comment.history_tracks.where(:version => 2).first.modified.should == {
388
+ "t" => "Test2"
285
389
  }
286
390
  end
287
391
 
288
392
  it "should assign original fields" do
289
- @comment.update_attributes(:title => "Test2")
290
- @comment.history_tracks.where(:version => 2).first.original.should == {
291
- "title" => "test"
393
+ comment.update_attributes(:title => "Test2")
394
+ comment.history_tracks.where(:version => 2).first.original.should == {
395
+ "t" => "test"
292
396
  }
293
397
  end
294
398
 
295
399
  it "should be possible to undo from parent" do
296
- @comment.update_attributes(:title => "Test 2")
297
- @post.history_tracks.last.undo!(@user)
298
- @comment.reload
299
- @comment.title.should == "test"
400
+ comment.update_attributes(:title => "Test 2")
401
+ user
402
+ post.history_tracks.last.undo!(user)
403
+ comment.reload
404
+ comment.title.should == "test"
300
405
  end
301
406
 
302
407
  it "should assign modifier" do
303
- @post.update_attributes(:title => "Another Test", :modifier => @another_user)
304
- @post.history_tracks.last.modifier.should == @another_user
408
+ post.update_attributes(:title => "Another Test", :modifier => another_user)
409
+ post.history_tracks.last.modifier.should == another_user
305
410
  end
306
411
  end
307
412
 
308
413
  describe "on update embedded 1..1 (embeds_one)" do
414
+ let(:section){ Section.new(:title => 'Technology') }
415
+
309
416
  before(:each) do
310
- @section = Section.new(:title => 'Technology')
311
- @post.section = @section
312
- @post.save!
313
- @post.reload
314
- @section = @post.section
417
+ post.section = section
418
+ post.save!
419
+ post.reload
420
+ section = post.section
315
421
  end
316
422
 
317
423
  it "should assign version on create section" do
318
- @section.version.should == 1
424
+ section.version.should == 1
319
425
  end
320
426
 
321
427
  it "should assign version on section" do
322
- @section.update_attributes(:title => 'Technology 2')
323
- @section.version.should == 2 # first track generated on creation
428
+ section.update_attributes(:title => 'Technology 2')
429
+ section.version.should == 2 # first track generated on creation
324
430
  end
325
431
 
326
432
  it "should create a history track of version 2" do
327
- @section.update_attributes(:title => 'Technology 2')
328
- @section.history_tracks.where(:version => 2).first.should_not be_nil
433
+ section.update_attributes(:title => 'Technology 2')
434
+ section.history_tracks.where(:version => 2).first.should_not be_nil
329
435
  end
330
436
 
331
437
  it "should assign modified fields" do
332
- @section.update_attributes(:title => 'Technology 2')
333
- @section.history_tracks.where(:version => 2).first.modified.should == {
334
- "title" => "Technology 2"
438
+ section.update_attributes(:title => 'Technology 2')
439
+ section.history_tracks.where(:version => 2).first.modified.should == {
440
+ "t" => "Technology 2"
335
441
  }
336
442
  end
337
443
 
338
444
  it "should assign original fields" do
339
- @section.update_attributes(:title => 'Technology 2')
340
- @section.history_tracks.where(:version => 2).first.original.should == {
341
- "title" => "Technology"
445
+ section.update_attributes(:title => 'Technology 2')
446
+ section.history_tracks.where(:version => 2).first.original.should == {
447
+ "t" => "Technology"
342
448
  }
343
449
  end
344
450
 
345
451
  it "should be possible to undo from parent" do
346
- @section.update_attributes(:title => 'Technology 2')
347
- @post.history_tracks.last.undo!(@user)
348
- @section.reload
349
- @section.title.should == "Technology"
452
+ section.update_attributes(:title => 'Technology 2')
453
+ post.history_tracks.last.undo!(user)
454
+ section.reload
455
+ section.title.should == "Technology"
350
456
  end
351
457
 
352
458
  it "should assign modifier" do
353
- @section.update_attributes(:title => "Business", :modifier => @another_user)
354
- @post.history_tracks.last.modifier.should == @another_user
459
+ section.update_attributes(:title => "Business", :modifier => another_user)
460
+ post.history_tracks.last.modifier.should == another_user
355
461
  end
356
462
  end
357
463
 
358
464
  describe "on destroy embedded" do
359
465
  it "should be possible to re-create destroyed embedded" do
360
- @comment.destroy
361
- @comment.history_tracks.last.undo!(@user)
362
- @post.reload
363
- @post.comments.first.title.should == "test"
466
+ comment.destroy
467
+ comment.history_tracks.last.undo!(user)
468
+ post.reload
469
+ post.comments.first.title.should == "test"
364
470
  end
365
471
 
366
472
  it "should be possible to re-create destroyed embedded from parent" do
367
- @comment.destroy
368
- @post.history_tracks.last.undo!(@user)
369
- @post.reload
370
- @post.comments.first.title.should == "test"
473
+ comment.destroy
474
+ post.history_tracks.last.undo!(user)
475
+ post.reload
476
+ post.comments.first.title.should == "test"
371
477
  end
372
478
 
373
479
  it "should be possible to destroy after re-create embedded from parent" do
374
- @comment.destroy
375
- @post.history_tracks.last.undo!(@user)
376
- @post.history_tracks.last.undo!(@user)
377
- @post.reload
378
- @post.comments.count.should == 0
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
379
485
  end
380
486
 
381
487
  it "should be possible to create with redo after undo create embedded from parent" do
382
- @post.comments.create!(:title => "The second one")
383
- @track = @post.history_tracks.last
384
- @track.undo!(@user)
385
- @track.redo!(@user)
386
- @post.reload
387
- @post.comments.count.should == 2
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
388
495
  end
389
496
  end
390
497
 
391
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
+
392
503
  before(:each) do
393
504
  Mongoid.instantiate_observers
394
- Thread.current[:mongoid_history_sweeper_controller] = self
395
- self.stub!(:current_user).and_return @user
396
- @tag_foo = @post.tags.create(:title => "foo", :updated_by => @user)
397
- @tag_bar = @post.tags.create(:title => "bar")
505
+ Thread.current[:mongoid_history_sweeper_controller] = Mongoid::History::Sweeper.instance
506
+ Mongoid::History::Sweeper.instance.stub(:current_user){ user }
398
507
  end
399
508
 
400
- it "should have cascaded the creation callbacks and set timestamps" do
401
- @tag_foo.created_at.should_not be_nil
402
- @tag_foo.updated_at.should_not be_nil
403
- end
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
404
514
 
405
515
  it "should allow an update through the parent model" do
406
- update_hash = { "post" => { "tags_attributes" => { "1234" => { "id" => @tag_bar.id, "title" => "baz" } } } }
407
- @post.update_attributes(update_hash["post"])
408
- @post.tags.last.title.should == "baz"
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"
409
519
  end
410
520
 
411
521
  it "should be possible to destroy through parent model using canoncial _destroy macro" do
412
- @post.tags.count.should == 2
413
- update_hash = { "post" => { "tags_attributes" => { "1234" => { "id" => @tag_bar.id, "title" => "baz", "_destroy" => "true"} } } }
414
- @post.update_attributes(update_hash["post"])
415
- @post.tags.count.should == 1
416
- @post.history_tracks.last.action.should == "destroy"
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"
417
528
  end
418
529
 
419
530
  it "should write relationship name for association_chain hiearchy instead of class name when using _destroy macro" do
420
- update_hash = {"tags_attributes" => { "1234" => { "id" => @tag_foo.id, "_destroy" => "1"} } }
421
- @post.update_attributes(update_hash)
531
+ update_hash = {"tags_attributes" => { "1234" => { "id" => tag_foo.id, "_destroy" => "1"} } }
532
+ post.update_attributes(update_hash)
422
533
 
423
534
  # historically this would have evaluated to 'Tags' and an error would be thrown
424
535
  # on any call that walked up the association_chain, e.g. 'trackable'
425
- @tag_foo.history_tracks.last.association_chain.last["name"].should == "tags"
426
- lambda{ @tag_foo.history_tracks.last.trackable }.should_not raise_error
536
+ tag_foo.history_tracks.last.association_chain.last["name"].should == "tags"
537
+ lambda{ tag_foo.history_tracks.last.trackable }.should_not raise_error
427
538
  end
428
539
 
429
540
  it "should save modifier" do
430
- @tag_foo.history_tracks.last.modifier.should eq @user
431
- @tag_bar.history_tracks.last.modifier.should eq @user
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
432
544
  end
433
545
  end
434
546
 
435
547
  describe "non-embedded" do
436
548
  it "should undo changes" do
437
- @post.update_attributes(:title => "Test2")
438
- @post.history_tracks.where(:version => 1).last.undo!(@user)
439
- @post.reload
440
- @post.title.should == "Test"
549
+ post.update_attributes(:title => "Test2")
550
+ post.history_tracks.where(:version => 1).last.undo!(user)
551
+ post.reload
552
+ post.title.should == "Test"
441
553
  end
442
554
 
443
555
  it "should undo destruction" do
444
- @post.destroy
445
- @post.history_tracks.where(:version => 1).last.undo!(@user)
446
- Post.find(@post.id).title.should == "Test"
556
+ post.destroy
557
+ post.history_tracks.where(:version => 1).last.undo!(user)
558
+ Post.find(post.id).title.should == "Test"
447
559
  end
448
560
 
449
561
  it "should create a new history track after undo" do
450
- @post.update_attributes(:title => "Test2")
451
- @post.history_tracks.last.undo!(@user)
452
- @post.reload
453
- @post.history_tracks.count.should == 3
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
454
567
  end
455
568
 
456
- it "should assign @user as the modifier of the newly created history track" do
457
- @post.update_attributes(:title => "Test2")
458
- @post.history_tracks.where(:version => 1).last.undo!(@user)
459
- @post.reload
460
- @post.history_tracks.where(:version => 2).last.modifier.should == @user
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
461
574
  end
462
575
 
463
576
  it "should stay the same after undo and redo" do
464
- @post.update_attributes(:title => "Test2")
465
- @track = @post.history_tracks.last
466
- @track.undo!(@user)
467
- @track.redo!(@user)
468
- @post2 = Post.where(:_id => @post.id).first
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
469
582
 
470
- @post.title.should == @post2.title
583
+ post.title.should == post2.title
471
584
  end
472
585
 
473
586
  it "should be destroyed after undo and redo" do
474
- @post.destroy
475
- @track = @post.history_tracks.where(:version => 1).last
476
- @track.undo!(@user)
477
- @track.redo!(@user)
478
- Post.where(:_id => @post.id).first.should == nil
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
479
592
  end
480
593
  end
481
594
 
482
595
  describe "embedded" do
483
596
  it "should undo changes" do
484
- @comment.update_attributes(:title => "Test2")
485
- @comment.history_tracks.where(:version => 2).first.undo!(@user)
486
- # reloading an embedded document === KAMIKAZE
487
- # at least for the current release of mongoid...
488
- @post.reload
489
- @comment = @post.comments.first
490
- @comment.title.should == "test"
597
+ comment.update_attributes(:title => "Test2")
598
+ comment.history_tracks.where(:version => 2).first.undo!(user)
599
+ comment.reload
600
+ comment.title.should == "test"
491
601
  end
492
602
 
493
603
  it "should create a new history track after undo" do
494
- @comment.update_attributes(:title => "Test2")
495
- @comment.history_tracks.where(:version => 2).first.undo!(@user)
496
- @post.reload
497
- @comment = @post.comments.first
498
- @comment.history_tracks.count.should == 3
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
499
608
  end
500
609
 
501
- it "should assign @user as the modifier of the newly created history track" do
502
- @comment.update_attributes(:title => "Test2")
503
- @comment.history_tracks.where(:version => 2).first.undo!(@user)
504
- @post.reload
505
- @comment = @post.comments.first
506
- @comment.history_tracks.where(:version => 3).first.modifier.should == @user
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
507
615
  end
508
616
 
509
617
  it "should stay the same after undo and redo" do
510
- @comment.update_attributes(:title => "Test2")
511
- @track = @comment.history_tracks.where(:version => 2).first
512
- @track.undo!(@user)
513
- @track.redo!(@user)
514
- @post2 = Post.where(:_id => @post.id).first
515
- @comment2 = @post2.comments.first
516
-
517
- @comment.title.should == @comment2.title
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"
518
624
  end
519
625
  end
520
626
 
521
627
  describe "trackables" do
522
628
  before :each do
523
- @comment.update_attributes(:title => "Test2") # version == 2
524
- @comment.update_attributes(:title => "Test3") # version == 3
525
- @comment.update_attributes(:title => "Test4") # version == 4
629
+ comment.update_attributes(:title => "Test2") # version == 2
630
+ comment.update_attributes(:title => "Test3") # version == 3
631
+ comment.update_attributes(:title => "Test4") # version == 4
526
632
  end
527
633
 
528
634
  describe "undo" do
529
635
  it "should recognize :from, :to options" do
530
- @comment.undo! @user, :from => 4, :to => 2
531
- @comment.title.should == "test"
636
+ comment.undo! user, :from => 4, :to => 2
637
+ comment.title.should == "test"
532
638
  end
533
639
 
534
640
  it "should recognize parameter as version number" do
535
- @comment.undo! @user, 3
536
- @comment.title.should == "Test2"
641
+ comment.undo! user, 3
642
+ comment.title.should == "Test2"
537
643
  end
538
644
 
539
645
  it "should undo last version when no parameter is specified" do
540
- @comment.undo! @user
541
- @comment.title.should == "Test3"
646
+ comment.undo! user
647
+ comment.title.should == "Test3"
542
648
  end
543
649
 
544
650
  it "should recognize :last options" do
545
- @comment.undo! @user, :last => 2
546
- @comment.title.should == "Test2"
651
+ comment.undo! user, :last => 2
652
+ comment.title.should == "Test2"
547
653
  end
548
654
 
549
655
  end
550
656
 
551
657
  describe "redo" do
552
658
  before :each do
553
- @comment.update_attributes(:title => "Test5")
659
+ comment.update_attributes(:title => "Test5")
554
660
  end
555
661
 
556
662
  it "should recognize :from, :to options" do
557
- @comment.redo! @user, :from => 2, :to => 4
558
- @comment.title.should == "Test4"
663
+ comment.redo! user, :from => 2, :to => 4
664
+ comment.title.should == "Test4"
559
665
  end
560
666
 
561
667
  it "should recognize parameter as version number" do
562
- @comment.redo! @user, 2
563
- @comment.title.should == "Test2"
668
+ comment.redo! user, 2
669
+ comment.title.should == "Test2"
564
670
  end
565
671
 
566
672
  it "should redo last version when no parameter is specified" do
567
- @comment.redo! @user
568
- @comment.title.should == "Test5"
673
+ comment.redo! user
674
+ comment.title.should == "Test5"
569
675
  end
570
676
 
571
677
  it "should recognize :last options" do
572
- @comment.redo! @user, :last => 1
573
- @comment.title.should == "Test5"
678
+ comment.redo! user, :last => 1
679
+ comment.title.should == "Test5"
574
680
  end
575
681
 
576
682
  end
@@ -593,18 +699,56 @@ describe Mongoid::History do
593
699
 
594
700
  track = sausage.history_tracks.last
595
701
 
596
- track.undo! @user
702
+ track.undo! user
597
703
  sausage.reload.flavour.should == "Apple"
598
704
 
599
- track.redo! @user
705
+ track.redo! user
600
706
  sausage.reload.flavour.should == "Guinness"
601
707
 
602
708
  sausage.destroy
603
709
  sausage.history_tracks.last.action.should == "destroy"
604
- sausage.history_tracks.last.undo! @user
710
+ sausage.history_tracks.last.undo! user
605
711
  sausage.reload.flavour.should == "Guinness"
606
712
  end
607
713
  end
608
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
609
753
  end
610
754
  end