paper_trail 1.3.1 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
|