paper_trail 1.3.1 → 1.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +22 -3
- data/VERSION +1 -1
- data/lib/paper_trail/has_paper_trail.rb +24 -8
- data/paper_trail.gemspec +2 -2
- data/test/paper_trail_model_test.rb +61 -2
- data/test/schema.rb +5 -0
- metadata +2 -2
data/README.md
CHANGED
@@ -6,11 +6,13 @@ PaperTrail lets you track changes to your models' data. It's good for auditing
|
|
6
6
|
## Features
|
7
7
|
|
8
8
|
* Stores every create, update and destroy.
|
9
|
-
* Does not store updates which don't change anything
|
9
|
+
* Does not store updates which don't change anything.
|
10
|
+
* Does not store updates which only change attributes you are ignoring.
|
10
11
|
* Allows you to get at every version, including the original, even once destroyed.
|
11
12
|
* Allows you to get at every version even if the schema has since changed.
|
12
13
|
* Automatically records who was responsible if your controller has a `current_user` method.
|
13
14
|
* Allows you to set who is responsible at model-level (useful for migrations).
|
15
|
+
* Allows you to store arbitrary metadata with each version (useful for filtering versions).
|
14
16
|
* Can be turned off/on (useful for migrations).
|
15
17
|
* No configuration necessary.
|
16
18
|
* Stores everything in a single database table (generates migration for you).
|
@@ -141,6 +143,23 @@ In a migration or in `script/console` you can set who is responsible like this:
|
|
141
143
|
>> widget.versions.last.whodunnit # Andy Stewart
|
142
144
|
|
143
145
|
|
146
|
+
## Storing metadata
|
147
|
+
|
148
|
+
You can store arbitrary metadata alongside each version like this:
|
149
|
+
|
150
|
+
class Article < ActiveRecord::Base
|
151
|
+
belongs_to :author
|
152
|
+
has_paper_trail :meta => { :author_id => Proc.new { |article| article.author_id },
|
153
|
+
:answer => 42 }
|
154
|
+
end
|
155
|
+
|
156
|
+
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.)
|
157
|
+
|
158
|
+
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:
|
159
|
+
|
160
|
+
Version.all(:conditions => ['author_id = ?', author_id])
|
161
|
+
|
162
|
+
|
144
163
|
## Turning PaperTrail Off/On
|
145
164
|
|
146
165
|
Sometimes you don't want to store changes. Perhaps you are only interested in changes made
|
@@ -158,9 +177,9 @@ And on again like this:
|
|
158
177
|
|
159
178
|
## Installation
|
160
179
|
|
161
|
-
1. Install PaperTrail either as a gem or as a plugin:
|
180
|
+
1. Install PaperTrail either as a gem (from Gemcutter; the ones on GitHub are obsolete) or as a plugin:
|
162
181
|
|
163
|
-
`config.gem '
|
182
|
+
`config.gem 'paper_trail', :source => 'http://gemcutter.org'`
|
164
183
|
|
165
184
|
or:
|
166
185
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.
|
1
|
+
1.4.0
|
@@ -8,11 +8,17 @@ module PaperTrail
|
|
8
8
|
module ClassMethods
|
9
9
|
# Options:
|
10
10
|
# :ignore an array of attributes for which a new +Version+ will not be created if only they change.
|
11
|
+
# :meta a hash of extra data to store. You must add a column to the versions table for each key.
|
12
|
+
# Values are objects or procs (which are called with +self+, i.e. the model with the paper
|
13
|
+
# trail).
|
11
14
|
def has_paper_trail(options = {})
|
12
15
|
send :include, InstanceMethods
|
13
16
|
|
14
17
|
cattr_accessor :ignore
|
15
18
|
self.ignore = (options[:ignore] || []).map &:to_s
|
19
|
+
|
20
|
+
cattr_accessor :meta
|
21
|
+
self.meta = options[:meta] || {}
|
16
22
|
|
17
23
|
cattr_accessor :paper_trail_active
|
18
24
|
self.paper_trail_active = true
|
@@ -36,26 +42,36 @@ module PaperTrail
|
|
36
42
|
|
37
43
|
module InstanceMethods
|
38
44
|
def record_create
|
39
|
-
|
40
|
-
|
45
|
+
if self.class.paper_trail_active
|
46
|
+
versions.create merge_metadata(:event => 'create', :whodunnit => PaperTrail.whodunnit)
|
47
|
+
end
|
41
48
|
end
|
42
49
|
|
43
50
|
def record_update
|
44
51
|
if changed_and_we_care? and self.class.paper_trail_active
|
45
|
-
versions.build :event => 'update',
|
46
|
-
|
47
|
-
|
52
|
+
versions.build merge_metadata(:event => 'update',
|
53
|
+
:object => object_to_string(previous_version),
|
54
|
+
:whodunnit => PaperTrail.whodunnit)
|
48
55
|
end
|
49
56
|
end
|
50
57
|
|
51
58
|
def record_destroy
|
52
|
-
|
53
|
-
|
54
|
-
|
59
|
+
if self.class.paper_trail_active
|
60
|
+
versions.create merge_metadata(:event => 'destroy',
|
61
|
+
:object => object_to_string(previous_version),
|
62
|
+
:whodunnit => PaperTrail.whodunnit)
|
63
|
+
end
|
55
64
|
end
|
56
65
|
|
57
66
|
private
|
58
67
|
|
68
|
+
def merge_metadata(data)
|
69
|
+
meta.each do |k,v|
|
70
|
+
data[k] = v.respond_to?(:call) ? v.call(self) : v
|
71
|
+
end
|
72
|
+
data
|
73
|
+
end
|
74
|
+
|
59
75
|
def previous_version
|
60
76
|
previous = self.clone
|
61
77
|
previous.id = id
|
data/paper_trail.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{paper_trail}
|
8
|
-
s.version = "1.
|
8
|
+
s.version = "1.4.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Andy Stewart"]
|
12
|
-
s.date = %q{
|
12
|
+
s.date = %q{2010-01-06}
|
13
13
|
s.email = %q{boss@airbladesoftware.com}
|
14
14
|
s.extra_rdoc_files = [
|
15
15
|
"README.md"
|
@@ -18,7 +18,10 @@ class Fluxor < ActiveRecord::Base
|
|
18
18
|
end
|
19
19
|
|
20
20
|
class Article < ActiveRecord::Base
|
21
|
-
has_paper_trail :ignore => [:title]
|
21
|
+
has_paper_trail :ignore => [:title],
|
22
|
+
:meta => {:answer => 42,
|
23
|
+
:question => Proc.new { "31 + 11 = #{31 + 11}" },
|
24
|
+
:article_id => Proc.new { |article| article.id } }
|
22
25
|
end
|
23
26
|
|
24
27
|
|
@@ -37,9 +40,9 @@ class HasPaperTrailModelTest < Test::Unit::TestCase
|
|
37
40
|
setup { @article.update_attributes :title => 'My first title', :content => 'Some text here.' }
|
38
41
|
should_change('the number of versions', :by => 1) { Version.count }
|
39
42
|
end
|
40
|
-
|
41
43
|
end
|
42
44
|
|
45
|
+
|
43
46
|
context 'A new record' do
|
44
47
|
setup { @widget = Widget.new }
|
45
48
|
|
@@ -386,4 +389,60 @@ class HasPaperTrailModelTest < Test::Unit::TestCase
|
|
386
389
|
end
|
387
390
|
end
|
388
391
|
|
392
|
+
|
393
|
+
context 'An item' do
|
394
|
+
setup { @article = Article.new }
|
395
|
+
|
396
|
+
context 'which is created' do
|
397
|
+
setup { @article.save }
|
398
|
+
|
399
|
+
should 'store fixed meta data' do
|
400
|
+
assert_equal 42, @article.versions.last.answer
|
401
|
+
end
|
402
|
+
|
403
|
+
should 'store dynamic meta data which is independent of the item' do
|
404
|
+
assert_equal '31 + 11 = 42', @article.versions.last.question
|
405
|
+
end
|
406
|
+
|
407
|
+
should 'store dynamic meta data which depends on the item' do
|
408
|
+
assert_equal @article.id, @article.versions.last.article_id
|
409
|
+
end
|
410
|
+
|
411
|
+
|
412
|
+
context 'and updated' do
|
413
|
+
setup { @article.update_attributes! :content => 'Better text.' }
|
414
|
+
|
415
|
+
should 'store fixed meta data' do
|
416
|
+
assert_equal 42, @article.versions.last.answer
|
417
|
+
end
|
418
|
+
|
419
|
+
should 'store dynamic meta data which is independent of the item' do
|
420
|
+
assert_equal '31 + 11 = 42', @article.versions.last.question
|
421
|
+
end
|
422
|
+
|
423
|
+
should 'store dynamic meta data which depends on the item' do
|
424
|
+
assert_equal @article.id, @article.versions.last.article_id
|
425
|
+
end
|
426
|
+
end
|
427
|
+
|
428
|
+
|
429
|
+
context 'and destroyd' do
|
430
|
+
setup { @article.destroy }
|
431
|
+
|
432
|
+
should 'store fixed meta data' do
|
433
|
+
assert_equal 42, @article.versions.last.answer
|
434
|
+
end
|
435
|
+
|
436
|
+
should 'store dynamic meta data which is independent of the item' do
|
437
|
+
assert_equal '31 + 11 = 42', @article.versions.last.question
|
438
|
+
end
|
439
|
+
|
440
|
+
should 'store dynamic meta data which depends on the item' do
|
441
|
+
assert_equal @article.id, @article.versions.last.article_id
|
442
|
+
end
|
443
|
+
|
444
|
+
end
|
445
|
+
end
|
446
|
+
end
|
447
|
+
|
389
448
|
end
|
data/test/schema.rb
CHANGED
@@ -22,6 +22,11 @@ ActiveRecord::Schema.define(:version => 0) do
|
|
22
22
|
t.string :whodunnit
|
23
23
|
t.text :object
|
24
24
|
t.datetime :created_at
|
25
|
+
|
26
|
+
# Metadata columns.
|
27
|
+
t.integer :answer
|
28
|
+
t.string :question
|
29
|
+
t.integer :article_id
|
25
30
|
end
|
26
31
|
add_index :versions, [:item_type, :item_id]
|
27
32
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: paper_trail
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andy Stewart
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date:
|
12
|
+
date: 2010-01-06 00:00:00 +00:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|