mongoid-history 0.6.1 → 0.8.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +5 -5
  2. data/.rubocop_todo.yml +50 -43
  3. data/.travis.yml +2 -1
  4. data/CHANGELOG.md +33 -0
  5. data/CONTRIBUTING.md +4 -4
  6. data/Gemfile +16 -11
  7. data/LICENSE.txt +1 -1
  8. data/README.md +97 -20
  9. data/RELEASING.md +67 -0
  10. data/Rakefile +1 -1
  11. data/UPGRADING.md +53 -0
  12. data/lib/mongoid/history.rb +33 -8
  13. data/lib/mongoid/history/attributes/base.rb +2 -2
  14. data/lib/mongoid/history/attributes/create.rb +6 -11
  15. data/lib/mongoid/history/attributes/destroy.rb +9 -9
  16. data/lib/mongoid/history/attributes/update.rb +82 -21
  17. data/lib/mongoid/history/options.rb +60 -67
  18. data/lib/mongoid/history/trackable.rb +173 -91
  19. data/lib/mongoid/history/tracker.rb +22 -13
  20. data/lib/mongoid/history/version.rb +1 -1
  21. data/mongoid-history.gemspec +1 -1
  22. data/perf/benchmark_modified_attributes_for_create.rb +65 -0
  23. data/perf/gc_suite.rb +21 -0
  24. data/spec/integration/embedded_in_polymorphic_spec.rb +26 -49
  25. data/spec/integration/integration_spec.rb +186 -146
  26. data/spec/integration/multi_relation_spec.rb +14 -20
  27. data/spec/integration/multiple_trackers_spec.rb +68 -0
  28. data/spec/integration/nested_embedded_documents_spec.rb +31 -51
  29. data/spec/integration/nested_embedded_documents_tracked_in_parent_spec.rb +124 -0
  30. data/spec/integration/nested_embedded_polymorphic_documents_spec.rb +64 -76
  31. data/spec/integration/subclasses_spec.rb +24 -6
  32. data/spec/integration/track_history_order_spec.rb +84 -0
  33. data/spec/integration/validation_failure_spec.rb +76 -0
  34. data/spec/spec_helper.rb +6 -1
  35. data/spec/support/mongoid_history.rb +0 -1
  36. data/spec/unit/attributes/base_spec.rb +17 -26
  37. data/spec/unit/attributes/create_spec.rb +152 -125
  38. data/spec/unit/attributes/destroy_spec.rb +69 -59
  39. data/spec/unit/attributes/update_spec.rb +165 -33
  40. data/spec/unit/callback_options_spec.rb +165 -0
  41. data/spec/unit/embedded_methods_spec.rb +42 -24
  42. data/spec/unit/history_spec.rb +33 -10
  43. data/spec/unit/my_instance_methods_spec.rb +191 -121
  44. data/spec/unit/options_spec.rb +121 -82
  45. data/spec/unit/singleton_methods_spec.rb +168 -100
  46. data/spec/unit/trackable_spec.rb +549 -148
  47. data/spec/unit/tracker_spec.rb +81 -54
  48. metadata +19 -6
@@ -1,5 +1,5 @@
1
1
  module Mongoid
2
2
  module History
3
- VERSION = '0.6.1'
3
+ VERSION = '0.8.3'.freeze
4
4
  end
5
5
  end
@@ -20,6 +20,6 @@ Gem::Specification.new do |s|
20
20
 
21
21
  s.add_runtime_dependency 'easy_diff'
22
22
  s.add_runtime_dependency 'mongoid', '>= 3.0'
23
- s.add_runtime_dependency 'mongoid-compatibility'
23
+ s.add_runtime_dependency 'mongoid-compatibility', '>= 0.5.1'
24
24
  s.add_runtime_dependency 'activesupport'
25
25
  end
@@ -0,0 +1,65 @@
1
+ $LOAD_PATH.push File.expand_path('../../lib', __FILE__)
2
+
3
+ require 'mongoid'
4
+ require 'mongoid/history'
5
+
6
+ require 'benchmark/ips'
7
+ require './perf/gc_suite'
8
+
9
+ Mongoid.connect_to('mongoid_history_perf_test')
10
+ Mongo::Logger.logger.level = ::Logger::FATAL
11
+ Mongoid.purge!
12
+
13
+ Attributes = Mongoid::History::Attributes
14
+
15
+ module ZeroPointEight
16
+ class Create < Attributes::Create
17
+ def attributes
18
+ @attributes = {}
19
+ trackable.attributes.each do |k, v|
20
+ next unless trackable_class.tracked_field?(k, :create)
21
+ modified = if changes[k]
22
+ changes[k].class == Array ? changes[k].last : changes[k]
23
+ else
24
+ v
25
+ end
26
+ @attributes[k] = [nil, format_field(k, modified)]
27
+ end
28
+ insert_embeds_one_changes
29
+ insert_embeds_many_changes
30
+ @attributes
31
+ end
32
+ end
33
+ end
34
+
35
+ class Person
36
+ include Mongoid::Document
37
+ include Mongoid::History::Trackable
38
+
39
+ field :first_name, type: String
40
+ field :last_name, type: String
41
+ field :birth_date, type: Date
42
+ field :title, type: String
43
+
44
+ track_history on: %i[first_name last_name birth_date]
45
+ end
46
+
47
+ new_person = Person.new(first_name: 'Eliot', last_name: 'Horowitz', birth_date: '1981-05-01', title: 'CTO')
48
+
49
+ Benchmark.ips do |bm|
50
+ bm.config(suite: GCSuite.new)
51
+
52
+ bm.report('HEAD') do
53
+ Attributes::Create.new(new_person).attributes
54
+ end
55
+
56
+ bm.report('v0.8.2') do
57
+ ZeroPointEight::Create.new(new_person).attributes
58
+ end
59
+
60
+ bm.report('v0.5.0') do
61
+ new_person.attributes.each_with_object({}) { |(k, v), h| h[k] = [nil, v] }.select { |k, _| new_person.class.tracked_field?(k, :create) }
62
+ end
63
+
64
+ bm.compare!
65
+ end
@@ -0,0 +1,21 @@
1
+ class GCSuite
2
+ def warming(*)
3
+ run_gc
4
+ end
5
+
6
+ def running(*)
7
+ run_gc
8
+ end
9
+
10
+ def warmup_stats(*); end
11
+
12
+ def add_report(*); end
13
+
14
+ private
15
+
16
+ def run_gc
17
+ GC.enable
18
+ GC.start
19
+ GC.disable
20
+ end
21
+ end
@@ -1,7 +1,7 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Mongoid::History::Tracker do
4
- before :all do
4
+ before :each do
5
5
  class RealState
