paper_trail 2.7.2 → 3.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.rspec +3 -0
  4. data/.travis.yml +13 -4
  5. data/CHANGELOG.md +15 -0
  6. data/README.md +109 -38
  7. data/Rakefile +9 -3
  8. data/gemfiles/3.0.gemfile +31 -0
  9. data/lib/generators/paper_trail/install_generator.rb +7 -4
  10. data/lib/paper_trail.rb +15 -9
  11. data/lib/paper_trail/cleaner.rb +34 -0
  12. data/lib/paper_trail/frameworks/cucumber.rb +31 -0
  13. data/lib/paper_trail/frameworks/rails.rb +79 -0
  14. data/lib/paper_trail/frameworks/rspec.rb +24 -0
  15. data/lib/paper_trail/frameworks/rspec/extensions.rb +20 -0
  16. data/lib/paper_trail/frameworks/sinatra.rb +31 -0
  17. data/lib/paper_trail/has_paper_trail.rb +22 -20
  18. data/lib/paper_trail/version.rb +188 -161
  19. data/lib/paper_trail/version_number.rb +1 -1
  20. data/paper_trail.gemspec +10 -6
  21. data/spec/models/widget_spec.rb +13 -0
  22. data/spec/paper_trail_spec.rb +47 -0
  23. data/spec/spec_helper.rb +41 -0
  24. data/test/dummy/app/controllers/widgets_controller.rb +10 -2
  25. data/test/dummy/app/models/protected_widget.rb +1 -1
  26. data/test/dummy/app/models/widget.rb +6 -1
  27. data/test/dummy/app/versions/post_version.rb +1 -1
  28. data/test/dummy/config/application.rb +5 -6
  29. data/test/dummy/config/environments/development.rb +6 -4
  30. data/test/dummy/config/environments/production.rb +6 -0
  31. data/test/dummy/config/environments/test.rb +4 -4
  32. data/test/dummy/config/initializers/paper_trail.rb +4 -2
  33. data/test/functional/controller_test.rb +2 -2
  34. data/test/functional/modular_sinatra_test.rb +44 -0
  35. data/test/functional/sinatra_test.rb +45 -0
  36. data/test/functional/thread_safety_test.rb +1 -1
  37. data/test/paper_trail_test.rb +2 -2
  38. data/test/unit/cleaner_test.rb +143 -0
  39. data/test/unit/inheritance_column_test.rb +3 -3
  40. data/test/unit/model_test.rb +74 -55
  41. data/test/unit/protected_attrs_test.rb +12 -7
  42. data/test/unit/timestamp_test.rb +2 -2
  43. data/test/unit/version_test.rb +37 -20
  44. metadata +86 -26
  45. data/lib/paper_trail/controller.rb +0 -75
