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