6
6
  include Mongoid::Document
7
7
  include Mongoid::History::Trackable
@@ -9,14 +9,9 @@ describe Mongoid::History::Tracker do
9
9
  field :name, type: String
10
10
  belongs_to :user
11
11
  embeds_one :address, class_name: 'Contact', as: :contactable
12
- embeds_one :embone, as: :embedable
13
-
14
- track_history on: :all, # track title and body fields only, default is :all
15
- modifier_field: :modifier, # adds "referenced_in :modifier" to track who made the change, default is :modifier
16
- version_field: :version, # adds "field :version, :type => Integer" to track current version, default is :version
17
- track_create: true, # track document creation, default is false
18
- track_update: true, # track document updates, default is true
19
- track_destroy: false # track document destruction, default is false
12
+ embeds_one :embone, as: :embedable
13
+
14
+ track_history
20
15
  end
21
16
 
22
17
  class Company
@@ -29,12 +24,7 @@ describe Mongoid::History::Tracker do
29
24
  embeds_one :second_address, class_name: 'Contact', as: :contactable
30
25
  embeds_one :embone, as: :embedable
31
26
 
32
- track_history on: :all, # track title and body fields only, default is :all
33
- modifier_field: :modifier, # adds "referenced_in :modifier" to track who made the change, default is :modifier
34
- version_field: :version, # adds "field :version, :type => Integer" to track current version, default is :version
35
- track_create: true, # track document creation, default is false
36
- track_update: true, # track document updates, default is true
37
- track_destroy: false # track document destruction, default is false
27
+ track_history
38
28
  end
39
29
 
40
30
  class Embone
@@ -44,13 +34,7 @@ describe Mongoid::History::Tracker do
44
34
  field :name
45
35
  embedded_in :embedable, polymorphic: true
46
36
 
47
- track_history on: :all, # track title and body fields only, default is :all
48
- modifier_field: :modifier, # adds "referenced_in :modifier" to track who made the change, default is :modifier
49
- version_field: :version, # adds "field :version, :type => Integer" to track current version, default is :version
50
- track_create: true, # track document creation, default is false
51
- track_update: true, # track document updates, default is true
52
- track_destroy: false, # track document destruction, default is false
53
- scope: :embedable
37
+ track_history scope: :embedable
54
38
  end
55
39
 
56
40
  class Contact
@@ -62,13 +46,7 @@ describe Mongoid::History::Tracker do
62
46
  field :state
63
47
  embedded_in :contactable, polymorphic: true
64
48
 
65
- track_history on: :all, # track title and body fields only, default is :all
66
- modifier_field: :modifier, # adds "referenced_in :modifier" to track who made the change, default is :modifier
67
- version_field: :version, # adds "field :version, :type => Integer" to track current version, default is :version
68
- track_create: true, # track document creation, default is false
69
- track_update: true, # track document updates, default is true
70
- track_destroy: false, # track document destruction, default is false
71
- scope: [:real_state, :company]
49
+ track_history scope: %i[real_state company]
72
50
  end
73
51
 
74
52
  class User
@@ -78,58 +56,57 @@ describe Mongoid::History::Tracker do
78
56
  end
79
57
  end
80
58
 
81
- it 'tracks history for nested embedded documents with polymorphic relations' do
82
- user = User.new
83
- user.save!
59
+ after :each do
60
+ Object.send(:remove_const, :RealState)
61
+ Object.send(:remove_const, :Company)
62
+ Object.send(:remove_const, :Embone)
63
+ Object.send(:remove_const, :Contact)
64
+ Object.send(:remove_const, :User)
65
+ end
84
66
 
85
- real_state = user.real_states.build(name: 'rs_name')
67
+ let!(:user) { User.create! }
68
+
69
+ it 'tracks history for nested embedded documents with polymorphic relations' do
70
+ real_state = user.real_states.build(name: 'rs_name', modifier: user)
86
71
  real_state.save!
87
- real_state.build_address(address: 'Main Street #123', city: 'Highland Park', state: 'IL').save!
72
+ real_state.build_address(address: 'Main Street #123', city: 'Highland Park', state: 'IL', modifier: user).save!
88
73
  expect(real_state.history_tracks.count).to eq(2)
89
74
  expect(real_state.address.history_tracks.count).to eq(1)
90
75
 
91
76
  real_state.reload
92
- real_state.address.update_attribute(:address, 'Second Street')
77
+ real_state.address.update_attributes!(address: 'Second Street', modifier: user)
93
78
  expect(real_state.history_tracks.count).to eq(3)
94
79
  expect(real_state.address.history_tracks.count).to eq(2)
95
80
  expect(real_state.history_tracks.last.action).to eq('update')
96
81
 
