paper_trail 2.6.3 → 2.6.4
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/.gitignore +2 -0
- data/CHANGELOG.md +17 -0
- data/README.md +413 -293
- data/lib/paper_trail.rb +0 -1
- data/lib/paper_trail/config.rb +2 -0
- data/lib/paper_trail/controller.rb +4 -5
- data/lib/paper_trail/has_paper_trail.rb +40 -24
- data/lib/paper_trail/version_number.rb +1 -1
- data/paper_trail.gemspec +2 -2
- data/test/dummy/app/models/article.rb +5 -2
- data/test/dummy/db/migrate/20110208155312_set_up_test_tables.rb +1 -0
- data/test/functional/controller_test.rb +19 -0
- data/test/test_helper.rb +1 -1
- data/test/unit/model_test.rb +64 -23
- data/test/unit/timestamp_test.rb +1 -0
- metadata +95 -109
data/lib/paper_trail.rb
CHANGED
data/lib/paper_trail/config.rb
CHANGED
@@ -2,16 +2,15 @@ module PaperTrail
|
|
2
2
|
module Controller
|
3
3
|
|
4
4
|
def self.included(base)
|
5
|
-
base.before_filter :set_paper_trail_whodunnit
|
6
|
-
base.before_filter :set_paper_trail_controller_info
|
7
5
|
base.before_filter :set_paper_trail_enabled_for_controller
|
6
|
+
base.before_filter :set_paper_trail_whodunnit, :set_paper_trail_controller_info
|
8
7
|
end
|
9
8
|
|
10
9
|
protected
|
11
10
|
|
12
11
|
# Returns the user who is responsible for any changes that occur.
|
13
12
|
# By default this calls `current_user` and returns the result.
|
14
|
-
#
|
13
|
+
#
|
15
14
|
# Override this method in your controller to call a different
|
16
15
|
# method, e.g. `current_person`, or anything you like.
|
17
16
|
def user_for_paper_trail
|
@@ -57,7 +56,7 @@ module PaperTrail
|
|
57
56
|
|
58
57
|
# Tells PaperTrail who is responsible for any changes that occur.
|
59
58
|
def set_paper_trail_whodunnit
|
60
|
-
::PaperTrail.whodunnit = user_for_paper_trail
|
59
|
+
::PaperTrail.whodunnit = user_for_paper_trail if paper_trail_enabled_for_controller
|
61
60
|
end
|
62
61
|
|
63
62
|
# DEPRECATED: please use `set_paper_trail_whodunnit` instead.
|
@@ -69,7 +68,7 @@ module PaperTrail
|
|
69
68
|
# Tells PaperTrail any information from the controller you want
|
70
69
|
# to store alongside any changes that occur.
|
71
70
|
def set_paper_trail_controller_info
|
72
|
-
::PaperTrail.controller_info = info_for_paper_trail
|
71
|
+
::PaperTrail.controller_info = info_for_paper_trail if paper_trail_enabled_for_controller
|
73
72
|
end
|
74
73
|
|
75
74
|
end
|
@@ -41,23 +41,15 @@ module PaperTrail
|
|
41
41
|
class_attribute :version_class_name
|
42
42
|
self.version_class_name = options[:class_name] || 'Version'
|
43
43
|
|
44
|
-
class_attribute :
|
45
|
-
self.
|
44
|
+
class_attribute :paper_trail_options
|
45
|
+
self.paper_trail_options = options.dup
|
46
46
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
self.unless_condition = options[:unless]
|
52
|
-
|
53
|
-
class_attribute :skip
|
54
|
-
self.skip = ([options[:skip]].flatten.compact || []).map &:to_s
|
55
|
-
|
56
|
-
class_attribute :only
|
57
|
-
self.only = ([options[:only]].flatten.compact || []).map &:to_s
|
47
|
+
[:ignore, :skip, :only].each do |k|
|
48
|
+
paper_trail_options[k] =
|
49
|
+
([paper_trail_options[k]].flatten.compact || []).map &:to_s
|
50
|
+
end
|
58
51
|
|
59
|
-
|
60
|
-
self.meta = options[:meta] || {}
|
52
|
+
paper_trail_options[:meta] ||= {}
|
61
53
|
|
62
54
|
class_attribute :paper_trail_enabled_for_model
|
63
55
|
self.paper_trail_enabled_for_model = true
|
@@ -68,11 +60,15 @@ module PaperTrail
|
|
68
60
|
has_many self.versions_association_name,
|
69
61
|
:class_name => version_class_name,
|
70
62
|
:as => :item,
|
71
|
-
:order => "#{PaperTrail.timestamp_field} ASC, #{self.
|
63
|
+
:order => "#{PaperTrail.timestamp_field} ASC, #{self.version_key} ASC"
|
72
64
|
|
73
65
|
after_create :record_create, :if => :save_version? if !options[:on] || options[:on].include?(:create)
|
74
66
|
before_update :record_update, :if => :save_version? if !options[:on] || options[:on].include?(:update)
|
75
|
-
after_destroy :record_destroy if !options[:on] || options[:on].include?(:destroy)
|
67
|
+
after_destroy :record_destroy, :if => :save_version? if !options[:on] || options[:on].include?(:destroy)
|
68
|
+
end
|
69
|
+
|
70
|
+
def version_key
|
71
|
+
self.version_class_name.constantize.primary_key
|
76
72
|
end
|
77
73
|
|
78
74
|
# Switches PaperTrail off for this class.
|
@@ -97,7 +93,7 @@ module PaperTrail
|
|
97
93
|
|
98
94
|
# Returns who put the object into its current state.
|
99
95
|
def originator
|
100
|
-
version_class.with_item_keys(self.class.name, id).last.try :whodunnit
|
96
|
+
version_class.with_item_keys(self.class.base_class.name, id).last.try :whodunnit
|
101
97
|
end
|
102
98
|
|
103
99
|
# Returns the object (not a Version) as it was at the given timestamp.
|
@@ -149,7 +145,17 @@ module PaperTrail
|
|
149
145
|
|
150
146
|
def record_create
|
151
147
|
if switched_on?
|
152
|
-
|
148
|
+
data = {
|
149
|
+
:event => 'create',
|
150
|
+
:whodunnit => PaperTrail.whodunnit
|
151
|
+
}
|
152
|
+
|
153
|
+
if changed_notably? and version_class.column_names.include?('object_changes')
|
154
|
+
# The double negative (reject, !include?) preserves the hash structure of self.changes.
|
155
|
+
data[:object_changes] = self.changes.reject { |k, _| !notably_changed.include?(k) }.to_yaml
|
156
|
+
end
|
157
|
+
|
158
|
+
send(self.class.versions_association_name).create merge_metadata(data)
|
153
159
|
end
|
154
160
|
end
|
155
161
|
|
@@ -183,12 +189,17 @@ module PaperTrail
|
|
183
189
|
|
184
190
|
def merge_metadata(data)
|
185
191
|
# First we merge the model-level metadata in `meta`.
|
186
|
-
meta.each do |k,v|
|
192
|
+
paper_trail_options[:meta].each do |k,v|
|
187
193
|
data[k] =
|
188
194
|
if v.respond_to?(:call)
|
189
195
|
v.call(self)
|
190
196
|
elsif v.is_a?(Symbol) && respond_to?(v)
|
191
|
-
|
197
|
+
# if it is an attribute that is changing, be sure to grab the current version
|
198
|
+
if has_attribute?(v) && send("#{v}_changed?".to_sym)
|
199
|
+
send("#{v}_was".to_sym)
|
200
|
+
else
|
201
|
+
send(v)
|
202
|
+
end
|
192
203
|
else
|
193
204
|
v
|
194
205
|
end
|
@@ -210,7 +221,7 @@ module PaperTrail
|
|
210
221
|
end
|
211
222
|
|
212
223
|
def object_to_string(object)
|
213
|
-
object.attributes.except(*self.class.skip).to_yaml
|
224
|
+
object.attributes.except(*self.class.paper_trail_options[:skip]).to_yaml
|
214
225
|
end
|
215
226
|
|
216
227
|
def changed_notably?
|
@@ -218,11 +229,14 @@ module PaperTrail
|
|
218
229
|
end
|
219
230
|
|
220
231
|
def notably_changed
|
221
|
-
|
232
|
+
only = self.class.paper_trail_options[:only]
|
233
|
+
only.empty? ? changed_and_not_ignored : (changed_and_not_ignored & only)
|
222
234
|
end
|
223
235
|
|
224
236
|
def changed_and_not_ignored
|
225
|
-
|
237
|
+
ignore = self.class.paper_trail_options[:ignore]
|
238
|
+
skip = self.class.paper_trail_options[:skip]
|
239
|
+
changed - ignore - skip
|
226
240
|
end
|
227
241
|
|
228
242
|
def switched_on?
|
@@ -230,6 +244,8 @@ module PaperTrail
|
|
230
244
|
end
|
231
245
|
|
232
246
|
def save_version?
|
247
|
+
if_condition = self.class.paper_trail_options[:if]
|
248
|
+
unless_condition = self.class.paper_trail_options[:unless]
|
233
249
|
(if_condition.blank? || if_condition.call(self)) && !unless_condition.try(:call, self)
|
234
250
|
end
|
235
251
|
end
|
data/paper_trail.gemspec
CHANGED
@@ -19,7 +19,7 @@ Gem::Specification.new do |s|
|
|
19
19
|
s.add_dependency 'activerecord', '~> 3.0'
|
20
20
|
|
21
21
|
s.add_development_dependency 'rake'
|
22
|
-
s.add_development_dependency 'shoulda', '
|
22
|
+
s.add_development_dependency 'shoulda', '~> 3.3'
|
23
23
|
s.add_development_dependency 'sqlite3', '~> 1.2'
|
24
|
-
s.add_development_dependency 'capybara', '~>
|
24
|
+
s.add_development_dependency 'capybara', '~> 2.0'
|
25
25
|
end
|
@@ -2,10 +2,13 @@ class Article < ActiveRecord::Base
|
|
2
2
|
has_paper_trail :ignore => :title,
|
3
3
|
:only => [:content],
|
4
4
|
:skip => [:file_upload],
|
5
|
-
:meta
|
5
|
+
:meta => {
|
6
|
+
:answer => 42,
|
6
7
|
:action => :action_data_provider_method,
|
7
8
|
:question => Proc.new { "31 + 11 = #{31 + 11}" },
|
8
|
-
:article_id => Proc.new { |article| article.id }
|
9
|
+
:article_id => Proc.new { |article| article.id },
|
10
|
+
:title => :title
|
11
|
+
}
|
9
12
|
|
10
13
|
def action_data_provider_method
|
11
14
|
self.object_id.to_s
|
@@ -68,4 +68,23 @@ class ControllerTest < ActionController::TestCase
|
|
68
68
|
assert_equal '127.0.0.1', versions_for_widget.last.ip
|
69
69
|
assert_equal 'Rails Testing', versions_for_widget.last.user_agent
|
70
70
|
end
|
71
|
+
|
72
|
+
test "controller metadata methods should get evaluated if paper trail is enabled for controller" do
|
73
|
+
@request.env['HTTP_USER_AGENT'] = 'User-Agent'
|
74
|
+
post :create, :widget => { :name => 'Flugel' }
|
75
|
+
assert PaperTrail.enabled_for_controller?
|
76
|
+
assert_equal 153, PaperTrail.whodunnit
|
77
|
+
assert PaperTrail.controller_info.present?
|
78
|
+
assert PaperTrail.controller_info.keys.include?(:ip)
|
79
|
+
assert PaperTrail.controller_info.keys.include?(:user_agent)
|
80
|
+
end
|
81
|
+
|
82
|
+
test "controller metadata methods should not get evaluated if paper trail is disabled for controller" do
|
83
|
+
@request.env['HTTP_USER_AGENT'] = 'Disable User-Agent'
|
84
|
+
post :create, :widget => { :name => 'Flugel' }
|
85
|
+
assert_equal 0, assigns(:widget).versions.length
|
86
|
+
assert !PaperTrail.enabled_for_controller?
|
87
|
+
assert PaperTrail.whodunnit.nil?
|
88
|
+
assert PaperTrail.controller_info.nil?
|
89
|
+
end
|
71
90
|
end
|
data/test/test_helper.rb
CHANGED
data/test/unit/model_test.rb
CHANGED
@@ -7,12 +7,12 @@ class HasPaperTrailModelTest < ActiveSupport::TestCase
|
|
7
7
|
|
8
8
|
context 'which updates an ignored column' do
|
9
9
|
setup { @article.update_attributes :title => 'My first title' }
|
10
|
-
|
10
|
+
should 'not change the number of versions' do assert_equal(1, Version.count) end
|
11
11
|
end
|
12
12
|
|
13
13
|
context 'which updates an ignored column and a selected column' do
|
14
14
|
setup { @article.update_attributes :title => 'My first title', :content => 'Some text here.' }
|
15
|
-
|
15
|
+
should 'change the number of versions' do assert_equal(2, Version.count) end
|
16
16
|
|
17
17
|
should 'have stored only non-ignored attributes' do
|
18
18
|
assert_equal ({'content' => [nil, 'Some text here.']}), @article.versions.last.changeset
|
@@ -21,22 +21,22 @@ class HasPaperTrailModelTest < ActiveSupport::TestCase
|
|
21
21
|
|
22
22
|
context 'which updates a selected column' do
|
23
23
|
setup { @article.update_attributes :content => 'Some text here.' }
|
24
|
-
|
24
|
+
should 'change the number of versions' do assert_equal(2, Version.count) end
|
25
25
|
end
|
26
26
|
|
27
27
|
context 'which updates a non-ignored and non-selected column' do
|
28
28
|
setup { @article.update_attributes :abstract => 'Other abstract'}
|
29
|
-
|
29
|
+
should 'not change the number of versions' do assert_equal(1, Version.count) end
|
30
30
|
end
|
31
31
|
|
32
32
|
context 'which updates a skipped column' do
|
33
33
|
setup { @article.update_attributes :file_upload => 'Your data goes here' }
|
34
|
-
|
34
|
+
should 'not change the number of versions' do assert_equal(1, Version.count) end
|
35
35
|
end
|
36
36
|
|
37
37
|
context 'which updates a skipped column and a selected column' do
|
38
38
|
setup { @article.update_attributes :file_upload => 'Your data goes here', :content => 'Some text here.' }
|
39
|
-
|
39
|
+
should 'change the number of versions' do assert_equal(2, Version.count) end
|
40
40
|
|
41
41
|
should 'have stored only non-skipped attributes' do
|
42
42
|
assert_equal ({'content' => [nil, 'Some text here.']}), @article.versions.last.changeset
|
@@ -64,7 +64,7 @@ class HasPaperTrailModelTest < ActiveSupport::TestCase
|
|
64
64
|
|
65
65
|
context 'which updates an ignored column' do
|
66
66
|
setup { @legacy_widget.update_attributes :version => 1 }
|
67
|
-
|
67
|
+
should 'not change the number of versions' do assert_equal(1, Version.count) end
|
68
68
|
end
|
69
69
|
end
|
70
70
|
|
@@ -73,11 +73,16 @@ class HasPaperTrailModelTest < ActiveSupport::TestCase
|
|
73
73
|
|
74
74
|
context 'for non-US translations' do
|
75
75
|
setup { @translation.save }
|
76
|
-
|
76
|
+
should 'not change the number of versions' do assert_equal(0, Version.count) end
|
77
77
|
|
78
78
|
context 'after update' do
|
79
79
|
setup { @translation.update_attributes :content => 'Content' }
|
80
|
-
|
80
|
+
should 'not change the number of versions' do assert_equal(0, Version.count) end
|
81
|
+
end
|
82
|
+
|
83
|
+
context 'after destroy' do
|
84
|
+
setup { @translation.destroy }
|
85
|
+
should 'not change the number of versions' do assert_equal(0, Version.count) end
|
81
86
|
end
|
82
87
|
end
|
83
88
|
|
@@ -90,22 +95,27 @@ class HasPaperTrailModelTest < ActiveSupport::TestCase
|
|
90
95
|
@translation.save
|
91
96
|
end
|
92
97
|
|
93
|
-
|
98
|
+
should 'not change the number of versions' do assert_equal(0, Version.count) end
|
94
99
|
|
95
100
|
context 'after update' do
|
96
101
|
setup { @translation.update_attributes :content => 'Content' }
|
97
|
-
|
102
|
+
should 'not change the number of versions' do assert_equal(0, Version.count) end
|
98
103
|
end
|
99
104
|
end
|
100
105
|
|
101
106
|
context 'that are not drafts' do
|
102
107
|
setup { @translation.save }
|
103
108
|
|
104
|
-
|
109
|
+
should 'change the number of versions' do assert_equal(1, Version.count) end
|
105
110
|
|
106
111
|
context 'after update' do
|
107
112
|
setup { @translation.update_attributes :content => 'Content' }
|
108
|
-
|
113
|
+
should 'change the number of versions' do assert_equal(2, Version.count) end
|
114
|
+
end
|
115
|
+
|
116
|
+
context 'after destroy' do
|
117
|
+
setup { @translation.destroy }
|
118
|
+
should 'change the number of versions' do assert_equal(2, Version.count) end
|
109
119
|
end
|
110
120
|
end
|
111
121
|
end
|
@@ -143,12 +153,19 @@ class HasPaperTrailModelTest < ActiveSupport::TestCase
|
|
143
153
|
assert @widget.live?
|
144
154
|
end
|
145
155
|
|
146
|
-
should '
|
147
|
-
|
156
|
+
should 'have changes' do
|
157
|
+
changes = {
|
158
|
+
'name' => [nil, 'Henry'],
|
159
|
+
'created_at' => [nil, @widget.created_at],
|
160
|
+
'updated_at' => [nil, @widget.updated_at],
|
161
|
+
'id' => [nil, 1]
|
162
|
+
}
|
163
|
+
|
164
|
+
assert_equal changes, @widget.versions.last.changeset
|
148
165
|
end
|
149
166
|
|
150
167
|
context 'and then updated without any changes' do
|
151
|
-
setup { @widget.
|
168
|
+
setup { @widget.touch }
|
152
169
|
|
153
170
|
should 'not have a new version' do
|
154
171
|
assert_equal 1, @widget.versions.length
|
@@ -353,6 +370,7 @@ class HasPaperTrailModelTest < ActiveSupport::TestCase
|
|
353
370
|
context "after a column is removed from the record's schema" do
|
354
371
|
setup do
|
355
372
|
change_schema
|
373
|
+
Widget.connection.schema_cache.clear!
|
356
374
|
Widget.reset_column_information
|
357
375
|
assert_raise(NoMethodError) { Widget.new.sacrificial_column }
|
358
376
|
@last = @widget.versions.last
|
@@ -516,6 +534,12 @@ class HasPaperTrailModelTest < ActiveSupport::TestCase
|
|
516
534
|
assert_nil Version.last.next
|
517
535
|
end
|
518
536
|
|
537
|
+
should 'should return the correct originator' do
|
538
|
+
PaperTrail.whodunnit = 'Ben'
|
539
|
+
@foo.update_attribute(:name, 'Geoffrey')
|
540
|
+
assert_equal PaperTrail.whodunnit, @foo.originator
|
541
|
+
end
|
542
|
+
|
519
543
|
context 'when destroyed' do
|
520
544
|
setup { @foo.destroy }
|
521
545
|
|
@@ -630,7 +654,10 @@ class HasPaperTrailModelTest < ActiveSupport::TestCase
|
|
630
654
|
|
631
655
|
|
632
656
|
context 'An item' do
|
633
|
-
setup
|
657
|
+
setup do
|
658
|
+
@initial_title = 'Foobar'
|
659
|
+
@article = Article.new :title => @initial_title
|
660
|
+
end
|
634
661
|
|
635
662
|
context 'which is created' do
|
636
663
|
setup { @article.save }
|
@@ -650,10 +677,16 @@ class HasPaperTrailModelTest < ActiveSupport::TestCase
|
|
650
677
|
should 'store dynamic meta data based on a method of the item' do
|
651
678
|
assert_equal @article.action_data_provider_method, @article.versions.last.action
|
652
679
|
end
|
680
|
+
|
681
|
+
should 'store dynamic meta data based on an attribute of the item prior to creation' do
|
682
|
+
assert_equal nil, @article.versions.last.title
|
683
|
+
end
|
653
684
|
|
654
685
|
|
655
686
|
context 'and updated' do
|
656
|
-
setup
|
687
|
+
setup do
|
688
|
+
@article.update_attributes! :content => 'Better text.', :title => 'Rhubarb'
|
689
|
+
end
|
657
690
|
|
658
691
|
should 'store fixed meta data' do
|
659
692
|
assert_equal 42, @article.versions.last.answer
|
@@ -666,6 +699,10 @@ class HasPaperTrailModelTest < ActiveSupport::TestCase
|
|
666
699
|
should 'store dynamic meta data which depends on the item' do
|
667
700
|
assert_equal @article.id, @article.versions.last.article_id
|
668
701
|
end
|
702
|
+
|
703
|
+
should 'store dynamic meta data based on an attribute of the item prior to the update' do
|
704
|
+
assert_equal @initial_title, @article.versions.last.title
|
705
|
+
end
|
669
706
|
end
|
670
707
|
|
671
708
|
|
@@ -683,7 +720,10 @@ class HasPaperTrailModelTest < ActiveSupport::TestCase
|
|
683
720
|
should 'store dynamic meta data which depends on the item' do
|
684
721
|
assert_equal @article.id, @article.versions.last.article_id
|
685
722
|
end
|
686
|
-
|
723
|
+
|
724
|
+
should 'store dynamic meta data based on an attribute of the item prior to the destruction' do
|
725
|
+
assert_equal @initial_title, @article.versions.last.title
|
726
|
+
end
|
687
727
|
end
|
688
728
|
end
|
689
729
|
end
|
@@ -881,13 +921,14 @@ class HasPaperTrailModelTest < ActiveSupport::TestCase
|
|
881
921
|
|
882
922
|
context 'which is then saved' do
|
883
923
|
setup { @post.save }
|
884
|
-
|
885
|
-
|
924
|
+
should 'change the number of post versions' do assert_equal 1, PostVersion.count end
|
925
|
+
should 'not change the number of versions' do assert_equal(0, Version.count) end
|
886
926
|
end
|
887
927
|
end
|
888
928
|
|
889
929
|
context 'An existing model instance which uses a custom Version class' do
|
890
930
|
setup { @post = Post.create }
|
931
|
+
should 'have one post version' do assert_equal(1, PostVersion.count) end
|
891
932
|
|
892
933
|
context 'on the first version' do
|
893
934
|
setup { @version = @post.versions.first }
|
@@ -903,8 +944,8 @@ class HasPaperTrailModelTest < ActiveSupport::TestCase
|
|
903
944
|
|
904
945
|
context 'which is modified' do
|
905
946
|
setup { @post.update_attributes({ :content => "Some new content" }) }
|
906
|
-
|
907
|
-
|
947
|
+
should 'change the number of post versions' do assert_equal(2, PostVersion.count) end
|
948
|
+
should 'not change the number of versions' do assert_equal(0, Version.count) end
|
908
949
|
should "not have stored changes when object_changes column doesn't exist" do
|
909
950
|
assert_nil @post.versions.last.changeset
|
910
951
|
end
|