@@ -0,0 +1,34 @@
1
+ module PaperTrail
2
+ module Cleaner
3
+ # Destroys all but the most recent version(s) for items on a given date (or on all dates). Useful for deleting drafts.
4
+ #
5
+ # Options:
6
+ # :keeping An `integer` indicating the number of versions to be kept for each item per date.
7
+ # Defaults to `1`.
8
+ # :date Should either be a `Date` object specifying which date to destroy versions for or `:all`,
9
+ # which will specify that all dates should be cleaned. Defaults to `:all`.
10
+ # :item_id The `id` for the item to be cleaned on, or `nil`, which causes all items to be cleaned.
11
+ # Defaults to `nil`.
12
+ def clean_versions!(options = {})
13
+ options = {:keeping => 1, :date => :all}.merge(options)
14
+ gather_versions(options[:item_id], options[:date]).each do |item_id, versions|
15
+ versions.group_by { |v| v.created_at.to_date }.each do |date, versions| # now group the versions by date and iterate through those
16
+ versions.pop(options[:keeping]) # remove the number of versions we wish to keep from the collection of versions prior to destruction
17
+ versions.map(&:destroy)
18
+ end
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ # Returns a hash of versions grouped by the `item_id` attribute formatted like this: {:item_id => PaperTrail::Version}.
25
+ # If `item_id` or `date` is set, versions will be narrowed to those pointing at items with those ids that were created on specified date.
26
+ def gather_versions(item_id = nil, date = :all)
27
+ raise "`date` argument must receive a Timestamp or `:all`" unless date == :all || date.respond_to?(:to_date)
28
+ versions = item_id ? PaperTrail::Version.where(:item_id => item_id) : PaperTrail::Version
29
+ versions = versions.between(date.to_date, date.to_date + 1.day) unless date == :all
30
+ versions = PaperTrail::Version.all if versions == PaperTrail::Version # if versions has not been converted to an ActiveRecord::Relation yet, do so now
31
+ versions.group_by(&:item_id)
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,31 @@
1
+ if defined? World
2
+ # before hook for Cucumber
3
+ before do
4
+ ::PaperTrail.enabled = false
5
+ ::PaperTrail.whodunnit = nil
6
+ ::PaperTrail.controller_info = {} if defined? ::Rails
7
+ end
8
+
9
+ module PaperTrail
10
+ module Cucumber
11
+ module Extensions
12
+ # :call-seq:
13
+ # with_versioning
14
+ #
15
+ # enable versioning for specific blocks
16
+
17
+ def with_versioning
18
+ was_enabled = ::PaperTrail.enabled?
19
+ ::PaperTrail.enabled = true
20
+ begin
21
+ yield
22
+ ensure
23
+ ::PaperTrail.enabled = was_enabled
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+
30
+ World PaperTrail::Cucumber::Extensions
31
+ end
@@ -0,0 +1,79 @@
1
+ module PaperTrail
2
+ module Rails
3
+ module Controller
4
+
5
+ def self.included(base)
6
+ if defined?(ActionController) && base == ActionController::Base
7
+ base.before_filter :set_paper_trail_enabled_for_controller
8
+ base.before_filter :set_paper_trail_whodunnit, :set_paper_trail_controller_info
9
+ end
10
+ end
11
+
12
+ protected
13
+
14
+ # Returns the user who is responsible for any changes that occur.
15
+ # By default this calls `current_user` and returns the result.
16
+ #
17
+ # Override this method in your controller to call a different
18
+ # method, e.g. `current_person`, or anything you like.
19
+ def user_for_paper_trail
20
+ current_user if defined?(current_user)
21
+ end
22
+
23
+ # Returns any information about the controller or request that you
24
+ # want PaperTrail to store alongside any changes that occur. By
25
+ # default this returns an empty hash.
26
+ #
27
+ # Override this method in your controller to return a hash of any
28
+ # information you need. The hash's keys must correspond to columns
29
+ # in your `versions` table, so don't forget to add any new columns
30
+ # you need.
31
+ #
32
+ # For example:
33
+ #
34
+ # {:ip => request.remote_ip, :user_agent => request.user_agent}
35
+ #
36
+ # The columns `ip` and `user_agent` must exist in your `versions` # table.
37
+ #
38
+ # Use the `:meta` option to `PaperTrail::Model::ClassMethods.has_paper_trail`
39
+ # to store any extra model-level data you need.
40
+ def info_for_paper_trail
41
+ {}
42
+ end
43
+
44
+ # Returns `true` (default) or `false` depending on whether PaperTrail should
45
+ # be active for the current request.
46
+ #
47
+ # Override this method in your controller to specify when PaperTrail should
48
+ # be off.
49
+ def paper_trail_enabled_for_controller
50
+ true
51
+ end
52
+
53
+ private
54
+
55
+ # Tells PaperTrail whether versions should be saved in the current request.
56
+ def set_paper_trail_enabled_for_controller
57
+ ::PaperTrail.enabled_for_controller = paper_trail_enabled_for_controller
58
+ end
59
+
60
+ # Tells PaperTrail who is responsible for any changes that occur.
61
+ def set_paper_trail_whodunnit
62
+ ::PaperTrail.whodunnit = user_for_paper_trail if paper_trail_enabled_for_controller
63
+ end
64
+
65
+ # DEPRECATED: please use `set_paper_trail_whodunnit` instead.
66
+ def set_whodunnit
67
+ logger.warn '[PaperTrail]: the `set_whodunnit` controller method has been deprecated. Please rename to `set_paper_trail_whodunnit`.'
68
+ set_paper_trail_whodunnit
69
+ end
70
+
71
+ # Tells PaperTrail any information from the controller you want
72
+ # to store alongside any changes that occur.
73
+ def set_paper_trail_controller_info
74
+ ::PaperTrail.controller_info = info_for_paper_trail if paper_trail_enabled_for_controller
75
+ end
76
+
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,24 @@
1
+ if defined? RSpec
2
+ require 'rspec/core'
3
+ require 'rspec/matchers'
4
+ require File.expand_path('../rspec/extensions', __FILE__)
5
+
6
+ RSpec.configure do |config|
7
+ config.include ::PaperTrail::RSpec::Extensions
8
+
9
+ config.before(:each) do
10
+ ::PaperTrail.enabled = false
11
+ ::PaperTrail.whodunnit = nil
12
+ ::PaperTrail.controller_info = {} if defined?(::Rails) && defined?(::RSpec::Rails)
13
+ end
14
+
15
+ config.before(:each, :versioning => true) do
16
+ ::PaperTrail.enabled = true
17
+ end
18
+ end
19
+
20
+ RSpec::Matchers.define :be_versioned do
21
+ # check to see if the model has `has_paper_trail` declared on it
22
+ match { |actual| actual.kind_of?(::PaperTrail::Model::InstanceMethods) }
23
+ end
24
+ end
@@ -0,0 +1,20 @@
1
+ module PaperTrail
2
+ module RSpec
3
+ module Extensions
4
+ # :call-seq:
5
+ # with_versioning
6
+ #
7
+ # enable versioning for specific blocks
8
+
9
+ def with_versioning
10
+ was_enabled = ::PaperTrail.enabled?
11
+ ::PaperTrail.enabled = true
12
+ begin
13
+ yield
14
+ ensure
15
+ ::PaperTrail.enabled = was_enabled
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,31 @@
1
+ module Sinatra
2
+ module PaperTrail
3
+
4
+ # Register this module inside your Sinatra application to gain access to controller-level methods used by PaperTrail
5
+ def self.registered(app)
6
+ app.helpers Sinatra::PaperTrail
7
+ app.before { set_paper_trail_whodunnit }
8
+ end
9
+
10
+ protected
11
+
12
+ # Returns the user who is responsible for any changes that occur.
13
+ # By default this calls `current_user` and returns the result.
14
+ #
15
+ # Override this method in your controller to call a different
16
+ # method, e.g. `current_person`, or anything you like.
17
+ def user_for_paper_trail
18
+ current_user if defined?(current_user)
19
+ end
20
+
21
+ private
22
+
23
+ # Tells PaperTrail who is responsible for any changes that occur.
24
+ def set_paper_trail_whodunnit
25
+ ::PaperTrail.whodunnit = user_for_paper_trail if ::PaperTrail.enabled?
26
+ end
27
+
28
+ end
29
+
30
+ register Sinatra::PaperTrail if defined?(register)
31
+ end
@@ -39,7 +39,7 @@ module PaperTrail
39
39
  attr_accessor self.version_association_name