97
- real_state.build_embone(name: 'Lorem ipsum').save!
82
+ real_state.build_embone(name: 'Lorem ipsum', modifier: user).save!
98
83
  expect(real_state.history_tracks.count).to eq(4)
99
84
  expect(real_state.embone.history_tracks.count).to eq(1)
100
85
  expect(real_state.embone.history_tracks.last.action).to eq('create')
101
86
  expect(real_state.embone.history_tracks.last.association_chain.last['name']).to eq('embone')
102
87
 
103
- company = user.companies.build(name: 'co_name')
88
+ company = user.companies.build(name: 'co_name', modifier: user)
104
89
  company.save!
105
- company.build_address(address: 'Main Street #456', city: 'Evanston', state: 'IL').save!
90
+ company.build_address(address: 'Main Street #456', city: 'Evanston', state: 'IL', modifier: user).save!
106
91
  expect(company.history_tracks.count).to eq(2)
107
92
  expect(company.address.history_tracks.count).to eq(1)
108
93
 
109
94
  company.reload
110
- company.address.update_attribute(:address, 'Second Street')
95
+ company.address.update_attributes!(address: 'Second Street', modifier: user)
111
96
  expect(company.history_tracks.count).to eq(3)
112
97
  expect(company.address.history_tracks.count).to eq(2)
113
98
  expect(company.history_tracks.last.action).to eq('update')
114
99
 
115
- company.build_second_address(address: 'Main Street #789', city: 'Highland Park', state: 'IL').save!
100
+ company.build_second_address(address: 'Main Street #789', city: 'Highland Park', state: 'IL', modifier: user).save!
116
101
  expect(company.history_tracks.count).to eq(4)
117
102
  expect(company.second_address.history_tracks.count).to eq(1)
118
103
  expect(company.second_address.history_tracks.last.action).to eq('create')
119
104
  expect(company.second_address.history_tracks.last.association_chain.last['name']).to eq('second_address')
120
105
 
121
- company.build_embone(name: 'Lorem ipsum').save!
106
+ company.build_embone(name: 'Lorem ipsum', modifier: user).save!
122
107
  expect(company.history_tracks.count).to eq(5)
123
108
  expect(company.embone.history_tracks.count).to eq(1)
124
109
  expect(company.embone.history_tracks.last.action).to eq('create')
125
110
  expect(company.embone.history_tracks.last.association_chain.last['name']).to eq('embone')
126
111
  end
127
-
128
- after :all do
129
- Object.send(:remove_const, :RealState)
130
- Object.send(:remove_const, :Company)
131
- Object.send(:remove_const, :Embone)
132
- Object.send(:remove_const, :Contact)
133
- Object.send(:remove_const, :User)
134
- end
135
112
  end
@@ -1,7 +1,7 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Mongoid::History do
4
- before :all do
4
+ before :each do
5
5
  class Post
6
6
  include Mongoid::Document
7
7
  include Mongoid::Timestamps
@@ -18,7 +18,7 @@ describe Mongoid::History do
18
18
 
19
19
  accepts_nested_attributes_for :tags, allow_destroy: true
20
20
 
21
- track_history on: [:title, :body], track_destroy: true
21
+ track_history on: %i[title body], track_destroy: true
22
22
  end
23
23
 
24
24
  class Comment
@@ -29,7 +29,8 @@ describe Mongoid::History do
29
29
  field :t, as: :title
30
30
  field :body
31
31
  embedded_in :commentable, polymorphic: true
32
- track_history on: [:title, :body], scope: :post, track_create: true, track_destroy: 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
33
34
  end
34
35
 
35
36
  class Section
@@ -54,7 +55,7 @@ describe Mongoid::History do
54
55
  field :city
55
56
  field :country
56
57
  field :aliases, type: Array
57
- track_history except: [:email, :updated_at]
58
+ track_history except: %i[email updated_at], modifier_field_optional: true
58
59
  end
59
60
 
60
61
  class Tag
@@ -70,16 +71,22 @@ describe Mongoid::History do
70
71
 
71
72
  class Foo < Comment
72
73
  end
74
+ end
73
75
 
74
- @persisted_history_options = Mongoid::History.trackable_class_options
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)
75
83
  end
76
84
 
77
- before(:each) { Mongoid::History.trackable_class_options = @persisted_history_options }
78
85
  let(:user) { User.create!(name: 'Aaron', email: 'aaron@randomemail.com', aliases: ['bob'], country: 'Canada', city: 'Toronto', address: '21 Jump Street') }
79
86
  let(:another_user) { User.create!(name: 'Another Guy', email: 'anotherguy@randomemail.com') }
80
87
  let(:post) { Post.create!(title: 'Test', body: 'Post', modifier: user, views: 100) }
81
88
  let(:comment) { post.comments.create!(title: 'test', body: 'comment', modifier: user) }
82
- let(:tag) { Tag.create!(title: 'test') }
89
+ let(:tag) { Tag.create!(title: 'test', updated_by: user) }
83
90
 
84
91
  describe 'track' do
85
92
  describe 'on creation' do
@@ -96,7 +103,7 @@ describe Mongoid::History do
96
103
  end
97
104
 
98
105
  it 'should assign modifier' do
99
- expect(comment.history_tracks.first.modifier).to eq(user)
106
+ expect(comment.history_tracks.first.modifier.id).to eq(user.id)
100
107
  end
101
108
 
102
109
  it 'should assign version' do
@@ -122,6 +129,7 @@ describe Mongoid::History do
122
129
 
123
130
  describe 'on destruction' do
124
131
  it 'should have two history track records in post' do
132
+ post # This will create history track records for creation
125
133
  expect do
126
134
  post.destroy
127
135
  end.to change(Tracker, :count).by(1)
