Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 628 lines (415 sloc) 25.943 kb
e9a8648c »
2009-05-27 First commit.
1 # PaperTrail
2
93270834 »
2009-06-23 Added info on reverting and undeleting.
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.
e9a8648c »
2009-05-27 First commit.
4
559eb91d »
2011-03-01 Link up Railscast.
5 There's an excellent [Railscast on implementing Undo with Paper Trail](http://railscasts.com/episodes/255-undo-with-paper-trail).
e9a8648c »
2009-05-27 First commit.
6
7 ## Features
8
9 * Stores every create, update and destroy.
55185cd4 »
2010-01-06 Store user-defined metadata.
10 * Does not store updates which don't change anything.
c93b364c »
2011-02-25 Update feature list in light of :only option.
11 * Allows you to specify attributes (by inclusion or exclusion) which must change for a Version to be stored.
e9a8648c »
2009-05-27 First commit.
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.
3a4187b5 »
2010-02-18 Describe version_at in README. Add JW as contributor.
14 * Allows you to get at the version as of a particular time.
4117c01e »
2011-06-20 Reify now defaults to :has_one => false
15 * Option to automatically restore `has_one` associations as they were at the time.
3036aa75 »
2010-03-19 Store controller info. Allow other current_user methods.
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.
e9a8648c »
2009-05-27 First commit.
17 * Allows you to set who is responsible at model-level (useful for migrations).
3036aa75 »
2010-03-19 Store controller info. Allow other current_user methods.
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.
4f4f8145 »
2010-03-19 Global enable/disable for PaperTrail.
20 * Can be turned off/on per class (useful for migrations).
bb79fab9 »
2011-04-05 Rename enable/disable methods to clarify intention.
21 * Can be turned off/on per request (useful for testing with an external service).
4f4f8145 »
2010-03-19 Global enable/disable for PaperTrail.
22 * Can be turned off/on globally (useful for testing).
e9a8648c »
2009-05-27 First commit.
23 * No configuration necessary.
1c43e853 »
2011-03-31 Add note about custom version subclasses.
24 * Stores everything in a single database table by default (generates migration for you), or can use separate tables for separate models.
25 * Supports custom version classes so different models' versions can have different behaviour.
e9a8648c »
2009-05-27 First commit.
26 * Thoroughly tested.
9332ff25 »
2010-03-19 Make PaperTrail threadsafe.
27 * Threadsafe.
e9a8648c »
2009-05-27 First commit.
28
29
30 ## Rails Version
31
2b5dbe05 »
2011-03-01 Add note about Rails 2.3.
32 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.
e9a8648c »
2009-05-27 First commit.
33
34
bfe96a71 »
2010-10-28 Add API summary as a quick reference.
35 ## API Summary
36
37 When you declare `has_paper_trail` in your model, you get these methods:
38
39 class Widget < ActiveRecord::Base
40 has_paper_trail # you can pass various options here
41 end
42
43 # Returns this widget's versions.
44 widget.versions
45
46 # Return the version this widget was reified from, or nil if it is live.
47 widget.version
48
49 # Returns true if this widget is the current, live one; or false if it is from a previous version.
50 widget.live?
51
52 # Returns who put the widget into its current state.
53 widget.originator
54
55 # Returns the widget (not a version) as it looked at the given timestamp.
56 widget.version_at(timestamp)
57
58 # Returns the widget (not a version) as it was most recently.
59 widget.previous_version
60
61 # Returns the widget (not a version) as it became next.
62 widget.next_version
63
64 # Turn PaperTrail off for all widgets.
65 Widget.paper_trail_off
66
67 # Turn PaperTrail on for all widgets.
68 Widget.paper_trail_on
69
70 And a `Version` instance has these methods:
71
72 # Returns the item restored from this version.
73 version.reify(options = {})
74
75 # Returns who put the item into the state stored in this version.
76 version.originator
77
78 # Returns who changed the item from the state it had in this version.
79 version.terminator
80 version.whodunnit
81
82 # Returns the next version.
83 version.next
84
85 # Returns the previous version.
86 version.previous
87
88 # Returns the index of this version in all the versions.
89 version.index
90
91 # Returns the event that caused this version (create|update|destroy).
92 version.event
93
94 In your controllers you can override these methods:
95
96 # Returns the user who is responsible for any changes that occur.
97 # Defaults to current_user.
98 user_for_paper_trail
99
100 # Returns any information about the controller or request that you want
101 # PaperTrail to store alongside any changes that occur.
102 info_for_paper_trail
103
104
e9a8648c »
2009-05-27 First commit.
105 ## Basic Usage
106
4da196ab »
2009-06-23 Expanded README.
107 PaperTrail is simple to use. Just add 15 characters to a model to get a paper trail of every `create`, `update`, and `destroy`.
e9a8648c »
2009-05-27 First commit.
108
109 class Widget < ActiveRecord::Base
110 has_paper_trail
111 end
112
113 This gives you a `versions` method which returns the paper trail of changes to your model.
114
115 >> widget = Widget.find 42
116 >> widget.versions # [<Version>, <Version>, ...]
117
118 Once you have a version, you can find out what happened:
119
120 >> v = widget.versions.last
121 >> v.event # 'update' (or 'create' or 'destroy')
122 >> v.whodunnit # '153' (if the update was via a controller and
123 # the controller has a current_user method,
124 # here returning the id of the current user)
125 >> v.created_at # when the update occurred
126 >> widget = v.reify # the widget as it was before the update;
127 # would be nil for a create event
128
4da196ab »
2009-06-23 Expanded README.
129 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.
e9a8648c »
2009-05-27 First commit.
130
131 >> widget = Widget.find 153
132 >> widget.name # 'Doobly'
4da196ab »
2009-06-23 Expanded README.
133
134 # Add has_paper_trail to Widget model.
135
e9a8648c »
2009-05-27 First commit.
136 >> widget.versions # []
137 >> widget.update_attributes :name => 'Wotsit'
138 >> widget.versions.first.reify.name # 'Doobly'
139 >> widget.versions.first.event # 'update'
140
4da196ab »
2009-06-23 Expanded README.
141 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.
e9a8648c »
2009-05-27 First commit.
142
143 Here's a helpful table showing what PaperTrail stores:
144
145 <table>
146 <tr>
147 <th>Event</th>
148 <th>Model Before</th>
149 <th>Model After</th>
150 </tr>
151 <tr>
152 <td>create</td>
153 <td>nil</td>
154 <td>widget</td>
155 </tr>
156 <tr>
157 <td>update</td>
158 <td>widget</td>
159 <td>widget'</td>
160 <tr>
161 <td>destroy</td>
162 <td>widget</td>
163 <td>nil</td>
164 </tr>
165 </table>
166
4da196ab »
2009-06-23 Expanded README.
167 PaperTrail stores the values in the Model Before column. Most other auditing/versioning plugins store the After column.
168
169
7e34a81e »
2011-02-25 Changed the way that notable attributes are worked out to make better…
170 ## Choosing Attributes To Monitor
52981bfc »
2009-10-28 Optionally ignore attributes.
171
172 You can ignore changes to certain attributes like this:
173
174 class Article < ActiveRecord::Base
175 has_paper_trail :ignore => [:title, :rating]
176 end
177
178 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:
179
180 >> a = Article.create
181 >> a.versions.length # 1
182 >> a.update_attributes :title => 'My Title', :rating => 3
183 >> a.versions.length # 1
184 >> a.update_attributes :content => 'Hello'
185 >> a.versions.length # 2
186 >> a.versions.last.reify.title # 'My Title'
187
7e34a81e »
2011-02-25 Changed the way that notable attributes are worked out to make better…
188 Or, you can specify a list of all attributes you care about:
189
190 class Article < ActiveRecord::Base
191 has_paper_trail :only => [:title]
192 end
193
194 This means that only changes to the `title` will save a version of the article:
195
2519c2b9 »
2011-02-25 Whitespace.
196 >> a = Article.create
7e34a81e »
2011-02-25 Changed the way that notable attributes are worked out to make better…
197 >> a.versions.length # 1
198 >> a.update_attributes :title => 'My Title'
199 >> a.versions.length # 2
200 >> a.update_attributes :content => 'Hello'
201 >> a.versions.length # 2
2519c2b9 »
2011-02-25 Whitespace.
202
7e34a81e »
2011-02-25 Changed the way that notable attributes are worked out to make better…
203 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`.
52981bfc »
2009-10-28 Optionally ignore attributes.
204
2519c2b9 »
2011-02-25 Whitespace.
205
93270834 »
2009-06-23 Added info on reverting and undeleting.
206 ## Reverting And Undeleting A Model
4da196ab »
2009-06-23 Expanded README.
207
93270834 »
2009-06-23 Added info on reverting and undeleting.
208 PaperTrail makes reverting to a previous version easy:
4da196ab »
2009-06-23 Expanded README.
209
210 >> widget = Widget.find 42
93270834 »
2009-06-23 Added info on reverting and undeleting.
211 >> widget.update_attributes :name => 'Blah blah'
4da196ab »
2009-06-23 Expanded README.
212 # Time passes....
93270834 »
2009-06-23 Added info on reverting and undeleting.
213 >> widget = widget.versions.last.reify # the widget as it was before the update
214 >> widget.save # reverted
215
3a4187b5 »
2010-02-18 Describe version_at in README. Add JW as contributor.
216 Alternatively you can find the version at a given time:
217
218 >> widget = widget.version_at(1.day.ago) # the widget as it was one day ago
219 >> widget.save # reverted
220
221 Note `version_at` gives you the object, not a version, so you don't need to call `reify`.
222
93270834 »
2009-06-23 Added info on reverting and undeleting.
223 Undeleting is just as simple:
4da196ab »
2009-06-23 Expanded README.
224
93270834 »
2009-06-23 Added info on reverting and undeleting.
225 >> widget = Widget.find 42
226 >> widget.destroy
227 # Time passes....
4da196ab »
2009-06-23 Expanded README.
228 >> widget = Version.find(153).reify # the widget as it was before it was destroyed
229 >> widget.save # the widget lives!
230
559eb91d »
2011-03-01 Link up Railscast.
231 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)!
e9a8648c »
2009-05-27 First commit.
232
233
cc835027 »
2010-06-22 Navigate back to reified item's version.
234 ## Navigating Versions
235
b43400e3 »
2010-06-22 Navigate to an item's previous and next version.
236 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.
237
238 >> widget = Widget.find 42
239 >> widget.versions.length # 4 for example
240 >> widget = widget.previous_version # => widget == widget.versions.last.reify
241 >> widget = widget.previous_version # => widget == widget.versions[-2].reify
242 >> widget.next_version # => widget == widget.versions.last.reify
243 >> widget.next_version # nil
244
245 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.
246
247 If instead you have a particular `version` of an item you can navigate to the previous and next versions.
cc835027 »
2010-06-22 Navigate back to reified item's version.
248
249 >> widget = Widget.find 42
250 >> version = widget.versions[-2] # assuming widget has several versions
251 >> previous = version.previous
252 >> next = version.next
253
254 You can find out which of an item's versions yours is:
255
256 >> current_version_number = version.index # 0-based
257
258 Finally, if you got an item by reifying one of its versions, you can navigate back to the version it came from:
259
260 >> latest_version = Widget.find(42).versions.last
261 >> widget = latest_version.reify
262 >> widget.version == latest_version # true
263
aa00ccae »
2010-10-21 Add convenience method to find out if instance is live.
264 You can find out whether a model instance is the current, live one -- or whether it came instead from a previous version -- with `live?`:
265
266 >> widget = Widget.find 42
267 >> widget.live? # true
268 >> widget = widget.versions.last.reify
269 >> widget.live? # false
270
cc835027 »
2010-06-22 Navigate back to reified item's version.
271
e9a8648c »
2009-05-27 First commit.
272 ## Finding Out Who Was Responsible For A Change
273
4da196ab »
2009-06-23 Expanded README.
274 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:
e9a8648c »
2009-05-27 First commit.
275
276 >> last_change = Widget.versions.last
277 >> user_who_made_the_change = User.find last_change.whodunnit.to_i
278
3036aa75 »
2010-03-19 Store controller info. Allow other current_user methods.
279 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:
280
281 class ApplicationController
282 def user_for_paper_trail
283 logged_in? ? current_member : 'Public user' # or whatever
284 end
285 end
286
e9a8648c »
2009-05-27 First commit.
287 In a migration or in `script/console` you can set who is responsible like this:
288
289 >> PaperTrail.whodunnit = 'Andy Stewart'
290 >> widget.update_attributes :name => 'Wibble'
291 >> widget.versions.last.whodunnit # Andy Stewart
292
f27763bf »
2010-06-28 Clarify who was responsible for changes.
293 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`.
294
295 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.
296
297 >> widget = Widget.find 153 # assume widget has 0 versions
298 >> PaperTrail.whodunnit = 'Alice'
299 >> widget.update_attributes :name => 'Yankee'
300 >> widget.originator # 'Alice'
301 >> PaperTrail.whodunnit = 'Bob'
302 >> widget.update_attributes :name => 'Zulu'
303 >> widget.originator # 'Bob'
304 >> first_version, last_version = widget.versions.first, widget.versions.last
305 >> first_version.whodunnit # 'Alice'
306 >> first_version.originator # nil
307 >> first_version.terminator # 'Alice'
308 >> last_version.whodunnit # 'Bob'
309 >> last_version.originator # 'Alice'
310 >> last_version.terminator # 'Bob'
311
e9a8648c »
2009-05-27 First commit.
312
1c43e853 »
2011-03-31 Add note about custom version subclasses.
313 ## Custom Version Classes
314
315 You can specify custom version subclasses with the `:class_name` option:
316
317 class Post < ActiveRecord::Base
318 has_paper_trail :class_name => 'PostVersion'
319 end
320
321 class PostVersion < Version
322 # custom behaviour, e.g:
323 set_table_name :post_versions
324 end
325
326 This allows you to store each model's versions in a separate table, which is useful if you have a lot of versions being created.
327
328 Alternatively you could store certain metadata for one type of version, and other metadata for other versions.
329
330
f664a454 »
2011-03-01 Add note about associations.
331 ## Associations
332
333 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).
334
335 If you can think of a good way to achieve this, please let me know.
336
337
9728e315 »
2010-10-20 Automatically restore has_one associations.
338 ## Has-One Associations
339
4117c01e »
2011-06-20 Reify now defaults to :has_one => false
340 PaperTrail can restore `:has_one` associations as they were at (actually, 3 seconds before) the time.
9728e315 »
2010-10-20 Automatically restore has_one associations.
341
342 class Treasure < ActiveRecord::Base
343 has_one :location
344 end
345
346 >> treasure.amount # 100
347 >> treasure.location.latitude # 12.345
348
349 >> treasure.update_attributes :amount => 153
350 >> treasure.location.update_attributes :latitude => 54.321
351
4117c01e »
2011-06-20 Reify now defaults to :has_one => false
352 >> t = treasure.versions.last.reify(:has_one => true)
9728e315 »
2010-10-20 Automatically restore has_one associations.
353 >> t.amount # 100
354 >> t.location.latitude # 12.345
4cf3cb95 »
2010-10-20 Add caveat about has_one and transactions.
355
100eb53c »
2010-10-21 Improve automatic reification of has_one associations.
356 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.
357
4117c01e »
2011-06-20 Reify now defaults to :has_one => false
358 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 desired number of seconds to the `:has_one` option:
100eb53c »
2010-10-21 Improve automatic reification of has_one associations.
359
360 >> t = treasure.versions.last.reify(:has_one => 1) # look back 1 second instead of 3
361
4117c01e »
2011-06-20 Reify now defaults to :has_one => false
362 If you are shuddering, take solace from knowing PaperTrail opts out of these shenanigans by default. This 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 ;)
100eb53c »
2010-10-21 Improve automatic reification of has_one associations.
363
9728e315 »
2010-10-20 Automatically restore has_one associations.
364
365
f65cd0f7 »
2010-07-05 Add info and tests for has_many :through.
366 ## Has-Many-Through Associations
367
368 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:
369
370 Given these models:
371
372 class Book < ActiveRecord::Base
373 has_many :authorships, :dependent => :destroy
374 has_many :authors, :through => :authorships, :source => :person
375 has_paper_trail
376 end
377
378 class Authorship < ActiveRecord::Base
379 belongs_to :book
380 belongs_to :person
381 has_paper_trail # NOTE
382 end
383
384 class Person < ActiveRecord::Base
385 has_many :authorships, :dependent => :destroy
386 has_many :books, :through => :authorships
387 has_paper_trail
388 end
389
390 Then each of the following will store authorship versions:
391
392 >> @book.authors << @dostoyevsky
393 >> @book.authors.create :name => 'Tolstoy'
394 >> @book.authorships.last.destroy
395 >> @book.authorships.clear
396
397 But none of these will:
398
399 >> @book.authors.delete @tolstoy
400 >> @book.author_ids = [@solzhenistyn.id, @dostoyevsky.id]
401 >> @book.authors = []
402
312ff243 »
2010-10-28 Update contributors. Inline the monkey patch.
403 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):
404
405 # In config/initializers/core_extensions.rb or lib/core_extensions.rb
406 ActiveRecord::Associations::HasManyThroughAssociation.class_eval do
407 def delete_records(records)
408 klass = @reflection.through_reflection.klass
409 records.each do |associate|
410 klass.destroy_all(construct_join_attributes(associate))
411 end
412 end
413 end
414
415 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).
416
636945b7 »
2010-09-03 Add comment on association delete.
417
f65cd0f7 »
2010-07-05 Add info and tests for has_many :through.
418 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.
419
420
55185cd4 »
2010-01-06 Store user-defined metadata.
421 ## Storing metadata
422
3036aa75 »
2010-03-19 Store controller info. Allow other current_user methods.
423 You can store arbitrary model-level metadata alongside each version like this:
55185cd4 »
2010-01-06 Store user-defined metadata.
424
425 class Article < ActiveRecord::Base
426 belongs_to :author
0f085d19 »
2011-02-08 Add note about symbolds in the meta hash.
427 has_paper_trail :meta => { :author_id => Proc.new { |article| article.author_id },
428 :word_count => :count_words,
429 :answer => 42 }
430 def count_words
431 153
432 end
55185cd4 »
2010-01-06 Store user-defined metadata.
433 end
434
435 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.)
436
437 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:
438
439 Version.all(:conditions => ['author_id = ?', author_id])
440
0f085d19 »
2011-02-08 Add note about symbolds in the meta hash.
441 Note you can pass a symbol as a value in the `meta` hash to signal a method to call.
442
3036aa75 »
2010-03-19 Store controller info. Allow other current_user methods.
443 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.:
444
445 class ApplicationController
446 def info_for_paper_trail
447 { :ip => request.remote_ip, :user_agent => request.user_agent }
448 end
449 end
450
451 Remember to add those extra columns to your `versions` table ;)
452
55185cd4 »
2010-01-06 Store user-defined metadata.
453
4aa1c989 »
2010-06-23 Add info on diffing.
454 ## Diffing Versions
455
9c1bf3da »
2011-07-13 Update diffing section of README.
456 There are two scenarios: diffing adjacent versions and diffing non-adjacent versions.
4aa1c989 »
2010-06-23 Add info on diffing.
457
9c1bf3da »
2011-07-13 Update diffing section of README.
458 The best way to diff adjacent versions is to get PaperTrail to do it for you. If you add an `object_changes` text column to your `versions` table, either at installation time with the `--with-changes` option or manually, PaperTrail will store the `changes` diff in each `update` version. You can use the `version.changeset` method to retrieve it. For example:
4aa1c989 »
2010-06-23 Add info on diffing.
459
9c1bf3da »
2011-07-13 Update diffing section of README.
460 >> widget = Widget.create :name => 'Bob'
461 >> widget.versions.last.changeset # nil
462 >> widget.update_attributes :name => 'Robert'
463 >> widget.versions.last.changeset # {'name' => ['Bob', 'Robert']}
464
465 Note PaperTrail only stores the changes for updates; there's no point storing them for created or destroyed objects.
466
467 Please be aware that PaperTrail doesn't use diffs internally. 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. This means you can delete any version without affecting any other.
468
469 To diff non-adjacent versions you'll have to write your own code. These libraries may help:
4aa1c989 »
2010-06-23 Add info on diffing.
470
471 For diffing two strings:
472
473 * [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.
474 * [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.
475 * [diff-lcs](http://github.com/halostatue/ruwiki/tree/master/diff-lcs/trunk): old-school, line-wise diffs.
476
477 For diffing two ActiveRecord objects:
478
479 * [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.
480 * [activerecord-diff](http://github.com/tim/activerecord-diff): rather like ActiveRecord::Dirty but also allows you to specify which columns to compare.
481
482
e9a8648c »
2009-05-27 First commit.
483 ## Turning PaperTrail Off/On
484
4f4f8145 »
2010-03-19 Global enable/disable for PaperTrail.
485 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.
e9a8648c »
2009-05-27 First commit.
486
bb79fab9 »
2011-04-05 Rename enable/disable methods to clarify intention.
487 You can turn PaperTrail on or off in three ways: globally, per request, or per class.
e9a8648c »
2009-05-27 First commit.
488
bb79fab9 »
2011-04-05 Rename enable/disable methods to clarify intention.
489 ### Globally
e9a8648c »
2009-05-27 First commit.
490
bb79fab9 »
2011-04-05 Rename enable/disable methods to clarify intention.
491 On a global level you can turn PaperTrail off like this:
4f4f8145 »
2010-03-19 Global enable/disable for PaperTrail.
492
493 >> PaperTrail.enabled = false
494
495 For example, you might want to disable PaperTrail in your Rails application's test environment to speed up your tests. This will do it:
496
497 # in config/environments/test.rb
498 config.after_initialize do
499 PaperTrail.enabled = false
500 end
501
502 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:
503
504 # in test/test_helper.rb
505 def with_versioning
506 was_enabled = PaperTrail.enabled?
507 PaperTrail.enabled = true
508 begin
509 yield
510 ensure
511 PaperTrail.enabled = was_enabled
512 end
513 end
514
515 And then use it in your tests like this:
516
517 test "something that needs versioning" do
518 with_versioning do
519 # your test
520 end
521 end
522
bb79fab9 »
2011-04-05 Rename enable/disable methods to clarify intention.
523 ### Per request
524
525 You can turn PaperTrail on or off per request by adding a `paper_trail_enabled_for_controller` method to your controller which returns true or false:
526
527 class ApplicationController < ActionController::Base
528 def paper_trail_enabled_for_controller
529 request.user_agent != 'Disable User-Agent'
530 end
531 end
532
533 ### Per class
534
535 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:
536
537 >> Widget.paper_trail_off
538
539 And on again like this:
540
541 >> Widget.paper_trail_on
542
543
e9a8648c »
2009-05-27 First commit.
544
6b5c8e1c »
2010-06-23 Add info on deleting old versions.
545 ## Deleting Old Versions
546
547 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:
548
549 sql> delete from versions where created_at < 2010-06-01;
550
551 >> Version.delete_all ["created_at < ?", 1.week.ago]
552
553
e9a8648c »
2009-05-27 First commit.
554 ## Installation
555
ba0d38c7 »
2010-10-11 Updated installation instructions for Rails 3.
556 ### Rails 3
557
558 1. Install PaperTrail as a gem via your `Gemfile`:
559
842e702b »
2011-02-08 Add Rails 3 / 2.3 instructions to README.
560 `gem 'paper_trail', '~> 2'`
ba0d38c7 »
2010-10-11 Updated installation instructions for Rails 3.
561
562 2. Generate a migration which will add a `versions` table to your database.
563
581ca47b »
2011-03-04 Update installation instructions.
564 `bundle exec rails generate paper_trail:install`
ba0d38c7 »
2010-10-11 Updated installation instructions for Rails 3.
565
566 3. Run the migration.
567
55a31e54 »
2011-02-09 Add 'bundle exec' to installation instructions.
568 `bundle exec rake db:migrate`
ba0d38c7 »
2010-10-11 Updated installation instructions for Rails 3.
569
570 4. Add `has_paper_trail` to the models you want to track.
571
572 ### Rails 2
573
2defd7cf »
2011-02-09 Tweak README about differences between Rails 3 and 2.3
574 Please see the `rails2` branch.
e9a8648c »
2009-05-27 First commit.
575
576
53d17589 »
2009-05-27 Note about the tests.
577 ## Testing
578
bd1877ca »
2010-10-15 Use Bundler to manage dependencies.
579 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.)
53d17589 »
2009-05-27 Note about the tests.
580
581
28f05c43 »
2010-02-23 Link up Linx magazine article.
582 ## Articles
583
584 [Keep a Paper Trail with PaperTrail](http://www.linux-mag.com/id/7528), Linux Magazine, 16th September 2009.
585
586
4da196ab »
2009-06-23 Expanded README.
587 ## Problems
588
589 Please use GitHub's [issue tracker](http://github.com/airblade/paper_trail/issues).
590
591
94c28e5a »
2009-11-23 Updated section on tests and added Contributors to README.
592 ## Contributors
593
594 Many thanks to:
595
596 * [Zachery Hostens](http://github.com/zacheryph)
3a4187b5 »
2010-02-18 Describe version_at in README. Add JW as contributor.
597 * [Jeremy Weiskotten](http://github.com/jeremyw)
9332ff25 »
2010-03-19 Make PaperTrail threadsafe.
598 * [Phan Le](http://github.com/revo)
599 * [jdrucza](http://github.com/jdrucza)
cd923926 »
2010-10-11 Update contributors.
600 * [conickal](http://github.com/conickal)
312ff243 »
2010-10-28 Update contributors. Inline the monkey patch.
601 * [Thibaud Guillaume-Gentil](http://github.com/thibaudgg)
602 * Danny Trelogan
5db477e0 »
2010-10-28 Update contributors.
603 * [Mikl Kurkov](http://github.com/mkurkov)
8a1fd653 »
2010-11-19 Update contributors.
604 * [Franco Catena](https://github.com/francocatena)
e69a3ae5 »
2011-02-21 Update contributors.
605 * [Emmanuel Gomez](https://github.com/emmanuel)
64fa2cbf »
2011-02-25 Update contributors.
606 * [Matthew MacLeod](https://github.com/mattmacleod)
a34fc026 »
2011-03-31 Add benzittlau as a contributor.
607 * [benzittlau](https://github.com/benzittlau)
42efa2cc »
2011-03-31 Add Tom Derks as contributor.
608 * [Tom Derks](https://github.com/EgoH)
69f71b00 »
2011-04-05 Add jhoglund as contributor.
609 * [Jonas Hoglund](https://github.com/jhoglund)
f5eb86d9 »
2011-04-07 Add Stefan Huber as contributor.
610 * [Stefan Huber](https://github.com/MSNexploder)
0f707e75 »
2011-04-28 Add thinkcast as contributor.
611 * [thinkcast](https://github.com/thinkcast)
62782f8e »
2011-05-06 Add Dominik Sander as contributor.
612 * [Dominik Sander](https://github.com/dsander)
1a031c23 »
2011-05-16 Add Burke Libbey as contributor.
613 * [Burke Libbey](https://github.com/burke)
6e901e88 »
2011-06-21 Update contributors.
614 * [6twenty](https://github.com/6twenty)
56c2accc »
2011-07-11 Add nir0 as a contributor.
615 * [nir0](https://github.com/nir0)
94c28e5a »
2009-11-23 Updated section on tests and added Contributors to README.
616
617
e9a8648c »
2009-05-27 First commit.
618 ## Inspirations
619
620 * [Simply Versioned](http://github.com/github/simply_versioned)
621 * [Acts As Audited](http://github.com/collectiveidea/acts_as_audited)
622
623
624 ## Intellectual Property
625
aa5454bf »
2011-02-09 Update copyright year.
626 Copyright (c) 2011 Andy Stewart (boss@airbladesoftware.com).
e9a8648c »
2009-05-27 First commit.
627 Released under the MIT licence.
Something went wrong with that request. Please try again.