paper_trail 2.6.3 → 2.6.4

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -11,3 +11,5 @@ pkg/*
11
11
  .rbenv-version
12
12
  Gemfile.lock
13
13
  vendor/*
14
+ .idea
15
+ .rvmrc
data/CHANGELOG.md ADDED
@@ -0,0 +1,17 @@
1
+ ## 2.6.4
2
+
3
+ - [#181](https://github.com/airblade/paper_trail/issues/181)/[#182](https://github.com/airblade/paper_trail/pull/182) -
4
+ Controller metadata methods should only be evaluated when `paper_trail_enabled_for_controller == true`.
5
+ - [#177](https://github.com/airblade/paper_trail/issues/177)/[#178](https://github.com/airblade/paper_trail/pull/178) -
6
+ Factored out `version_key` into it's own method to prevent `ConnectionNotEstablished` error from getting thrown in
7
+ instances where `has_paper_trail` is declared on class prior to ActiveRecord establishing a connection.
8
+ - [#176](https://github.com/airblade/paper_trail/pull/176) - Force metadata calls for attributes to use current value
9
+ if attribute value is changing.
10
+ - [#173](https://github.com/airblade/paper_trail/pull/173) - Update link to [diff-lcs](https://github.com/halostatue/diff-lcs).
11
+ - [#172](https://github.com/airblade/paper_trail/pull/172) - Save `object_changes` on creation.
12
+ - [#168](https://github.com/airblade/paper_trail/pull/168) - Respect conditional `:if` or `:unless` arguments to the
13
+ `has_paper_trail` method for `destroy` events.
14
+ - [#167](https://github.com/airblade/paper_trail/pull/167) - Fix `originator` method so that it works with subclasses and STI.
15
+ - [#160](https://github.com/airblade/paper_trail/pull/160) - Fixed failing tests and resolved out of date dependency issues.
16
+ - [#157](https://github.com/airblade/paper_trail/pull/157) - Refactored `class_attribute` names on the `ClassMethods` module
17
+ for names that are not obviously pertaining to PaperTrail to prevent method name collision.
data/README.md CHANGED
@@ -38,108 +38,121 @@ Works on Rails 3 and Rails 2.3. The Rails 3 code is on the `master` branch and
38
38
 
39
39
  When you declare `has_paper_trail` in your model, you get these methods:
40
40
 
41
- class Widget < ActiveRecord::Base
42
- has_paper_trail # you can pass various options here
43
- end
41
+ ```ruby
42
+ class Widget < ActiveRecord::Base
43
+ has_paper_trail # you can pass various options here
44
+ end
44
45
 
45
- # Returns this widget's versions. You can customise the name of the association.
46
- widget.versions
46
+ # Returns this widget's versions. You can customise the name of the association.
47
+ widget.versions
47
48
 
48
- # Return the version this widget was reified from, or nil if it is live.
49
- # You can customise the name of the method.
50
- widget.version
49
+ # Return the version this widget was reified from, or nil if it is live.
50
+ # You can customise the name of the method.
51
+ widget.version
51
52
 
52
- # Returns true if this widget is the current, live one; or false if it is from a previous version.
53
- widget.live?
53
+ # Returns true if this widget is the current, live one; or false if it is from a previous version.
54
+ widget.live?
54
55
 
55
- # Returns who put the widget into its current state.
56
- widget.originator
56
+ # Returns who put the widget into its current state.
57
+ widget.originator
57
58
 
58
- # Returns the widget (not a version) as it looked at the given timestamp.
59
- widget.version_at(timestamp)
59
+ # Returns the widget (not a version) as it looked at the given timestamp.
60
+ widget.version_at(timestamp)
60
61
 
61
- # Returns the widget (not a version) as it was most recently.
62
- widget.previous_version
62
+ # Returns the widget (not a version) as it was most recently.
63
+ widget.previous_version
63
64
 
64
- # Returns the widget (not a version) as it became next.
65
- widget.next_version
65
+ # Returns the widget (not a version) as it became next.
66
+ widget.next_version
66
67
 
67
- # Turn PaperTrail off for all widgets.
68
- Widget.paper_trail_off
68
+ # Turn PaperTrail off for all widgets.
69
+ Widget.paper_trail_off
69
70
 
70
- # Turn PaperTrail on for all widgets.
71
- Widget.paper_trail_on
71
+ # Turn PaperTrail on for all widgets.
72
+ Widget.paper_trail_on
73
+ ```
72
74
 
73
75
  And a `Version` instance has these methods:
74
76
 
75
- # Returns the item restored from this version.
76
- version.reify(options = {})
77
+ ```ruby
78
+ # Returns the item restored from this version.
79
+ version.reify(options = {})
77
80
 
78
- # Returns who put the item into the state stored in this version.
79
- version.originator
81
+ # Returns who put the item into the state stored in this version.
82
+ version.originator
80
83
 
81
- # Returns who changed the item from the state it had in this version.
82
- version.terminator
83
- version.whodunnit
84
+ # Returns who changed the item from the state it had in this version.
85
+ version.terminator
86
+ version.whodunnit
84
87
 
85
- # Returns the next version.
86
- version.next
88
+ # Returns the next version.
89
+ version.next
87
90
 
88
- # Returns the previous version.
89
- version.previous
91
+ # Returns the previous version.
92
+ version.previous
90
93
 
91
- # Returns the index of this version in all the versions.
92
- version.index
94
+ # Returns the index of this version in all the versions.
95
+ version.index
93
96
 
94
- # Returns the event that caused this version (create|update|destroy).
95
- version.event
97
+ # Returns the event that caused this version (create|update|destroy).
98
+ version.event
99
+ ```
96
100
 
97
101
  In your controllers you can override these methods:
98
102
 
99
- # Returns the user who is responsible for any changes that occur.
100
- # Defaults to current_user.
101
- user_for_paper_trail
102
-
103
- # Returns any information about the controller or request that you want
104
- # PaperTrail to store alongside any changes that occur.
105
- info_for_paper_trail
103
+ ```ruby
104
+ # Returns the user who is responsible for any changes that occur.
105
+ # Defaults to current_user.
106
+ user_for_paper_trail
106
107
 
108
+ # Returns any information about the controller or request that you want
109
+ # PaperTrail to store alongside any changes that occur.
110
+ info_for_paper_trail
111
+ ```
107
112
 
108
113
  ## Basic Usage
109
114
 
110
115
  PaperTrail is simple to use. Just add 15 characters to a model to get a paper trail of every `create`, `update`, and `destroy`.
111
116
 
112
- class Widget < ActiveRecord::Base
113
- has_paper_trail
114
- end
117
+ ```ruby
118
+ class Widget < ActiveRecord::Base
119
+ has_paper_trail
120
+ end
121
+ ```
115
122
 
116
123
  This gives you a `versions` method which returns the paper trail of changes to your model.
117
124
 
118
- >> widget = Widget.find 42
119
- >> widget.versions # [<Version>, <Version>, ...]
125
+ ```ruby
126
+ >> widget = Widget.find 42
127
+ >> widget.versions # [<Version>, <Version>, ...]
128
+ ```
120
129
 
121
130
  Once you have a version, you can find out what happened:
122
131
 
123
- >> v = widget.versions.last
124
- >> v.event # 'update' (or 'create' or 'destroy')
125
- >> v.whodunnit # '153' (if the update was via a controller and
126
- # the controller has a current_user method,
127
- # here returning the id of the current user)
128
- >> v.created_at # when the update occurred
129
- >> widget = v.reify # the widget as it was before the update;
130
- # would be nil for a create event
132
+ ```ruby
133
+ >> v = widget.versions.last
134
+ >> v.event # 'update' (or 'create' or 'destroy')
135
+ >> v.whodunnit # '153' (if the update was via a controller and
136
+ # the controller has a current_user method,
137
+ # here returning the id of the current user)
138
+ >> v.created_at # when the update occurred
139
+ >> widget = v.reify # the widget as it was before the update;
140
+ # would be nil for a create event
141
+ ```
131
142
 
132
143
  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.
133
144
 
134
- >> widget = Widget.find 153
135
- >> widget.name # 'Doobly'
145
+ ```ruby
146
+ >> widget = Widget.find 153
147
+ >> widget.name # 'Doobly'
136
148
 
137
- # Add has_paper_trail to Widget model.
149
+ # Add has_paper_trail to Widget model.
138
150
 
139
- >> widget.versions # []
140
- >> widget.update_attributes :name => 'Wotsit'
141
- >> widget.versions.first.reify.name # 'Doobly'
142
- >> widget.versions.first.event # 'update'
151
+ >> widget.versions # []
152
+ >> widget.update_attributes :name => 'Wotsit'
153
+ >> widget.last.reify.name # 'Doobly'
154
+ >> widget.versions.last.event # 'update'
155
+ ```
143
156
 
144
157
  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.
145
158
 
@@ -174,54 +187,66 @@ PaperTrail stores the values in the Model Before column. Most other auditing/ve
174
187
 
175
188
  You can choose which events to track with the `on` option. For example, to ignore `create` events:
176
189
 
177
- class Article < ActiveRecord::Base
178
- has_paper_trail :on => [:update, :destroy]
179
- end
190
+ ```ruby
191
+ class Article < ActiveRecord::Base
192
+ has_paper_trail :on => [:update, :destroy]
193
+ end
194
+ ```
180
195
 
181
196
 
182
197
  ## Choosing When To Save New Versions
183
198
 
184
199
  You can choose the conditions when to add new versions with the `if` and `unless` options. For example, to save versions only for US non-draft translations:
185
200
 
186
- class Translation < ActiveRecord::Base
187
- has_paper_trail :if => Proc.new { |t| t.language_code == 'US' },
188
- :unless => Proc.new { |t| t.type == 'DRAFT' }
189
- end
190
-
201
+ ```ruby
202
+ class Translation < ActiveRecord::Base
203
+ has_paper_trail :if => Proc.new { |t| t.language_code == 'US' },
204
+ :unless => Proc.new { |t| t.type == 'DRAFT' }
205
+ end
206
+ ```
191
207
 
192
208
 
193
209
  ## Choosing Attributes To Monitor
194
210
 
195
211
  You can ignore changes to certain attributes like this:
196
212
 
197
- class Article < ActiveRecord::Base
198
- has_paper_trail :ignore => [:title, :rating]
199
- end
213
+ ```ruby
214
+ class Article < ActiveRecord::Base
215
+ has_paper_trail :ignore => [:title, :rating]
216
+ end
217
+ ```
200
218
 
201
219
  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 created. For example:
202
220
 
203
- >> a = Article.create
204
- >> a.versions.length # 1
205
- >> a.update_attributes :title => 'My Title', :rating => 3
206
- >> a.versions.length # 1
207
- >> a.update_attributes :content => 'Hello'
208
- >> a.versions.length # 2
209
- >> a.versions.last.reify.title # 'My Title'
221
+ ```ruby
222
+ >> a = Article.create
223
+ >> a.versions.length # 1
224
+ >> a.update_attributes :title => 'My Title', :rating => 3
225
+ >> a.versions.length # 1
226
+ >> a.update_attributes :title => 'Greeting', :content => 'Hello'
227
+ >> a.versions.length # 2
228
+ >> a.previous_version.title # 'My Title'
229
+ ```
210
230
 
211
231
  Or, you can specify a list of all attributes you care about:
212
232
 
213
- class Article < ActiveRecord::Base
214
- has_paper_trail :only => [:title]
215
- end
233
+ ```ruby
234
+ class Article < ActiveRecord::Base
235
+ has_paper_trail :only => [:title]
236
+ end
237
+ ```
216
238
 
217
239
  This means that only changes to the `title` will save a version of the article:
218
240
 
219
- >> a = Article.create
220
- >> a.versions.length # 1
221
- >> a.update_attributes :title => 'My Title'
222
- >> a.versions.length # 2
223
- >> a.update_attributes :content => 'Hello'
224
- >> a.versions.length # 2
241
+ ```ruby
242
+ >> a = Article.create
243
+ >> a.versions.length # 1
244
+ >> a.update_attributes :title => 'My Title'
245
+ >> a.versions.length # 2
246
+ >> a.update_attributes :content => 'Hello'
247
+ >> a.versions.length # 2
248
+ >> a.previous_version.content # nil
249
+ ```
225
250
 
226
251
  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`.
227
252
 
@@ -229,35 +254,42 @@ You can skip fields altogether with the `:skip` option. As with `:ignore`, upda
229
254
 
230
255
  For example:
231
256
 
232
- class Article < ActiveRecord::Base
233
- has_paper_trail :skip => [:file_upload]
234
- end
235
-
257
+ ```ruby
258
+ class Article < ActiveRecord::Base
259
+ has_paper_trail :skip => [:file_upload]
260
+ end
261
+ ```
236
262
 
237
263
  ## Reverting And Undeleting A Model
238
264
 
239
265
  PaperTrail makes reverting to a previous version easy:
240
266
 
241
- >> widget = Widget.find 42
242
- >> widget.update_attributes :name => 'Blah blah'
243
- # Time passes....
244
- >> widget = widget.versions.last.reify # the widget as it was before the update
245
- >> widget.save # reverted
267
+ ```ruby
268
+ >> widget = Widget.find 42
269
+ >> widget.update_attributes :name => 'Blah blah'
270
+ # Time passes....
271
+ >> widget = widget.previous_version # the widget as it was before the update
272
+ >> widget.save # reverted
273
+ ```
246
274
 
247
275
  Alternatively you can find the version at a given time:
248
276
 
249
- >> widget = widget.version_at(1.day.ago) # the widget as it was one day ago
250
- >> widget.save # reverted
277
+ ```ruby
278
+ >> widget = widget.version_at(1.day.ago) # the widget as it was one day ago
279
+ >> widget.save # reverted
280
+ ```
251
281
 
252
282
  Note `version_at` gives you the object, not a version, so you don't need to call `reify`.
253
283
 
254
284
  Undeleting is just as simple:
255
285
 
256
- >> widget = Widget.find 42
257
- >> widget.destroy
258
- # Time passes....
259
- >> widget = Version.find(153).reify # the widget as it was before it was destroyed
260
- >> widget.save # the widget lives!
286
+ ```ruby
287
+ >> widget = Widget.find 42
288
+ >> widget.destroy
289
+ # Time passes....
290
+ >> widget = Version.find(153).reify # the widget as it was before it was destroyed
291
+ >> widget.save # the widget lives!
292
+ ```
261
293
 
262
294
  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)!
263
295
 
@@ -266,124 +298,166 @@ In fact you could use PaperTrail to implement an undo system, though I haven't h
266
298
 
267
299
  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.
268
300
 
269
- >> widget = Widget.find 42
270
- >> widget.versions.length # 4 for example
271
- >> widget = widget.previous_version # => widget == widget.versions.last.reify
272
- >> widget = widget.previous_version # => widget == widget.versions[-2].reify
273
- >> widget.next_version # => widget == widget.versions.last.reify
274
- >> widget.next_version # nil
301
+ ```ruby
302
+ >> live_widget = Widget.find 42
303
+ >> live_widget.versions.length # 4 for example
304
+ >> widget = live_widget.previous_version # => widget == live_widget.versions.last.reify
305
+ >> widget = widget.previous_version # => widget == live_widget.versions[-2].reify
306
+ >> widget = widget.next_version # => widget == live_widget.versions.last.reify
307
+ >> widget.next_version # nil
308
+ ```
275
309
 
276
- 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.
310
+ As an aside, I'm undecided about whether `widget.previous_version.next_version` should return `nil` or `self` (i.e. `widget`). Let me know if you have a view.
277
311
 
278
312
  If instead you have a particular `version` of an item you can navigate to the previous and next versions.
279
313
 
280
- >> widget = Widget.find 42
281
- >> version = widget.versions[-2] # assuming widget has several versions
282
- >> previous = version.previous
283
- >> next = version.next
314
+ ```ruby
315
+ >> widget = Widget.find 42
316
+ >> version = widget.versions[-2] # assuming widget has several versions
317
+ >> previous = version.previous
318
+ >> next = version.next
319
+ ```
284
320
 
285
321
  You can find out which of an item's versions yours is:
286
322
 
287
- >> current_version_number = version.index # 0-based
323
+ ```ruby
324
+ >> current_version_number = version.index # 0-based
325
+ ```
288
326
 
289
327
  Finally, if you got an item by reifying one of its versions, you can navigate back to the version it came from:
290
328
 
291
- >> latest_version = Widget.find(42).versions.last
292
- >> widget = latest_version.reify
293
- >> widget.version == latest_version # true
329
+ ```ruby
330
+ >> latest_version = Widget.find(42).versions.last
331
+ >> widget = latest_version.reify
332
+ >> widget.version == latest_version # true
333
+ ```
294
334
 
295
335
  You can find out whether a model instance is the current, live one -- or whether it came instead from a previous version -- with `live?`:
296
336
 
297
- >> widget = Widget.find 42
298
- >> widget.live? # true
299
- >> widget = widget.versions.last.reify
300
- >> widget.live? # false
301
-
337
+ ```ruby
338
+ >> widget = Widget.find 42
339
+ >> widget.live? # true
340
+ >> widget = widget.previous_version
341
+ >> widget.live? # false
342
+ ```
302
343
 
303
344
  ## Finding Out Who Was Responsible For A Change
304
345
 
305
346
  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:
306
347
 
307
- >> last_change = Widget.versions.last
308
- >> user_who_made_the_change = User.find last_change.whodunnit.to_i
348
+ ```ruby
349
+ >> last_change = Widget.versions.last
350
+ >> user_who_made_the_change = User.find last_change.whodunnit.to_i
351
+ ```
309
352
 
310
353
  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:
311
354
 
312
- class ApplicationController
313
- def user_for_paper_trail
314
- logged_in? ? current_member : 'Public user' # or whatever
315
- end
316
- end
317
-
318
- In a migration or in `script/console` you can set who is responsible like this:
319
-
320
- >> PaperTrail.whodunnit = 'Andy Stewart'
321
- >> widget.update_attributes :name => 'Wibble'
322
- >> widget.versions.last.whodunnit # Andy Stewart
355
+ ```ruby
356
+ class ApplicationController
357
+ def user_for_paper_trail
358
+ logged_in? ? current_member : 'Public user' # or whatever
359
+ end
360
+ end
361
+ ```
362
+
363
+ In a migration or in `rails console` you can set who is responsible like this:
364
+
365
+ ```ruby
366
+ >> PaperTrail.whodunnit = 'Andy Stewart'
367
+ >> widget.update_attributes :name => 'Wibble'
368
+ >> widget.versions.last.whodunnit # Andy Stewart
369
+ ```
370
+
371
+ You can avoid having to do this manually by setting your initializer to pick up the username of the current user from the OS, like this:
372
+
373
+ ```ruby
374
+ class Version < ActiveRecord::Base
375
+ if defined?(Rails::Console)
376
+ PaperTrail.whodunnit = "#{`whoami`.strip}: console"
377
+ elsif File.basename($0) == "rake"
378
+ PaperTrail.whodunnit = "#{`whoami`.strip}: rake #{ARGV.join ' '}"
379
+ end
380
+ end
381
+ ```
323
382
 
324
383
  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`.
325
384
 
326
385
  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.
327
386
 
328
- >> widget = Widget.find 153 # assume widget has 0 versions
329
- >> PaperTrail.whodunnit = 'Alice'
330
- >> widget.update_attributes :name => 'Yankee'
331
- >> widget.originator # 'Alice'
332
- >> PaperTrail.whodunnit = 'Bob'
333
- >> widget.update_attributes :name => 'Zulu'
334
- >> widget.originator # 'Bob'
335
- >> first_version, last_version = widget.versions.first, widget.versions.last
336
- >> first_version.whodunnit # 'Alice'
337
- >> first_version.originator # nil
338
- >> first_version.terminator # 'Alice'
339
- >> last_version.whodunnit # 'Bob'
340
- >> last_version.originator # 'Alice'
341
- >> last_version.terminator # 'Bob'
342
-
387
+ ```ruby
388
+ >> widget = Widget.find 153 # assume widget has 0 versions
389
+ >> PaperTrail.whodunnit = 'Alice'
390
+ >> widget.update_attributes :name => 'Yankee'
391
+ >> widget.originator # 'Alice'
392
+ >> PaperTrail.whodunnit = 'Bob'
393
+ >> widget.update_attributes :name => 'Zulu'
394
+ >> widget.originator # 'Bob'
395
+ >> first_version, last_version = widget.versions.first, widget.versions.last
396
+ >> first_version.whodunnit # 'Alice'
397
+ >> first_version.originator # nil
398
+ >> first_version.terminator # 'Alice'
399
+ >> last_version.whodunnit # 'Bob'
400
+ >> last_version.originator # 'Alice'
401
+ >> last_version.terminator # 'Bob'
402
+ ```
343
403
 
344
404
  ## Custom Version Classes
345
405
 
346
406
  You can specify custom version subclasses with the `:class_name` option:
347
407
 
348
- class PostVersion < Version
349
- # custom behaviour, e.g:
350
- self.table_name = :post_versions
351
- end
408
+ ```ruby
409
+ class PostVersion < Version
410
+ # custom behaviour, e.g:
411
+ self.table_name = :post_versions
412
+ end
352
413
 
353
- class Post < ActiveRecord::Base
354
- has_paper_trail :class_name => 'PostVersion'
355
- end
414
+ class Post < ActiveRecord::Base
415
+ has_paper_trail :class_name => 'PostVersion'
416
+ end
417
+ ```
356
418
 
357
419
  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.
358
420
 
421
+ If you are using Postgres, you should also define the sequence that your custom version class will use:
422
+
423
+ ```ruby
424
+ class PostVersion < Version
425
+ self.table_name = :post_versions
426
+ self.sequence_name = :post_version_id_seq
427
+ end
428
+ ```
429
+
359
430
  Alternatively you could store certain metadata for one type of version, and other metadata for other versions.
360
431
 
361
432
  If you only use custom version classes and don't use PaperTrail's built-in one, on Rails 3.2 you must:
362
433
 
363
434
  - either declare PaperTrail's version class abstract like this (in `config/initializers/paper_trail_patch.rb`):
364
435
 
365
- Version.module_eval do
366
- self.abstract_class = true
367
- end
436
+ ```ruby
437
+ Version.module_eval do
438
+ self.abstract_class = true
439
+ end
440
+ ```
368
441
 
369
442
  - or define a `versions` table in the database so Rails can instantiate the version superclass.
370
443
 
371
444
  You can also specify custom names for the versions and version associations. This is useful if you already have `versions` or/and `version` methods on your model. For example:
372
445
 
373
- class Post < ActiveRecord::Base
374
- has_paper_trail :versions => :paper_trail_versions,
375
- :version => :paper_trail_version
376
-
377
- # Existing versions method. We don't want to clash.
378
- def versions
379
- ...
380
- end
381
- # Existing version method. We don't want to clash.
382
- def version
383
- ...
384
- end
385
- end
386
-
446
+ ```ruby
447
+ class Post < ActiveRecord::Base
448
+ has_paper_trail :versions => :paper_trail_versions,
449
+ :version => :paper_trail_version
450
+
451
+ # Existing versions method. We don't want to clash.
452
+ def versions
453
+ ...
454
+ end
455
+ # Existing version method. We don't want to clash.
456
+ def version
457
+ ...
458
+ end
459
+ end
460
+ ```
387
461
 
388
462
  ## Associations
389
463
 
@@ -396,25 +470,29 @@ If you can think of a good way to achieve this, please let me know.
396
470
 
397
471
  PaperTrail can restore `:has_one` associations as they were at (actually, 3 seconds before) the time.
398
472
 
399
- class Treasure < ActiveRecord::Base
400
- has_one :location
401
- end
473
+ ```ruby
474
+ class Treasure < ActiveRecord::Base
475
+ has_one :location
476
+ end
402
477
 
403
- >> treasure.amount # 100
404
- >> treasure.location.latitude # 12.345
478
+ >> treasure.amount # 100
479
+ >> treasure.location.latitude # 12.345
405
480
 
406
- >> treasure.update_attributes :amount => 153
407
- >> treasure.location.update_attributes :latitude => 54.321
481
+ >> treasure.update_attributes :amount => 153
482
+ >> treasure.location.update_attributes :latitude => 54.321
408
483
 
409
- >> t = treasure.versions.last.reify(:has_one => true)
410
- >> t.amount # 100
411
- >> t.location.latitude # 12.345
484
+ >> t = treasure.versions.last.reify(:has_one => true)
485
+ >> t.amount # 100
486
+ >> t.location.latitude # 12.345
487
+ ```
412
488
 
413
489
  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.
414
490
 
415
491
  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:
416
492
 
417
- >> t = treasure.versions.last.reify(:has_one => 1) # look back 1 second instead of 3
493
+ ```ruby
494
+ >> t = treasure.versions.last.reify(:has_one => 1) # look back 1 second instead of 3
495
+ ```
418
496
 
419
497
  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 ;)
420
498
 
@@ -426,53 +504,61 @@ PaperTrail can track most changes to the join table. Specifically it can track
426
504
 
427
505
  Given these models:
428
506
 
429
- class Book < ActiveRecord::Base
430
- has_many :authorships, :dependent => :destroy
431
- has_many :authors, :through => :authorships, :source => :person
432
- has_paper_trail
433
- end
434
-
435
- class Authorship < ActiveRecord::Base
436
- belongs_to :book
437
- belongs_to :person
438
- has_paper_trail # NOTE
439
- end
440
-
441
- class Person < ActiveRecord::Base
442
- has_many :authorships, :dependent => :destroy
443
- has_many :books, :through => :authorships
444
- has_paper_trail
445
- end
507
+ ```ruby
508
+ class Book < ActiveRecord::Base
509
+ has_many :authorships, :dependent => :destroy
510
+ has_many :authors, :through => :authorships, :source => :person
511
+ has_paper_trail
512
+ end
513
+
514
+ class Authorship < ActiveRecord::Base
515
+ belongs_to :book
516
+ belongs_to :person
517
+ has_paper_trail # NOTE
518
+ end
519
+
520
+ class Person < ActiveRecord::Base
521
+ has_many :authorships, :dependent => :destroy
522
+ has_many :books, :through => :authorships
523
+ has_paper_trail
524
+ end
525
+ ```
446
526
 
447
527
  Then each of the following will store authorship versions:
448
528
 
449
- >> @book.authors << @dostoyevsky
450
- >> @book.authors.create :name => 'Tolstoy'
451
- >> @book.authorships.last.destroy
452
- >> @book.authorships.clear
529
+ ```ruby
530
+ >> @book.authors << @dostoyevsky
531
+ >> @book.authors.create :name => 'Tolstoy'
532
+ >> @book.authorships.last.destroy
533
+ >> @book.authorships.clear
534
+ ```
453
535
 
454
536
  But none of these will:
455
537
 
456
- >> @book.authors.delete @tolstoy
457
- >> @book.author_ids = [@solzhenistyn.id, @dostoyevsky.id]
458
- >> @book.authors = []
538
+ ```ruby
539
+ >> @book.authors.delete @tolstoy
540
+ >> @book.author_ids = [@solzhenistyn.id, @dostoyevsky.id]
541
+ >> @book.authors = []
542
+ ```
459
543
 
460
544
  Having said that, you can apparently get all these working (I haven't tested it myself) with this patch:
461
545
 
462
- # In config/initializers/active_record_patch.rb
463
- module ActiveRecord
464
- # = Active Record Has Many Through Association
465
- module Associations
466
- class HasManyThroughAssociation < HasManyAssociation #:nodoc:
467
- alias_method :original_delete_records, :delete_records
468
-
469
- def delete_records(records, method)
470
- method ||= :destroy
471
- original_delete_records(records, method)
472
- end
473
- end
546
+ ```ruby
547
+ # In config/initializers/active_record_patch.rb
548
+ module ActiveRecord
549
+ # = Active Record Has Many Through Association
550
+ module Associations
551
+ class HasManyThroughAssociation < HasManyAssociation #:nodoc:
552
+ alias_method :original_delete_records, :delete_records
553
+
554
+ def delete_records(records, method)
555
+ method ||= :destroy
556
+ original_delete_records(records, method)
474
557
  end
475
558
  end
559
+ end
560
+ end
561
+ ```
476
562
 
477
563
  See [issue 113](https://github.com/airblade/paper_trail/issues/113) for a discussion about this.
478
564
 
@@ -483,15 +569,17 @@ There may be a way to store authorship versions, probably using association call
483
569
 
484
570
  You can store arbitrary model-level metadata alongside each version like this:
485
571
 
486
- class Article < ActiveRecord::Base
487
- belongs_to :author
488
- has_paper_trail :meta => { :author_id => Proc.new { |article| article.author_id },
489
- :word_count => :count_words,
490
- :answer => 42 }
491
- def count_words
492
- 153
493
- end
494
- end
572
+ ```ruby
573
+ class Article < ActiveRecord::Base
574
+ belongs_to :author
575
+ has_paper_trail :meta => { :author_id => Proc.new { |article| article.author_id },
576
+ :word_count => :count_words,
577
+ :answer => 42 }
578
+ def count_words
579
+ 153
580
+ end
581
+ end
582
+ ```
495
583
 
496
584
  PaperTrail will call your proc with the current article and store the result in the `author_id` column of the `versions` table.
497
585
 
@@ -502,24 +590,30 @@ N.B. You must also:
502
590
 
503
591
  For example:
504
592
 
505
- # config/initializers/paper_trail.rb
506
- class Version < ActiveRecord::Base
507
- attr_accessible :author_id, :word_count, :answer
508
- end
593
+ ```ruby
594
+ # config/initializers/paper_trail.rb
595
+ class Version < ActiveRecord::Base
596
+ attr_accessible :author_id, :word_count, :answer
597
+ end
598
+ ```
509
599
 
510
600
  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:
511
601
 
512
- Version.all(:conditions => ['author_id = ?', author_id])
602
+ ```ruby
603
+ Version.all(:conditions => ['author_id = ?', author_id])
604
+ ```
513
605
 
514
606
  Note you can pass a symbol as a value in the `meta` hash to signal a method to call.
515
607
 
516
608
  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.:
517
609
 
518
- class ApplicationController
519
- def info_for_paper_trail
520
- { :ip => request.remote_ip, :user_agent => request.user_agent }
521
- end
522
- end
610
+ ```ruby
611
+ class ApplicationController
612
+ def info_for_paper_trail
613
+ { :ip => request.remote_ip, :user_agent => request.user_agent }
614
+ end
615
+ end
616
+ ```
523
617
 
524
618
  Remember to add those extra columns to your `versions` table and use `attr_accessible` ;)
525
619
 
@@ -530,12 +624,16 @@ There are two scenarios: diffing adjacent versions and diffing non-adjacent vers
530
624
 
531
625
  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 (excluding any attributes PaperTrail is ignoring) in each `update` version. You can use the `version.changeset` method to retrieve it. For example:
532
626
 
533
- >> widget = Widget.create :name => 'Bob'
534
- >> widget.versions.last.changeset # {}
535
- >> widget.update_attributes :name => 'Robert'
536
- >> widget.versions.last.changeset # {'name' => ['Bob', 'Robert']}
627
+ ```ruby
628
+ >> widget = Widget.create :name => 'Bob'
629
+ >> widget.versions.last.changeset # {'name' => [nil, 'Bob']}
630
+ >> widget.update_attributes :name => 'Robert'
631
+ >> widget.versions.last.changeset # {'name' => ['Bob', 'Robert']}
632
+ >> widget.destroy
633
+ >> widget.versions.last.changeset # {}
634
+ ```
537
635
 
538
- Note PaperTrail only stores the changes for updates; there's no point storing them for created or destroyed objects.
636
+ Note PaperTrail only stores the changes for creation and updates; it doesn't store anything when an object is destroyed.
539
637
 
540
638
  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.
541
639
 
@@ -545,7 +643,7 @@ For diffing two strings:
545
643
 
546
644
  * [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.
547
645
  * [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.
548
- * [diff-lcs](http://github.com/halostatue/ruwiki/tree/master/diff-lcs/trunk): old-school, line-wise diffs.
646
+ * [diff-lcs](https://github.com/halostatue/diff-lcs): old-school, line-wise diffs.
549
647
 
550
648
  For diffing two ActiveRecord objects:
551
649
 
@@ -563,77 +661,97 @@ You can turn PaperTrail on or off in three ways: globally, per request, or per c
563
661
 
564
662
  On a global level you can turn PaperTrail off like this:
565
663
 
566
- >> PaperTrail.enabled = false
664
+ ```ruby
665
+ >> PaperTrail.enabled = false
666
+ ```
567
667
 
568
668
  For example, you might want to disable PaperTrail in your Rails application's test environment to speed up your tests. This will do it:
569
669
 
570
- # in config/environments/test.rb
571
- config.after_initialize do
572
- PaperTrail.enabled = false
573
- end
670
+ ```ruby
671
+ # in config/environments/test.rb
672
+ config.after_initialize do
673
+ PaperTrail.enabled = false
674
+ end
675
+ ```
574
676
 
575
677
  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:
576
678
 
577
- # in test/test_helper.rb
578
- def with_versioning
579
- was_enabled = PaperTrail.enabled?
580
- PaperTrail.enabled = true
581
- begin
582
- yield
583
- ensure
584
- PaperTrail.enabled = was_enabled
585
- end
586
- end
679
+ ```ruby
680
+ # in test/test_helper.rb
681
+ def with_versioning
682
+ was_enabled = PaperTrail.enabled?
683
+ PaperTrail.enabled = true
684
+ begin
685
+ yield
686
+ ensure
687
+ PaperTrail.enabled = was_enabled
688
+ end
689
+ end
690
+ ```
587
691
 
588
692
  And then use it in your tests like this:
589
693
 
590
- test "something that needs versioning" do
591
- with_versioning do
592
- # your test
593
- end
594
- end
694
+ ```ruby
695
+ test "something that needs versioning" do
696
+ with_versioning do
697
+ # your test
698
+ end
699
+ end
700
+ ```
595
701
 
596
702
  ### Per request
597
703
 
598
704
  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:
599
705
 
600
- class ApplicationController < ActionController::Base
601
- def paper_trail_enabled_for_controller
602
- request.user_agent != 'Disable User-Agent'
603
- end
604
- end
706
+ ```ruby
707
+ class ApplicationController < ActionController::Base
708
+ def paper_trail_enabled_for_controller
709
+ request.user_agent != 'Disable User-Agent'
710
+ end
711
+ end
712
+ ```
605
713
 
606
714
  ### Per class
607
715
 
608
716
  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:
609
717
 
610
- >> Widget.paper_trail_off
718
+ ```ruby
719
+ >> Widget.paper_trail_off
720
+ ```
611
721
 
612
722
  And on again like this:
613
723
 
614
- >> Widget.paper_trail_on
724
+ ```ruby
725
+ >> Widget.paper_trail_on
726
+ ```
615
727
 
616
728
  ### Per method call
617
729
 
618
730
  You can call a method without creating a new version using `without_versioning`. It takes either a method name as a symbol:
619
731
 
620
- @widget.without_versioning :destroy
732
+ ```ruby
733
+ @widget.without_versioning :destroy
734
+ ```
621
735
 
622
736
  Or a block:
623
737
 
624
- @widget.without_versioning do
625
- @widget.update_attributes :name => 'Ford'
626
- end
627
-
738
+ ```ruby
739
+ @widget.without_versioning do
740
+ @widget.update_attributes :name => 'Ford'
741
+ end
742
+ ```
628
743
 
629
744
  ## Deleting Old Versions
630
745
 
631
746
  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:
632
747
 
633
- sql> delete from versions where created_at < 2010-06-01;
634
-
635
- >> Version.delete_all ["created_at < ?", 1.week.ago]
748
+ ```sql
749
+ sql> delete from versions where created_at < 2010-06-01;
750
+ ```
636
751
 
752
+ ```ruby
753
+ >> Version.delete_all ["created_at < ?", 1.week.ago]
754
+ ```
637
755
 
638
756
  ## Installation
639
757
 
@@ -664,13 +782,14 @@ PaperTrail uses Bundler to manage its dependencies (in development and testing).
664
782
 
665
783
  It's a good idea to reset PaperTrail before each test so data from one test doesn't spill over another. For example:
666
784
 
667
- RSpec.configure do |config|
668
- config.before :each do
669
- PaperTrail.controller_info = {}
670
- PaperTrail.whodunnit = nil
671
- end
672
- end
673
-
785
+ ```ruby
786
+ RSpec.configure do |config|
787
+ config.before :each do
788
+ PaperTrail.controller_info = {}
789
+ PaperTrail.whodunnit = nil
790
+ end
791
+ end
792
+ ```
674
793
  You may want to turn PaperTrail off to speed up your tests. See the "Turning PaperTrail Off/On" section above.
675
794
 
676
795
 
@@ -720,6 +839,7 @@ Many thanks to:
720
839
  * [Eric Schwartz](https://github.com/emschwar)
721
840
  * [Ben Woosley](https://github.com/Empact)
722
841
  * [Philip Arndt](https://github.com/parndt)
842
+ * [Daniel Vydra](https://github.com/dvydra)
723
843
 
724
844
 
725
845
  ## Inspirations