@@ -147,97 +155,100 @@ describe Mongoid::History do
147
155
 
148
156
  describe 'on update non-embedded' do
149
157
  it 'should create a history track if changed attributes match tracked attributes' do
158
+ post # This will create history track records for creation
150
159
  expect do
151
- post.update_attributes(title: 'Another Test')
160
+ post.update_attributes!(title: 'Another Test')
152
161
  end.to change(Tracker, :count).by(1)
153
162
  end
154
163
 
155
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
156
166
  expect do
157
- post.update_attributes(rating: 'untracked')
167
+ post.update_attributes!(rating: 'untracked')
158
168
  end.to change(Tracker, :count).by(0)
159
169
  end
160
170
 
161
171
  it 'should assign modified fields' do
162
- post.update_attributes(title: 'Another Test')
172
+ post.update_attributes!(title: 'Another Test')
163
173
  expect(post.history_tracks.last.modified).to eq(
164
174
  'title' => 'Another Test'
165
175
  )
166
176
  end
167
177
 
168
178
  it 'should assign method field' do
169
- post.update_attributes(title: 'Another Test')
179
+ post.update_attributes!(title: 'Another Test')
170
180
  expect(post.history_tracks.last.action).to eq('update')
171
181
  end
172
182
 
173
183
  it 'should assign original fields' do
174
- post.update_attributes(title: 'Another Test')
184
+ post.update_attributes!(title: 'Another Test')
175
185
  expect(post.history_tracks.last.original).to eq(
176
186
  'title' => 'Test'
177
187
  )
178
188
  end
179
189
 
180
190
  it 'should assign modifier' do
181
- post.update_attributes(title: 'Another Test')
182
- expect(post.history_tracks.first.modifier).to eq(user)
191
+ post.update_attributes!(title: 'Another Test')
192
+ expect(post.history_tracks.first.modifier.id).to eq(user.id)
183
193
  end
184
194
 
185
195
  it 'should assign version on history tracks' do
186
- post.update_attributes(title: 'Another Test')
196
+ post.update_attributes!(title: 'Another Test')
187
197
  expect(post.history_tracks.first.version).to eq(1)
188
198
  end
189
199
 
190
200
  it 'should assign version on post' do
191
- post.update_attributes(title: 'Another Test')
192
- expect(post.version).to eq(1)
201
+ expect(post.version).to eq(1) # Created
202
+ post.update_attributes!(title: 'Another Test')
203
+ expect(post.version).to eq(2) # Updated
193
204
  end
194
205
 
195
206
  it 'should assign scope' do
196
- post.update_attributes(title: 'Another Test')
207
+ post.update_attributes!(title: 'Another Test')
197
208
  expect(post.history_tracks.first.scope).to eq('post')
198
209
  end
199
210
 
200
211
  it 'should assign association_chain' do
201
- post.update_attributes(title: 'Another Test')
212
+ post.update_attributes!(title: 'Another Test')
202
213
  expect(post.history_tracks.last.association_chain).to eq([{ 'id' => post.id, 'name' => 'Post' }])
203
214
  end
204
215
 
205
216
  it 'should exclude defined options' do
206
217
  name = user.name
207
- user.update_attributes(name: 'Aaron2', email: 'aaronsnewemail@randomemail.com')
208
- expect(user.history_tracks.first.original.keys).to eq(['n'])
209
- expect(user.history_tracks.first.original['n']).to eq(name)
210
- expect(user.history_tracks.first.modified.keys).to eq(['n'])
211
- expect(user.history_tracks.first.modified['n']).to eq(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)
212
223
  end
213
224
 
214
225
  it 'should undo field changes' do
215
226
  name = user.name
216
- user.update_attributes(name: 'Aaron2', email: 'aaronsnewemail@randomemail.com')
217
- user.history_tracks.first.undo! nil
227
+ user.update_attributes!(name: 'Aaron2', email: 'aaronsnewemail@randomemail.com')
228
+ user.history_tracks.last.undo! nil
218
229
  expect(user.reload.name).to eq(name)
219
230
  end
220
231
 
221
232
  it 'should undo non-existing field changes' do
222
233
  post = Post.create!(modifier: user, views: 100)
223
234
  expect(post.reload.title).to be_nil
224
- post.update_attributes(title: 'Aaron2')
235
+ post.update_attributes!(title: 'Aaron2')
225
236
  expect(post.reload.title).to eq('Aaron2')
226
- post.history_tracks.first.undo! nil
237
+ post.history_tracks.last.undo! user
227
238
  expect(post.reload.title).to be_nil
228
239
  end
229
240
 
230
241
  it 'should track array changes' do
231
242
  aliases = user.aliases
232
- user.update_attributes(aliases: %w(bob joe))
233
- expect(user.history_tracks.first.original['aliases']).to eq(aliases)
234
- expect(user.history_tracks.first.modified['aliases']).to eq(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)
235
246
  end
236
247
 
237
248
  it 'should undo array changes' do
238
249
  aliases = user.aliases
239
- user.update_attributes(aliases: %w(bob joe))
240
- user.history_tracks.first.undo! nil
250
+ user.update_attributes!(aliases: %w[bob joe])
251
+ user.history_tracks.last.undo! nil
241
252
  expect(user.reload.aliases).to eq(aliases)
242
253
  end
243
254
  end
@@ -259,9 +270,9 @@ describe Mongoid::History do
259
270
  end
260
271
  end
261
272
  context 'update action' do
262
- subject { user.history_tracks.first.tracked_changes }
273
+ subject { user.history_tracks.last.tracked_changes }
263
274
  before do
