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.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +2 -0
  3. data/CHANGELOG.md +27 -0
  4. data/CONTRIBUTING.md +78 -5
  5. data/README.md +328 -268
  6. data/doc/bug_report_template.rb +65 -0
  7. data/gemfiles/3.0.gemfile +7 -4
  8. data/lib/generators/paper_trail/templates/add_object_changes_to_versions.rb +1 -1
  9. data/lib/generators/paper_trail/templates/create_versions.rb +14 -0
  10. data/lib/paper_trail.rb +11 -9
  11. data/lib/paper_trail/attributes_serialization.rb +89 -0
  12. data/lib/paper_trail/cleaner.rb +8 -1
  13. data/lib/paper_trail/config.rb +15 -18
  14. data/lib/paper_trail/frameworks/rails/controller.rb +16 -2
  15. data/lib/paper_trail/has_paper_trail.rb +102 -99
  16. data/lib/paper_trail/record_history.rb +59 -0
  17. data/lib/paper_trail/reifier.rb +270 -0
  18. data/lib/paper_trail/version_association_concern.rb +3 -1
  19. data/lib/paper_trail/version_concern.rb +60 -226
  20. data/lib/paper_trail/version_number.rb +2 -2
  21. data/paper_trail.gemspec +7 -10
  22. data/spec/models/animal_spec.rb +17 -0
  23. data/spec/models/callback_modifier_spec.rb +96 -0
  24. data/spec/models/json_version_spec.rb +20 -17
  25. data/spec/paper_trail/config_spec.rb +52 -0
  26. data/spec/spec_helper.rb +6 -0
  27. data/test/dummy/app/models/callback_modifier.rb +45 -0
  28. data/test/dummy/app/models/chapter.rb +9 -0
  29. data/test/dummy/app/models/citation.rb +5 -0
  30. data/test/dummy/app/models/paragraph.rb +5 -0
  31. data/test/dummy/app/models/quotation.rb +5 -0
  32. data/test/dummy/app/models/section.rb +6 -0
  33. data/test/dummy/config/database.postgres.yml +1 -1
  34. data/test/dummy/config/initializers/paper_trail.rb +3 -1
  35. data/test/dummy/db/migrate/20110208155312_set_up_test_tables.rb +33 -0
  36. data/test/dummy/db/schema.rb +27 -0
  37. data/test/test_helper.rb +36 -0
  38. data/test/unit/associations_test.rb +726 -0
  39. data/test/unit/inheritance_column_test.rb +6 -6
  40. data/test/unit/model_test.rb +62 -594
  41. data/test/unit/protected_attrs_test.rb +3 -2
  42. data/test/unit/version_test.rb +87 -69
  43. 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
@@ -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
 
@@ -5,6 +5,6 @@ class AddObjectChangesToVersions < ActiveRecord::Migration
5
5
  TEXT_BYTES = 1_073_741_823
6
6
 
7
7
  def change
8
- add_column :versions, :object_changes, :text, limit: TEXT_BYTES
8
+ add_column :versions, :object_changes, :text, :limit => TEXT_BYTES
9
9
  end
10
10
  end
@@ -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]
@@ -1,6 +1,6 @@
1
1
  require 'request_store'
2
2
 
3
- # Require core library
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
- !!PaperTrail.config.serialized_attributes && ::ActiveRecord::VERSION::MAJOR < 5
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 || !!defined?(ProtectedAttributes)
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
- @@config ||= PaperTrail::Config.instance
130
- yield @@config if block_given?
131
- @@config
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
@@ -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.group_by { |v| v.send(PaperTrail.timestamp_field).to_date }.each do |date, _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
@@ -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
- # This setting only defaults to false on AR 4.2+, because that's when
16
- # it was deprecated. We want it to function with older versions of
17
- # ActiveRecord by default.
18
- if ::ActiveRecord::VERSION::STRING < '4.2'
19
- @serialized_attributes = true
20
- end
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=(value)
24
- if ::ActiveRecord::VERSION::MAJOR >= 5
25
- ::ActiveSupport::Deprecation.warn(
26
- "ActiveRecord 5.0 deprecated `serialized_attributes` " +
27
- "without replacement, so this PaperTrail config setting does " +
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
- PaperTrail.paper_trail_store[:paper_trail_enabled].nil? || PaperTrail.paper_trail_store[:paper_trail_enabled]
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
- base.before_filter :set_paper_trail_enabled_for_controller
7
- base.before_filter :set_paper_trail_whodunnit, :set_paper_trail_controller_info
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 go generate a version
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 paper_trail_options[:save_changes] && changed_notably? && self.class.paper_trail_version_class.column_names.include?('object_changes')
333
- data[:object_changes] = self.class.paper_trail_version_class.object_changes_col_is_json? ? changes_for_paper_trail :
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 => self.class.paper_trail_version_class.object_col_is_json? ? object_attrs : PaperTrail.serializer.dump(object_attrs),
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 paper_trail_options[:save_changes] && self.class.paper_trail_version_class.column_names.include?('object_changes')
357
- data[:object_changes] = self.class.paper_trail_version_class.object_changes_col_is_json? ? changes_for_paper_trail :
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
- if PaperTrail.serialized_attributes?
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 => self.class.paper_trail_version_class.object_col_is_json? ? object_attrs : PaperTrail.serializer.dump(object_attrs),
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
- if PaperTrail.serialized_attributes?
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