paper_trail 1.5.5 → 1.6.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 -0
- data/lib/paper_trail/has_paper_trail.rb +15 -8
- data/lib/paper_trail/version.rb +21 -0
- data/lib/paper_trail/version_number.rb +1 -1
- data/test/paper_trail_model_test.rb +62 -2
- data/test/schema.rb +1 -0
- metadata +26 -26
data/README.md
CHANGED
@@ -11,6 +11,7 @@ PaperTrail lets you track changes to your models' data. It's good for auditing
|
|
11
11
|
* Allows you to get at every version, including the original, even once destroyed.
|
12
12
|
* Allows you to get at every version even if the schema has since changed.
|
13
13
|
* Allows you to get at the version as of a particular time.
|
14
|
+
* Automatically restores the `has_one` associations as they were at the time.
|
14
15
|
* 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.
|
15
16
|
* Allows you to set who is responsible at model-level (useful for migrations).
|
16
17
|
* Allows you to store arbitrary model-level metadata with each version (useful for filtering versions).
|
@@ -212,6 +213,27 @@ To find out who made a `version`'s object look that way, use `version.originator
|
|
212
213
|
>> last_version.terminator # 'Bob'
|
213
214
|
|
214
215
|
|
216
|
+
## Has-One Associations
|
217
|
+
|
218
|
+
PaperTrail automatically restores `:has_one` associations as they were at the time.
|
219
|
+
|
220
|
+
class Treasure < ActiveRecord::Base
|
221
|
+
has_one :location
|
222
|
+
end
|
223
|
+
|
224
|
+
>> treasure.amount # 100
|
225
|
+
>> treasure.location.latitude # 12.345
|
226
|
+
|
227
|
+
>> treasure.update_attributes :amount => 153
|
228
|
+
>> treasure.location.update_attributes :latitude => 54.321
|
229
|
+
|
230
|
+
>> t = treasure.versions.last.reify
|
231
|
+
>> t.amount # 100
|
232
|
+
>> t.location.latitude # 12.345
|
233
|
+
|
234
|
+
Unfortunately PaperTrail doesn't do this for `:has_many` associations (I can't get it to work) or `:belongs_to` (I ran out of time looking at `:has_many`).
|
235
|
+
|
236
|
+
|
215
237
|
## Has-Many-Through Associations
|
216
238
|
|
217
239
|
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:
|
@@ -85,14 +85,10 @@ module PaperTrail
|
|
85
85
|
|
86
86
|
# Returns the object (not a Version) as it was at the given timestamp.
|
87
87
|
def version_at(timestamp)
|
88
|
-
#
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
# change.
|
93
|
-
version = versions.first :conditions => ['created_at > ?', timestamp],
|
94
|
-
:order => 'created_at ASC'
|
95
|
-
version.try :reify
|
88
|
+
# Because a version stores how its object looked *before* the change,
|
89
|
+
# we need to look for the first version created *after* the timestamp.
|
90
|
+
version = versions.first :conditions => ['created_at > ?', timestamp], :order => 'created_at ASC, id ASC'
|
91
|
+
version ? version.reify : self
|
96
92
|
end
|
97
93
|
|
98
94
|
# Returns the object (not a Version) as it was most recently.
|
@@ -109,6 +105,17 @@ module PaperTrail
|
|
109
105
|
subsequent_version.reify if subsequent_version
|
110
106
|
end
|
111
107
|
|
108
|
+
protected
|
109
|
+
|
110
|
+
# Returns the object (not a Version) as it was until the version record
|
111
|
+
# with the given id.
|
112
|
+
def version_until(id)
|
113
|
+
# Because a version stores how its object looked *before* the change,
|
114
|
+
# we need to look for the first version created *on or after* the id.
|
115
|
+
version = versions.first :conditions => ['id >= ?', id], :order => 'id ASC'
|
116
|
+
version ? version.reify : self
|
117
|
+
end
|
118
|
+
|
112
119
|
private
|
113
120
|
|
114
121
|
def merge_metadata(data)
|
data/lib/paper_trail/version.rb
CHANGED
@@ -36,6 +36,10 @@ class Version < ActiveRecord::Base
|
|
36
36
|
end
|
37
37
|
|
38
38
|
model.version = self
|
39
|
+
# Restore the model's has_one associations as they were when this version was
|
40
|
+
# superseded by the next (because that's what the user was looking at when they
|
41
|
+
# made the change).
|
42
|
+
reify_has_ones model
|
39
43
|
model
|
40
44
|
end
|
41
45
|
end
|
@@ -66,4 +70,21 @@ class Version < ActiveRecord::Base
|
|
66
70
|
:order => 'id ASC').index(self)
|
67
71
|
end
|
68
72
|
|
73
|
+
private
|
74
|
+
|
75
|
+
def reify_has_ones(model)
|
76
|
+
model.class.reflect_on_all_associations(:has_one).each do |assoc|
|
77
|
+
child = model.send assoc.name
|
78
|
+
if child.respond_to? :version_until
|
79
|
+
if (version_until = child.version_until(id))
|
80
|
+
version_until.attributes.each do |k,v|
|
81
|
+
model.send(assoc.name).send "#{k}=", v rescue nil
|
82
|
+
end
|
83
|
+
else
|
84
|
+
model.send "#{assoc.name}=", nil
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
69
90
|
end
|
@@ -10,6 +10,7 @@ class FooWidget < Widget
|
|
10
10
|
end
|
11
11
|
|
12
12
|
class Wotsit < ActiveRecord::Base
|
13
|
+
has_paper_trail
|
13
14
|
belongs_to :widget
|
14
15
|
end
|
15
16
|
|
@@ -45,7 +46,8 @@ end
|
|
45
46
|
|
46
47
|
class HasPaperTrailModelTest < Test::Unit::TestCase
|
47
48
|
load_schema
|
48
|
-
|
49
|
+
=begin
|
50
|
+
=end
|
49
51
|
context 'A record' do
|
50
52
|
setup { @article = Article.create }
|
51
53
|
|
@@ -126,7 +128,8 @@ class HasPaperTrailModelTest < Test::Unit::TestCase
|
|
126
128
|
end
|
127
129
|
|
128
130
|
should 'copy the has_one association when reifying' do
|
129
|
-
|
131
|
+
assert_nil @reified_widget.wotsit # wotsit wasn't there at the last version
|
132
|
+
assert_equal @wotsit, @widget.wotsit # wotsit came into being on the live object
|
130
133
|
end
|
131
134
|
end
|
132
135
|
|
@@ -644,4 +647,61 @@ class HasPaperTrailModelTest < Test::Unit::TestCase
|
|
644
647
|
end
|
645
648
|
end
|
646
649
|
|
650
|
+
|
651
|
+
context 'A model with a has_one association' do
|
652
|
+
setup { @widget = Widget.create :name => 'widget_0' }
|
653
|
+
context 'before the associated was created' do
|
654
|
+
setup do
|
655
|
+
@widget.update_attributes :name => 'widget_1'
|
656
|
+
@wotsit = @widget.create_wotsit :name => 'wotsit_0'
|
657
|
+
end
|
658
|
+
context 'when reified' do
|
659
|
+
setup { @widget_0 = @widget.versions.last.reify }
|
660
|
+
should 'see the associated as it was at the time' do
|
661
|
+
assert_nil @widget_0.wotsit
|
662
|
+
end
|
663
|
+
end
|
664
|
+
end
|
665
|
+
|
666
|
+
context 'where the associated is created between model versions' do
|
667
|
+
setup do
|
668
|
+
@wotsit = @widget.create_wotsit :name => 'wotsit_0'
|
669
|
+
@widget.update_attributes :name => 'widget_1'
|
670
|
+
end
|
671
|
+
context 'when reified' do
|
672
|
+
setup { @widget_0 = @widget.versions.last.reify }
|
673
|
+
should 'see the associated as it was at the time' do
|
674
|
+
assert_equal 'wotsit_0', @widget_0.wotsit.name
|
675
|
+
end
|
676
|
+
end
|
677
|
+
|
678
|
+
context 'and then the associated is updated between model versions' do
|
679
|
+
setup do
|
680
|
+
@wotsit.update_attributes :name => 'wotsit_1'
|
681
|
+
@wotsit.update_attributes :name => 'wotsit_2'
|
682
|
+
@widget.update_attributes :name => 'widget_2'
|
683
|
+
end
|
684
|
+
context 'when reified' do
|
685
|
+
setup { @widget_1 = @widget.versions.last.reify }
|
686
|
+
should 'see the associated as it was at the time' do
|
687
|
+
assert_equal 'wotsit_2', @widget_1.wotsit.name
|
688
|
+
end
|
689
|
+
end
|
690
|
+
end
|
691
|
+
|
692
|
+
context 'and then the associated is destroyed between model versions' do
|
693
|
+
setup do
|
694
|
+
@wotsit.destroy
|
695
|
+
@widget.update_attributes :name => 'widget_3'
|
696
|
+
end
|
697
|
+
context 'when reified' do
|
698
|
+
setup { @widget_2 = @widget.versions.last.reify }
|
699
|
+
should 'see the associated as it was at the time' do
|
700
|
+
assert_nil @widget_2.wotsit
|
701
|
+
end
|
702
|
+
end
|
703
|
+
end
|
704
|
+
end
|
705
|
+
end
|
706
|
+
|
647
707
|
end
|
data/test/schema.rb
CHANGED
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: paper_trail
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 15
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 1
|
8
|
-
-
|
9
|
-
-
|
10
|
-
version: 1.
|
8
|
+
- 6
|
9
|
+
- 0
|
10
|
+
version: 1.6.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Andy Stewart
|
@@ -15,13 +15,14 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2010-10-
|
18
|
+
date: 2010-10-20 00:00:00 +01:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
22
22
|
name: bundler
|
23
|
+
type: :development
|
23
24
|
prerelease: false
|
24
|
-
|
25
|
+
version_requirements: &id001 !ruby/object:Gem::Requirement
|
25
26
|
none: false
|
26
27
|
requirements:
|
27
28
|
- - ~>
|
@@ -31,12 +32,12 @@ dependencies:
|
|
31
32
|
- 1
|
32
33
|
- 0
|
33
34
|
version: "1.0"
|
34
|
-
|
35
|
-
version_requirements: *id001
|
35
|
+
requirement: *id001
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: rake
|
38
|
+
type: :development
|
38
39
|
prerelease: false
|
39
|
-
|
40
|
+
version_requirements: &id002 !ruby/object:Gem::Requirement
|
40
41
|
none: false
|
41
42
|
requirements:
|
42
43
|
- - "="
|
@@ -47,12 +48,12 @@ dependencies:
|
|
47
48
|
- 8
|
48
49
|
- 7
|
49
50
|
version: 0.8.7
|
50
|
-
|
51
|
-
version_requirements: *id002
|
51
|
+
requirement: *id002
|
52
52
|
- !ruby/object:Gem::Dependency
|
53
53
|
name: shoulda
|
54
|
+
type: :development
|
54
55
|
prerelease: false
|
55
|
-
|
56
|
+
version_requirements: &id003 !ruby/object:Gem::Requirement
|
56
57
|
none: false
|
57
58
|
requirements:
|
58
59
|
- - "="
|
@@ -63,12 +64,12 @@ dependencies:
|
|
63
64
|
- 10
|
64
65
|
- 3
|
65
66
|
version: 2.10.3
|
66
|
-
|
67
|
-
version_requirements: *id003
|
67
|
+
requirement: *id003
|
68
68
|
- !ruby/object:Gem::Dependency
|
69
69
|
name: activesupport
|
70
|
+
type: :development
|
70
71
|
prerelease: false
|
71
|
-
|
72
|
+
version_requirements: &id004 !ruby/object:Gem::Requirement
|
72
73
|
none: false
|
73
74
|
requirements:
|
74
75
|
- - ~>
|
@@ -78,12 +79,12 @@ dependencies:
|
|
78
79
|
- 2
|
79
80
|
- 3
|
80
81
|
version: "2.3"
|
81
|
-
|
82
|
-
version_requirements: *id004
|
82
|
+
requirement: *id004
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
84
|
name: sqlite3-ruby
|
85
|
+
type: :development
|
85
86
|
prerelease: false
|
86
|
-
|
87
|
+
version_requirements: &id005 !ruby/object:Gem::Requirement
|
87
88
|
none: false
|
88
89
|
requirements:
|
89
90
|
- - ~>
|
@@ -93,12 +94,12 @@ dependencies:
|
|
93
94
|
- 1
|
94
95
|
- 2
|
95
96
|
version: "1.2"
|
96
|
-
|
97
|
-
version_requirements: *id005
|
97
|
+
requirement: *id005
|
98
98
|
- !ruby/object:Gem::Dependency
|
99
99
|
name: activerecord
|
100
|
+
type: :runtime
|
100
101
|
prerelease: false
|
101
|
-
|
102
|
+
version_requirements: &id006 !ruby/object:Gem::Requirement
|
102
103
|
none: false
|
103
104
|
requirements:
|
104
105
|
- - ">="
|
@@ -108,12 +109,12 @@ dependencies:
|
|
108
109
|
- 2
|
109
110
|
- 3
|
110
111
|
version: "2.3"
|
111
|
-
|
112
|
-
version_requirements: *id006
|
112
|
+
requirement: *id006
|
113
113
|
- !ruby/object:Gem::Dependency
|
114
114
|
name: actionpack
|
115
|
+
type: :runtime
|
115
116
|
prerelease: false
|
116
|
-
|
117
|
+
version_requirements: &id007 !ruby/object:Gem::Requirement
|
117
118
|
none: false
|
118
119
|
requirements:
|
119
120
|
- - ">="
|
@@ -123,8 +124,7 @@ dependencies:
|
|
123
124
|
- 2
|
124
125
|
- 3
|
125
126
|
version: "2.3"
|
126
|
-
|
127
|
-
version_requirements: *id007
|
127
|
+
requirement: *id007
|
128
128
|
description: Track changes to your models' data. Good for auditing or versioning.
|
129
129
|
email: boss@airbladesoftware.com
|
130
130
|
executables: []
|