264
- user.update_attributes(name: 'Aaron2', email: nil, country: '', city: nil, phone: '867-5309', aliases: ['', 'bill', 'james'])
275
+ user.update_attributes!(name: 'Aaron2', email: nil, country: '', city: nil, phone: '867-5309', aliases: ['', 'bill', 'james'])
265
276
  end
266
277
  it { is_expected.to be_a HashWithIndifferentAccess }
267
278
  it 'should track changed field' do
@@ -305,9 +316,9 @@ describe Mongoid::History do
305
316
  end
306
317
  end
307
318
  context 'update action' do
308
- subject { user.history_tracks.first.tracked_edits }
319
+ subject { user.history_tracks.last.tracked_edits }
309
320
  before do
310
- user.update_attributes(name: 'Aaron2', email: nil, country: '', city: nil, phone: '867-5309', aliases: ['', 'bill', 'james'])
321
+ user.update_attributes!(name: 'Aaron2', email: nil, country: '', city: nil, phone: '867-5309', aliases: ['', 'bill', 'james'])
311
322
  end
312
323
  it { is_expected.to be_a HashWithIndifferentAccess }
313
324
  it 'should track changed field' do
@@ -323,12 +334,12 @@ describe Mongoid::History do
323
334
  expect(subject[:array]).to eq({ aliases: { remove: ['bob'], add: ['', 'bill', 'james'] } }.with_indifferent_access)
324
335
  end
325
336
  it 'should not track unmodified field' do
326
- %w(add modify remove array).each do |edit|
337
+ %w[add modify remove array].each do |edit|
327
338
  expect(subject[edit][:address]).to be_nil
328
339
  end
329
340
  end
330
341
  it 'should not track untracked fields' do
331
- %w(add modify remove array).each do |edit|
342
+ %w[add modify remove array].each do |edit|
332
343
  expect(subject[edit][:email]).to be_nil
333
344
  end
334
345
  end
@@ -348,73 +359,75 @@ describe Mongoid::History do
348
359
 
349
360
  describe 'on update non-embedded twice' do
350
361
  it 'should assign version on post' do
351
- post.update_attributes(title: 'Test2')
352
- post.update_attributes(title: 'Test3')
353
- expect(post.version).to eq(2)
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)
354
366
  end
355
367
 
356
368
  it 'should create a history track if changed attributes match tracked attributes' do
369
+ post # Created
357
370
  expect do
358
- post.update_attributes(title: 'Test2')
359
- post.update_attributes(title: 'Test3')
371
+ post.update_attributes!(title: 'Test2')
372
+ post.update_attributes!(title: 'Test3')
360
373
  end.to change(Tracker, :count).by(2)
361
374
  end
362
375
 
363
376
  it 'should create a history track of version 2' do
364
- post.update_attributes(title: 'Test2')
365
- post.update_attributes(title: 'Test3')
377
+ post.update_attributes!(title: 'Test2')
378
+ post.update_attributes!(title: 'Test3')
366
379
  expect(post.history_tracks.where(version: 2).first).not_to be_nil
367
380
  end
368
381
 
369
382
  it 'should assign modified fields' do
