paper_trail 2.6.3 → 2.6.4
Sign up to get free protection for your applications and to get access to all the features.
- 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
|