paper_trail 4.0.2 → 4.1.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/.travis.yml +2 -0
- data/CHANGELOG.md +27 -0
- data/CONTRIBUTING.md +78 -5
- data/README.md +328 -268
- data/doc/bug_report_template.rb +65 -0
- data/gemfiles/3.0.gemfile +7 -4
- data/lib/generators/paper_trail/templates/add_object_changes_to_versions.rb +1 -1
- data/lib/generators/paper_trail/templates/create_versions.rb +14 -0
- data/lib/paper_trail.rb +11 -9
- data/lib/paper_trail/attributes_serialization.rb +89 -0
- data/lib/paper_trail/cleaner.rb +8 -1
- data/lib/paper_trail/config.rb +15 -18
- data/lib/paper_trail/frameworks/rails/controller.rb +16 -2
- data/lib/paper_trail/has_paper_trail.rb +102 -99
- data/lib/paper_trail/record_history.rb +59 -0
- data/lib/paper_trail/reifier.rb +270 -0
- data/lib/paper_trail/version_association_concern.rb +3 -1
- data/lib/paper_trail/version_concern.rb +60 -226
- data/lib/paper_trail/version_number.rb +2 -2
- data/paper_trail.gemspec +7 -10
- data/spec/models/animal_spec.rb +17 -0
- data/spec/models/callback_modifier_spec.rb +96 -0
- data/spec/models/json_version_spec.rb +20 -17
- data/spec/paper_trail/config_spec.rb +52 -0
- data/spec/spec_helper.rb +6 -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/paragraph.rb +5 -0
- data/test/dummy/app/models/quotation.rb +5 -0
- data/test/dummy/app/models/section.rb +6 -0
- data/test/dummy/config/database.postgres.yml +1 -1
- data/test/dummy/config/initializers/paper_trail.rb +3 -1
- data/test/dummy/db/migrate/20110208155312_set_up_test_tables.rb +33 -0
- data/test/dummy/db/schema.rb +27 -0
- data/test/test_helper.rb +36 -0
- data/test/unit/associations_test.rb +726 -0
- data/test/unit/inheritance_column_test.rb +6 -6
- data/test/unit/model_test.rb +62 -594
- data/test/unit/protected_attrs_test.rb +3 -2
- data/test/unit/version_test.rb +87 -69
- metadata +38 -2
@@ -0,0 +1,65 @@
|
|
1
|
+
# Use this template to report PaperTrail bugs.
|
2
|
+
# It is based on the ActiveRecord template.
|
3
|
+
# https://github.com/rails/rails/blob/master/guides/bug_report_templates/active_record_gem.rb
|
4
|
+
begin
|
5
|
+
require 'bundler/inline'
|
6
|
+
rescue LoadError => e
|
7
|
+
$stderr.puts 'Bundler version 1.10 or later is required. Please update your Bundler'
|
8
|
+
raise e
|
9
|
+
end
|
10
|
+
|
11
|
+
gemfile(true) do
|
12
|
+
ruby '2.2.3'
|
13
|
+
source 'https://rubygems.org'
|
14
|
+
gem 'activerecord', '4.2.0'
|
15
|
+
gem 'minitest', '5.8.3'
|
16
|
+
gem 'paper_trail', '4.0.0', require: false
|
17
|
+
gem 'sqlite3'
|
18
|
+
end
|
19
|
+
|
20
|
+
require 'active_record'
|
21
|
+
require 'minitest/autorun'
|
22
|
+
require 'logger'
|
23
|
+
ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:')
|
24
|
+
ActiveRecord::Base.logger = Logger.new(STDOUT)
|
25
|
+
require 'paper_trail'
|
26
|
+
|
27
|
+
ActiveRecord::Schema.define do
|
28
|
+
create_table :users, force: true do |t|
|
29
|
+
t.text :first_name, null: false
|
30
|
+
t.timestamps null: false
|
31
|
+
end
|
32
|
+
create_table :versions do |t|
|
33
|
+
t.string :item_type, null: false
|
34
|
+
t.integer :item_id, null: false
|
35
|
+
t.string :event, null: false
|
36
|
+
t.string :whodunnit
|
37
|
+
t.text :object, limit: 1_073_741_823
|
38
|
+
t.text :object_changes, limit: 1_073_741_823
|
39
|
+
t.integer :transaction_id
|
40
|
+
t.datetime :created_at
|
41
|
+
end
|
42
|
+
add_index :versions, [:item_type, :item_id]
|
43
|
+
add_index :versions, [:transaction_id]
|
44
|
+
|
45
|
+
create_table :version_associations do |t|
|
46
|
+
t.integer :version_id
|
47
|
+
t.string :foreign_key_name, null: false
|
48
|
+
t.integer :foreign_key_id
|
49
|
+
end
|
50
|
+
add_index :version_associations, [:version_id]
|
51
|
+
add_index :version_associations, [:foreign_key_name, :foreign_key_id],
|
52
|
+
name: 'index_version_associations_on_foreign_key'
|
53
|
+
end
|
54
|
+
|
55
|
+
class User < ActiveRecord::Base
|
56
|
+
has_paper_trail
|
57
|
+
end
|
58
|
+
|
59
|
+
class BugTest < ActiveSupport::TestCase
|
60
|
+
def test_1
|
61
|
+
assert_difference(-> { PaperTrail::Version.count }, +1) {
|
62
|
+
User.create(first_name: "Jane")
|
63
|
+
}
|
64
|
+
end
|
65
|
+
end
|
data/gemfiles/3.0.gemfile
CHANGED
@@ -4,6 +4,12 @@ gem 'activerecord', '~> 3.0'
|
|
4
4
|
gem 'i18n', '~> 0.6.11'
|
5
5
|
gem 'request_store', '~> 1.1.0'
|
6
6
|
|
7
|
+
# actionpack 3 depends on rack-cache ~> 1.2, but bundler seems to ignore that.
|
8
|
+
# Also rack-cache 1.3 dropped support for ruby 1.8.7. The simplest thing for now
|
9
|
+
# was to specify rack-cache ~> 1.2 here, though it should be unnecessary given
|
10
|
+
# the actionpack 3 dependency.
|
11
|
+
gem 'rack-cache', '~> 1.2.0'
|
12
|
+
|
7
13
|
group :development, :test do
|
8
14
|
gem 'rake', '~> 10.1.1'
|
9
15
|
gem 'shoulda', '~> 3.5'
|
@@ -23,13 +29,10 @@ group :development, :test do
|
|
23
29
|
# To do proper transactional testing with ActiveSupport::TestCase on MySQL
|
24
30
|
gem 'database_cleaner', '~> 1.2.0'
|
25
31
|
|
32
|
+
# Allow time travel in testing. timecop is only supported after 1.9.2 but does a better cleanup at 'return'
|
26
33
|
if RUBY_VERSION < "1.9.2"
|
27
34
|
gem 'delorean'
|
28
|
-
|
29
|
-
# rack-cache 1.3 drops ruby 1.8.7 support
|
30
|
-
gem 'rack-cache', '1.2'
|
31
35
|
else
|
32
|
-
# timecop is only supported after 1.9.2 but does a better cleanup at 'return'
|
33
36
|
gem 'timecop'
|
34
37
|
end
|
35
38
|
|
@@ -13,6 +13,20 @@ class CreateVersions < ActiveRecord::Migration
|
|
13
13
|
t.string :event, :null => false
|
14
14
|
t.string :whodunnit
|
15
15
|
t.text :object, :limit => TEXT_BYTES
|
16
|
+
|
17
|
+
# Known issue in MySQL: fractional second precision
|
18
|
+
# -------------------------------------------------
|
19
|
+
#
|
20
|
+
# MySQL timestamp columns do not support fractional seconds unless
|
21
|
+
# defined with "fractional seconds precision". MySQL users should manually
|
22
|
+
# add fractional seconds precision to this migration, specifically, to
|
23
|
+
# the `created_at` column.
|
24
|
+
# (https://dev.mysql.com/doc/refman/5.6/en/fractional-seconds.html)
|
25
|
+
#
|
26
|
+
# MySQL users should also upgrade to rails 4.2, which is the first
|
27
|
+
# version of ActiveRecord with support for fractional seconds in MySQL.
|
28
|
+
# (https://github.com/rails/rails/pull/14359)
|
29
|
+
#
|
16
30
|
t.datetime :created_at
|
17
31
|
end
|
18
32
|
add_index :versions, [:item_type, :item_id]
|
data/lib/paper_trail.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'request_store'
|
2
2
|
|
3
|
-
# Require
|
3
|
+
# Require files in lib/paper_trail, but not its subdirectories.
|
4
4
|
Dir[File.join(File.dirname(__FILE__), 'paper_trail', '*.rb')].each do |file|
|
5
5
|
require File.join('paper_trail', File.basename(file, '.rb'))
|
6
6
|
end
|
@@ -24,11 +24,12 @@ module PaperTrail
|
|
24
24
|
!!PaperTrail.config.enabled
|
25
25
|
end
|
26
26
|
|
27
|
-
# ActiveRecord 5 drops support for serialized attributes; for previous
|
28
|
-
# versions of ActiveRecord it is supported, we have a config option
|
29
|
-
# to enable it within PaperTrail.
|
30
27
|
def self.serialized_attributes?
|
31
|
-
|
28
|
+
ActiveSupport::Deprecation.warn(
|
29
|
+
"PaperTrail.serialized_attributes? is deprecated without replacement " +
|
30
|
+
"and always returns false."
|
31
|
+
)
|
32
|
+
false
|
32
33
|
end
|
33
34
|
|
34
35
|
# Sets whether PaperTrail is enabled or disabled for the current request.
|
@@ -101,7 +102,8 @@ module PaperTrail
|
|
101
102
|
end
|
102
103
|
|
103
104
|
def self.active_record_protected_attributes?
|
104
|
-
@active_record_protected_attributes ||= ::ActiveRecord::VERSION::MAJOR < 4 ||
|
105
|
+
@active_record_protected_attributes ||= ::ActiveRecord::VERSION::MAJOR < 4 ||
|
106
|
+
!!defined?(ProtectedAttributes)
|
105
107
|
end
|
106
108
|
|
107
109
|
def self.transaction?
|
@@ -126,9 +128,9 @@ module PaperTrail
|
|
126
128
|
|
127
129
|
# Returns PaperTrail's configuration object.
|
128
130
|
def self.config
|
129
|
-
|
130
|
-
yield
|
131
|
-
|
131
|
+
@config ||= PaperTrail::Config.instance
|
132
|
+
yield @config if block_given?
|
133
|
+
@config
|
132
134
|
end
|
133
135
|
|
134
136
|
class << self
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module PaperTrail
|
2
|
+
module AttributesSerialization
|
3
|
+
class NoOpAttribute
|
4
|
+
def type_cast_for_database(value)
|
5
|
+
value
|
6
|
+
end
|
7
|
+
|
8
|
+
def type_cast_from_database(data)
|
9
|
+
data
|
10
|
+
end
|
11
|
+
end
|
12
|
+
NO_OP_ATTRIBUTE = NoOpAttribute.new
|
13
|
+
|
14
|
+
class SerializedAttribute
|
15
|
+
def initialize(coder)
|
16
|
+
@coder = coder.respond_to?(:dump) ? coder : PaperTrail.serializer
|
17
|
+
end
|
18
|
+
|
19
|
+
def type_cast_for_database(value)
|
20
|
+
@coder.dump(value)
|
21
|
+
end
|
22
|
+
|
23
|
+
def type_cast_from_database(data)
|
24
|
+
@coder.load(data)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
SERIALIZE, DESERIALIZE =
|
29
|
+
if ::ActiveRecord::VERSION::MAJOR >= 5
|
30
|
+
[:serialize, :deserialize]
|
31
|
+
else
|
32
|
+
[:type_cast_for_database, :type_cast_from_database]
|
33
|
+
end
|
34
|
+
|
35
|
+
if ::ActiveRecord::VERSION::STRING < '4.2'
|
36
|
+
# Backport Rails 4.2 and later's `type_for_attribute` to build
|
37
|
+
# on a common interface.
|
38
|
+
def type_for_attribute(attr_name)
|
39
|
+
serialized_attribute_types[attr_name.to_s] || NO_OP_ATTRIBUTE
|
40
|
+
end
|
41
|
+
|
42
|
+
def serialized_attribute_types
|
43
|
+
@attribute_types ||= Hash[serialized_attributes.map do |attr_name, coder|
|
44
|
+
[attr_name, SerializedAttribute.new(coder)]
|
45
|
+
end]
|
46
|
+
end
|
47
|
+
private :serialized_attribute_types
|
48
|
+
end
|
49
|
+
|
50
|
+
# Used for `Version#object` attribute.
|
51
|
+
def serialize_attributes_for_paper_trail!(attributes)
|
52
|
+
alter_attributes_for_paper_trail!(SERIALIZE, attributes)
|
53
|
+
end
|
54
|
+
|
55
|
+
def unserialize_attributes_for_paper_trail!(attributes)
|
56
|
+
alter_attributes_for_paper_trail!(DESERIALIZE, attributes)
|
57
|
+
end
|
58
|
+
|
59
|
+
def alter_attributes_for_paper_trail!(serializer, attributes)
|
60
|
+
# Don't serialize before values before inserting into columns of type
|
61
|
+
# `JSON` on `PostgreSQL` databases.
|
62
|
+
return attributes if paper_trail_version_class.object_col_is_json?
|
63
|
+
|
64
|
+
attributes.each do |key, value|
|
65
|
+
attributes[key] = type_for_attribute(key).send(serializer, value)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Used for Version#object_changes attribute.
|
70
|
+
def serialize_attribute_changes_for_paper_trail!(changes)
|
71
|
+
alter_attribute_changes_for_paper_trail!(SERIALIZE, changes)
|
72
|
+
end
|
73
|
+
|
74
|
+
def unserialize_attribute_changes_for_paper_trail!(changes)
|
75
|
+
alter_attribute_changes_for_paper_trail!(DESERIALIZE, changes)
|
76
|
+
end
|
77
|
+
|
78
|
+
def alter_attribute_changes_for_paper_trail!(serializer, changes)
|
79
|
+
# Don't serialize before values before inserting into columns of type
|
80
|
+
# `JSON` on `PostgreSQL` databases.
|
81
|
+
return changes if paper_trail_version_class.object_changes_col_is_json?
|
82
|
+
|
83
|
+
changes.clone.each do |key, change|
|
84
|
+
type = type_for_attribute(key)
|
85
|
+
changes[key] = Array(change).map { |value| type.send(serializer, value) }
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
data/lib/paper_trail/cleaner.rb
CHANGED
@@ -16,7 +16,7 @@ module PaperTrail
|
|
16
16
|
def clean_versions!(options = {})
|
17
17
|
options = {:keeping => 1, :date => :all}.merge(options)
|
18
18
|
gather_versions(options[:item_id], options[:date]).each do |item_id, versions|
|
19
|
-
versions
|
19
|
+
group_versions_by_date(versions).each do |date, _versions|
|
20
20
|
# Remove the number of versions we wish to keep from the collection
|
21
21
|
# of versions prior to destruction.
|
22
22
|
_versions.pop(options[:keeping])
|
@@ -41,5 +41,12 @@ module PaperTrail
|
|
41
41
|
versions = PaperTrail::Version.all if versions == PaperTrail::Version
|
42
42
|
versions.group_by(&:item_id)
|
43
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
|
44
51
|
end
|
45
52
|
end
|
data/lib/paper_trail/config.rb
CHANGED
@@ -5,30 +5,26 @@ module PaperTrail
|
|
5
5
|
class Config
|
6
6
|
include Singleton
|
7
7
|
attr_accessor :timestamp_field, :serializer, :version_limit
|
8
|
-
attr_reader :serialized_attributes
|
9
8
|
attr_writer :track_associations
|
10
9
|
|
11
10
|
def initialize
|
12
11
|
@timestamp_field = :created_at
|
13
12
|
@serializer = PaperTrail::Serializers::YAML
|
13
|
+
end
|
14
14
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
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
21
|
end
|
22
22
|
|
23
|
-
def serialized_attributes=(
|
24
|
-
|
25
|
-
|
26
|
-
"
|
27
|
-
|
28
|
-
"nothing with this version, and is always turned off"
|
29
|
-
)
|
30
|
-
end
|
31
|
-
@serialized_attributes = value
|
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
|
+
)
|
32
28
|
end
|
33
29
|
|
34
30
|
def track_associations
|
@@ -38,9 +34,10 @@ module PaperTrail
|
|
38
34
|
end
|
39
35
|
alias_method :track_associations?, :track_associations
|
40
36
|
|
41
|
-
# Indicates whether PaperTrail is on or off.
|
37
|
+
# Indicates whether PaperTrail is on or off. Default: true.
|
42
38
|
def enabled
|
43
|
-
|
39
|
+
value = PaperTrail.paper_trail_store.fetch(:paper_trail_enabled, true)
|
40
|
+
value.nil? ? true : value
|
44
41
|
end
|
45
42
|
|
46
43
|
def enabled= enable
|
@@ -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
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'active_support/core_ext/object' # provides the `try` method
|
2
|
+
require 'paper_trail/attributes_serialization'
|
2
3
|
|
3
4
|
module PaperTrail
|
4
5
|
module Model
|
@@ -46,9 +47,22 @@ module PaperTrail
|
|
46
47
|
# column if it exists. Default is true
|
47
48
|
#
|
48
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 = {})
|
49
62
|
# Lazily include the instance methods so we don't clutter up
|
50
63
|
# any more ActiveRecord models than we have to.
|
51
64
|
send :include, InstanceMethods
|
65
|
+
send :extend, AttributesSerialization
|
52
66
|
|
53
67
|
class_attribute :version_association_name
|
54
68
|
self.version_association_name = options[:version] || :version
|
@@ -60,6 +74,7 @@ module PaperTrail
|
|
60
74
|
self.version_class_name = options[:class_name] || 'PaperTrail::Version'
|
61
75
|
|
62
76
|
class_attribute :paper_trail_options
|
77
|
+
|
63
78
|
self.paper_trail_options = options.dup
|
64
79
|
|
65
80
|
[:ignore, :skip, :only].each do |k|
|
@@ -87,26 +102,53 @@ module PaperTrail
|
|
87
102
|
:order => self.paper_trail_version_class.timestamp_sort_order
|
88
103
|
end
|
89
104
|
|
90
|
-
options[:on] ||= [:create, :update, :destroy]
|
91
|
-
|
92
|
-
# Wrap the :on option in an array if necessary. This allows a single
|
93
|
-
# symbol to be passed in.
|
94
|
-
options_on = Array(options[:on])
|
95
|
-
|
96
|
-
after_create :record_create, :if => :save_version? if options_on.include?(:create)
|
97
|
-
if options_on.include?(:update)
|
98
|
-
before_save :reset_timestamp_attrs_for_update_if_needed!, :on => :update
|
99
|
-
after_update :record_update, :if => :save_version?
|
100
|
-
after_update :clear_version_instance!
|
101
|
-
end
|
102
|
-
after_destroy :record_destroy, :if => :save_version? if options_on.include?(:destroy)
|
103
|
-
|
104
105
|
# Reset the transaction id when the transaction is closed.
|
105
106
|
after_commit :reset_transaction_id
|
106
107
|
after_rollback :reset_transaction_id
|
107
108
|
after_rollback :clear_rolled_back_versions
|
108
109
|
end
|
109
110
|
|
111
|
+
def setup_callbacks_from_options(options_on = [])
|
112
|
+
options_on.each do |option|
|
113
|
+
send "paper_trail_on_#{option}"
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
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
|
122
|
+
|
123
|
+
send "#{recording_order}_destroy",
|
124
|
+
:record_destroy,
|
125
|
+
:if => :save_version?
|
126
|
+
|
127
|
+
return if paper_trail_options[:on].include?(:destroy)
|
128
|
+
paper_trail_options[:on] << :destroy
|
129
|
+
end
|
130
|
+
|
131
|
+
# Record version after "update" event
|
132
|
+
def paper_trail_on_update
|
133
|
+
before_save :reset_timestamp_attrs_for_update_if_needed!,
|
134
|
+
:on => :update
|
135
|
+
after_update :record_update,
|
136
|
+
:if => :save_version?
|
137
|
+
after_update :clear_version_instance!
|
138
|
+
|
139
|
+
return if paper_trail_options[:on].include?(:update)
|
140
|
+
paper_trail_options[:on] << :update
|
141
|
+
end
|
142
|
+
|
143
|
+
# Record version after "create" event
|
144
|
+
def paper_trail_on_create
|
145
|
+
after_create :record_create,
|
146
|
+
:if => :save_version?
|
147
|
+
|
148
|
+
return if paper_trail_options[:on].include?(:create)
|
149
|
+
paper_trail_options[:on] << :create
|
150
|
+
end
|
151
|
+
|
110
152
|
# Switches PaperTrail off for this class.
|
111
153
|
def paper_trail_off!
|
112
154
|
PaperTrail.enabled_for_model(self, false)
|
@@ -125,74 +167,6 @@ module PaperTrail
|
|
125
167
|
def paper_trail_version_class
|
126
168
|
@paper_trail_version_class ||= version_class_name.constantize
|
127
169
|
end
|
128
|
-
|
129
|
-
# Used for `Version#object` attribute.
|
130
|
-
def serialize_attributes_for_paper_trail!(attributes)
|
131
|
-
# Don't serialize before values before inserting into columns of type
|
132
|
-
# `JSON` on `PostgreSQL` databases.
|
133
|
-
return attributes if self.paper_trail_version_class.object_col_is_json?
|
134
|
-
|
135
|
-
serialized_attributes.each do |key, coder|
|
136
|
-
if attributes.key?(key)
|
137
|
-
# Fall back to current serializer if `coder` has no `dump` method.
|
138
|
-
coder = PaperTrail.serializer unless coder.respond_to?(:dump)
|
139
|
-
attributes[key] = coder.dump(attributes[key])
|
140
|
-
end
|
141
|
-
end
|
142
|
-
end
|
143
|
-
|
144
|
-
# TODO: There is a lot of duplication between this and
|
145
|
-
# `serialize_attributes_for_paper_trail!`.
|
146
|
-
def unserialize_attributes_for_paper_trail!(attributes)
|
147
|
-
# Don't serialize before values before inserting into columns of type
|
148
|
-
# `JSON` on `PostgreSQL` databases.
|
149
|
-
return attributes if self.paper_trail_version_class.object_col_is_json?
|
150
|
-
|
151
|
-
serialized_attributes.each do |key, coder|
|
152
|
-
if attributes.key?(key)
|
153
|
-
# Fall back to current serializer if `coder` has no `dump` method.
|
154
|
-
# TODO: Shouldn't this be `:load`?
|
155
|
-
coder = PaperTrail.serializer unless coder.respond_to?(:dump)
|
156
|
-
attributes[key] = coder.load(attributes[key])
|
157
|
-
end
|
158
|
-
end
|
159
|
-
end
|
160
|
-
|
161
|
-
# Used for Version#object_changes attribute.
|
162
|
-
def serialize_attribute_changes_for_paper_trail!(changes)
|
163
|
-
# Don't serialize before values before inserting into columns of type `JSON`
|
164
|
-
# on `PostgreSQL` databases.
|
165
|
-
return changes if self.paper_trail_version_class.object_changes_col_is_json?
|
166
|
-
|
167
|
-
serialized_attributes.each do |key, coder|
|
168
|
-
if changes.key?(key)
|
169
|
-
# Fall back to current serializer if `coder` has no `dump` method.
|
170
|
-
coder = PaperTrail.serializer unless coder.respond_to?(:dump)
|
171
|
-
old_value, new_value = changes[key]
|
172
|
-
changes[key] = [coder.dump(old_value),
|
173
|
-
coder.dump(new_value)]
|
174
|
-
end
|
175
|
-
end
|
176
|
-
end
|
177
|
-
|
178
|
-
# TODO: There is a lot of duplication between this and
|
179
|
-
# `serialize_attribute_changes_for_paper_trail!`.
|
180
|
-
def unserialize_attribute_changes_for_paper_trail!(changes)
|
181
|
-
# Don't serialize before values before inserting into columns of type
|
182
|
-
# `JSON` on `PostgreSQL` databases.
|
183
|
-
return changes if self.paper_trail_version_class.object_changes_col_is_json?
|
184
|
-
|
185
|
-
serialized_attributes.each do |key, coder|
|
186
|
-
if changes.key?(key)
|
187
|
-
# Fall back to current serializer if `coder` has no `dump` method.
|
188
|
-
# TODO: Shouldn't this be `:load`?
|
189
|
-
coder = PaperTrail.serializer unless coder.respond_to?(:dump)
|
190
|
-
old_value, new_value = changes[key]
|
191
|
-
changes[key] = [coder.load(old_value),
|
192
|
-
coder.load(new_value)]
|
193
|
-
end
|
194
|
-
end
|
195
|
-
end
|
196
170
|
end
|
197
171
|
|
198
172
|
# Wrap the following methods in a module so we can include them only in the
|
@@ -291,7 +265,7 @@ module PaperTrail
|
|
291
265
|
# `:on`, `:if`, or `:unless`.
|
292
266
|
#
|
293
267
|
# TODO: look into leveraging the `after_touch` callback from
|
294
|
-
# `ActiveRecord` to allow the regular `touch` method
|
268
|
+
# `ActiveRecord` to allow the regular `touch` method to generate a version
|
295
269
|
# as normal. May make sense to switch the `record_update` method to
|
296
270
|
# leverage an `after_update` callback anyways (likely for v4.0.0)
|
297
271
|
def touch_with_version(name = nil)
|
@@ -329,9 +303,8 @@ module PaperTrail
|
|
329
303
|
if respond_to?(:updated_at)
|
330
304
|
data[PaperTrail.timestamp_field] = updated_at
|
331
305
|
end
|
332
|
-
if
|
333
|
-
data[:object_changes] =
|
334
|
-
PaperTrail.serializer.dump(changes_for_paper_trail)
|
306
|
+
if pt_record_object_changes? && changed_notably?
|
307
|
+
data[:object_changes] = pt_recordable_object_changes
|
335
308
|
end
|
336
309
|
if self.class.paper_trail_version_class.column_names.include?('transaction_id')
|
337
310
|
data[:transaction_id] = PaperTrail.transaction_id
|
@@ -344,18 +317,16 @@ module PaperTrail
|
|
344
317
|
|
345
318
|
def record_update(force = nil)
|
346
319
|
if paper_trail_switched_on? && (force || changed_notably?)
|
347
|
-
object_attrs = object_attrs_for_paper_trail(attributes_before_change)
|
348
320
|
data = {
|
349
321
|
:event => paper_trail_event || 'update',
|
350
|
-
:object =>
|
322
|
+
:object => pt_recordable_object,
|
351
323
|
:whodunnit => PaperTrail.whodunnit
|
352
324
|
}
|
353
325
|
if respond_to?(:updated_at)
|
354
326
|
data[PaperTrail.timestamp_field] = updated_at
|
355
327
|
end
|
356
|
-
if
|
357
|
-
data[:object_changes] =
|
358
|
-
PaperTrail.serializer.dump(changes_for_paper_trail)
|
328
|
+
if pt_record_object_changes?
|
329
|
+
data[:object_changes] = pt_recordable_object_changes
|
359
330
|
end
|
360
331
|
if self.class.paper_trail_version_class.column_names.include?('transaction_id')
|
361
332
|
data[:transaction_id] = PaperTrail.transaction_id
|
@@ -366,11 +337,46 @@ module PaperTrail
|
|
366
337
|
end
|
367
338
|
end
|
368
339
|
|
340
|
+
# Returns a boolean indicating whether to store serialized version diffs
|
341
|
+
# in the `object_changes` column of the version record.
|
342
|
+
# @api private
|
343
|
+
def pt_record_object_changes?
|
344
|
+
paper_trail_options[:save_changes] &&
|
345
|
+
self.class.paper_trail_version_class.column_names.include?('object_changes')
|
346
|
+
end
|
347
|
+
|
348
|
+
# Returns an object which can be assigned to the `object` attribute of a
|
349
|
+
# nascent version record. If the `object` column is a postgres `json`
|
350
|
+
# column, then a hash can be used in the assignment, otherwise the column
|
351
|
+
# is a `text` column, and we must perform the serialization here, using
|
352
|
+
# `PaperTrail.serializer`.
|
353
|
+
# @api private
|
354
|
+
def pt_recordable_object
|
355
|
+
object_attrs = object_attrs_for_paper_trail(attributes_before_change)
|
356
|
+
if self.class.paper_trail_version_class.object_col_is_json?
|
357
|
+
object_attrs
|
358
|
+
else
|
359
|
+
PaperTrail.serializer.dump(object_attrs)
|
360
|
+
end
|
361
|
+
end
|
362
|
+
|
363
|
+
# Returns an object which can be assigned to the `object_changes`
|
364
|
+
# attribute of a nascent version record. If the `object_changes` column is
|
365
|
+
# a postgres `json` column, then a hash can be used in the assignment,
|
366
|
+
# otherwise the column is a `text` column, and we must perform the
|
367
|
+
# serialization here, using `PaperTrail.serializer`.
|
368
|
+
# @api private
|
369
|
+
def pt_recordable_object_changes
|
370
|
+
if self.class.paper_trail_version_class.object_changes_col_is_json?
|
371
|
+
changes_for_paper_trail
|
372
|
+
else
|
373
|
+
PaperTrail.serializer.dump(changes_for_paper_trail)
|
374
|
+
end
|
375
|
+
end
|
376
|
+
|
369
377
|
def changes_for_paper_trail
|
370
378
|
_changes = changes.delete_if { |k,v| !notably_changed.include?(k) }
|
371
|
-
|
372
|
-
self.class.serialize_attribute_changes_for_paper_trail!(_changes)
|
373
|
-
end
|
379
|
+
self.class.serialize_attribute_changes_for_paper_trail!(_changes)
|
374
380
|
_changes.to_hash
|
375
381
|
end
|
376
382
|
|
@@ -397,12 +403,11 @@ module PaperTrail
|
|
397
403
|
|
398
404
|
def record_destroy
|
399
405
|
if paper_trail_switched_on? and not new_record?
|
400
|
-
object_attrs = object_attrs_for_paper_trail(attributes_before_change)
|
401
406
|
data = {
|
402
407
|
:item_id => self.id,
|
403
408
|
:item_type => self.class.base_class.name,
|
404
409
|
:event => paper_trail_event || 'destroy',
|
405
|
-
:object =>
|
410
|
+
:object => pt_recordable_object,
|
406
411
|
:whodunnit => PaperTrail.whodunnit
|
407
412
|
}
|
408
413
|
if self.class.paper_trail_version_class.column_names.include?('transaction_id')
|
@@ -488,9 +493,7 @@ module PaperTrail
|
|
488
493
|
# ommitting attributes to be skipped.
|
489
494
|
def object_attrs_for_paper_trail(attributes_hash)
|
490
495
|
attrs = attributes_hash.except(*self.paper_trail_options[:skip])
|
491
|
-
|
492
|
-
self.class.serialize_attributes_for_paper_trail!(attrs)
|
493
|
-
end
|
496
|
+
self.class.serialize_attributes_for_paper_trail!(attrs)
|
494
497
|
attrs
|
495
498
|
end
|
496
499
|
|