mongoid-history 0.4.0 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,307 +1,309 @@
1
- mongoid-history
2
- ===============
3
-
4
- [![Build Status](https://secure.travis-ci.org/aq1018/mongoid-history.png?branch=master)](http://travis-ci.org/aq1018/mongoid-history)
5
- [![Code Climate](https://codeclimate.com/github/aq1018/mongoid-history.png)](https://codeclimate.com/github/aq1018/mongoid-history)
6
-
7
- Mongoid-history tracks historical changes for any document, including embedded ones. It achieves this by storing all history tracks in a single collection that you define. Embedded documents are referenced by storing an association path, which is an array of `document_name` and `document_id` fields starting from the top most parent document and down to the embedded document that should track history.
8
-
9
- This gem also implements multi-user undo, which allows users to undo any history change in any order. Undoing a document also creates a new history track. This is great for auditing and preventing vandalism, but is probably not suitable for use cases such as a wiki.
10
-
11
- Stable Release
12
- --------------
13
-
14
- You're reading the documentation the 0.4.x release that supports Mongoid 3.x. For 2.x compatible mongoid-history, please use a 0.2.x version from the [2.x-stable branch](https://github.com/aq1018/mongoid-history/tree/2.4-stable).
15
-
16
- Install
17
- -------
18
-
19
- This gem supports Mongoid 3.x on Ruby 1.9.3 only. Add it to your `Gemfile` or run `gem install mongoid-history`.
20
-
21
- ```ruby
22
- gem 'mongoid-history'
23
- ```
24
-
25
- Usage
26
- -----
27
-
28
- **Create a history tracker**
29
-
30
- Create a new class to track histories. All histories are stored in this tracker. The name of the class can be anything you like. The only requirement is that it includes `Mongoid::History::Tracker`
31
-
32
- ```ruby
33
- # app/models/history_tracker.rb
34
- class HistoryTracker
35
- include Mongoid::History::Tracker
36
- end
37
- ```
38
-
39
- **Set tracker class name**
40
-
41
- Manually set the tracker class name to make sure your tracker can be found and loaded properly. You can skip this step if you manually require your tracker before using any trackables.
42
-
43
- The following example sets the tracker class name using a Rails initializer.
44
-
45
- ```ruby
46
- # config/initializers/mongoid-history.rb
47
- # initializer for mongoid-history
48
- # assuming HistoryTracker is your tracker class
49
- Mongoid::History.tracker_class_name = :history_tracker
50
- ```
51
-
52
- **Set `#current_user` method name**
53
-
54
- You can set the name of the method that returns currently logged in user if you don't want to set `modifier` explicitly on every update.
55
-
56
- The following example sets the `current_user_method` using a Rails initializer
57
-
58
- ```ruby
59
- # config/initializers/mongoid-history.rb
60
- # initializer for mongoid-history
61
- # assuming you're using devise/authlogic
62
- Mongoid::History.current_user_method = :current_user
63
- ```
64
-
65
- When `current_user_method` is set, mongoid-history will invoke this method on each update and set its result as the instance modifier.
66
-
67
- ```ruby
68
- # assume that current_user return #<User _id: 1>
69
- post = Post.first
70
- post.update_attributes(:title => 'New title')
71
-
72
- post.history_tracks.last.modifier #=> #<User _id: 1>
73
- ```
74
-
75
- **Create trackable classes and objects**
76
-
77
- ```ruby
78
- class Post
79
- include Mongoid::Document
80
- include Mongoid::Timestamps
81
-
82
- # history tracking all Post documents
83
- # note: tracking will not work until #track_history is invoked
84
- include Mongoid::History::Trackable
85
-
86
- field :title
87
- field :body
88
- field :rating
89
- embeds_many :comments
90
-
91
- # telling Mongoid::History how you want to track changes
92
- track_history :on => [:title, :body], # track title and body fields only, default is :all
93
- :modifier_field => :modifier, # adds "belongs_to :modifier" to track who made the change, default is :modifier
94
- :modifier_field_inverse_of => :nil, # adds an ":inverse_of" option to the "belongs_to :modifier" relation, default is not set
95
- :version_field => :version, # adds "field :version, :type => Integer" to track current version, default is :version
96
- :track_create => false, # track document creation, default is false
97
- :track_update => true, # track document updates, default is true
98
- :track_destroy => false, # track document destruction, default is false
99
- end
100
-
101
- class Comment
102
- include Mongoid::Document
103
- include Mongoid::Timestamps
104
-
105
- # declare that we want to track comments
106
- include Mongoid::History::Trackable
107
-
108
- field :title
109
- field :body
110
- embedded_in :post, :inverse_of => :comments
111
-
112
- # track title and body for all comments, scope it to post (the parent)
113
- # also track creation and destruction
114
- track_history :on => [:title, :body], :scope => :post, :track_create => true, :track_destroy => true
115
- end
116
-
117
- # the modifier class
118
- class User
119
- include Mongoid::Document
120
- include Mongoid::Timestamps
121
-
122
- field :name
123
- end
124
-
125
- user = User.create(:name => "Aaron")
126
- post = Post.create(:title => "Test", :body => "Post", :modifier => user)
127
- comment = post.comments.create(:title => "test", :body => "comment", :modifier => user)
128
- comment.history_tracks.count # should be 1
129
-
130
- comment.update_attributes(:title => "Test 2")
131
- comment.history_tracks.count # should be 2
132
-
133
- track = comment.history_tracks.last
134
-
135
- track.undo! user # comment title should be "Test"
136
-
137
- track.redo! user # comment title should be "Test 2"
138
-
139
- # undo last change
140
- comment.undo! user
141
-
142
- # undo versions 1 - 4
143
- comment.undo! user, :from => 4, :to => 1
144
-
145
- # undo last 3 versions
146
- comment.undo! user, :last => 3
147
-
148
- # redo versions 1 - 4
149
- comment.redo! user, :from => 1, :to => 4
150
-
151
- # redo last 3 versions
152
- comment.redo! user, :last => 3
153
-
154
- # delete post
155
- post.destroy
156
-
157
- # undelete post
158
- post.undo! user
159
-
160
- # disable tracking for comments within a block
161
- Comment.disable_tracking do
162
- comment.update_attributes(:title => "Test 3")
163
- end
164
-
165
- # globally disable all history tracking
166
- Mongoid::History.disable do
167
- comment.update_attributes(:title => "Test 3")
168
- user.update_attributes(:name => "Eddie Van Halen")
169
- end
170
- ```
171
-
172
- **Retrieving the list of tracked fields**
173
-
174
- ```ruby
175
- class Book
176
- ...
177
- field :title
178
- field :author
179
- field :price
180
- track_history :on => [:title, :price]
181
- end
182
-
183
- Book.tracked_fields #=> ["title", "price"]
184
- Book.tracked_field?(:title) #=> true
185
- Book.tracked_field?(:author) #=> false
186
- ```
187
-
188
- **Displaying history trackers as an audit trail**
189
-
190
- In your Controller:
191
-
192
- ```ruby
193
- # Fetch history trackers
194
- @trackers = HistoryTracker.limit(25)
195
-
196
- # get change set for the first tracker
197
- @changes = @trackers.first.tracked_changes
198
- #=> {field: {to: val1, from: val2}}
199
-
200
- # get edit set for the first tracker
201
- @edits = @trackers.first.tracked_changes
202
- #=> { add: {field: val},
203
- # remove: {field: val},
204
- # modify: { to: val1, from: val2 },
205
- # array: { add: [val2], remove: [val1] } }
206
- ```
207
-
208
- In your View, you might do something like (example in HAML format):
209
-
210
- ```haml
211
- %ul.changes
212
- - (@edits[:add]||[]).each do |k,v|
213
- %li.remove Added field #{k} value #{v}
214
-
215
- - (@edits[:modify]||[]).each do |k,v|
216
- %li.modify Changed field #{k} from #{v[:from]} to #{v[:to]}
217
-
218
- - (@edits[:array]||[]).each do |k,v|
219
- %li.modify
220
- - if v[:remove].nil?
221
- Changed field #{k} by adding #{v[:add]}
222
- - elsif v[:add].nil?
223
- Changed field #{k} by removing #{v[:remove]}
224
- - else
225
- Changed field #{k} by adding #{v[:add]} and removing #{v[:remove]}
226
-
227
- - (@edits[:remove]||[]).each do |k,v|
228
- %li.remove Removed field #{k} (was previously #{v})
229
- ```
230
-
231
- **Using an alternate changes method**
232
-
233
- Sometimes you may wish to provide an alternate method for determining which changes should be tracked. For example, if you are using embedded documents
234
- and nested attributes, you may wish to write your own changes method that includes changes from the embedded documents.
235
-
236
- Mongoid::History provides an option named `:changes_method` which allows you to do this. It defaults to `:changes`, which is the standard changes method.
237
-
238
- Example:
239
-
240
- ```ruby
241
- class Foo
242
- include Mongoid::Document
243
- include Mongoid::Timestamps
244
- include Mongoid::History::Trackable
245
-
246
- field :bar
247
- embeds_one :baz
248
- accepts_nested_attributes_for :baz
249
-
250
- # use changes_with_baz to include baz's changes in this document's
251
- # history.
252
- track_history :changes_method => :changes_with_baz
253
-
254
- def changes_with_baz
255
- if baz.changed?
256
- changes.merge( :baz => summarized_changes(baz) )
257
- else
258
- changes
259
- end
260
- end
261
-
262
- private
263
- # This method takes the changes from an embedded doc and formats them
264
- # in a summarized way, similar to how the embedded doc appears in the
265
- # parent document's attributes
266
- def summarized_changes obj
267
- obj.changes.keys.map do |field|
268
- next unless obj.respond_to?("#{field}_change")
269
- [ { field => obj.send("#{field}_change")[0] },
270
- { field => obj.send("#{field}_change")[1] } ]
271
- end.compact.transpose.map do |fields|
272
- fields.inject({}) {|map,f| map.merge(f)}
273
- end
274
- end
275
- end
276
-
277
- class Baz
278
- include Mongoid::Document
279
- include Mongoid::Timestamps
280
-
281
- embedded_in :foo
282
- field :value
283
- end
284
- ```
285
-
286
- For more examples, check out [spec/integration/integration_spec.rb](https://github.com/aq1018/mongoid-history/blob/master/spec/integration/integration_spec.rb).
287
-
288
- Contributing to mongoid-history
289
- -------------------------------
290
-
291
- * Check out the latest code to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
292
- * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
293
- * Fork the project.
294
- * Create a feature/bugfix branch.
295
- * Commit and push until you are happy with your changes.
296
- * Make sure to add tests.
297
- * Update the CHANGELOG for the next release.
298
- * Try not to mess with the Rakefile or version.
299
- * Make a pull request.
300
-
301
- Copyright
302
- ---------
303
-
304
- Copyright (c) 2011-2012 Aaron Qian. MIT License.
305
-
306
- See [LICENSE.txt](https://github.com/aq1018/mongoid-history/blob/master/LICENSE.txt) for further details.
307
-
1
+ mongoid-history
2
+ ===============
3
+
4
+ [![Build Status](https://secure.travis-ci.org/aq1018/mongoid-history.png?branch=master)](http://travis-ci.org/aq1018/mongoid-history)
5
+ [![Code Climate](https://codeclimate.com/github/aq1018/mongoid-history.png)](https://codeclimate.com/github/aq1018/mongoid-history)
6
+
7
+ Mongoid-history tracks historical changes for any document, including embedded ones. It achieves this by storing all history tracks in a single collection that you define. Embedded documents are referenced by storing an association path, which is an array of `document_name` and `document_id` fields starting from the top most parent document and down to the embedded document that should track history.
8
+
9
+ This gem also implements multi-user undo, which allows users to undo any history change in any order. Undoing a document also creates a new history track. This is great for auditing and preventing vandalism, but is probably not suitable for use cases such as a wiki.
10
+
11
+ Stable Release
12
+ --------------
13
+
14
+ You're reading the documentation the 0.4.x release that supports Mongoid 3.x. For 2.x compatible mongoid-history, please use a 0.2.x version from the [2.x-stable branch](https://github.com/aq1018/mongoid-history/tree/2.4-stable).
15
+
16
+ Install
17
+ -------
18
+
19
+ This gem supports Mongoid 3.x on Ruby 1.9.3 only. Add it to your `Gemfile` or run `gem install mongoid-history`.
20
+
21
+ ```ruby
22
+ gem 'mongoid-history'
23
+ ```
24
+
25
+ Usage
26
+ -----
27
+
28
+ **Create a history tracker**
29
+
30
+ Create a new class to track histories. All histories are stored in this tracker. The name of the class can be anything you like. The only requirement is that it includes `Mongoid::History::Tracker`
31
+
32
+ ```ruby
33
+ # app/models/history_tracker.rb
34
+ class HistoryTracker
35
+ include Mongoid::History::Tracker
36
+ end
37
+ ```
38
+
39
+ **Set tracker class name**
40
+
41
+ Manually set the tracker class name to make sure your tracker can be found and loaded properly. You can skip this step if you manually require your tracker before using any trackables.
42
+
43
+ The following example sets the tracker class name using a Rails initializer.
44
+
45
+ ```ruby
46
+ # config/initializers/mongoid-history.rb
47
+ # initializer for mongoid-history
48
+ # assuming HistoryTracker is your tracker class
49
+ Mongoid::History.tracker_class_name = :history_tracker
50
+ ```
51
+
52
+ **Create trackable classes and objects**
53
+
54
+ ```ruby
55
+ class Post
56
+ include Mongoid::Document
57
+ include Mongoid::Timestamps
58
+
59
+ # history tracking all Post documents
60
+ # note: tracking will not work until #track_history is invoked
61
+ include Mongoid::History::Trackable
62
+
63
+ field :title
64
+ field :body
65
+ field :rating
66
+ embeds_many :comments
67
+
68
+ # telling Mongoid::History how you want to track changes
69
+ track_history :on => [:title, :body], # track title and body fields only, default is :all
70
+ :modifier_field => :modifier, # adds "belongs_to :modifier" to track who made the change, default is :modifier
71
+ :modifier_field_inverse_of => :nil, # adds an ":inverse_of" option to the "belongs_to :modifier" relation, default is not set
72
+ :version_field => :version, # adds "field :version, :type => Integer" to track current version, default is :version
73
+ :track_create => false, # track document creation, default is false
74
+ :track_update => true, # track document updates, default is true
75
+ :track_destroy => false # track document destruction, default is false
76
+ end
77
+
78
+ class Comment
79
+ include Mongoid::Document
80
+ include Mongoid::Timestamps
81
+
82
+ # declare that we want to track comments
83
+ include Mongoid::History::Trackable
84
+
85
+ field :title
86
+ field :body
87
+ embedded_in :post, :inverse_of => :comments
88
+
89
+ # track title and body for all comments, scope it to post (the parent)
90
+ # also track creation and destruction
91
+ track_history :on => [:title, :body], :scope => :post, :track_create => true, :track_destroy => true
92
+ end
93
+
94
+ # the modifier class
95
+ class User
96
+ include Mongoid::Document
97
+ include Mongoid::Timestamps
98
+
99
+ field :name
100
+ end
101
+
102
+ user = User.create(:name => "Aaron")
103
+ post = Post.create(:title => "Test", :body => "Post", :modifier => user)
104
+ comment = post.comments.create(:title => "test", :body => "comment", :modifier => user)
105
+ comment.history_tracks.count # should be 1
106
+
107
+ comment.update_attributes(:title => "Test 2")
108
+ comment.history_tracks.count # should be 2
109
+
110
+ track = comment.history_tracks.last
111
+
112
+ track.undo! user # comment title should be "Test"
113
+
114
+ track.redo! user # comment title should be "Test 2"
115
+
116
+ # undo last change
117
+ comment.undo! user
118
+
119
+ # undo versions 1 - 4
120
+ comment.undo! user, :from => 4, :to => 1
121
+
122
+ # undo last 3 versions
123
+ comment.undo! user, :last => 3
124
+
125
+ # redo versions 1 - 4
126
+ comment.redo! user, :from => 1, :to => 4
127
+
128
+ # redo last 3 versions
129
+ comment.redo! user, :last => 3
130
+
131
+ # redo version 1
132
+ comment.redo! user, 1
133
+
134
+ # delete post
135
+ post.destroy
136
+
137
+ # undelete post
138
+ post.undo! user
139
+
140
+ # disable tracking for comments within a block
141
+ Comment.disable_tracking do
142
+ comment.update_attributes(:title => "Test 3")
143
+ end
144
+
145
+ # globally disable all history tracking
146
+ Mongoid::History.disable do
147
+ comment.update_attributes(:title => "Test 3")
148
+ user.update_attributes(:name => "Eddie Van Halen")
149
+ end
150
+ ```
151
+
152
+ **Retrieving the list of tracked fields**
153
+
154
+ ```ruby
155
+ class Book
156
+ ...
157
+ field :title
158
+ field :author
159
+ field :price
160
+ track_history :on => [:title, :price]
161
+ end
162
+
163
+ Book.tracked_fields #=> ["title", "price"]
164
+ Book.tracked_field?(:title) #=> true
165
+ Book.tracked_field?(:author) #=> false
166
+ ```
167
+
168
+ **Displaying history trackers as an audit trail**
169
+
170
+ In your Controller:
171
+
172
+ ```ruby
173
+ # Fetch history trackers
174
+ @trackers = HistoryTracker.limit(25)
175
+
176
+ # get change set for the first tracker
177
+ @changes = @trackers.first.tracked_changes
178
+ #=> {field: {to: val1, from: val2}}
179
+
180
+ # get edit set for the first tracker
181
+ @edits = @trackers.first.tracked_edits
182
+ #=> { add: {field: val},
183
+ # remove: {field: val},
184
+ # modify: { to: val1, from: val2 },
185
+ # array: { add: [val2], remove: [val1] } }
186
+ ```
187
+
188
+ In your View, you might do something like (example in HAML format):
189
+
190
+ ```haml
191
+ %ul.changes
192
+ - (@edits[:add]||[]).each do |k,v|
193
+ %li.remove Added field #{k} value #{v}
194
+
195
+ - (@edits[:modify]||[]).each do |k,v|
196
+ %li.modify Changed field #{k} from #{v[:from]} to #{v[:to]}
197
+
198
+ - (@edits[:array]||[]).each do |k,v|
199
+ %li.modify
200
+ - if v[:remove].nil?
201
+ Changed field #{k} by adding #{v[:add]}
202
+ - elsif v[:add].nil?
203
+ Changed field #{k} by removing #{v[:remove]}
204
+ - else
205
+ Changed field #{k} by adding #{v[:add]} and removing #{v[:remove]}
206
+
207
+ - (@edits[:remove]||[]).each do |k,v|
208
+ %li.remove Removed field #{k} (was previously #{v})
209
+ ```
210
+
211
+ **Adding Userstamp on History Trackers**
212
+
213
+ To track the User in the application who created the HistoryTracker, please add the
214
+ [Mongoid::Userstamp gem](https://github.com/tbpro/mongoid_userstamp) to your HistoryTracker class.
215
+ This will add a field called `created_by` and an accessor `creator` to the model (you can rename these via gem config).
216
+
217
+ ```
218
+ class MyHistoryTracker
219
+ include Mongoid::History::Tracker
220
+ include Mongoid::Userstamp
221
+ end
222
+ ```
223
+
224
+ *Migrating Userstamp from Previous Versions*
225
+
226
+ Since October 2013, Mongoid::History itself no longer supports the userstamp natively. In order to migrate, follow the
227
+ instructions above then run the following command:
228
+
229
+ ```
230
+ MyHistoryTracker.all.each{|ht| ht.rename(:modifier_id, :created_by)
231
+ ```
232
+
233
+ **Using an alternate changes method**
234
+
235
+ Sometimes you may wish to provide an alternate method for determining which changes should be tracked. For example, if you are using embedded documents
236
+ and nested attributes, you may wish to write your own changes method that includes changes from the embedded documents.
237
+
238
+ Mongoid::History provides an option named `:changes_method` which allows you to do this. It defaults to `:changes`, which is the standard changes method.
239
+
240
+ Example:
241
+
242
+ ```ruby
243
+ class Foo
244
+ include Mongoid::Document
245
+ include Mongoid::Timestamps
246
+ include Mongoid::History::Trackable
247
+
248
+ field :bar
249
+ embeds_one :baz
250
+ accepts_nested_attributes_for :baz
251
+
252
+ # use changes_with_baz to include baz's changes in this document's
253
+ # history.
254
+ track_history :changes_method => :changes_with_baz
255
+
256
+ def changes_with_baz
257
+ if baz.changed?
258
+ changes.merge( :baz => summarized_changes(baz) )
259
+ else
260
+ changes
261
+ end
262
+ end
263
+
264
+ private
265
+ # This method takes the changes from an embedded doc and formats them
266
+ # in a summarized way, similar to how the embedded doc appears in the
267
+ # parent document's attributes
268
+ def summarized_changes obj
269
+ obj.changes.keys.map do |field|
270
+ next unless obj.respond_to?("#{field}_change")
271
+ [ { field => obj.send("#{field}_change")[0] },
272
+ { field => obj.send("#{field}_change")[1] } ]
273
+ end.compact.transpose.map do |fields|
274
+ fields.inject({}) {|map,f| map.merge(f)}
275
+ end
276
+ end
277
+ end
278
+
279
+ class Baz
280
+ include Mongoid::Document
281
+ include Mongoid::Timestamps
282
+
283
+ embedded_in :foo
284
+ field :value
285
+ end
286
+ ```
287
+
288
+ For more examples, check out [spec/integration/integration_spec.rb](https://github.com/aq1018/mongoid-history/blob/master/spec/integration/integration_spec.rb).
289
+
290
+ Contributing to mongoid-history
291
+ -------------------------------
292
+
293
+ * Check out the latest code to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
294
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
295
+ * Fork the project.
296
+ * Create a feature/bugfix branch.
297
+ * Commit and push until you are happy with your changes.
298
+ * Make sure to add tests.
299
+ * Update the CHANGELOG for the next release.
300
+ * Try not to mess with the Rakefile or version.
301
+ * Make a pull request.
302
+
303
+ Copyright
304
+ ---------
305
+
306
+ Copyright (c) 2011-2012 Aaron Qian. MIT License.
307
+
308
+ See [LICENSE.txt](https://github.com/aq1018/mongoid-history/blob/master/LICENSE.txt) for further details.
309
+