370
- post.update_attributes(title: 'Test2')
371
- post.update_attributes(title: 'Test3')
372
- expect(post.history_tracks.where(version: 2).first.modified).to eq(
383
+ post.update_attributes!(title: 'Test2')
384
+ post.update_attributes!(title: 'Test3')
385
+ expect(post.history_tracks.where(version: 3).first.modified).to eq(
373
386
  'title' => 'Test3'
374
387
  )
375
388
  end
376
389
 
377
390
  it 'should assign original fields' do
378
- post.update_attributes(title: 'Test2')
379
- post.update_attributes(title: 'Test3')
380
- expect(post.history_tracks.where(version: 2).first.original).to eq(
391
+ post.update_attributes!(title: 'Test2')
392
+ post.update_attributes!(title: 'Test3')
393
+ expect(post.history_tracks.where(version: 3).first.original).to eq(
381
394
  'title' => 'Test2'
382
395
  )
383
396
  end
384
397
 
385
398
  it 'should assign modifier' do
386
- post.update_attributes(title: 'Another Test', modifier: another_user)
387
- expect(post.history_tracks.last.modifier).to eq(another_user)
399
+ post.update_attributes!(title: 'Another Test', modifier: another_user)
400
+ expect(post.history_tracks.last.modifier.id).to eq(another_user.id)
388
401
  end
389
402
  end
390
403
 
391
404
  describe 'on update embedded 1..N (embeds_many)' do
392
405
  it 'should assign version on comment' do
393
- comment.update_attributes(title: 'Test2')
406
+ comment.update_attributes!(title: 'Test2')
394
407
  expect(comment.version).to eq(2) # first track generated on creation
395
408
  end
396
409
 
397
410
  it 'should create a history track of version 2' do
398
- comment.update_attributes(title: 'Test2')
411
+ comment.update_attributes!(title: 'Test2')
399
412
  expect(comment.history_tracks.where(version: 2).first).not_to be_nil
400
413
  end
401
414
 
402
415
  it 'should assign modified fields' do
403
- comment.update_attributes(t: 'Test2')
416
+ comment.update_attributes!(t: 'Test2')
404
417
  expect(comment.history_tracks.where(version: 2).first.modified).to eq(
405
418
  't' => 'Test2'
406
419
  )
407
420
  end
408
421
 
409
422
  it 'should assign original fields' do
410
- comment.update_attributes(title: 'Test2')
423
+ comment.update_attributes!(title: 'Test2')
411
424
  expect(comment.history_tracks.where(version: 2).first.original).to eq(
412
425
  't' => 'test'
413
426
  )
414
427
  end
415
428
 
416
429
  it 'should be possible to undo from parent' do
417
- comment.update_attributes(title: 'Test 2')
430
+ comment.update_attributes!(title: 'Test 2')
418
431
  user
419
432
  post.history_tracks.last.undo!(user)
420
433
  comment.reload
@@ -422,16 +435,17 @@ describe Mongoid::History do
422
435
  end
423
436
 
424
437
  it 'should assign modifier' do
425
- post.update_attributes(title: 'Another Test', modifier: another_user)
426
- expect(post.history_tracks.last.modifier).to eq(another_user)
438
+ post.update_attributes!(title: 'Another Test', modifier: another_user)
439
+ expect(post.history_tracks.last.modifier.id).to eq(another_user.id)
427
440
  end
428
441
  end
429
442
 
430
443
  describe 'on update embedded 1..1 (embeds_one)' do
431
- let(:section) { Section.new(title: 'Technology') }
444
+ let(:section) { Section.new(title: 'Technology', modifier: user) }
432
445
 
433
446
  before(:each) do
434
447
  post.section = section
448
+ post.modifier = user
435
449
  post.save!
436
450
  post.reload
437
451
  post.section
@@ -442,39 +456,39 @@ describe Mongoid::History do
442
456
  end
443
457
 
444
458
  it 'should assign version on section' do
445
- section.update_attributes(title: 'Technology 2')
459
+ section.update_attributes!(title: 'Technology 2')
446
460
  expect(section.version).to eq(2) # first track generated on creation
447
461
  end
448
462
 
449
463
  it 'should create a history track of version 2' do
450
- section.update_attributes(title: 'Technology 2')
464
+ section.update_attributes!(title: 'Technology 2')
451
465
  expect(section.history_tracks.where(version: 2).first).not_to be_nil
452
466
  end
453
467
 
454
468
  it 'should assign modified fields' do
455
- section.update_attributes(title: 'Technology 2')
469
+ section.update_attributes!(title: 'Technology 2')
456
470
  expect(section.history_tracks.where(version: 2).first.modified).to eq(
457
471
  't' => 'Technology 2'
458
472
  )
459
473
  end
460
474
 
461
475
  it 'should assign original fields' do
462
- section.update_attributes(title: 'Technology 2')
476
+ section.update_attributes!(title: 'Technology 2')
463
477
  expect(section.history_tracks.where(version: 2).first.original).to eq(
464
478
  't' => 'Technology'
465
479
  )
466
480
  end
467
481
 
468
482
  it 'should be possible to undo from parent' do
469
- section.update_attributes(title: 'Technology 2')
483
+ section.update_attributes!(title: 'Technology 2')
470
484
  post.history_tracks.last.undo!(user)
471
485
  section.reload
472
486
  expect(section.title).to eq('Technology')
473
487
  end
474
488
 
475
489
  it 'should assign modifier' do
476
- section.update_attributes(title: 'Business', modifier: another_user)
477
- expect(post.history_tracks.last.modifier).to eq(another_user)
490
+ section.update_attributes!(title: 'Business', modifier: another_user)
491
+ expect(post.history_tracks.last.modifier.id).to eq(another_user.id)
478
492
  end
479
493
  end
480
494
 
@@ -503,28 +517,23 @@ describe Mongoid::History do
503
517
 
504
518
  it 'should be possible to create with redo after undo create embedded from parent' do
505
519
  comment # initialize
506
- post.comments.create!(title: 'The second one')
507
- track = post.history_tracks.last
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
508
523
  track.undo!(user)
524
+ expect(post.reload.comments.count).to eq 1
509
525
  track.redo!(user)
510
- post.reload
511
- expect(post.comments.count).to eq(2)
526
+ expect(post.reload.comments.count).to eq 2
512
527
  end
513
528
  end
514
529
 
515
530
  describe 'embedded with cascading callbacks' do
516
531
  let(:tag_foo) { post.tags.create!(title: 'foo', updated_by: user) }
517
- let(:tag_bar) { post.tags.create!(title: 'bar') }
518
-
519
- # it "should have cascaded the creation callbacks and set timestamps" do
520
- # tag_foo; tag_bar # initialize
521
- # tag_foo.created_at.should_not be_nil
522
- # tag_foo.updated_at.should_not be_nil
523
- # end
532
+ let(:tag_bar) { post.tags.create!(title: 'bar', updated_by: user) }
524
533
 
525
534
  it 'should allow an update through the parent model' do
526
535
  update_hash = { 'post' => { 'tags_attributes' => { '1234' => { 'id' => tag_bar.id, 'title' => 'baz' } } } }
527
- post.update_attributes(update_hash['post'])
536
+ post.update_attributes!(update_hash['post'])
528
537
  expect(post.tags.last.title).to eq('baz')
529
538
  end
530
539
 
@@ -533,14 +542,14 @@ describe Mongoid::History do
533
542
  tag_bar # initialize
534
543
  expect(post.tags.count).to eq(2)
535
544
  update_hash = { 'post' => { 'tags_attributes' => { '1234' => { 'id' => tag_bar.id, 'title' => 'baz', '_destroy' => 'true' } } } }
536
- post.update_attributes(update_hash['post'])
545
+ post.update_attributes!(update_hash['post'])
537
546
  expect(post.tags.count).to eq(1)
538
547
  expect(post.history_tracks.to_a.last.action).to eq('destroy')
539
548
  end
540
549
 
541
550
  it 'should write relationship name for association_chain hiearchy instead of class name when using _destroy macro' do
542
551
  update_hash = { 'tags_attributes' => { '1234' => { 'id' => tag_foo.id, '_destroy' => '1' } } }
543
- post.update_attributes(update_hash)
552
+ post.update_attributes!(update_hash)
544
553
 
545
554
  # historically this would have evaluated to 'Tags' and an error would be thrown
546
555
  # on any call that walked up the association_chain, e.g. 'trackable'
@@ -551,35 +560,35 @@ describe Mongoid::History do
551
560
 
552
561
  describe 'non-embedded' do
553
562
  it 'should undo changes' do
554
- post.update_attributes(title: 'Test2')
555
- post.history_tracks.where(version: 1).last.undo!(user)
563
+ post.update_attributes!(title: 'Test2')
564
+ post.history_tracks.where(version: 2).last.undo!(user)
556
565
  post.reload
557
566
  expect(post.title).to eq('Test')
558
567
  end
559
568
 
560
569
  it 'should undo destruction' do
561
570
  post.destroy
562
- post.history_tracks.where(version: 1).last.undo!(user)
571
+ post.history_tracks.where(version: 2).last.undo!(user)
563
572
  expect(Post.find(post.id).title).to eq('Test')
564
573
  end
565
574
 
566
575
  it 'should create a new history track after undo' do
567
576
  comment # initialize
568
- post.update_attributes(title: 'Test2')
577
+ post.update_attributes!(title: 'Test2')
569
578
  post.history_tracks.last.undo!(user)
570
579
  post.reload
571
- expect(post.history_tracks.count).to eq(3)
580
+ expect(post.history_tracks.count).to eq(4)
572
581
  end
573
582
 
574
583
  it 'should assign user as the modifier of the newly created history track' do
575
- post.update_attributes(title: 'Test2')
576
- post.history_tracks.where(version: 1).last.undo!(user)
584
+ post.update_attributes!(title: 'Test2')
585
+ post.history_tracks.where(version: 2).last.undo!(user)
577
586
  post.reload
578
- expect(post.history_tracks.where(version: 2).last.modifier).to eq(user)
587
+ expect(post.history_tracks.where(version: 2).last.modifier.id).to eq(user.id)
579
588
  end
580
589
 
581
590
  it 'should stay the same after undo and redo' do
582
- post.update_attributes(title: 'Test2')
591
+ post.update_attributes!(title: 'Test2')
583
592
  track = post.history_tracks.last
584
593
  track.undo!(user)
585
594
  track.redo!(user)
@@ -590,7 +599,7 @@ describe Mongoid::History do
590
599
 
591
600
  it 'should be destroyed after undo and redo' do
592
601
  post.destroy
593
- track = post.history_tracks.where(version: 1).last
602
+ track = post.history_tracks.where(version: 2).last
594
603
  track.undo!(user)
595
604
  track.redo!(user)
596
605
  expect(Post.where(_id: post.id).first).to be_nil
@@ -599,28 +608,28 @@ describe Mongoid::History do
599
608
 
600
609
  describe 'embedded' do
601
610
  it 'should undo changes' do
602
- comment.update_attributes(title: 'Test2')
611
+ comment.update_attributes!(title: 'Test2')
603
612
  comment.history_tracks.where(version: 2).first.undo!(user)
604
613
  comment.reload
605
614
  expect(comment.title).to eq('test')
606
615
  end
607
616
 
608
617
  it 'should create a new history track after undo' do
609
- comment.update_attributes(title: 'Test2')
618
+ comment.update_attributes!(title: 'Test2')
610
619
  comment.history_tracks.where(version: 2).first.undo!(user)
611
620
  comment.reload
612
621
  expect(comment.history_tracks.count).to eq(3)
613
622
  end
614
623
 
615
624
  it 'should assign user as the modifier of the newly created history track' do
616
- comment.update_attributes(title: 'Test2')
625
+ comment.update_attributes!(title: 'Test2')
617
626
  comment.history_tracks.where(version: 2).first.undo!(user)
618
627
  comment.reload
619
- expect(comment.history_tracks.where(version: 3).first.modifier).to eq(user)
628
+ expect(comment.history_tracks.where(version: 3).first.modifier.id).to eq(user.id)
620
629
  end
621
630
 
622
631
  it 'should stay the same after undo and redo' do
623
- comment.update_attributes(title: 'Test2')
632
+ comment.update_attributes!(title: 'Test2')
624
633
  track = comment.history_tracks.where(version: 2).first
625
634
  track.undo!(user)
626
635
  track.redo!(user)
@@ -639,7 +648,7 @@ describe Mongoid::History do
639
648
  describe 'undo' do
640
649
  { 'undo' => [nil], 'undo!' => [nil, :reload] }.each do |test_method, methods|
641
650
  methods.each do |method|
642
- context "#{method || 'instance'}" do
651
+ context (method || 'instance').to_s do
643
652
  it 'recognizes :from, :to options' do
644
653
  comment.send test_method, user, from: 4, to: 2
645
654
  comment.send(method) if method
@@ -694,9 +703,9 @@ describe Mongoid::History do
694
703
 
695
704
  describe 'redo' do
696
705
  [nil, :reload].each do |method|
697
- context "#{method || 'instance'}" do
706
+ context (method || 'instance').to_s do
698
707
  before :each do
699
- comment.update_attributes(title: 'Test5')
708
+ comment.update_attributes!(title: 'Test5')
700
709
  end
701
710
 
702
711
  it 'should recognize :from, :to options' do
@@ -751,39 +760,8 @@ describe Mongoid::History do
751
760
  end
752
761
  end
753
762
 
754
- describe 'localized fields' do
755
- before :each do
756
- class Sausage
757
- include Mongoid::Document
758
- include Mongoid::History::Trackable
759
-
760
- field :flavour, localize: true
761
- track_history on: [:flavour], track_destroy: true
762
- end
763
- end
764
- it 'should correctly undo and redo' do
765
- if Sausage.respond_to?(:localized_fields)
766
- sausage = Sausage.create!(flavour_translations: { 'en' => 'Apple', 'nl' => 'Appel' })
767
- sausage.update_attributes(flavour: 'Guinness')
768
-
769
- track = sausage.history_tracks.last
770
-
771
- track.undo! user
772
- expect(sausage.reload.flavour).to eq('Apple')
773
-
774
- track.redo! user
775
- expect(sausage.reload.flavour).to eq('Guinness')
776
-
777
- sausage.destroy
778
- expect(sausage.history_tracks.last.action).to eq('destroy')
779
- sausage.history_tracks.last.undo! user
780
- expect(sausage.reload.flavour).to eq('Guinness')
781
- end
782
- end
783
- end
784
-
785
763
  describe 'embedded with a polymorphic trackable' do
786
- let(:foo) { Foo.new(title: 'a title', body: 'a body') }
764
+ let(:foo) { Foo.new(title: 'a title', body: 'a body', modifier: user) }
787
765
  before :each do
788
766
  post.comments << foo
789
767
  post.save!
@@ -809,7 +787,7 @@ describe Mongoid::History do
809
787
  end
810
788
  context 'an embedded model' do
811
789
  it 'should return the trackable parent class' do
812
- comment.update_attributes(title: 'Foo')
790
+ comment.update_attributes!(title: 'Foo')
813
791
  expect(comment.history_tracks.first.trackable_parent_class).to eq(Post)
814
792
  end
815
793
  it 'should return the parent class even if the trackable is deleted' do
@@ -821,7 +799,7 @@ describe Mongoid::History do
821
799
  end
822
800
 
823
801
  describe 'when default scope is present' do
824
- before do
802
+ before :each do
825
803
  class Post
826
804
  default_scope -> { where(title: nil) }
827
805
  end
@@ -838,7 +816,7 @@ describe Mongoid::History do
838
816
 
839
817
  describe 'post' do
840
818
  it 'should correctly undo and redo' do
841
- post.update_attributes(title: 'a new title')
819
+ post.update_attributes!(title: 'a new title')
842
820
  track = post.history_tracks.last
843
821
  track.undo! user
844
822
  expect(post.reload.title).to eq('Test')
@@ -847,7 +825,7 @@ describe Mongoid::History do
847
825
  end
848
826
 
849
827
  it 'should stay the same after undo and redo' do
850
- post.update_attributes(title: 'testing')
828
+ post.update_attributes!(title: 'testing')
851
829
  track = post.history_tracks.last
852
830
  track.undo! user
853
831
  track.redo! user
@@ -856,7 +834,7 @@ describe Mongoid::History do
856
834
  end
857
835
  describe 'comment' do
858
836
  it 'should correctly undo and redo' do
859
- comment.update_attributes(title: 'a new title')
837
+ comment.update_attributes!(title: 'a new title')
860
838
  track = comment.history_tracks.last
861
839
  track.undo! user
862
840
  expect(comment.reload.title).to eq('test')
@@ -865,7 +843,7 @@ describe Mongoid::History do
865
843
  end
866
844
 
867
845
  it 'should stay the same after undo and redo' do
868
- comment.update_attributes(title: 'testing')
846
+ comment.update_attributes!(title: 'testing')
869
847
  track = comment.history_tracks.last
870
848
  track.undo! user
871
849
  track.redo! user
@@ -874,7 +852,7 @@ describe Mongoid::History do
874
852
  end
875
853
  describe 'user' do
876
854
  it 'should correctly undo and redo' do
877
- user.update_attributes(name: 'a new name')
855
+ user.update_attributes!(name: 'a new name')
878
856
  track = user.history_tracks.last
879
857
  track.undo! user
880
858
  expect(user.reload.name).to eq('Aaron')
@@ -883,7 +861,7 @@ describe Mongoid::History do
883
861
  end
884
862
 
885
863
  it 'should stay the same after undo and redo' do
886
- user.update_attributes(name: 'testing')
864
+ user.update_attributes!(name: 'testing')
887
865
  track = user.history_tracks.last
888
866
  track.undo! user
889
867
  track.redo! user
@@ -892,7 +870,7 @@ describe Mongoid::History do
892
870
  end
893
871
  describe 'tag' do
894
872
  it 'should correctly undo and redo' do
895
- tag.update_attributes(title: 'a new title')
873
+ tag.update_attributes!(title: 'a new title')
896
874
  track = tag.history_tracks.last
897
875
  track.undo! user
898
876
  expect(tag.reload.title).to eq('test')
@@ -901,7 +879,7 @@ describe Mongoid::History do
901
879
  end
902
880
 
903
881
  it 'should stay the same after undo and redo' do
904
- tag.update_attributes(title: 'testing')
882
+ tag.update_attributes!(title: 'testing')
905
883
  track = tag.history_tracks.last
906
884
  track.undo! user
907
885
  track.redo! user
@@ -919,18 +897,80 @@ describe Mongoid::History do
919
897
  track_history on: [:foo], changes_method: :my_changes
920
898
 
921
899
  def my_changes
922
- { foo: %w(bar baz) }
900
+ { foo: %w[bar baz] }
923
901
  end
924
902
  end
925
903
  end
926
904
 
905
+ after :each do
906
+ Object.send(:remove_const, :OverriddenChangesMethod)
907
+ end
908
+
927
909
  it 'should add foo to the changes history' do
928
- o = OverriddenChangesMethod.create
910
+ o = OverriddenChangesMethod.create(modifier: user)
929
911
  o.save!
930
912
  track = o.history_tracks.last
931
913
  expect(track.modified).to eq('foo' => 'baz')
932
914
  expect(track.original).to eq('foo' => 'bar')
933
915
  end
934
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
935
975
  end
936
976
  end