40
40
 
41
41
  class_attribute :version_class_name
42
- self.version_class_name = options[:class_name] || '::Version'
42
+ self.version_class_name = options[:class_name] || 'PaperTrail::Version'
43
43
 
44
44
  class_attribute :paper_trail_options
45
45
  self.paper_trail_options = options.dup
@@ -59,18 +59,21 @@ module PaperTrail
59
59
 
60
60
  attr_accessor :paper_trail_event
61
61
 
62
- has_many self.versions_association_name,
63
- :class_name => version_class_name,
64
- :as => :item,
65
- :order => "#{PaperTrail.timestamp_field} ASC, #{self.version_key} ASC"
66
-
67
- after_create :record_create, :if => :save_version? if !options[:on] || options[:on].include?(:create)
68
- before_update :record_update, :if => :save_version? if !options[:on] || options[:on].include?(:update)
69
- after_destroy :record_destroy, :if => :save_version? if !options[:on] || options[:on].include?(:destroy)
70
- end
62
+ if ActiveRecord::VERSION::STRING.to_f >= 4.0 # `has_many` syntax for specifying order uses a lambda in Rails 4
63
+ has_many self.versions_association_name,
64
+ lambda { order("#{PaperTrail.timestamp_field} ASC") },
65
+ :class_name => self.version_class_name, :as => :item
66
+ else
67
+ has_many self.versions_association_name,
68
+ :class_name => version_class_name,
69
+ :as => :item,
70
+ :order => "#{PaperTrail.timestamp_field} ASC"
71
+ end
71
72
 
