paper_trail 2.0.2 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +3 -0
- data/README.md +21 -1
- data/lib/paper_trail/has_paper_trail.rb +13 -9
- data/lib/paper_trail/version.rb +6 -6
- data/lib/paper_trail/version_number.rb +1 -1
- data/test/dummy/app/models/post.rb +4 -0
- data/test/dummy/app/versions/post_version.rb +3 -0
- data/test/dummy/db/development.sqlite3 +0 -0
- data/test/dummy/db/migrate/20110208155312_set_up_test_tables.rb +22 -0
- data/test/dummy/db/schema.rb +18 -0
- data/test/dummy/db/test.sqlite3 +0 -0
- data/test/unit/model_test.rb +32 -0
- metadata +19 -16
- data/Gemfile.lock +0 -107
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -20,7 +20,8 @@ There's an excellent [Railscast on implementing Undo with Paper Trail](http://ra
|
|
20
20
|
* Can be turned off/on per class (useful for migrations).
|
21
21
|
* Can be turned off/on globally (useful for testing).
|
22
22
|
* No configuration necessary.
|
23
|
-
* Stores everything in a single database table (generates migration for you).
|
23
|
+
* Stores everything in a single database table by default (generates migration for you), or can use separate tables for separate models.
|
24
|
+
* Supports custom version classes so different models' versions can have different behaviour.
|
24
25
|
* Thoroughly tested.
|
25
26
|
* Threadsafe.
|
26
27
|
|
@@ -308,6 +309,24 @@ To find out who made a `version`'s object look that way, use `version.originator
|
|
308
309
|
>> last_version.terminator # 'Bob'
|
309
310
|
|
310
311
|
|
312
|
+
## Custom Version Classes
|
313
|
+
|
314
|
+
You can specify custom version subclasses with the `:class_name` option:
|
315
|
+
|
316
|
+
class Post < ActiveRecord::Base
|
317
|
+
has_paper_trail :class_name => 'PostVersion'
|
318
|
+
end
|
319
|
+
|
320
|
+
class PostVersion < Version
|
321
|
+
# custom behaviour, e.g:
|
322
|
+
set_table_name :post_versions
|
323
|
+
end
|
324
|
+
|
325
|
+
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.
|
326
|
+
|
327
|
+
Alternatively you could store certain metadata for one type of version, and other metadata for other versions.
|
328
|
+
|
329
|
+
|
311
330
|
## Associations
|
312
331
|
|
313
332
|
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).
|
@@ -562,6 +581,7 @@ Many thanks to:
|
|
562
581
|
* [Franco Catena](https://github.com/francocatena)
|
563
582
|
* [Emmanuel Gomez](https://github.com/emmanuel)
|
564
583
|
* [Matthew MacLeod](https://github.com/mattmacleod)
|
584
|
+
* [benzittlau](https://github.com/benzittlau)
|
565
585
|
|
566
586
|
|
567
587
|
## Inspirations
|
@@ -11,12 +11,13 @@ module PaperTrail
|
|
11
11
|
# the model is available in the `versions` association.
|
12
12
|
#
|
13
13
|
# Options:
|
14
|
-
# :
|
15
|
-
# :
|
16
|
-
# :
|
17
|
-
#
|
18
|
-
#
|
19
|
-
#
|
14
|
+
# :class_name the name of a custom Version class. This class should inherit from Version.
|
15
|
+
# :ignore an array of attributes for which a new `Version` will not be created if only they change.
|
16
|
+
# :only inverse of `ignore` - a new `Version` will be created only for these attributes if supplied
|
17
|
+
# :meta a hash of extra data to store. You must add a column to the `versions` table for each key.
|
18
|
+
# Values are objects or procs (which are called with `self`, i.e. the model with the paper
|
19
|
+
# trail). See `PaperTrail::Controller.info_for_paper_trail` for how to store data from
|
20
|
+
# the controller.
|
20
21
|
def has_paper_trail(options = {})
|
21
22
|
# Lazily include the instance methods so we don't clutter up
|
22
23
|
# any more ActiveRecord models than we have to.
|
@@ -24,6 +25,9 @@ module PaperTrail
|
|
24
25
|
|
25
26
|
# The version this instance was reified from.
|
26
27
|
attr_accessor :version
|
28
|
+
|
29
|
+
cattr_accessor :version_class_name
|
30
|
+
self.version_class_name = options[:class_name] || "Version"
|
27
31
|
|
28
32
|
cattr_accessor :ignore
|
29
33
|
self.ignore = ([options[:ignore]].flatten.compact || []).map &:to_s
|
@@ -39,7 +43,7 @@ module PaperTrail
|
|
39
43
|
cattr_accessor :paper_trail_active
|
40
44
|
self.paper_trail_active = true
|
41
45
|
|
42
|
-
has_many :versions, :as => :item, :order => 'created_at ASC, id ASC'
|
46
|
+
has_many :versions, :class_name => version_class_name, :as => :item, :order => 'created_at ASC, id ASC'
|
43
47
|
|
44
48
|
after_create :record_create
|
45
49
|
before_update :record_update
|
@@ -68,7 +72,7 @@ module PaperTrail
|
|
68
72
|
|
69
73
|
# Returns who put the object into its current state.
|
70
74
|
def originator
|
71
|
-
|
75
|
+
version_class_name.constantize.with_item_keys(self.class.name, id).last.try :whodunnit
|
72
76
|
end
|
73
77
|
|
74
78
|
# Returns the object (not a Version) as it was at the given timestamp.
|
@@ -111,7 +115,7 @@ module PaperTrail
|
|
111
115
|
|
112
116
|
def record_destroy
|
113
117
|
if switched_on? and not new_record?
|
114
|
-
|
118
|
+
version_class_name.constantize.create merge_metadata(:item_id => self.id,
|
115
119
|
:item_type => self.class.name,
|
116
120
|
:event => 'destroy',
|
117
121
|
:object => object_to_string(item_before_change),
|
data/lib/paper_trail/version.rb
CHANGED
@@ -2,16 +2,16 @@ class Version < ActiveRecord::Base
|
|
2
2
|
belongs_to :item, :polymorphic => true
|
3
3
|
validates_presence_of :event
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
5
|
+
def self.with_item_keys(item_type, item_id)
|
6
|
+
scoped(:conditions => { :item_type => item_type, :item_id => item_id })
|
7
|
+
end
|
8
8
|
|
9
9
|
scope :subsequent, lambda { |version|
|
10
|
-
where(["id > ?", version.is_a?(
|
10
|
+
where(["id > ?", version.is_a?(self) ? version.id : version]).order("id ASC")
|
11
11
|
}
|
12
12
|
|
13
13
|
scope :preceding, lambda { |version|
|
14
|
-
where(["id < ?", version.is_a?(
|
14
|
+
where(["id < ?", version.is_a?(self) ? version.id : version]).order("id DESC")
|
15
15
|
}
|
16
16
|
|
17
17
|
scope :after, lambda { |timestamp|
|
@@ -87,7 +87,7 @@ class Version < ActiveRecord::Base
|
|
87
87
|
end
|
88
88
|
|
89
89
|
def sibling_versions
|
90
|
-
|
90
|
+
self.class.with_item_keys(item_type, item_id)
|
91
91
|
end
|
92
92
|
|
93
93
|
def next
|
Binary file
|
@@ -34,6 +34,20 @@ class SetUpTestTables < ActiveRecord::Migration
|
|
34
34
|
t.string :user_agent
|
35
35
|
end
|
36
36
|
add_index :versions, [:item_type, :item_id]
|
37
|
+
|
38
|
+
create_table :post_versions, :force => true do |t|
|
39
|
+
t.string :item_type, :null => false
|
40
|
+
t.integer :item_id, :null => false
|
41
|
+
t.string :event, :null => false
|
42
|
+
t.string :whodunnit
|
43
|
+
t.text :object
|
44
|
+
t.datetime :created_at
|
45
|
+
|
46
|
+
# Controller info columns.
|
47
|
+
t.string :ip
|
48
|
+
t.string :user_agent
|
49
|
+
end
|
50
|
+
add_index :post_versions, [:item_type, :item_id]
|
37
51
|
|
38
52
|
create_table :wotsits, :force => true do |t|
|
39
53
|
t.integer :widget_id
|
@@ -68,9 +82,15 @@ class SetUpTestTables < ActiveRecord::Migration
|
|
68
82
|
create_table :songs, :force => true do |t|
|
69
83
|
t.integer :length
|
70
84
|
end
|
85
|
+
|
86
|
+
create_table :posts, :force => true do |t|
|
87
|
+
t.string :title
|
88
|
+
t.string :content
|
89
|
+
end
|
71
90
|
end
|
72
91
|
|
73
92
|
def self.down
|
93
|
+
drop_table :posts
|
74
94
|
drop_table :songs
|
75
95
|
drop_table :people
|
76
96
|
drop_table :authorships
|
@@ -78,6 +98,8 @@ class SetUpTestTables < ActiveRecord::Migration
|
|
78
98
|
drop_table :articles
|
79
99
|
drop_table :fluxors
|
80
100
|
drop_table :wotsits
|
101
|
+
remove_index :post_versions, :column => [:item_type, :item_id]
|
102
|
+
drop_table :post_versions
|
81
103
|
remove_index :versions, :column => [:item_type, :item_id]
|
82
104
|
drop_table :versions
|
83
105
|
drop_table :widgets
|
data/test/dummy/db/schema.rb
CHANGED
@@ -36,6 +36,24 @@ ActiveRecord::Schema.define(:version => 20110208155312) do
|
|
36
36
|
t.string "name"
|
37
37
|
end
|
38
38
|
|
39
|
+
create_table "post_versions", :force => true do |t|
|
40
|
+
t.string "item_type", :null => false
|
41
|
+
t.integer "item_id", :null => false
|
42
|
+
t.string "event", :null => false
|
43
|
+
t.string "whodunnit"
|
44
|
+
t.text "object"
|
45
|
+
t.datetime "created_at"
|
46
|
+
t.string "ip"
|
47
|
+
t.string "user_agent"
|
48
|
+
end
|
49
|
+
|
50
|
+
add_index "post_versions", ["item_type", "item_id"], :name => "index_post_versions_on_item_type_and_item_id"
|
51
|
+
|
52
|
+
create_table "posts", :force => true do |t|
|
53
|
+
t.string "title"
|
54
|
+
t.string "content"
|
55
|
+
end
|
56
|
+
|
39
57
|
create_table "songs", :force => true do |t|
|
40
58
|
t.integer "length"
|
41
59
|
end
|
data/test/dummy/db/test.sqlite3
CHANGED
Binary file
|
data/test/unit/model_test.rb
CHANGED
@@ -710,6 +710,38 @@ class HasPaperTrailModelTest < ActiveSupport::TestCase
|
|
710
710
|
end
|
711
711
|
end
|
712
712
|
|
713
|
+
context 'A new model instance which uses a custom Version class' do
|
714
|
+
setup { @post = Post.new }
|
715
|
+
|
716
|
+
context 'which is then saved' do
|
717
|
+
setup { @post.save }
|
718
|
+
should_change('the number of post versions') { PostVersion.count }
|
719
|
+
should_not_change('the number of versions') { Version.count }
|
720
|
+
end
|
721
|
+
end
|
722
|
+
|
723
|
+
context 'An existing model instance which uses a custom Version class' do
|
724
|
+
setup { @post = Post.create }
|
725
|
+
|
726
|
+
context 'on the first version' do
|
727
|
+
setup { @version = @post.versions.first }
|
728
|
+
|
729
|
+
should 'have the correct index' do
|
730
|
+
assert_equal 0, @version.index
|
731
|
+
end
|
732
|
+
end
|
733
|
+
|
734
|
+
should 'should have versions of the custom class' do
|
735
|
+
assert_equal "PostVersion", @post.versions.first.class.name
|
736
|
+
end
|
737
|
+
|
738
|
+
context 'which is modified' do
|
739
|
+
setup { @post.update_attributes({ :content => "Some new content" }) }
|
740
|
+
should_change('the number of post versions') { PostVersion.count }
|
741
|
+
should_not_change('the number of versions') { Version.count }
|
742
|
+
end
|
743
|
+
end
|
744
|
+
|
713
745
|
|
714
746
|
context 'An overwritten default accessor' do
|
715
747
|
setup do
|
metadata
CHANGED
@@ -5,9 +5,9 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 2
|
8
|
+
- 1
|
8
9
|
- 0
|
9
|
-
|
10
|
-
version: 2.0.2
|
10
|
+
version: 2.1.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Andy Stewart
|
@@ -15,12 +15,13 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2011-03-
|
18
|
+
date: 2011-03-31 00:00:00 +01:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
22
|
+
name: rails
|
22
23
|
prerelease: false
|
23
|
-
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
25
|
none: false
|
25
26
|
requirements:
|
26
27
|
- - ~>
|
@@ -29,12 +30,12 @@ dependencies:
|
|
29
30
|
segments:
|
30
31
|
- 3
|
31
32
|
version: "3"
|
32
|
-
requirement: *id001
|
33
|
-
name: rails
|
34
33
|
type: :runtime
|
34
|
+
version_requirements: *id001
|
35
35
|
- !ruby/object:Gem::Dependency
|
36
|
+
name: shoulda
|
36
37
|
prerelease: false
|
37
|
-
|
38
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
38
39
|
none: false
|
39
40
|
requirements:
|
40
41
|
- - "="
|
@@ -45,12 +46,12 @@ dependencies:
|
|
45
46
|
- 10
|
46
47
|
- 3
|
47
48
|
version: 2.10.3
|
48
|
-
requirement: *id002
|
49
|
-
name: shoulda
|
50
49
|
type: :development
|
50
|
+
version_requirements: *id002
|
51
51
|
- !ruby/object:Gem::Dependency
|
52
|
+
name: sqlite3-ruby
|
52
53
|
prerelease: false
|
53
|
-
|
54
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
54
55
|
none: false
|
55
56
|
requirements:
|
56
57
|
- - ~>
|
@@ -60,12 +61,12 @@ dependencies:
|
|
60
61
|
- 1
|
61
62
|
- 2
|
62
63
|
version: "1.2"
|
63
|
-
requirement: *id003
|
64
|
-
name: sqlite3-ruby
|
65
64
|
type: :development
|
65
|
+
version_requirements: *id003
|
66
66
|
- !ruby/object:Gem::Dependency
|
67
|
+
name: capybara
|
67
68
|
prerelease: false
|
68
|
-
|
69
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
69
70
|
none: false
|
70
71
|
requirements:
|
71
72
|
- - ">="
|
@@ -76,9 +77,8 @@ dependencies:
|
|
76
77
|
- 4
|
77
78
|
- 0
|
78
79
|
version: 0.4.0
|
79
|
-
requirement: *id004
|
80
|
-
name: capybara
|
81
80
|
type: :development
|
81
|
+
version_requirements: *id004
|
82
82
|
description: Track changes to your models' data. Good for auditing or versioning.
|
83
83
|
email: boss@airbladesoftware.com
|
84
84
|
executables: []
|
@@ -90,7 +90,6 @@ extra_rdoc_files: []
|
|
90
90
|
files:
|
91
91
|
- .gitignore
|
92
92
|
- Gemfile
|
93
|
-
- Gemfile.lock
|
94
93
|
- MIT-LICENSE
|
95
94
|
- README.md
|
96
95
|
- Rakefile
|
@@ -115,9 +114,11 @@ files:
|
|
115
114
|
- test/dummy/app/models/fluxor.rb
|
116
115
|
- test/dummy/app/models/foo_widget.rb
|
117
116
|
- test/dummy/app/models/person.rb
|
117
|
+
- test/dummy/app/models/post.rb
|
118
118
|
- test/dummy/app/models/song.rb
|
119
119
|
- test/dummy/app/models/widget.rb
|
120
120
|
- test/dummy/app/models/wotsit.rb
|
121
|
+
- test/dummy/app/versions/post_version.rb
|
121
122
|
- test/dummy/app/views/layouts/application.html.erb
|
122
123
|
- test/dummy/config.ru
|
123
124
|
- test/dummy/config/application.rb
|
@@ -203,9 +204,11 @@ test_files:
|
|
203
204
|
- test/dummy/app/models/fluxor.rb
|
204
205
|
- test/dummy/app/models/foo_widget.rb
|
205
206
|
- test/dummy/app/models/person.rb
|
207
|
+
- test/dummy/app/models/post.rb
|
206
208
|
- test/dummy/app/models/song.rb
|
207
209
|
- test/dummy/app/models/widget.rb
|
208
210
|
- test/dummy/app/models/wotsit.rb
|
211
|
+
- test/dummy/app/versions/post_version.rb
|
209
212
|
- test/dummy/app/views/layouts/application.html.erb
|
210
213
|
- test/dummy/config.ru
|
211
214
|
- test/dummy/config/application.rb
|
data/Gemfile.lock
DELETED
@@ -1,107 +0,0 @@
|
|
1
|
-
PATH
|
2
|
-
remote: .
|
3
|
-
specs:
|
4
|
-
paper_trail (2.0.2)
|
5
|
-
rails (~> 3)
|
6
|
-
|
7
|
-
GEM
|
8
|
-
remote: http://rubygems.org/
|
9
|
-
specs:
|
10
|
-
abstract (1.0.0)
|
11
|
-
actionmailer (3.0.4)
|
12
|
-
actionpack (= 3.0.4)
|
13
|
-
mail (~> 2.2.15)
|
14
|
-
actionpack (3.0.4)
|
15
|
-
activemodel (= 3.0.4)
|
16
|
-
activesupport (= 3.0.4)
|
17
|
-
builder (~> 2.1.2)
|
18
|
-
erubis (~> 2.6.6)
|
19
|
-
i18n (~> 0.4)
|
20
|
-
rack (~> 1.2.1)
|
21
|
-
rack-mount (~> 0.6.13)
|
22
|
-
rack-test (~> 0.5.7)
|
23
|
-
tzinfo (~> 0.3.23)
|
24
|
-
activemodel (3.0.4)
|
25
|
-
activesupport (= 3.0.4)
|
26
|
-
builder (~> 2.1.2)
|
27
|
-
i18n (~> 0.4)
|
28
|
-
activerecord (3.0.4)
|
29
|
-
activemodel (= 3.0.4)
|
30
|
-
activesupport (= 3.0.4)
|
31
|
-
arel (~> 2.0.2)
|
32
|
-
tzinfo (~> 0.3.23)
|
33
|
-
activeresource (3.0.4)
|
34
|
-
activemodel (= 3.0.4)
|
35
|
-
activesupport (= 3.0.4)
|
36
|
-
activesupport (3.0.4)
|
37
|
-
arel (2.0.9)
|
38
|
-
builder (2.1.2)
|
39
|
-
capybara (0.4.1.1)
|
40
|
-
celerity (>= 0.7.9)
|
41
|
-
culerity (>= 0.2.4)
|
42
|
-
mime-types (>= 1.16)
|
43
|
-
nokogiri (>= 1.3.3)
|
44
|
-
rack (>= 1.0.0)
|
45
|
-
rack-test (>= 0.5.4)
|
46
|
-
selenium-webdriver (>= 0.0.27)
|
47
|
-
xpath (~> 0.1.3)
|
48
|
-
celerity (0.8.7)
|
49
|
-
childprocess (0.1.6)
|
50
|
-
ffi (~> 0.6.3)
|
51
|
-
culerity (0.2.15)
|
52
|
-
erubis (2.6.6)
|
53
|
-
abstract (>= 1.0.0)
|
54
|
-
ffi (0.6.3)
|
55
|
-
rake (>= 0.8.7)
|
56
|
-
i18n (0.5.0)
|
57
|
-
json_pure (1.5.1)
|
58
|
-
mail (2.2.15)
|
59
|
-
activesupport (>= 2.3.6)
|
60
|
-
i18n (>= 0.4.0)
|
61
|
-
mime-types (~> 1.16)
|
62
|
-
treetop (~> 1.4.8)
|
63
|
-
mime-types (1.16)
|
64
|
-
nokogiri (1.4.4)
|
65
|
-
polyglot (0.3.1)
|
66
|
-
rack (1.2.1)
|
67
|
-
rack-mount (0.6.13)
|
68
|
-
rack (>= 1.0.0)
|
69
|
-
rack-test (0.5.7)
|
70
|
-
rack (>= 1.0)
|
71
|
-
rails (3.0.4)
|
72
|
-
actionmailer (= 3.0.4)
|
73
|
-
actionpack (= 3.0.4)
|
74
|
-
activerecord (= 3.0.4)
|
75
|
-
activeresource (= 3.0.4)
|
76
|
-
activesupport (= 3.0.4)
|
77
|
-
bundler (~> 1.0)
|
78
|
-
railties (= 3.0.4)
|
79
|
-
railties (3.0.4)
|
80
|
-
actionpack (= 3.0.4)
|
81
|
-
activesupport (= 3.0.4)
|
82
|
-
rake (>= 0.8.7)
|
83
|
-
thor (~> 0.14.4)
|
84
|
-
rake (0.8.7)
|
85
|
-
rubyzip (0.9.4)
|
86
|
-
selenium-webdriver (0.1.2)
|
87
|
-
childprocess (~> 0.1.5)
|
88
|
-
ffi (~> 0.6.3)
|
89
|
-
json_pure
|
90
|
-
rubyzip
|
91
|
-
shoulda (2.10.3)
|
92
|
-
sqlite3-ruby (1.3.1)
|
93
|
-
thor (0.14.6)
|
94
|
-
treetop (1.4.9)
|
95
|
-
polyglot (>= 0.3.1)
|
96
|
-
tzinfo (0.3.24)
|
97
|
-
xpath (0.1.3)
|
98
|
-
nokogiri (~> 1.3)
|
99
|
-
|
100
|
-
PLATFORMS
|
101
|
-
ruby
|
102
|
-
|
103
|
-
DEPENDENCIES
|
104
|
-
capybara (>= 0.4.0)
|
105
|
-
paper_trail!
|
106
|
-
shoulda (= 2.10.3)
|
107
|
-
sqlite3-ruby (~> 1.2)
|