paper_trail 3.0.6 → 4.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +5 -0
- data/.rspec +1 -2
- data/.travis.yml +14 -5
- data/CHANGELOG.md +215 -8
- data/CONTRIBUTING.md +84 -0
- data/README.md +922 -502
- data/Rakefile +2 -2
- data/doc/bug_report_template.rb +65 -0
- data/gemfiles/ar3.gemfile +61 -0
- data/lib/generators/paper_trail/install_generator.rb +22 -3
- data/lib/generators/paper_trail/templates/add_object_changes_to_versions.rb +6 -1
- data/lib/generators/paper_trail/templates/add_transaction_id_column_to_versions.rb +11 -0
- data/lib/generators/paper_trail/templates/create_version_associations.rb +17 -0
- data/lib/generators/paper_trail/templates/create_versions.rb +22 -1
- data/lib/paper_trail.rb +52 -22
- data/lib/paper_trail/attributes_serialization.rb +89 -0
- data/lib/paper_trail/cleaner.rb +32 -15
- data/lib/paper_trail/config.rb +35 -2
- data/lib/paper_trail/frameworks/active_record.rb +4 -5
- data/lib/paper_trail/frameworks/active_record/models/paper_trail/version_association.rb +7 -0
- data/lib/paper_trail/frameworks/rails.rb +1 -0
- data/lib/paper_trail/frameworks/rails/controller.rb +27 -11
- data/lib/paper_trail/frameworks/rspec.rb +5 -0
- data/lib/paper_trail/frameworks/sinatra.rb +3 -1
- data/lib/paper_trail/has_paper_trail.rb +304 -148
- data/lib/paper_trail/record_history.rb +59 -0
- data/lib/paper_trail/reifier.rb +270 -0
- data/lib/paper_trail/serializers/json.rb +13 -2
- data/lib/paper_trail/serializers/yaml.rb +16 -2
- data/lib/paper_trail/version_association_concern.rb +15 -0
- data/lib/paper_trail/version_concern.rb +160 -122
- data/lib/paper_trail/version_number.rb +3 -3
- data/paper_trail.gemspec +22 -9
- data/spec/generators/install_generator_spec.rb +4 -4
- data/spec/models/animal_spec.rb +36 -0
- data/spec/models/boolit_spec.rb +48 -0
- data/spec/models/callback_modifier_spec.rb +96 -0
- data/spec/models/fluxor_spec.rb +19 -0
- data/spec/models/gadget_spec.rb +14 -12
- data/spec/models/joined_version_spec.rb +9 -9
- data/spec/models/json_version_spec.rb +103 -0
- data/spec/models/kitchen/banana_spec.rb +14 -0
- data/spec/models/not_on_update_spec.rb +19 -0
- data/spec/models/post_with_status_spec.rb +3 -3
- data/spec/models/skipper_spec.rb +46 -0
- data/spec/models/thing_spec.rb +11 -0
- data/spec/models/version_spec.rb +195 -44
- data/spec/models/widget_spec.rb +136 -76
- data/spec/modules/paper_trail_spec.rb +27 -0
- data/spec/modules/version_concern_spec.rb +8 -8
- data/spec/modules/version_number_spec.rb +16 -16
- data/spec/paper_trail/config_spec.rb +52 -0
- data/spec/paper_trail_spec.rb +17 -17
- data/spec/rails_helper.rb +34 -0
- data/spec/requests/articles_spec.rb +10 -14
- data/spec/spec_helper.rb +81 -34
- data/spec/support/alt_db_init.rb +1 -1
- data/test/dummy/app/controllers/application_controller.rb +1 -1
- data/test/dummy/app/controllers/articles_controller.rb +4 -1
- data/test/dummy/app/models/animal.rb +2 -0
- data/test/dummy/app/models/book.rb +4 -0
- data/test/dummy/app/models/boolit.rb +4 -0
- data/test/dummy/app/models/callback_modifier.rb +45 -0
- data/test/dummy/app/models/chapter.rb +9 -0
- data/test/dummy/app/models/citation.rb +5 -0
- data/test/dummy/app/models/customer.rb +4 -0
- data/test/dummy/app/models/editor.rb +4 -0
- data/test/dummy/app/models/editorship.rb +5 -0
- data/test/dummy/app/models/fruit.rb +5 -0
- data/test/dummy/app/models/kitchen/banana.rb +5 -0
- data/test/dummy/app/models/line_item.rb +4 -0
- data/test/dummy/app/models/not_on_update.rb +4 -0
- data/test/dummy/app/models/order.rb +5 -0
- data/test/dummy/app/models/paragraph.rb +5 -0
- data/test/dummy/app/models/person.rb +13 -3
- data/test/dummy/app/models/post.rb +0 -1
- data/test/dummy/app/models/quotation.rb +5 -0
- data/test/dummy/app/models/section.rb +6 -0
- data/test/dummy/app/models/skipper.rb +6 -0
- data/test/dummy/app/models/song.rb +20 -0
- data/test/dummy/app/models/thing.rb +3 -0
- data/test/dummy/app/models/whatchamajigger.rb +4 -0
- data/test/dummy/app/models/widget.rb +5 -0
- data/test/dummy/app/versions/json_version.rb +3 -0
- data/test/dummy/app/versions/kitchen/banana_version.rb +5 -0
- data/test/dummy/config/application.rb +6 -0
- data/test/dummy/config/database.postgres.yml +1 -1
- data/test/dummy/config/environments/test.rb +5 -1
- data/test/dummy/config/initializers/paper_trail.rb +6 -1
- data/test/dummy/db/migrate/20110208155312_set_up_test_tables.rb +143 -3
- data/test/dummy/db/schema.rb +169 -25
- data/test/functional/controller_test.rb +4 -2
- data/test/functional/modular_sinatra_test.rb +1 -1
- data/test/functional/sinatra_test.rb +1 -1
- data/test/paper_trail_test.rb +7 -0
- data/test/test_helper.rb +38 -2
- data/test/time_travel_helper.rb +15 -0
- data/test/unit/associations_test.rb +726 -0
- data/test/unit/inheritance_column_test.rb +6 -6
- data/test/unit/model_test.rb +109 -125
- data/test/unit/protected_attrs_test.rb +4 -3
- data/test/unit/serializer_test.rb +6 -6
- data/test/unit/serializers/json_test.rb +17 -4
- data/test/unit/serializers/yaml_test.rb +5 -1
- data/test/unit/version_test.rb +87 -69
- metadata +172 -75
- data/gemfiles/3.0.gemfile +0 -42
- data/test/dummy/public/404.html +0 -26
- data/test/dummy/public/422.html +0 -26
- data/test/dummy/public/500.html +0 -26
- data/test/dummy/public/favicon.ico +0 -0
- data/test/dummy/public/javascripts/application.js +0 -2
- data/test/dummy/public/javascripts/controls.js +0 -965
- data/test/dummy/public/javascripts/dragdrop.js +0 -974
- data/test/dummy/public/javascripts/effects.js +0 -1123
- data/test/dummy/public/javascripts/prototype.js +0 -6001
- data/test/dummy/public/javascripts/rails.js +0 -175
- data/test/dummy/public/stylesheets/.gitkeep +0 -0
data/lib/paper_trail/cleaner.rb
CHANGED
|
@@ -1,35 +1,52 @@
|
|
|
1
1
|
module PaperTrail
|
|
2
2
|
module Cleaner
|
|
3
|
-
# Destroys all but the most recent version(s) for items on a given date
|
|
3
|
+
# Destroys all but the most recent version(s) for items on a given date
|
|
4
|
+
# (or on all dates). Useful for deleting drafts.
|
|
4
5
|
#
|
|
5
6
|
# Options:
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
7
|
+
#
|
|
8
|
+
# - :keeping - An `integer` indicating the number of versions to be kept for
|
|
9
|
+
# each item per date. Defaults to `1`.
|
|
10
|
+
# - :date - Should either be a `Date` object specifying which date to
|
|
11
|
+
# destroy versions for or `:all`, which will specify that all dates
|
|
12
|
+
# should be cleaned. Defaults to `:all`.
|
|
13
|
+
# - :item_id - The `id` for the item to be cleaned on, or `nil`, which
|
|
14
|
+
# causes all items to be cleaned. Defaults to `nil`.
|
|
15
|
+
#
|
|
12
16
|
def clean_versions!(options = {})
|
|
13
17
|
options = {:keeping => 1, :date => :all}.merge(options)
|
|
14
18
|
gather_versions(options[:item_id], options[:date]).each do |item_id, versions|
|
|
15
|
-
versions
|
|
16
|
-
#
|
|
17
|
-
versions.
|
|
18
|
-
|
|
19
|
+
group_versions_by_date(versions).each do |date, _versions|
|
|
20
|
+
# Remove the number of versions we wish to keep from the collection
|
|
21
|
+
# of versions prior to destruction.
|
|
22
|
+
_versions.pop(options[:keeping])
|
|
23
|
+
_versions.map(&:destroy)
|
|
19
24
|
end
|
|
20
25
|
end
|
|
21
26
|
end
|
|
22
27
|
|
|
23
28
|
private
|
|
24
29
|
|
|
25
|
-
# Returns a hash of versions grouped by the `item_id` attribute formatted
|
|
26
|
-
# If `item_id` or `date` is
|
|
30
|
+
# Returns a hash of versions grouped by the `item_id` attribute formatted
|
|
31
|
+
# like this: {:item_id => PaperTrail::Version}. If `item_id` or `date` is
|
|
32
|
+
# set, versions will be narrowed to those pointing at items with those ids
|
|
33
|
+
# that were created on specified date.
|
|
27
34
|
def gather_versions(item_id = nil, date = :all)
|
|
28
|
-
raise "`date` argument must receive a Timestamp or `:all`" unless date == :all || date.respond_to?(:to_date)
|
|
35
|
+
raise ArgumentError.new("`date` argument must receive a Timestamp or `:all`") unless date == :all || date.respond_to?(:to_date)
|
|
29
36
|
versions = item_id ? PaperTrail::Version.where(:item_id => item_id) : PaperTrail::Version
|
|
30
37
|
versions = versions.between(date.to_date, date.to_date + 1.day) unless date == :all
|
|
31
|
-
|
|
38
|
+
|
|
39
|
+
# If `versions` has not been converted to an ActiveRecord::Relation yet,
|
|
40
|
+
# do so now.
|
|
41
|
+
versions = PaperTrail::Version.all if versions == PaperTrail::Version
|
|
32
42
|
versions.group_by(&:item_id)
|
|
33
43
|
end
|
|
44
|
+
|
|
45
|
+
# Given an array of versions, returns a hash mapping dates to arrays of
|
|
46
|
+
# versions.
|
|
47
|
+
# @api private
|
|
48
|
+
def group_versions_by_date(versions)
|
|
49
|
+
versions.group_by { |v| v.send(PaperTrail.timestamp_field).to_date }
|
|
50
|
+
end
|
|
34
51
|
end
|
|
35
52
|
end
|
data/lib/paper_trail/config.rb
CHANGED
|
@@ -1,14 +1,47 @@
|
|
|
1
1
|
require 'singleton'
|
|
2
|
+
require 'paper_trail/serializers/yaml'
|
|
2
3
|
|
|
3
4
|
module PaperTrail
|
|
4
5
|
class Config
|
|
5
6
|
include Singleton
|
|
6
|
-
attr_accessor :
|
|
7
|
+
attr_accessor :timestamp_field, :serializer, :version_limit
|
|
8
|
+
attr_writer :track_associations
|
|
7
9
|
|
|
8
10
|
def initialize
|
|
9
|
-
@enabled = true # Indicates whether PaperTrail is on or off.
|
|
10
11
|
@timestamp_field = :created_at
|
|
11
12
|
@serializer = PaperTrail::Serializers::YAML
|
|
12
13
|
end
|
|
14
|
+
|
|
15
|
+
def serialized_attributes
|
|
16
|
+
ActiveSupport::Deprecation.warn(
|
|
17
|
+
"PaperTrail.config.serialized_attributes is deprecated without " +
|
|
18
|
+
"replacement and always returns false."
|
|
19
|
+
)
|
|
20
|
+
false
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def serialized_attributes=(_)
|
|
24
|
+
ActiveSupport::Deprecation.warn(
|
|
25
|
+
"PaperTrail.config.serialized_attributes= is deprecated without " +
|
|
26
|
+
"replacement and no longer has any effect."
|
|
27
|
+
)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def track_associations
|
|
31
|
+
@track_associations.nil? ?
|
|
32
|
+
PaperTrail::VersionAssociation.table_exists? :
|
|
33
|
+
@track_associations
|
|
34
|
+
end
|
|
35
|
+
alias_method :track_associations?, :track_associations
|
|
36
|
+
|
|
37
|
+
# Indicates whether PaperTrail is on or off. Default: true.
|
|
38
|
+
def enabled
|
|
39
|
+
value = PaperTrail.paper_trail_store.fetch(:paper_trail_enabled, true)
|
|
40
|
+
value.nil? ? true : value
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def enabled= enable
|
|
44
|
+
PaperTrail.paper_trail_store[:paper_trail_enabled] = enable
|
|
45
|
+
end
|
|
13
46
|
end
|
|
14
47
|
end
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
# This file only needs to be loaded if the gem is being used outside of Rails,
|
|
2
|
-
# the model(s) will get loaded in via the `Rails::Engine
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
end
|
|
1
|
+
# This file only needs to be loaded if the gem is being used outside of Rails,
|
|
2
|
+
# since otherwise the model(s) will get loaded in via the `Rails::Engine`.
|
|
3
|
+
require "paper_trail/frameworks/active_record/models/paper_trail/version_association"
|
|
4
|
+
require "paper_trail/frameworks/active_record/models/paper_trail/version"
|
|
@@ -3,8 +3,22 @@ module PaperTrail
|
|
|
3
3
|
module Controller
|
|
4
4
|
|
|
5
5
|
def self.included(base)
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
before = [
|
|
7
|
+
:set_paper_trail_enabled_for_controller,
|
|
8
|
+
:set_paper_trail_whodunnit,
|
|
9
|
+
:set_paper_trail_controller_info
|
|
10
|
+
]
|
|
11
|
+
after = []
|
|
12
|
+
|
|
13
|
+
if base.respond_to? :before_action
|
|
14
|
+
# Rails 4+
|
|
15
|
+
before.map {|sym| base.before_action sym }
|
|
16
|
+
after.map {|sym| base.after_action sym }
|
|
17
|
+
else
|
|
18
|
+
# Rails 3.
|
|
19
|
+
before.map {|sym| base.before_filter sym }
|
|
20
|
+
after.map {|sym| base.after_filter sym }
|
|
21
|
+
end
|
|
8
22
|
end
|
|
9
23
|
|
|
10
24
|
protected
|
|
@@ -36,24 +50,26 @@ module PaperTrail
|
|
|
36
50
|
#
|
|
37
51
|
# The columns `ip` and `user_agent` must exist in your `versions` # table.
|
|
38
52
|
#
|
|
39
|
-
# Use the `:meta` option to
|
|
40
|
-
# to store any extra
|
|
53
|
+
# Use the `:meta` option to
|
|
54
|
+
# `PaperTrail::Model::ClassMethods.has_paper_trail` to store any extra
|
|
55
|
+
# model-level data you need.
|
|
41
56
|
def info_for_paper_trail
|
|
42
57
|
{}
|
|
43
58
|
end
|
|
44
59
|
|
|
45
|
-
# Returns `true` (default) or `false` depending on whether PaperTrail
|
|
46
|
-
# be active for the current request.
|
|
60
|
+
# Returns `true` (default) or `false` depending on whether PaperTrail
|
|
61
|
+
# should be active for the current request.
|
|
47
62
|
#
|
|
48
|
-
# Override this method in your controller to specify when PaperTrail
|
|
49
|
-
# be off.
|
|
63
|
+
# Override this method in your controller to specify when PaperTrail
|
|
64
|
+
# should be off.
|
|
50
65
|
def paper_trail_enabled_for_controller
|
|
51
66
|
::PaperTrail.enabled?
|
|
52
67
|
end
|
|
53
68
|
|
|
54
69
|
private
|
|
55
70
|
|
|
56
|
-
# Tells PaperTrail whether versions should be saved in the current
|
|
71
|
+
# Tells PaperTrail whether versions should be saved in the current
|
|
72
|
+
# request.
|
|
57
73
|
def set_paper_trail_enabled_for_controller
|
|
58
74
|
::PaperTrail.enabled_for_controller = paper_trail_enabled_for_controller
|
|
59
75
|
end
|
|
@@ -63,8 +79,8 @@ module PaperTrail
|
|
|
63
79
|
::PaperTrail.whodunnit = user_for_paper_trail if ::PaperTrail.enabled_for_controller?
|
|
64
80
|
end
|
|
65
81
|
|
|
66
|
-
# Tells PaperTrail any information from the controller you want
|
|
67
|
-
#
|
|
82
|
+
# Tells PaperTrail any information from the controller you want to store
|
|
83
|
+
# alongside any changes that occur.
|
|
68
84
|
def set_paper_trail_controller_info
|
|
69
85
|
::PaperTrail.controller_info = info_for_paper_trail if ::PaperTrail.enabled_for_controller?
|
|
70
86
|
end
|
|
@@ -22,3 +22,8 @@ RSpec::Matchers.define :be_versioned do
|
|
|
22
22
|
# check to see if the model has `has_paper_trail` declared on it
|
|
23
23
|
match { |actual| actual.kind_of?(::PaperTrail::Model::InstanceMethods) }
|
|
24
24
|
end
|
|
25
|
+
|
|
26
|
+
RSpec::Matchers.define :have_a_version_with do |attributes|
|
|
27
|
+
# check if the model has a version with the specified attributes
|
|
28
|
+
match { |actual| actual.versions.where_object(attributes).any? }
|
|
29
|
+
end
|
|
@@ -3,8 +3,10 @@ require 'active_support/core_ext/object' # provides the `try` method
|
|
|
3
3
|
module PaperTrail
|
|
4
4
|
module Sinatra
|
|
5
5
|
|
|
6
|
-
# Register this module inside your Sinatra application to gain access to
|
|
6
|
+
# Register this module inside your Sinatra application to gain access to
|
|
7
|
+
# controller-level methods used by PaperTrail.
|
|
7
8
|
def self.registered(app)
|
|
9
|
+
app.use RequestStore::Middleware
|
|
8
10
|
app.helpers self
|
|
9
11
|
app.before { set_paper_trail_whodunnit }
|
|
10
12
|
end
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
require 'active_support/core_ext/object' # provides the `try` method
|
|
2
|
+
require 'paper_trail/attributes_serialization'
|
|
3
|
+
|
|
1
4
|
module PaperTrail
|
|
2
5
|
module Model
|
|
3
6
|
|
|
@@ -6,34 +9,60 @@ module PaperTrail
|
|
|
6
9
|
end
|
|
7
10
|
|
|
8
11
|
module ClassMethods
|
|
9
|
-
# Declare this in your model to track every create, update, and destroy.
|
|
10
|
-
# the model is available in the `versions` association.
|
|
12
|
+
# Declare this in your model to track every create, update, and destroy.
|
|
13
|
+
# Each version of the model is available in the `versions` association.
|
|
11
14
|
#
|
|
12
15
|
# Options:
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
# :
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
# :
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
#
|
|
27
|
-
#
|
|
28
|
-
#
|
|
29
|
-
#
|
|
30
|
-
# :
|
|
31
|
-
#
|
|
32
|
-
#
|
|
16
|
+
#
|
|
17
|
+
# - :on - The events to track (optional; defaults to all of them). Set
|
|
18
|
+
# to an array of `:create`, `:update`, `:destroy` as desired.
|
|
19
|
+
# - :class_name - The name of a custom Version class. This class should
|
|
20
|
+
# inherit from `PaperTrail::Version`.
|
|
21
|
+
# - :ignore - An array of attributes for which a new `Version` will not be
|
|
22
|
+
# created if only they change. It can also aceept a Hash as an
|
|
23
|
+
# argument where the key is the attribute to ignore (a `String` or
|
|
24
|
+
# `Symbol`), which will only be ignored if the value is a `Proc` which
|
|
25
|
+
# returns truthily.
|
|
26
|
+
# - :if, :unless - Procs that allow to specify conditions when to save
|
|
27
|
+
# versions for an object.
|
|
28
|
+
# - :only - Inverse of `ignore`. A new `Version` will be created only
|
|
29
|
+
# for these attributes if supplied it can also aceept a Hash as an
|
|
30
|
+
# argument where the key is the attribute to track (a `String` or
|
|
31
|
+
# `Symbol`), which will only be counted if the value is a `Proc` which
|
|
32
|
+
# returns truthily.
|
|
33
|
+
# - :skip - Fields to ignore completely. As with `ignore`, updates to
|
|
34
|
+
# these fields will not create a new `Version`. In addition, these
|
|
35
|
+
# fields will not be included in the serialized versions of the object
|
|
36
|
+
# whenever a new `Version` is created.
|
|
37
|
+
# - :meta - A hash of extra data to store. You must add a column to the
|
|
38
|
+
# `versions` table for each key. Values are objects or procs (which
|
|
39
|
+
# are called with `self`, i.e. the model with the paper trail). See
|
|
40
|
+
# `PaperTrail::Controller.info_for_paper_trail` for how to store data
|
|
41
|
+
# from the controller.
|
|
42
|
+
# - :versions - The name to use for the versions association. Default
|
|
43
|
+
# is `:versions`.
|
|
44
|
+
# - :version - The name to use for the method which returns the version
|
|
45
|
+
# the instance was reified from. Default is `:version`.
|
|
46
|
+
# - :save_changes - Whether or not to save changes to the object_changes
|
|
47
|
+
# column if it exists. Default is true
|
|
48
|
+
#
|
|
33
49
|
def has_paper_trail(options = {})
|
|
50
|
+
options[:on] ||= [:create, :update, :destroy]
|
|
51
|
+
|
|
52
|
+
# Wrap the :on option in an array if necessary. This allows a single
|
|
53
|
+
# symbol to be passed in.
|
|
54
|
+
options[:on] = Array(options[:on])
|
|
55
|
+
|
|
56
|
+
setup_model_for_paper_trail(options)
|
|
57
|
+
|
|
58
|
+
setup_callbacks_from_options options[:on]
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def setup_model_for_paper_trail(options = {})
|
|
34
62
|
# Lazily include the instance methods so we don't clutter up
|
|
35
63
|
# any more ActiveRecord models than we have to.
|
|
36
64
|
send :include, InstanceMethods
|
|
65
|
+
send :extend, AttributesSerialization
|
|
37
66
|
|
|
38
67
|
class_attribute :version_association_name
|
|
39
68
|
self.version_association_name = options[:version] || :version
|
|
@@ -45,6 +74,7 @@ module PaperTrail
|
|
|
45
74
|
self.version_class_name = options[:class_name] || 'PaperTrail::Version'
|
|
46
75
|
|
|
47
76
|
class_attribute :paper_trail_options
|
|
77
|
+
|
|
48
78
|
self.paper_trail_options = options.dup
|
|
49
79
|
|
|
50
80
|
[:ignore, :skip, :only].each do |k|
|
|
@@ -53,13 +83,15 @@ module PaperTrail
|
|
|
53
83
|
end
|
|
54
84
|
|
|
55
85
|
paper_trail_options[:meta] ||= {}
|
|
86
|
+
paper_trail_options[:save_changes] = true if paper_trail_options[:save_changes].nil?
|
|
56
87
|
|
|
57
88
|
class_attribute :versions_association_name
|
|
58
89
|
self.versions_association_name = options[:versions] || :versions
|
|
59
90
|
|
|
60
91
|
attr_accessor :paper_trail_event
|
|
61
92
|
|
|
62
|
-
|
|
93
|
+
# `has_many` syntax for specifying order uses a lambda in Rails 4
|
|
94
|
+
if ::ActiveRecord::VERSION::MAJOR >= 4
|
|
63
95
|
has_many self.versions_association_name,
|
|
64
96
|
lambda { order(model.timestamp_sort_order) },
|
|
65
97
|
:class_name => self.version_class_name, :as => :item
|
|
@@ -70,96 +102,79 @@ module PaperTrail
|
|
|
70
102
|
:order => self.paper_trail_version_class.timestamp_sort_order
|
|
71
103
|
end
|
|
72
104
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
before_update :record_update, :if => :save_version?
|
|
78
|
-
after_update :clear_version_instance!
|
|
79
|
-
end
|
|
80
|
-
after_destroy :record_destroy, :if => :save_version? if options_on.empty? || options_on.include?(:destroy)
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
# Switches PaperTrail off for this class.
|
|
84
|
-
def paper_trail_off!
|
|
85
|
-
PaperTrail.enabled_for_model(self, false)
|
|
105
|
+
# Reset the transaction id when the transaction is closed.
|
|
106
|
+
after_commit :reset_transaction_id
|
|
107
|
+
after_rollback :reset_transaction_id
|
|
108
|
+
after_rollback :clear_rolled_back_versions
|
|
86
109
|
end
|
|
87
110
|
|
|
88
|
-
def
|
|
89
|
-
|
|
90
|
-
|
|
111
|
+
def setup_callbacks_from_options(options_on = [])
|
|
112
|
+
options_on.each do |option|
|
|
113
|
+
send "paper_trail_on_#{option}"
|
|
114
|
+
end
|
|
91
115
|
end
|
|
92
116
|
|
|
93
|
-
#
|
|
94
|
-
def
|
|
95
|
-
|
|
96
|
-
|
|
117
|
+
# Record version before or after "destroy" event
|
|
118
|
+
def paper_trail_on_destroy(recording_order = 'after')
|
|
119
|
+
unless %w[after before].include?(recording_order.to_s)
|
|
120
|
+
fail ArgumentError, 'recording order can only be "after" or "before"'
|
|
121
|
+
end
|
|
97
122
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
123
|
+
if recording_order.to_s == 'after' and
|
|
124
|
+
Gem::Version.new(ActiveRecord::VERSION::STRING).release >= Gem::Version.new("5")
|
|
125
|
+
if ::ActiveRecord::Base.belongs_to_required_by_default
|
|
126
|
+
::ActiveSupport::Deprecation.warn(
|
|
127
|
+
"paper_trail_on_destroy(:after) is incompatible with ActiveRecord " +
|
|
128
|
+
"belongs_to_required_by_default and has no effect. Please use :before " +
|
|
129
|
+
"or disable belongs_to_required_by_default."
|
|
130
|
+
)
|
|
131
|
+
end
|
|
132
|
+
end
|
|
102
133
|
|
|
103
|
-
|
|
104
|
-
PaperTrail.enabled_for_model?(self)
|
|
105
|
-
end
|
|
134
|
+
send "#{recording_order}_destroy", :record_destroy, :if => :save_version?
|
|
106
135
|
|
|
107
|
-
|
|
108
|
-
|
|
136
|
+
return if paper_trail_options[:on].include?(:destroy)
|
|
137
|
+
paper_trail_options[:on] << :destroy
|
|
109
138
|
end
|
|
110
139
|
|
|
111
|
-
#
|
|
112
|
-
def
|
|
113
|
-
|
|
114
|
-
|
|
140
|
+
# Record version after "update" event
|
|
141
|
+
def paper_trail_on_update
|
|
142
|
+
before_save :reset_timestamp_attrs_for_update_if_needed!,
|
|
143
|
+
:on => :update
|
|
144
|
+
after_update :record_update,
|
|
145
|
+
:if => :save_version?
|
|
146
|
+
after_update :clear_version_instance!
|
|
115
147
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
coder = PaperTrail::Serializers::YAML unless coder.respond_to?(:dump) # Fall back to YAML if `coder` has no `dump` method
|
|
119
|
-
attributes[key] = coder.dump(attributes[key])
|
|
120
|
-
end
|
|
121
|
-
end
|
|
148
|
+
return if paper_trail_options[:on].include?(:update)
|
|
149
|
+
paper_trail_options[:on] << :update
|
|
122
150
|
end
|
|
123
151
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
152
|
+
# Record version after "create" event
|
|
153
|
+
def paper_trail_on_create
|
|
154
|
+
after_create :record_create,
|
|
155
|
+
:if => :save_version?
|
|
127
156
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
coder = PaperTrail::Serializers::YAML unless coder.respond_to?(:dump)
|
|
131
|
-
attributes[key] = coder.load(attributes[key])
|
|
132
|
-
end
|
|
133
|
-
end
|
|
157
|
+
return if paper_trail_options[:on].include?(:create)
|
|
158
|
+
paper_trail_options[:on] << :create
|
|
134
159
|
end
|
|
135
160
|
|
|
136
|
-
#
|
|
137
|
-
def
|
|
138
|
-
|
|
139
|
-
|
|
161
|
+
# Switches PaperTrail off for this class.
|
|
162
|
+
def paper_trail_off!
|
|
163
|
+
PaperTrail.enabled_for_model(self, false)
|
|
164
|
+
end
|
|
140
165
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
old_value, new_value = changes[key]
|
|
145
|
-
changes[key] = [coder.dump(old_value),
|
|
146
|
-
coder.dump(new_value)]
|
|
147
|
-
end
|
|
148
|
-
end
|
|
166
|
+
# Switches PaperTrail on for this class.
|
|
167
|
+
def paper_trail_on!
|
|
168
|
+
PaperTrail.enabled_for_model(self, true)
|
|
149
169
|
end
|
|
150
170
|
|
|
151
|
-
def
|
|
152
|
-
|
|
153
|
-
|
|
171
|
+
def paper_trail_enabled_for_model?
|
|
172
|
+
return false unless self.include?(PaperTrail::Model::InstanceMethods)
|
|
173
|
+
PaperTrail.enabled_for_model?(self)
|
|
174
|
+
end
|
|
154
175
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
coder = PaperTrail::Serializers::YAML unless coder.respond_to?(:dump)
|
|
158
|
-
old_value, new_value = changes[key]
|
|
159
|
-
changes[key] = [coder.load(old_value),
|
|
160
|
-
coder.load(new_value)]
|
|
161
|
-
end
|
|
162
|
-
end
|
|
176
|
+
def paper_trail_version_class
|
|
177
|
+
@paper_trail_version_class ||= version_class_name.constantize
|
|
163
178
|
end
|
|
164
179
|
end
|
|
165
180
|
|
|
@@ -173,10 +188,21 @@ module PaperTrail
|
|
|
173
188
|
end
|
|
174
189
|
|
|
175
190
|
# Returns who put the object into its current state.
|
|
176
|
-
def
|
|
191
|
+
def paper_trail_originator
|
|
177
192
|
(source_version || send(self.class.versions_association_name).last).try(:whodunnit)
|
|
178
193
|
end
|
|
179
194
|
|
|
195
|
+
def originator
|
|
196
|
+
::ActiveSupport::Deprecation.warn "Use paper_trail_originator instead of originator."
|
|
197
|
+
self.paper_trail_originator
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# Invoked after rollbacks to ensure versions records are not created
|
|
201
|
+
# for changes that never actually took place
|
|
202
|
+
def clear_rolled_back_versions
|
|
203
|
+
send(self.class.versions_association_name).reload
|
|
204
|
+
end
|
|
205
|
+
|
|
180
206
|
# Returns the object (not a Version) as it was at the given timestamp.
|
|
181
207
|
def version_at(timestamp, reify_options={})
|
|
182
208
|
# Because a version stores how its object looked *before* the change,
|
|
@@ -221,7 +247,19 @@ module PaperTrail
|
|
|
221
247
|
self.class.paper_trail_on! if paper_trail_was_enabled
|
|
222
248
|
end
|
|
223
249
|
|
|
224
|
-
#
|
|
250
|
+
# Utility method for reifying. Anything executed inside the block will
|
|
251
|
+
# appear like a new record.
|
|
252
|
+
def appear_as_new_record
|
|
253
|
+
instance_eval {
|
|
254
|
+
alias :old_new_record? :new_record?
|
|
255
|
+
alias :new_record? :present?
|
|
256
|
+
}
|
|
257
|
+
yield
|
|
258
|
+
instance_eval { alias :new_record? :old_new_record? }
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
# Temporarily overwrites the value of whodunnit and then executes the
|
|
262
|
+
# provided block.
|
|
225
263
|
def whodunnit(value)
|
|
226
264
|
raise ArgumentError, 'expected to receive a block' unless block_given?
|
|
227
265
|
current_whodunnit = PaperTrail.whodunnit
|
|
@@ -231,11 +269,14 @@ module PaperTrail
|
|
|
231
269
|
PaperTrail.whodunnit = current_whodunnit
|
|
232
270
|
end
|
|
233
271
|
|
|
234
|
-
#
|
|
272
|
+
# Mimics the `touch` method from `ActiveRecord::Persistence`, but also
|
|
273
|
+
# creates a version. A version is created regardless of options such as
|
|
274
|
+
# `:on`, `:if`, or `:unless`.
|
|
235
275
|
#
|
|
236
|
-
# TODO:
|
|
237
|
-
#
|
|
238
|
-
#
|
|
276
|
+
# TODO: look into leveraging the `after_touch` callback from
|
|
277
|
+
# `ActiveRecord` to allow the regular `touch` method to generate a version
|
|
278
|
+
# as normal. May make sense to switch the `record_update` method to
|
|
279
|
+
# leverage an `after_update` callback anyways (likely for v4.0.0)
|
|
239
280
|
def touch_with_version(name = nil)
|
|
240
281
|
raise ActiveRecordError, "can not touch on a new record object" unless persisted?
|
|
241
282
|
|
|
@@ -244,11 +285,20 @@ module PaperTrail
|
|
|
244
285
|
current_time = current_time_from_proper_timezone
|
|
245
286
|
|
|
246
287
|
attributes.each { |column| write_attribute(column, current_time) }
|
|
247
|
-
|
|
288
|
+
|
|
289
|
+
record_update(true) unless will_record_after_update?
|
|
290
|
+
save!(:validate => false)
|
|
248
291
|
end
|
|
249
292
|
|
|
250
293
|
private
|
|
251
294
|
|
|
295
|
+
# Returns true if `save` will cause `record_update`
|
|
296
|
+
# to be called via the `after_update` callback.
|
|
297
|
+
def will_record_after_update?
|
|
298
|
+
on = paper_trail_options[:on]
|
|
299
|
+
on.nil? || on.include?(:update)
|
|
300
|
+
end
|
|
301
|
+
|
|
252
302
|
def source_version
|
|
253
303
|
send self.class.version_association_name
|
|
254
304
|
end
|
|
@@ -259,63 +309,162 @@ module PaperTrail
|
|
|
259
309
|
:event => paper_trail_event || 'create',
|
|
260
310
|
:whodunnit => PaperTrail.whodunnit
|
|
261
311
|
}
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
312
|
+
if respond_to?(:updated_at)
|
|
313
|
+
data[PaperTrail.timestamp_field] = updated_at
|
|
314
|
+
end
|
|
315
|
+
if pt_record_object_changes? && changed_notably?
|
|
316
|
+
data[:object_changes] = pt_recordable_object_changes
|
|
317
|
+
end
|
|
318
|
+
if self.class.paper_trail_version_class.column_names.include?('transaction_id')
|
|
319
|
+
data[:transaction_id] = PaperTrail.transaction_id
|
|
266
320
|
end
|
|
267
|
-
send(self.class.versions_association_name).create! merge_metadata(data)
|
|
321
|
+
version = send(self.class.versions_association_name).create! merge_metadata(data)
|
|
322
|
+
set_transaction_id(version)
|
|
323
|
+
save_associations(version)
|
|
268
324
|
end
|
|
269
325
|
end
|
|
270
326
|
|
|
271
|
-
def record_update
|
|
272
|
-
if paper_trail_switched_on? && changed_notably?
|
|
273
|
-
object_attrs = object_attrs_for_paper_trail(item_before_change)
|
|
327
|
+
def record_update(force = nil)
|
|
328
|
+
if paper_trail_switched_on? && (force || changed_notably?)
|
|
274
329
|
data = {
|
|
275
330
|
:event => paper_trail_event || 'update',
|
|
276
|
-
:object =>
|
|
331
|
+
:object => pt_recordable_object,
|
|
277
332
|
:whodunnit => PaperTrail.whodunnit
|
|
278
333
|
}
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
334
|
+
if respond_to?(:updated_at)
|
|
335
|
+
data[PaperTrail.timestamp_field] = updated_at
|
|
336
|
+
end
|
|
337
|
+
if pt_record_object_changes?
|
|
338
|
+
data[:object_changes] = pt_recordable_object_changes
|
|
339
|
+
end
|
|
340
|
+
if self.class.paper_trail_version_class.column_names.include?('transaction_id')
|
|
341
|
+
data[:transaction_id] = PaperTrail.transaction_id
|
|
283
342
|
end
|
|
284
|
-
send(self.class.versions_association_name).
|
|
343
|
+
version = send(self.class.versions_association_name).create merge_metadata(data)
|
|
344
|
+
set_transaction_id(version)
|
|
345
|
+
save_associations(version)
|
|
346
|
+
end
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
# Returns a boolean indicating whether to store serialized version diffs
|
|
350
|
+
# in the `object_changes` column of the version record.
|
|
351
|
+
# @api private
|
|
352
|
+
def pt_record_object_changes?
|
|
353
|
+
paper_trail_options[:save_changes] &&
|
|
354
|
+
self.class.paper_trail_version_class.column_names.include?('object_changes')
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
# Returns an object which can be assigned to the `object` attribute of a
|
|
358
|
+
# nascent version record. If the `object` column is a postgres `json`
|
|
359
|
+
# column, then a hash can be used in the assignment, otherwise the column
|
|
360
|
+
# is a `text` column, and we must perform the serialization here, using
|
|
361
|
+
# `PaperTrail.serializer`.
|
|
362
|
+
# @api private
|
|
363
|
+
def pt_recordable_object
|
|
364
|
+
object_attrs = object_attrs_for_paper_trail(attributes_before_change)
|
|
365
|
+
if self.class.paper_trail_version_class.object_col_is_json?
|
|
366
|
+
object_attrs
|
|
367
|
+
else
|
|
368
|
+
PaperTrail.serializer.dump(object_attrs)
|
|
369
|
+
end
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
# Returns an object which can be assigned to the `object_changes`
|
|
373
|
+
# attribute of a nascent version record. If the `object_changes` column is
|
|
374
|
+
# a postgres `json` column, then a hash can be used in the assignment,
|
|
375
|
+
# otherwise the column is a `text` column, and we must perform the
|
|
376
|
+
# serialization here, using `PaperTrail.serializer`.
|
|
377
|
+
# @api private
|
|
378
|
+
def pt_recordable_object_changes
|
|
379
|
+
if self.class.paper_trail_version_class.object_changes_col_is_json?
|
|
380
|
+
changes_for_paper_trail
|
|
381
|
+
else
|
|
382
|
+
PaperTrail.serializer.dump(changes_for_paper_trail)
|
|
285
383
|
end
|
|
286
384
|
end
|
|
287
385
|
|
|
288
386
|
def changes_for_paper_trail
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
387
|
+
_changes = changes.delete_if { |k,v| !notably_changed.include?(k) }
|
|
388
|
+
self.class.serialize_attribute_changes_for_paper_trail!(_changes)
|
|
389
|
+
_changes.to_hash
|
|
292
390
|
end
|
|
293
391
|
|
|
294
|
-
# Invoked via`after_update` callback for when a previous version is
|
|
392
|
+
# Invoked via`after_update` callback for when a previous version is
|
|
393
|
+
# reified and then saved.
|
|
295
394
|
def clear_version_instance!
|
|
296
395
|
send("#{self.class.version_association_name}=", nil)
|
|
297
396
|
end
|
|
298
397
|
|
|
398
|
+
# Invoked via callback when a user attempts to persist a reified
|
|
399
|
+
# `Version`.
|
|
299
400
|
def reset_timestamp_attrs_for_update_if_needed!
|
|
300
|
-
return if self.live?
|
|
301
|
-
timestamp_attributes_for_update_in_model.each
|
|
401
|
+
return if self.live?
|
|
402
|
+
timestamp_attributes_for_update_in_model.each do |column|
|
|
403
|
+
# ActiveRecord 4.2 deprecated `reset_column!` in favor of
|
|
404
|
+
# `restore_column!`.
|
|
405
|
+
if respond_to?("restore_#{column}!")
|
|
406
|
+
send("restore_#{column}!")
|
|
407
|
+
else
|
|
408
|
+
send("reset_#{column}!")
|
|
409
|
+
end
|
|
410
|
+
end
|
|
302
411
|
end
|
|
303
412
|
|
|
304
413
|
def record_destroy
|
|
305
414
|
if paper_trail_switched_on? and not new_record?
|
|
306
|
-
object_attrs = object_attrs_for_paper_trail(item_before_change)
|
|
307
415
|
data = {
|
|
308
416
|
:item_id => self.id,
|
|
309
417
|
:item_type => self.class.base_class.name,
|
|
310
418
|
:event => paper_trail_event || 'destroy',
|
|
311
|
-
:object =>
|
|
419
|
+
:object => pt_recordable_object,
|
|
312
420
|
:whodunnit => PaperTrail.whodunnit
|
|
313
421
|
}
|
|
314
|
-
|
|
422
|
+
if self.class.paper_trail_version_class.column_names.include?('transaction_id')
|
|
423
|
+
data[:transaction_id] = PaperTrail.transaction_id
|
|
424
|
+
end
|
|
425
|
+
version = self.class.paper_trail_version_class.create(merge_metadata(data))
|
|
426
|
+
send("#{self.class.version_association_name}=", version)
|
|
315
427
|
send(self.class.versions_association_name).send :load_target
|
|
428
|
+
set_transaction_id(version)
|
|
429
|
+
save_associations(version)
|
|
316
430
|
end
|
|
317
431
|
end
|
|
318
432
|
|
|
433
|
+
# Saves associations if the join table for `VersionAssociation` exists.
|
|
434
|
+
def save_associations(version)
|
|
435
|
+
return unless PaperTrail.config.track_associations?
|
|
436
|
+
self.class.reflect_on_all_associations(:belongs_to).each do |assoc|
|
|
437
|
+
assoc_version_args = {
|
|
438
|
+
:version_id => version.id,
|
|
439
|
+
:foreign_key_name => assoc.foreign_key
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
if assoc.options[:polymorphic]
|
|
443
|
+
associated_record = send(assoc.name) if send(assoc.foreign_type)
|
|
444
|
+
if associated_record && associated_record.class.paper_trail_enabled_for_model?
|
|
445
|
+
assoc_version_args.merge!(:foreign_key_id => associated_record.id)
|
|
446
|
+
end
|
|
447
|
+
elsif assoc.klass.paper_trail_enabled_for_model?
|
|
448
|
+
assoc_version_args.merge!(:foreign_key_id => send(assoc.foreign_key))
|
|
449
|
+
end
|
|
450
|
+
|
|
451
|
+
PaperTrail::VersionAssociation.create(assoc_version_args) if assoc_version_args.has_key?(:foreign_key_id)
|
|
452
|
+
end
|
|
453
|
+
end
|
|
454
|
+
|
|
455
|
+
def set_transaction_id(version)
|
|
456
|
+
return unless self.class.paper_trail_version_class.column_names.include?('transaction_id')
|
|
457
|
+
if PaperTrail.transaction? && PaperTrail.transaction_id.nil?
|
|
458
|
+
PaperTrail.transaction_id = version.id
|
|
459
|
+
version.transaction_id = version.id
|
|
460
|
+
version.save
|
|
461
|
+
end
|
|
462
|
+
end
|
|
463
|
+
|
|
464
|
+
def reset_transaction_id
|
|
465
|
+
PaperTrail.transaction_id = nil
|
|
466
|
+
end
|
|
467
|
+
|
|
319
468
|
def merge_metadata(data)
|
|
320
469
|
# First we merge the model-level metadata in `meta`.
|
|
321
470
|
paper_trail_options[:meta].each do |k,v|
|
|
@@ -323,8 +472,9 @@ module PaperTrail
|
|
|
323
472
|
if v.respond_to?(:call)
|
|
324
473
|
v.call(self)
|
|
325
474
|
elsif v.is_a?(Symbol) && respond_to?(v)
|
|
326
|
-
#
|
|
327
|
-
|
|
475
|
+
# If it is an attribute that is changing in an existing object,
|
|
476
|
+
# be sure to grab the current version.
|
|
477
|
+
if has_attribute?(v) && send("#{v}_changed?".to_sym) && data[:event] != 'create'
|
|
328
478
|
send("#{v}_was".to_sym)
|
|
329
479
|
else
|
|
330
480
|
send(v)
|
|
@@ -333,19 +483,14 @@ module PaperTrail
|
|
|
333
483
|
v
|
|
334
484
|
end
|
|
335
485
|
end
|
|
486
|
+
|
|
336
487
|
# Second we merge any extra data from the controller (if available).
|
|
337
488
|
data.merge(PaperTrail.controller_info || {})
|
|
338
489
|
end
|
|
339
490
|
|
|
340
|
-
def
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
all_timestamp_attributes.each do |column|
|
|
344
|
-
previous[column] = send(column) if self.class.column_names.include?(column.to_s) and not send(column).nil?
|
|
345
|
-
end
|
|
346
|
-
enums = previous.respond_to?(:defined_enums) ? previous.defined_enums : {}
|
|
347
|
-
previous.tap do |prev|
|
|
348
|
-
prev.id = id # `dup` clears the `id` so we add that back
|
|
491
|
+
def attributes_before_change
|
|
492
|
+
attributes.tap do |prev|
|
|
493
|
+
enums = self.respond_to?(:defined_enums) ? self.defined_enums : {}
|
|
349
494
|
changed_attributes.select { |k,v| self.class.column_names.include?(k) }.each do |attr, before|
|
|
350
495
|
before = enums[attr][before] if enums[attr]
|
|
351
496
|
prev[attr] = before
|
|
@@ -353,28 +498,38 @@ module PaperTrail
|
|
|
353
498
|
end
|
|
354
499
|
end
|
|
355
500
|
|
|
356
|
-
#
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
501
|
+
# Returns hash of attributes (with appropriate attributes serialized),
|
|
502
|
+
# ommitting attributes to be skipped.
|
|
503
|
+
def object_attrs_for_paper_trail(attributes_hash)
|
|
504
|
+
attrs = attributes_hash.except(*self.paper_trail_options[:skip])
|
|
505
|
+
self.class.serialize_attributes_for_paper_trail!(attrs)
|
|
506
|
+
attrs
|
|
361
507
|
end
|
|
362
508
|
|
|
363
|
-
#
|
|
364
|
-
#
|
|
365
|
-
#
|
|
366
|
-
# this by checking to ensure attributes other than those ignored and update timestamps are really being modified.
|
|
509
|
+
# Determines whether it is appropriate to generate a new version
|
|
510
|
+
# instance. A timestamp-only update (e.g. only `updated_at` changed) is
|
|
511
|
+
# considered notable unless an ignored attribute was also changed.
|
|
367
512
|
def changed_notably?
|
|
368
|
-
if
|
|
369
|
-
|
|
513
|
+
if ignored_attr_has_changed?
|
|
514
|
+
timestamps = timestamp_attributes_for_update_in_model.map(&:to_s)
|
|
515
|
+
(notably_changed - timestamps).any?
|
|
370
516
|
else
|
|
371
517
|
notably_changed.any?
|
|
372
518
|
end
|
|
373
519
|
end
|
|
374
520
|
|
|
521
|
+
# An attributed is "ignored" if it is listed in the `:ignore` option
|
|
522
|
+
# and/or the `:skip` option. Returns true if an ignored attribute has
|
|
523
|
+
# changed.
|
|
524
|
+
def ignored_attr_has_changed?
|
|
525
|
+
ignored = self.paper_trail_options[:ignore] + self.paper_trail_options[:skip]
|
|
526
|
+
ignored.any? && (changed & ignored).any?
|
|
527
|
+
end
|
|
528
|
+
|
|
375
529
|
def notably_changed
|
|
376
530
|
only = self.paper_trail_options[:only].dup
|
|
377
|
-
#
|
|
531
|
+
# Remove Hash arguments and then evaluate whether the attributes (the
|
|
532
|
+
# keys of the hash) should also get pushed into the collection.
|
|
378
533
|
only.delete_if do |obj|
|
|
379
534
|
obj.is_a?(Hash) && obj.each { |attr, condition| only << attr if condition.respond_to?(:call) && condition.call(self) }
|
|
380
535
|
end
|
|
@@ -383,7 +538,8 @@ module PaperTrail
|
|
|
383
538
|
|
|
384
539
|
def changed_and_not_ignored
|
|
385
540
|
ignore = self.paper_trail_options[:ignore].dup
|
|
386
|
-
|
|
541
|
+
# Remove Hash arguments and then evaluate whether the attributes (the
|
|
542
|
+
# keys of the hash) should also get pushed into the collection.
|
|
387
543
|
ignore.delete_if do |obj|
|
|
388
544
|
obj.is_a?(Hash) && obj.each { |attr, condition| ignore << attr if condition.respond_to?(:call) && condition.call(self) }
|
|
389
545
|
end
|