72
- def version_key
73
- self.version_class_name.constantize.primary_key
73
+ options_on = Array(options[:on]) # so that a single symbol can be passed in without wrapping it in an `Array`
74
+ after_create :record_create, :if => :save_version? if options_on.empty? || options_on.include?(:create)
75
+ before_update :record_update, :if => :save_version? if options_on.empty? || options_on.include?(:update)
76
+ after_destroy :record_destroy, :if => :save_version? if options_on.empty? || options_on.include?(:destroy)
74
77
  end
75
78
 
76
79
  # Switches PaperTrail off for this class.
@@ -190,7 +193,7 @@ module PaperTrail
190
193
  end
191
194
 
192
195
  def record_create
193
- if switched_on?
196
+ if paper_trail_switched_on?
194
197
  data = {
195
198
  :event => paper_trail_event || 'create',
196
199
  :whodunnit => PaperTrail.whodunnit
@@ -199,13 +202,12 @@ module PaperTrail
199
202
  if changed_notably? and version_class.column_names.include?('object_changes')
200
203
  data[:object_changes] = PaperTrail.serializer.dump(changes_for_paper_trail)
201
204
  end
202
-
203
- send(self.class.versions_association_name).create merge_metadata(data)
205
+ send(self.class.versions_association_name).create! merge_metadata(data)
204
206
  end
205
207
  end
206
208
 
207
209
  def record_update
208
- if switched_on? && changed_notably?
210
+ if paper_trail_switched_on? && changed_notably?
209
211
  data = {
210
212
  :event => paper_trail_event || 'update',
211
213
  :object => object_to_string(item_before_change),
@@ -227,14 +229,14 @@ module PaperTrail
227
229
  end
228
230
 
229
231
  def record_destroy
230
- if switched_on? and not new_record?
232
+ if paper_trail_switched_on? and not new_record?
231
233
  version_class.create merge_metadata(:item_id => self.id,
232
234
  :item_type => self.class.base_class.name,
233
235
  :event => paper_trail_event || 'destroy',
234
236
  :object => object_to_string(item_before_change),
235
237
  :whodunnit => PaperTrail.whodunnit)
238
+ send(self.class.versions_association_name).send :load_target
236
239
  end
237
- send(self.class.versions_association_name).send :load_target
238
240
  end
239
241
 
240
242
  def merge_metadata(data)
@@ -266,7 +268,7 @@ module PaperTrail
266
268
  end
267
269
  previous.tap do |prev|
268
270
  prev.id = id
269
- changed_attributes.each { |attr, before| prev[attr] = before }
271
+ changed_attributes.select { |k,v| self.class.column_names.include?(k) }.each { |attr, before| prev[attr] = before }
270
272
  end
271
273
  end
272
274
 
@@ -292,7 +294,7 @@ module PaperTrail
292
294
  changed - ignore - skip
293
295
  end
294
296
 
295
- def switched_on?
297
+ def paper_trail_switched_on?
296
298
  PaperTrail.enabled? && PaperTrail.enabled_for_controller? && self.class.paper_trail_enabled_for_model
297
299
  end
298
300
 
@@ -1,198 +1,225 @@
1
- class Version < ActiveRecord::Base
2
- belongs_to :item, :polymorphic => true
3
- validates_presence_of :event
4
- attr_accessible :item_type, :item_id, :event, :whodunnit, :object, :object_changes
1
+ module PaperTrail
2
+ class Version < ActiveRecord::Base
3
+ belongs_to :item, :polymorphic => true
4
+ validates_presence_of :event
5
+ attr_accessible :item_type, :item_id, :event, :whodunnit, :object, :object_changes if PaperTrail.active_record_protected_attributes?
5
6
 
6
- after_create :enforce_version_limit!
7
+ after_create :enforce_version_limit!
7
8
 
8
- def self.with_item_keys(item_type, item_id)
9
- where :item_type => item_type, :item_id => item_id
10
- end
9
+ def self.with_item_keys(item_type, item_id)
10
+ where :item_type => item_type, :item_id => item_id
11
+ end
11
12
 
12
- def self.creates
13
- where :event => 'create'
14
- end
13
+ def self.creates
14
+ where :event => 'create'
15
+ end
15
16
 
16
- def self.updates
17
- where :event => 'update'
18
- end
17
+ def self.updates
18
+ where :event => 'update'
19
+ end
19
20
 
20
- def self.destroys
21
- where :event => 'destroy'
22
- end
21
+ def self.destroys
22
+ where :event => 'destroy'
23
+ end
23
24
 
24
- def self.not_creates
25
- where 'event <> ?', 'create'
26
- end
25
+ def self.not_creates
26
+ where 'event <> ?', 'create'
27
+ end
27
28
 
28
- scope :subsequent, lambda { |version|
29
- where("#{self.primary_key} > ?", version).order("#{self.primary_key} ASC")
30
- }
31
-
32
- scope :preceding, lambda { |version|
33
- where("#{self.primary_key} < ?", version).order("#{self.primary_key} DESC")
34
- }
35
-
36
- scope :following, lambda { |timestamp|
37
- # TODO: is this :order necessary, considering its presence on the has_many :versions association?
38
- where("#{PaperTrail.timestamp_field} > ?", timestamp).
39
- order("#{PaperTrail.timestamp_field} ASC, #{self.primary_key} ASC")
40
- }
41
-
42
- scope :between, lambda { |start_time, end_time|
43
- where("#{PaperTrail.timestamp_field} > ? AND #{PaperTrail.timestamp_field} < ?", start_time, end_time).
44
- order("#{PaperTrail.timestamp_field} ASC, #{self.primary_key} ASC")
45
- }
46
-
47
- # Restore the item from this version.
48
- #
49
- # This will automatically restore all :has_one associations as they were "at the time",
50
- # if they are also being versioned by PaperTrail. NOTE: this isn't always guaranteed
51
- # to work so you can either change the lookback period (from the default 3 seconds) or
52
- # opt out.
53
- #
54
- # Options:
55
- # +:has_one+ set to `false` to opt out of has_one reification.
56
- # set to a float to change the lookback time (check whether your db supports
57
- # sub-second datetimes if you want them).
58
- def reify(options = {})
59
- without_identity_map do
60
- options[:has_one] = 3 if options[:has_one] == true
61
- options.reverse_merge! :has_one => false
62
-
63
- unless object.nil?
64
- attrs = PaperTrail.serializer.load object
65
-
66
- # Normally a polymorphic belongs_to relationship allows us
67
- # to get the object we belong to by calling, in this case,
68
- # +item+. However this returns nil if +item+ has been
69
- # destroyed, and we need to be able to retrieve destroyed
70
- # objects.
71
- #
72
- # In this situation we constantize the +item_type+ to get hold of
73
- # the class...except when the stored object's attributes
74
- # include a +type+ key. If this is the case, the object
75
- # we belong to is using single table inheritance and the
76
- # +item_type+ will be the base class, not the actual subclass.
77
- # If +type+ is present but empty, the class is the base class.
78
-
79
- if item
80
- model = item
81
- # Look for attributes that exist in the model and not in this version. These attributes should be set to nil.
82
- (model.attribute_names - attrs.keys).each { |k| attrs[k] = nil }
83
- else
84
- inheritance_column_name = item_type.constantize.inheritance_column
85
- class_name = attrs[inheritance_column_name].blank? ? item_type : attrs[inheritance_column_name]
86
- klass = class_name.constantize
87
- model = klass.new
88
- end
29
+ scope :subsequent, lambda { |version|
30
+ where("#{self.primary_key} > ?", version).order("#{self.primary_key} ASC")
31
+ }
32
+
33
+ scope :preceding, lambda { |version|
34
+ where("#{self.primary_key} < ?", version).order("#{self.primary_key} DESC")
35
+ }
36
+
37
+ scope :following, lambda { |timestamp|
38
+ # TODO: is this :order necessary, considering its presence on the has_many :versions association?
39
+ where("#{PaperTrail.timestamp_field} > ?", timestamp).
40
+ order("#{PaperTrail.timestamp_field} ASC, #{self.primary_key} ASC")
41
+ }
42
+
43
+ scope :between, lambda { |start_time, end_time|
44
+ where("#{PaperTrail.timestamp_field} > ? AND #{PaperTrail.timestamp_field} < ?", start_time, end_time).
45
+ order("#{PaperTrail.timestamp_field} ASC, #{self.primary_key} ASC")
46
+ }
47
+
48
+ # Restore the item from this version.
49
+ #
50
+ # This will automatically restore all :has_one associations as they were "at the time",
51
+ # if they are also being versioned by PaperTrail. NOTE: this isn't always guaranteed
52
+ # to work so you can either change the lookback period (from the default 3 seconds) or
53
+ # opt out.
54
+ #
55
+ # Options:
56
+ # +:has_one+ set to `false` to opt out of has_one reification.
57
+ # set to a float to change the lookback time (check whether your db supports
58
+ # sub-second datetimes if you want them).
59
+ def reify(options = {})
60
+ without_identity_map do
61
+ options[:has_one] = 3 if options[:has_one] == true
62
+ options.reverse_merge! :has_one => false
63
+
64
+ unless object.nil?
65
+ attrs = PaperTrail.serializer.load object
66
+
67
+ # Normally a polymorphic belongs_to relationship allows us
68
+ # to get the object we belong to by calling, in this case,
69
+ # +item+. However this returns nil if +item+ has been
70
+ # destroyed, and we need to be able to retrieve destroyed
71
+ # objects.
72
+ #
73
+ # In this situation we constantize the +item_type+ to get hold of
74
+ # the class...except when the stored object's attributes
75
+ # include a +type+ key. If this is the case, the object
76
+ # we belong to is using single table inheritance and the
77
+ # +item_type+ will be the base class, not the actual subclass.
78
+ # If +type+ is present but empty, the class is the base class.
79
+
80
+ if item
81
+ model = item
82
+ # Look for attributes that exist in the model and not in this version. These attributes should be set to nil.
83
+ (model.attribute_names - attrs.keys).each { |k| attrs[k] = nil }
84
+ else
85
+ inheritance_column_name = item_type.constantize.inheritance_column
86
+ class_name = attrs[inheritance_column_name].blank? ? item_type : attrs[inheritance_column_name]
87
+ klass = class_name.constantize
88
+ model = klass.new
89
+ end
89
90
 
90
- model.class.unserialize_attributes_for_paper_trail attrs
91
+ model.class.unserialize_attributes_for_paper_trail attrs
91
92
 
92
- # Set all the attributes in this version on the model
93
- attrs.each do |k, v|
94
- if model.respond_to?("#{k}=")
95
- model[k.to_sym] = v
96
- else
97
- logger.warn "Attribute #{k} does not exist on #{item_type} (Version id: #{id})."
93
+ # Set all the attributes in this version on the model
94
+ attrs.each do |k, v|
95
+ if model.respond_to?("#{k}=")
96
+ model[k.to_sym] = v
97
+ else
98
+ logger.warn "Attribute #{k} does not exist on #{item_type} (Version id: #{id})."
99
+ end
98
100
  end
99
- end
100
101
 
101
- model.send "#{model.class.version_association_name}=", self
102
+ model.send "#{model.class.version_association_name}=", self
102
103
 
103
- unless options[:has_one] == false
104
- reify_has_ones model, options[:has_one]
105
- end
104
+ unless options[:has_one] == false
105
+ reify_has_ones model, options[:has_one]
106
+ end
106
107
 
107
- model
108
+ model
109
+ end
108
110
  end
109
111
  end
110
- end
111
112
 
112
- # Returns what changed in this version of the item. Cf. `ActiveModel::Dirty#changes`.
113
- # Returns nil if your `versions` table does not have an `object_changes` text column.
114
- def changeset
115
- return nil unless self.class.column_names.include? 'object_changes'
113
+ # Returns what changed in this version of the item. Cf. `ActiveModel::Dirty#changes`.
114
+ # Returns nil if your `versions` table does not have an `object_changes` text column.
115
+ def changeset
116
+ return nil unless self.class.column_names.include? 'object_changes'
116
117
 
117
- HashWithIndifferentAccess.new(PaperTrail.serializer.load(object_changes)).tap do |changes|
118
- item_type.constantize.unserialize_attribute_changes(changes)
118
+ HashWithIndifferentAccess.new(PaperTrail.serializer.load(object_changes)).tap do |changes|
119
+ item_type.constantize.unserialize_attribute_changes(changes)
120
+ end
121
+ rescue
122
+ {}
119
123
  end
120
- rescue
121
- {}
122
- end
123
124
 
124
- # Returns who put the item into the state stored in this version.
125
- def originator
126
- previous.try :whodunnit
127
- end
125
+ # Returns who put the item into the state stored in this version.
126
+ def originator
127
+ previous.try :whodunnit
128
+ end
128
129
 
129
- # Returns who changed the item from the state it had in this version.
130
- # This is an alias for `whodunnit`.
131
- def terminator
132
- whodunnit
133
- end
130
+ # Returns who changed the item from the state it had in this version.
131
+ # This is an alias for `whodunnit`.
132
+ def terminator
133
+ whodunnit
134
+ end
134
135
 
135
- def sibling_versions
136
- self.class.with_item_keys(item_type, item_id)
137
- end
136
+ def sibling_versions
137
+ self.class.with_item_keys(item_type, item_id)
138
+ end
138
139
 
139
- def next
140
- sibling_versions.subsequent(self).first
141
- end
140
+ def next
141
+ sibling_versions.subsequent(self).first
142
+ end
142
143
 
143
- def previous
144
- sibling_versions.preceding(self).first
145
- end
144
+ def previous
145
+ sibling_versions.preceding(self).first
146
+ end
146
147
 
147
- def index
148
- id_column = self.class.primary_key.to_sym
149
- sibling_versions.select(id_column).order("#{id_column} ASC").map(&id_column).index(self.send(id_column))
150
- end
148
+ def index
149
+ id_column = self.class.primary_key.to_sym
150
+ sibling_versions.select(id_column).order("#{id_column} ASC").map(&id_column).index(self.send(id_column))
151
+ end
151
152
 
152
- private
153
+ private
153
154
 
154
- # In Rails 3.1+, calling reify on a previous version confuses the
155
- # IdentityMap, if enabled. This prevents insertion into the map.
156
- def without_identity_map(&block)
157
- if defined?(ActiveRecord::IdentityMap) && ActiveRecord::IdentityMap.respond_to?(:without)
158
- ActiveRecord::IdentityMap.without(&block)
159
- else
160
- block.call
155
+ # In Rails 3.1+, calling reify on a previous version confuses the
156
+ # IdentityMap, if enabled. This prevents insertion into the map.
157
+ def without_identity_map(&block)
158
+ if defined?(ActiveRecord::IdentityMap) && ActiveRecord::IdentityMap.respond_to?(:without)
159
+ ActiveRecord::IdentityMap.without(&block)
160
+ else
161
+ block.call
162
+ end
161
163
  end
162
- end
163
164
 
164
- # Restore the `model`'s has_one associations as they were when this version was
165
- # superseded by the next (because that's what the user was looking at when they
166
- # made the change).
167
- #
168
- # The `lookback` sets how many seconds before the model's change we go.
169
- def reify_has_ones(model, lookback)
170
- model.class.reflect_on_all_associations(:has_one).each do |assoc|
171
- child = model.send assoc.name
172
- if child.respond_to? :version_at
173
- # N.B. we use version of the child as it was `lookback` seconds before the parent was updated.
174
- # Ideally we want the version of the child as it was just before the parent was updated...
175
- # but until PaperTrail knows which updates are "together" (e.g. parent and child being
176
- # updated on the same form), it's impossible to tell when the overall update started;
177
- # and therefore impossible to know when "just before" was.
178
- if (child_as_it_was = child.version_at(send(PaperTrail.timestamp_field) - lookback.seconds))
179
- child_as_it_was.attributes.each do |k,v|
180
- model.send(assoc.name).send :write_attribute, k.to_sym, v rescue nil
165
+ # Restore the `model`'s has_one associations as they were when this version was
166
+ # superseded by the next (because that's what the user was looking at when they
167
+ # made the change).
168
+ #
169
+ # The `lookback` sets how many seconds before the model's change we go.
170
+ def reify_has_ones(model, lookback)
171
+ model.class.reflect_on_all_associations(:has_one).each do |assoc|
172
+ child = model.send assoc.name
173
+ if child.respond_to? :version_at
174
+ # N.B. we use version of the child as it was `lookback` seconds before the parent was updated.
175
+ # Ideally we want the version of the child as it was just before the parent was updated...
176
+ # but until PaperTrail knows which updates are "together" (e.g. parent and child being
177
+ # updated on the same form), it's impossible to tell when the overall update started;
178
+ # and therefore impossible to know when "just before" was.
179
+ if (child_as_it_was = child.version_at(send(PaperTrail.timestamp_field) - lookback.seconds))
180
+ child_as_it_was.attributes.each do |k,v|
181
+ model.send(assoc.name).send :write_attribute, k.to_sym, v rescue nil
182
+ end
183
+ else
184
+ model.send "#{assoc.name}=", nil
181
185
  end
182
- else
183
- model.send "#{assoc.name}=", nil
184
186
  end
185
187
  end
186
188
  end
189
+
190
+ # checks to see if a value has been set for the `version_limit` config option, and if so enforces it
191
+ def enforce_version_limit!
192
+ return unless PaperTrail.config.version_limit.is_a? Numeric
193
+ previous_versions = sibling_versions.not_creates
194
+ return unless previous_versions.size > PaperTrail.config.version_limit
195
+ excess_previous_versions = previous_versions - previous_versions.last(PaperTrail.config.version_limit)
196
+ excess_previous_versions.map(&:destroy)
197
+ end
198
+
187
199
  end
200
+ end
188
201
 
189
- # checks to see if a value has been set for the `version_limit` config option, and if so enforces it
190
- def enforce_version_limit!
191
- return unless PaperTrail.config.version_limit.is_a? Numeric
192
- previous_versions = sibling_versions.not_creates
193
- return unless previous_versions.size > PaperTrail.config.version_limit
194
- excess_previous_versions = previous_versions - previous_versions.last(PaperTrail.config.version_limit)
195
- excess_previous_versions.map(&:destroy)
202
+ # Legacy support for old applications using the original non-namespaced `Version` class
203
+ class Version < PaperTrail::Version
204
+ def initialize(*args)
205
+ warn "DEPRECATED: Please use the namespaced `PaperTrail::Version` class instead. Support for the non-namespaced `Version` class will be removed in PaperTrail 3.1."
206
+ super
196
207
  end
197
208
 
209
+ class << self
210
+ def find(*args)
211
+ warn "DEPRECATED: Please use the namespaced `PaperTrail::Version` class instead. Support for the non-namespaced `Version` class will be removed in PaperTrail 3.1."
212
+ super
213
+ end
214
+
215
+ def first(*args)
216
+ warn "DEPRECATED: Please use the namespaced `PaperTrail::Version` class instead. Support for the non-namespaced `Version` class will be removed in PaperTrail 3.1."
217
+ super
218
+ end
219
+
220
+ def last(*args)
221
+ warn "DEPRECATED: Please use the namespaced `PaperTrail::Version` class instead. Support for the non-namespaced `Version` class will be removed in PaperTrail 3.1."
222
+ super
223
+ end
224
+ end
198
225
  end