paper_trail 2.7.2 → 3.0.0.beta1

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.
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