Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 577 lines (379 sloc) 23.834 kb
e9a8648 @airblade First commit.
airblade authored
1 # PaperTrail
2
9327083 @airblade Added info on reverting and undeleting.
airblade authored
3 PaperTrail lets you track changes to your models' data. It's good for auditing or versioning. You can see how a model looked at any stage in its lifecycle, revert it to any version, and even undelete it after it's been destroyed.
e9a8648 @airblade First commit.
airblade authored
4
559eb91 @airblade Link up Railscast.
airblade authored
5 There's an excellent [Railscast on implementing Undo with Paper Trail](http://railscasts.com/episodes/255-undo-with-paper-trail).
e9a8648 @airblade First commit.
airblade authored
6
7 ## Features
8
9 * Stores every create, update and destroy.
55185cd @airblade Store user-defined metadata.
airblade authored
10 * Does not store updates which don't change anything.
c93b364 @airblade Update feature list in light of :only option.
airblade authored
11 * Allows you to specify attributes (by inclusion or exclusion) which must change for a Version to be stored.
e9a8648 @airblade First commit.
airblade authored
12 * Allows you to get at every version, including the original, even once destroyed.
13 * Allows you to get at every version even if the schema has since changed.
3a4187b @airblade Describe version_at in README. Add JW as contributor.
airblade authored
14 * Allows you to get at the version as of a particular time.
9728e31 @airblade Automatically restore has_one associations.
airblade authored
15 * Automatically restores the `has_one` associations as they were at the time.
3036aa7 @airblade Store controller info. Allow other current_user methods.
airblade authored
16 * Automatically records who was responsible via your controller. PaperTrail calls `current_user` by default, if it exists, but you can have it call any method you like.
e9a8648 @airblade First commit.
airblade authored
17 * Allows you to set who is responsible at model-level (useful for migrations).
3036aa7 @airblade Store controller info. Allow other current_user methods.
airblade authored
18 * Allows you to store arbitrary model-level metadata with each version (useful for filtering versions).
19 * Allows you to store arbitrary controller-level information with each version, e.g. remote IP.
4f4f814 @airblade Global enable/disable for PaperTrail.
airblade authored
20 * Can be turned off/on per class (useful for migrations).
21 * Can be turned off/on globally (useful for testing).
e9a8648 @airblade First commit.
airblade authored
22 * No configuration necessary.
23 * Stores everything in a single database table (generates migration for you).
24 * Thoroughly tested.
9332ff2 @airblade Make PaperTrail threadsafe.
airblade authored
25 * Threadsafe.
e9a8648 @airblade First commit.
airblade authored
26
27
28 ## Rails Version
29
2b5dbe0 @airblade Add note about Rails 2.3.
airblade authored
30 Works on Rails 3 and Rails 2.3. The Rails 3 code is on the `master` branch and tagged `v2.x`. The Rails 2.3 code is on the `rails2` branch and tagged `v1.x`. Please note I'm not adding new features to the Rails 2.3 codebase.
e9a8648 @airblade First commit.
airblade authored
31
32
bfe96a7 @airblade Add API summary as a quick reference.
airblade authored
33 ## API Summary
34
35 When you declare `has_paper_trail` in your model, you get these methods:
36
37 class Widget < ActiveRecord::Base
38 has_paper_trail # you can pass various options here
39 end
40
41 # Returns this widget's versions.
42 widget.versions
43
44 # Return the version this widget was reified from, or nil if it is live.
45 widget.version
46
47 # Returns true if this widget is the current, live one; or false if it is from a previous version.
48 widget.live?
49
50 # Returns who put the widget into its current state.
51 widget.originator
52
53 # Returns the widget (not a version) as it looked at the given timestamp.
54 widget.version_at(timestamp)
55
56 # Returns the widget (not a version) as it was most recently.
57 widget.previous_version
58
59 # Returns the widget (not a version) as it became next.
60 widget.next_version
61
62 # Turn PaperTrail off for all widgets.
63 Widget.paper_trail_off
64
65 # Turn PaperTrail on for all widgets.
66 Widget.paper_trail_on
67
68 And a `Version` instance has these methods:
69
70 # Returns the item restored from this version.
71 version.reify(options = {})
72
73 # Returns who put the item into the state stored in this version.
74 version.originator
75
76 # Returns who changed the item from the state it had in this version.
77 version.terminator
78 version.whodunnit
79
80 # Returns the next version.
81 version.next
82
83 # Returns the previous version.
84 version.previous
85
86 # Returns the index of this version in all the versions.
87 version.index
88
89 # Returns the event that caused this version (create|update|destroy).
90 version.event
91
92 In your controllers you can override these methods:
93
94 # Returns the user who is responsible for any changes that occur.
95 # Defaults to current_user.
96 user_for_paper_trail
97
98 # Returns any information about the controller or request that you want
99 # PaperTrail to store alongside any changes that occur.
100 info_for_paper_trail
101
102
e9a8648 @airblade First commit.
airblade authored
103 ## Basic Usage
104
4da196a @airblade Expanded README.
airblade authored
105 PaperTrail is simple to use. Just add 15 characters to a model to get a paper trail of every `create`, `update`, and `destroy`.
e9a8648 @airblade First commit.
airblade authored
106
107 class Widget < ActiveRecord::Base
108 has_paper_trail
109 end
110
111 This gives you a `versions` method which returns the paper trail of changes to your model.
112
113 >> widget = Widget.find 42
114 >> widget.versions # [<Version>, <Version>, ...]
115
116 Once you have a version, you can find out what happened:
117
118 >> v = widget.versions.last
119 >> v.event # 'update' (or 'create' or 'destroy')
120 >> v.whodunnit # '153' (if the update was via a controller and
121 # the controller has a current_user method,
122 # here returning the id of the current user)
123 >> v.created_at # when the update occurred
124 >> widget = v.reify # the widget as it was before the update;
125 # would be nil for a create event
126
4da196a @airblade Expanded README.
airblade authored
127 PaperTrail stores the pre-change version of the model, unlike some other auditing/versioning plugins, so you can retrieve the original version. This is useful when you start keeping a paper trail for models that already have records in the database.
e9a8648 @airblade First commit.
airblade authored
128
129 >> widget = Widget.find 153
130 >> widget.name # 'Doobly'
4da196a @airblade Expanded README.
airblade authored
131
132 # Add has_paper_trail to Widget model.
133
e9a8648 @airblade First commit.
airblade authored
134 >> widget.versions # []
135 >> widget.update_attributes :name => 'Wotsit'
136 >> widget.versions.first.reify.name # 'Doobly'
137 >> widget.versions.first.event # 'update'
138
4da196a @airblade Expanded README.
airblade authored
139 This also means that PaperTrail does not waste space storing a version of the object as it currently stands. The `versions` method gives you previous versions; to get the current one just call a finder on your `Widget` model as usual.
e9a8648 @airblade First commit.
airblade authored
140
141 Here's a helpful table showing what PaperTrail stores:
142
143 <table>
144 <tr>
145 <th>Event</th>
146 <th>Model Before</th>
147 <th>Model After</th>
148 </tr>
149 <tr>
150 <td>create</td>
151 <td>nil</td>
152 <td>widget</td>
153 </tr>
154 <tr>
155 <td>update</td>
156 <td>widget</td>
157 <td>widget'</td>
158 <tr>
159 <td>destroy</td>
160 <td>widget</td>
161 <td>nil</td>
162 </tr>
163 </table>
164
4da196a @airblade Expanded README.
airblade authored
165 PaperTrail stores the values in the Model Before column. Most other auditing/versioning plugins store the After column.
166
167
7e34a81 @mattmacleod Changed the way that notable attributes are worked out to make better se...
mattmacleod authored
168 ## Choosing Attributes To Monitor
52981bf @airblade Optionally ignore attributes.
airblade authored
169
170 You can ignore changes to certain attributes like this:
171
172 class Article < ActiveRecord::Base
173 has_paper_trail :ignore => [:title, :rating]
174 end
175
176 This means that changes to just the `title` or `rating` will not store another version of the article. It does not mean that the `title` and `rating` attributes will be ignored if some other change causes a new `Version` to be crated. For example:
177
178 >> a = Article.create
179 >> a.versions.length # 1
180 >> a.update_attributes :title => 'My Title', :rating => 3
181 >> a.versions.length # 1
182 >> a.update_attributes :content => 'Hello'
183 >> a.versions.length # 2
184 >> a.versions.last.reify.title # 'My Title'
185
7e34a81 @mattmacleod Changed the way that notable attributes are worked out to make better se...
mattmacleod authored
186 Or, you can specify a list of all attributes you care about:
187
188 class Article < ActiveRecord::Base
189 has_paper_trail :only => [:title]
190 end
191
192 This means that only changes to the `title` will save a version of the article:
193
2519c2b @airblade Whitespace.
airblade authored
194 >> a = Article.create
7e34a81 @mattmacleod Changed the way that notable attributes are worked out to make better se...
mattmacleod authored
195 >> a.versions.length # 1
196 >> a.update_attributes :title => 'My Title'
197 >> a.versions.length # 2
198 >> a.update_attributes :content => 'Hello'
199 >> a.versions.length # 2
2519c2b @airblade Whitespace.
airblade authored
200
7e34a81 @mattmacleod Changed the way that notable attributes are worked out to make better se...
mattmacleod authored
201 Passing both `:ignore` and `:only` options will result in the article being saved if a changed attribute is included in `:only` but not in `:ignore`.
52981bf @airblade Optionally ignore attributes.
airblade authored
202
2519c2b @airblade Whitespace.
airblade authored
203
9327083 @airblade Added info on reverting and undeleting.
airblade authored
204 ## Reverting And Undeleting A Model
4da196a @airblade Expanded README.
airblade authored
205
9327083 @airblade Added info on reverting and undeleting.
airblade authored
206 PaperTrail makes reverting to a previous version easy:
4da196a @airblade Expanded README.
airblade authored
207
208 >> widget = Widget.find 42
9327083 @airblade Added info on reverting and undeleting.
airblade authored
209 >> widget.update_attributes :name => 'Blah blah'
4da196a @airblade Expanded README.
airblade authored
210 # Time passes....
9327083 @airblade Added info on reverting and undeleting.
airblade authored
211 >> widget = widget.versions.last.reify # the widget as it was before the update
212 >> widget.save # reverted
213
3a4187b @airblade Describe version_at in README. Add JW as contributor.
airblade authored
214 Alternatively you can find the version at a given time:
215
216 >> widget = widget.version_at(1.day.ago) # the widget as it was one day ago
217 >> widget.save # reverted
218
219 Note `version_at` gives you the object, not a version, so you don't need to call `reify`.
220
9327083 @airblade Added info on reverting and undeleting.
airblade authored
221 Undeleting is just as simple:
4da196a @airblade Expanded README.
airblade authored
222
9327083 @airblade Added info on reverting and undeleting.
airblade authored
223 >> widget = Widget.find 42
224 >> widget.destroy
225 # Time passes....
4da196a @airblade Expanded README.
airblade authored
226 >> widget = Version.find(153).reify # the widget as it was before it was destroyed
227 >> widget.save # the widget lives!
228
559eb91 @airblade Link up Railscast.
airblade authored
229 In fact you could use PaperTrail to implement an undo system, though I haven't had the opportunity yet to do it myself. However [Ryan Bates has](http://railscasts.com/episodes/255-undo-with-paper-trail)!
e9a8648 @airblade First commit.
airblade authored
230
231
cc83502 @airblade Navigate back to reified item's version.
airblade authored
232 ## Navigating Versions
233
b43400e @airblade Navigate to an item's previous and next version.
airblade authored
234 You can call `previous_version` and `next_version` on an item to get it as it was/became. Note that these methods reify the item for you.
235
236 >> widget = Widget.find 42
237 >> widget.versions.length # 4 for example
238 >> widget = widget.previous_version # => widget == widget.versions.last.reify
239 >> widget = widget.previous_version # => widget == widget.versions[-2].reify
240 >> widget.next_version # => widget == widget.versions.last.reify
241 >> widget.next_version # nil
242
243 As an aside, I'm undecided about whether `widget.versions.last.next_version` should return `nil` or `self` (i.e. `widget`). Let me know if you have a view.
244
245 If instead you have a particular `version` of an item you can navigate to the previous and next versions.
cc83502 @airblade Navigate back to reified item's version.
airblade authored
246
247 >> widget = Widget.find 42
248 >> version = widget.versions[-2] # assuming widget has several versions
249 >> previous = version.previous
250 >> next = version.next
251
252 You can find out which of an item's versions yours is:
253
254 >> current_version_number = version.index # 0-based
255
256 Finally, if you got an item by reifying one of its versions, you can navigate back to the version it came from:
257
258 >> latest_version = Widget.find(42).versions.last
259 >> widget = latest_version.reify
260 >> widget.version == latest_version # true
261
aa00cca @airblade Add convenience method to find out if instance is live.
airblade authored
262 You can find out whether a model instance is the current, live one -- or whether it came instead from a previous version -- with `live?`:
263
264 >> widget = Widget.find 42
265 >> widget.live? # true
266 >> widget = widget.versions.last.reify
267 >> widget.live? # false
268
cc83502 @airblade Navigate back to reified item's version.
airblade authored
269
e9a8648 @airblade First commit.
airblade authored
270 ## Finding Out Who Was Responsible For A Change
271
4da196a @airblade Expanded README.
airblade authored
272 If your `ApplicationController` has a `current_user` method, PaperTrail will store the value it returns in the `version`'s `whodunnit` column. Note that this column is a string so you will have to convert it to an integer if it's an id and you want to look up the user later on:
e9a8648 @airblade First commit.
airblade authored
273
274 >> last_change = Widget.versions.last
275 >> user_who_made_the_change = User.find last_change.whodunnit.to_i
276
3036aa7 @airblade Store controller info. Allow other current_user methods.
airblade authored
277 You may want PaperTrail to call a different method to find out who is responsible. To do so, override the `user_for_paper_trail` method in your controller like this:
278
279 class ApplicationController
280 def user_for_paper_trail
281 logged_in? ? current_member : 'Public user' # or whatever
282 end
283 end
284
e9a8648 @airblade First commit.
airblade authored
285 In a migration or in `script/console` you can set who is responsible like this:
286
287 >> PaperTrail.whodunnit = 'Andy Stewart'
288 >> widget.update_attributes :name => 'Wibble'
289 >> widget.versions.last.whodunnit # Andy Stewart
290
f27763b @airblade Clarify who was responsible for changes.
airblade authored
291 N.B. A `version`'s `whodunnit` records who changed the object causing the `version` to be stored. Because a `version` stores the object as it looked before the change (see the table above), `whodunnit` returns who stopped the object looking like this -- not who made it look like this. Hence `whodunnit` is aliased as `terminator`.
292
293 To find out who made a `version`'s object look that way, use `version.originator`. And to find out who made a "live" object look like it does, use `originator` on the object.
294
295 >> widget = Widget.find 153 # assume widget has 0 versions
296 >> PaperTrail.whodunnit = 'Alice'
297 >> widget.update_attributes :name => 'Yankee'
298 >> widget.originator # 'Alice'
299 >> PaperTrail.whodunnit = 'Bob'
300 >> widget.update_attributes :name => 'Zulu'
301 >> widget.originator # 'Bob'
302 >> first_version, last_version = widget.versions.first, widget.versions.last
303 >> first_version.whodunnit # 'Alice'
304 >> first_version.originator # nil
305 >> first_version.terminator # 'Alice'
306 >> last_version.whodunnit # 'Bob'
307 >> last_version.originator # 'Alice'
308 >> last_version.terminator # 'Bob'
309
e9a8648 @airblade First commit.
airblade authored
310
f664a45 @airblade Add note about associations.
airblade authored
311 ## Associations
312
313 I haven't yet found a good way to get PaperTrail to automatically restore associations when you reify a model. See [here for a little more info](http://airbladesoftware.com/notes/undo-and-redo-with-papertrail).
314
315 If you can think of a good way to achieve this, please let me know.
316
317
9728e31 @airblade Automatically restore has_one associations.
airblade authored
318 ## Has-One Associations
319
100eb53 @airblade Improve automatic reification of has_one associations.
airblade authored
320 PaperTrail automatically restores `:has_one` associations as they were at (actually, 3 seconds before) the time.
9728e31 @airblade Automatically restore has_one associations.
airblade authored
321
322 class Treasure < ActiveRecord::Base
323 has_one :location
324 end
325
326 >> treasure.amount # 100
327 >> treasure.location.latitude # 12.345
328
329 >> treasure.update_attributes :amount => 153
330 >> treasure.location.update_attributes :latitude => 54.321
331
332 >> t = treasure.versions.last.reify
333 >> t.amount # 100
334 >> t.location.latitude # 12.345
4cf3cb9 @airblade Add caveat about has_one and transactions.
airblade authored
335
100eb53 @airblade Improve automatic reification of has_one associations.
airblade authored
336 The implementation is complicated by the edge case where the parent and child are updated in one go, e.g. in one web request or database transaction. PaperTrail doesn't know about different models being updated "together", so you can't ask it definitively to get the child as it was before the joint parent-and-child update.
337
338 The correct solution is to make PaperTrail aware of requests or transactions (c.f. [Efficiency's transaction ID middleware](http://github.com/efficiency20/ops_middleware/blob/master/lib/e20/ops/middleware/transaction_id_middleware.rb)). In the meantime we work around the problem by finding the child as it was a few seconds before the parent was updated. By default we go 3 seconds before but you can change this by passing the `:has_one` option to `reify`:
339
340 >> t = treasure.versions.last.reify(:has_one => 1) # look back 1 second instead of 3
341
342 If you are shuddering, take solace from knowing you can opt out of these shenanigans:
343
344 >> t = treasure.versions.last.reify(:has_one => false) # I say no to "workarounds"!
345
346 Opting out means your `:has_one` associated objects will be the live ones, not the ones the user saw at the time. Since PaperTrail doesn't auto-restore `:has_many` associations (I can't get it to work) or `:belongs_to` (I ran out of time looking at `:has_many`), this at least makes your associations wrong consistently ;)
347
9728e31 @airblade Automatically restore has_one associations.
airblade authored
348
349
f65cd0f @airblade Add info and tests for has_many :through.
airblade authored
350 ## Has-Many-Through Associations
351
352 PaperTrail can track most changes to the join table. Specifically it can track all additions but it can only track removals which fire the `after_destroy` callback on the join table. Here are some examples:
353
354 Given these models:
355
356 class Book < ActiveRecord::Base
357 has_many :authorships, :dependent => :destroy
358 has_many :authors, :through => :authorships, :source => :person
359 has_paper_trail
360 end
361
362 class Authorship < ActiveRecord::Base
363 belongs_to :book
364 belongs_to :person
365 has_paper_trail # NOTE
366 end
367
368 class Person < ActiveRecord::Base
369 has_many :authorships, :dependent => :destroy
370 has_many :books, :through => :authorships
371 has_paper_trail
372 end
373
374 Then each of the following will store authorship versions:
375
376 >> @book.authors << @dostoyevsky
377 >> @book.authors.create :name => 'Tolstoy'
378 >> @book.authorships.last.destroy
379 >> @book.authorships.clear
380
381 But none of these will:
382
383 >> @book.authors.delete @tolstoy
384 >> @book.author_ids = [@solzhenistyn.id, @dostoyevsky.id]
385 >> @book.authors = []
386
312ff24 @airblade Update contributors. Inline the monkey patch.
airblade authored
387 Having said that, you can apparently get all these working (I haven't tested it myself) with this [monkey patch](http://stackoverflow.com/questions/2381033/how-to-create-a-full-audit-log-in-rails-for-every-table/2381411#2381411):
388
389 # In config/initializers/core_extensions.rb or lib/core_extensions.rb
390 ActiveRecord::Associations::HasManyThroughAssociation.class_eval do
391 def delete_records(records)
392 klass = @reflection.through_reflection.klass
393 records.each do |associate|
394 klass.destroy_all(construct_join_attributes(associate))
395 end
396 end
397 end
398
399 The difference is the call to `destroy_all` instead of `delete_all` in [the original](http://github.com/rails/rails/blob/master/activerecord/lib/active_record/associations/has_many_through_association.rb#L76-81).
400
636945b @airblade Add comment on association delete.
airblade authored
401
f65cd0f @airblade Add info and tests for has_many :through.
airblade authored
402 There may be a way to store authorship versions, probably using association callbacks, no matter how the collection is manipulated but I haven't found it yet. Let me know if you do.
403
404
55185cd @airblade Store user-defined metadata.
airblade authored
405 ## Storing metadata
406
3036aa7 @airblade Store controller info. Allow other current_user methods.
airblade authored
407 You can store arbitrary model-level metadata alongside each version like this:
55185cd @airblade Store user-defined metadata.
airblade authored
408
409 class Article < ActiveRecord::Base
410 belongs_to :author
0f085d1 @airblade Add note about symbolds in the meta hash.
airblade authored
411 has_paper_trail :meta => { :author_id => Proc.new { |article| article.author_id },
412 :word_count => :count_words,
413 :answer => 42 }
414 def count_words
415 153
416 end
55185cd @airblade Store user-defined metadata.
airblade authored
417 end
418
419 PaperTrail will call your proc with the current article and store the result in the `author_id` column of the `versions` table. (Remember to add your metadata columns to the table.)
420
421 Why would you do this? In this example, `author_id` is an attribute of `Article` and PaperTrail will store it anyway in serialized (YAML) form in the `object` column of the `version` record. But let's say you wanted to pull out all versions for a particular author; without the metadata you would have to deserialize (reify) each `version` object to see if belonged to the author in question. Clearly this is inefficient. Using the metadata you can find just those versions you want:
422
423 Version.all(:conditions => ['author_id = ?', author_id])
424
0f085d1 @airblade Add note about symbolds in the meta hash.
airblade authored
425 Note you can pass a symbol as a value in the `meta` hash to signal a method to call.
426
3036aa7 @airblade Store controller info. Allow other current_user methods.
airblade authored
427 You can also store any information you like from your controller. Just override the `info_for_paper_trail` method in your controller to return a hash whose keys correspond to columns in your `versions` table. E.g.:
428
429 class ApplicationController
430 def info_for_paper_trail
431 { :ip => request.remote_ip, :user_agent => request.user_agent }
432 end
433 end
434
435 Remember to add those extra columns to your `versions` table ;)
436
55185cd @airblade Store user-defined metadata.
airblade authored
437
4aa1c98 @airblade Add info on diffing.
airblade authored
438 ## Diffing Versions
439
e0eb4f2 @airblade Remove redundant text.
airblade authored
440 When you're storing every version of an object, as PaperTrail lets you do, you're almost certainly going to want to diff those versions against each other. However I haven't built a diff method into PaperTrail because I think diffing is best left to dedicated libraries, and also it's hard to come up with a diff method to suit all the use cases.
4aa1c98 @airblade Add info on diffing.
airblade authored
441
442 You might be surprised that PaperTrail doesn't use diffs internally anyway. When I designed PaperTrail I wanted simplicity and robustness so I decided to make each version of an object self-contained. A version stores all of its object's data, not a diff from the previous version.
443
444 So instead here are some specialised diffing libraries which you can use on top of PaperTrail.
445
446 For diffing two strings:
447
448 * [htmldiff](http://github.com/myobie/htmldiff): expects but doesn't require HTML input and produces HTML output. Works very well but slows down significantly on large (e.g. 5,000 word) inputs.
449 * [differ](http://github.com/pvande/differ): expects plain text input and produces plain text/coloured/HTML/any output. Can do character-wise, word-wise, line-wise, or arbitrary-boundary-string-wise diffs. Works very well on non-HTML input.
450 * [diff-lcs](http://github.com/halostatue/ruwiki/tree/master/diff-lcs/trunk): old-school, line-wise diffs.
451
452 For diffing two ActiveRecord objects:
453
454 * [Jeremy Weiskotten's PaperTrail fork](http://github.com/jeremyw/paper_trail/blob/master/lib/paper_trail/has_paper_trail.rb#L151-156): uses ActiveSupport's diff to return an array of hashes of the changes.
455 * [activerecord-diff](http://github.com/tim/activerecord-diff): rather like ActiveRecord::Dirty but also allows you to specify which columns to compare.
456
457
e9a8648 @airblade First commit.
airblade authored
458 ## Turning PaperTrail Off/On
459
4f4f814 @airblade Global enable/disable for PaperTrail.
airblade authored
460 Sometimes you don't want to store changes. Perhaps you are only interested in changes made by your users and don't need to store changes you make yourself in, say, a migration -- or when testing your application.
e9a8648 @airblade First commit.
airblade authored
461
4f4f814 @airblade Global enable/disable for PaperTrail.
airblade authored
462 If you are about change some widgets and you don't want a paper trail of your changes, you can turn PaperTrail off like this:
e9a8648 @airblade First commit.
airblade authored
463
464 >> Widget.paper_trail_off
465
466 And on again like this:
467
468 >> Widget.paper_trail_on
469
4f4f814 @airblade Global enable/disable for PaperTrail.
airblade authored
470 You can also disable PaperTrail for all models:
471
472 >> PaperTrail.enabled = false
473
474 For example, you might want to disable PaperTrail in your Rails application's test environment to speed up your tests. This will do it:
475
476 # in config/environments/test.rb
477 config.after_initialize do
478 PaperTrail.enabled = false
479 end
480
481 If you disable PaperTrail in your test environment but want to enable it for specific tests, you can add a helper like this to your test helper:
482
483 # in test/test_helper.rb
484 def with_versioning
485 was_enabled = PaperTrail.enabled?
486 PaperTrail.enabled = true
487 begin
488 yield
489 ensure
490 PaperTrail.enabled = was_enabled
491 end
492 end
493
494 And then use it in your tests like this:
495
496 test "something that needs versioning" do
497 with_versioning do
498 # your test
499 end
500 end
501
e9a8648 @airblade First commit.
airblade authored
502
6b5c8e1 @airblade Add info on deleting old versions.
airblade authored
503 ## Deleting Old Versions
504
505 Over time your `versions` table will grow to an unwieldy size. Because each version is self-contained (see the Diffing section above for more) you can simply delete any records you don't want any more. For example:
506
507 sql> delete from versions where created_at < 2010-06-01;
508
509 >> Version.delete_all ["created_at < ?", 1.week.ago]
510
511
e9a8648 @airblade First commit.
airblade authored
512 ## Installation
513
ba0d38c @airblade Updated installation instructions for Rails 3.
airblade authored
514 ### Rails 3
515
516 1. Install PaperTrail as a gem via your `Gemfile`:
517
842e702 @airblade Add Rails 3 / 2.3 instructions to README.
airblade authored
518 `gem 'paper_trail', '~> 2'`
ba0d38c @airblade Updated installation instructions for Rails 3.
airblade authored
519
520 2. Generate a migration which will add a `versions` table to your database.
521
4a55b23 @airblade Fix installation instructions for running generator.
airblade authored
522 `bundle exec rails generate paper_trail`
ba0d38c @airblade Updated installation instructions for Rails 3.
airblade authored
523
524 3. Run the migration.
525
55a31e5 @airblade Add 'bundle exec' to installation instructions.
airblade authored
526 `bundle exec rake db:migrate`
ba0d38c @airblade Updated installation instructions for Rails 3.
airblade authored
527
528 4. Add `has_paper_trail` to the models you want to track.
529
530 ### Rails 2
531
2defd7c @airblade Tweak README about differences between Rails 3 and 2.3
airblade authored
532 Please see the `rails2` branch.
e9a8648 @airblade First commit.
airblade authored
533
534
53d1758 @airblade Note about the tests.
airblade authored
535 ## Testing
536
bd1877c @airblade Use Bundler to manage dependencies.
airblade authored
537 PaperTrail uses Bundler to manage its dependencies (in development and testing). You can run the tests with `bundle exec rake test`. (You may need to `bundle install` first.)
53d1758 @airblade Note about the tests.
airblade authored
538
539
28f05c4 @airblade Link up Linx magazine article.
airblade authored
540 ## Articles
541
542 [Keep a Paper Trail with PaperTrail](http://www.linux-mag.com/id/7528), Linux Magazine, 16th September 2009.
543
544
4da196a @airblade Expanded README.
airblade authored
545 ## Problems
546
547 Please use GitHub's [issue tracker](http://github.com/airblade/paper_trail/issues).
548
549
94c28e5 @airblade Updated section on tests and added Contributors to README.
airblade authored
550 ## Contributors
551
552 Many thanks to:
553
554 * [Zachery Hostens](http://github.com/zacheryph)
3a4187b @airblade Describe version_at in README. Add JW as contributor.
airblade authored
555 * [Jeremy Weiskotten](http://github.com/jeremyw)
9332ff2 @airblade Make PaperTrail threadsafe.
airblade authored
556 * [Phan Le](http://github.com/revo)
557 * [jdrucza](http://github.com/jdrucza)
cd92392 @airblade Update contributors.
airblade authored
558 * [conickal](http://github.com/conickal)
312ff24 @airblade Update contributors. Inline the monkey patch.
airblade authored
559 * [Thibaud Guillaume-Gentil](http://github.com/thibaudgg)
560 * Danny Trelogan
5db477e @airblade Update contributors.
airblade authored
561 * [Mikl Kurkov](http://github.com/mkurkov)
8a1fd65 @airblade Update contributors.
airblade authored
562 * [Franco Catena](https://github.com/francocatena)
e69a3ae @airblade Update contributors.
airblade authored
563 * [Emmanuel Gomez](https://github.com/emmanuel)
64fa2cb @airblade Update contributors.
airblade authored
564 * [Matthew MacLeod](https://github.com/mattmacleod)
94c28e5 @airblade Updated section on tests and added Contributors to README.
airblade authored
565
566
e9a8648 @airblade First commit.
airblade authored
567 ## Inspirations
568
569 * [Simply Versioned](http://github.com/github/simply_versioned)
570 * [Acts As Audited](http://github.com/collectiveidea/acts_as_audited)
571
572
573 ## Intellectual Property
574
aa5454b @airblade Update copyright year.
airblade authored
575 Copyright (c) 2011 Andy Stewart (boss@airbladesoftware.com).
e9a8648 @airblade First commit.
airblade authored
576 Released under the MIT licence.
Something went wrong with that request. Please try again.