paper_trail 9.2.0 → 10.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a2e53b5f5457783341505d9c0341c5fdc93c52191644db689260311c6c63de72
4
- data.tar.gz: bd94edaeb1bd3ca9d329b925241c1fe8774e7f058e64f82e06411e04397833ae
3
+ metadata.gz: b53e84ebfeca4497e5ec413afd5db52751dcec8865d6b3282468fcdad0d8d5d8
4
+ data.tar.gz: a78df196db71dfa88c60709f22ea194f54902c66ff4a1fe90c4e5dabf89d6c74
5
5
  SHA512:
6
- metadata.gz: 63d9377e20237a70d024e857f64dcd089126efc5bbce13f4dffc65a1c9f4d8210426269bfb60af9bdcdaa7d23211c2359d3a8676ceaa3346b0d0e414cd813ffd
7
- data.tar.gz: 952ea2469901473c0fe66b5b5e6aaaf65070957c0211147c60372c5a172029415aea87c93c6daac2c00243a087aa4135ca304b0ac69ad0f32a1a4fb940543e50
6
+ metadata.gz: 2716b1c5ec7a22b549f2a932d1d9f34a53437cb8c4f862ce3dc7e881d71903fd568187efdd144226822444e8c19ecb0df8e92152899f87fd91d71e6571707f44
7
+ data.tar.gz: 0354eebcdc0994e107e02440b665d74bbe7e644b02629cf681542624691a01b691206accc7f47b081db2e0d58672ee394e1ebbffe75b9bb5410608a18acae631
@@ -0,0 +1,3 @@
1
+ Description:
2
+ Generates (but does not run) a migration to add a versions table. Also generates an initializer
3
+ file for configuring PaperTrail. See section 5.c. Generators in README.md for more information.
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../migration_generator"
4
+
5
+ module PaperTrail
6
+ # Installs PaperTrail in a rails app.
7
+ class InstallGenerator < MigrationGenerator
8
+ # Class names of MySQL adapters.
9
+ # - `MysqlAdapter` - Used by gems: `mysql`, `activerecord-jdbcmysql-adapter`.
10
+ # - `Mysql2Adapter` - Used by `mysql2` gem.
11
+ MYSQL_ADAPTERS = [
12
+ "ActiveRecord::ConnectionAdapters::MysqlAdapter",
13
+ "ActiveRecord::ConnectionAdapters::Mysql2Adapter"
14
+ ].freeze
15
+
16
+ source_root File.expand_path("templates", __dir__)
17
+ class_option(
18
+ :with_changes,
19
+ type: :boolean,
20
+ default: false,
21
+ desc: "Store changeset (diff) with each version"
22
+ )
23
+
24
+ desc "Generates (but does not run) a migration to add a versions table." \
25
+ " Also generates an initializer file for configuring PaperTrail." \
26
+ " See section 5.c. Generators in README.md for more information."
27
+
28
+ def create_migration_file
29
+ add_paper_trail_migration("create_versions",
30
+ item_type_options: item_type_options,
31
+ versions_table_options: versions_table_options)
32
+ add_paper_trail_migration("add_object_changes_to_versions") if options.with_changes?
33
+ end
34
+
35
+ private
36
+
37
+ # MySQL 5.6 utf8mb4 limit is 191 chars for keys used in indexes.
38
+ # See https://github.com/paper-trail-gem/paper_trail/issues/651
39
+ def item_type_options
40
+ opt = { null: false }
41
+ opt[:limit] = 191 if mysql?
42
+ ", #{opt}"
43
+ end
44
+
45
+ def mysql?
46
+ MYSQL_ADAPTERS.include?(ActiveRecord::Base.connection.class.name)
47
+ end
48
+
49
+ # Even modern versions of MySQL still use `latin1` as the default character
50
+ # encoding. Many users are not aware of this, and run into trouble when they
51
+ # try to use PaperTrail in apps that otherwise tend to use UTF-8. Postgres, by
52
+ # comparison, uses UTF-8 except in the unusual case where the OS is configured
53
+ # with a custom locale.
54
+ #
55
+ # - https://dev.mysql.com/doc/refman/5.7/en/charset-applications.html
56
+ # - http://www.postgresql.org/docs/9.4/static/multibyte.html
57
+ #
58
+ # Furthermore, MySQL's original implementation of UTF-8 was flawed, and had
59
+ # to be fixed later by introducing a new charset, `utf8mb4`.
60
+ #
61
+ # - https://mathiasbynens.be/notes/mysql-utf8mb4
62
+ # - https://dev.mysql.com/doc/refman/5.5/en/charset-unicode-utf8mb4.html
63
+ #
64
+ def versions_table_options
65
+ if mysql?
66
+ ', { options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci" }'
67
+ else
68
+ ""
69
+ end
70
+ end
71
+ end
72
+ end
@@ -25,7 +25,7 @@ class CreateVersions < ActiveRecord::Migration<%= migration_version %>
25
25
  # the `created_at` column.
26
26
  # (https://dev.mysql.com/doc/refman/5.6/en/fractional-seconds.html)
27
27
  #
28
- # MySQL users should also upgrade to rails 4.2, which is the first
28
+ # MySQL users should also upgrade to at least rails 4.2, which is the first
29
29
  # version of ActiveRecord with support for fractional seconds in MySQL.
30
30
  # (https://github.com/rails/rails/pull/14359)
31
31
  #
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+ require "rails/generators/active_record"
5
+
6
+ module PaperTrail
7
+ # Basic structure to support a generator that builds a migration
8
+ class MigrationGenerator < ::Rails::Generators::Base
9
+ include ::Rails::Generators::Migration
10
+
11
+ def self.next_migration_number(dirname)
12
+ ::ActiveRecord::Generators::Base.next_migration_number(dirname)
13
+ end
14
+
15
+ protected
16
+
17
+ def add_paper_trail_migration(template, extra_options = {})
18
+ migration_dir = File.expand_path("db/migrate")
19
+ if self.class.migration_exists?(migration_dir, template)
20
+ ::Kernel.warn "Migration already exists: #{template}"
21
+ else
22
+ migration_template(
23
+ "#{template}.rb.erb",
24
+ "db/migrate/#{template}.rb",
25
+ { migration_version: migration_version }.merge(extra_options)
26
+ )
27
+ end
28
+ end
29
+
30
+ def migration_version
31
+ major = ActiveRecord::VERSION::MAJOR
32
+ if major >= 5
33
+ "[#{major}.#{ActiveRecord::VERSION::MINOR}]"
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,4 @@
1
+ Description:
2
+ Generates (but does not run) a migration to update item_type for STI entries
3
+ in an existing versions table. See section 5.c. Generators in README.md for
4
+ more information.
@@ -0,0 +1,85 @@
1
+ # This migration updates existing `versions` that have `item_type` that refers to
2
+ # the base_class, and changes them to refer to the subclass instead.
3
+ class UpdateVersionsForItemSubtype < ActiveRecord::Migration<%= migration_version %>
4
+ include ActionView::Helpers::TextHelper
5
+ def up
6
+ <%=
7
+ # Returns class, column, range
8
+ def self.parse_custom_entry(text)
9
+ parts = text.split("):")
10
+ range = parts.last.split("..").map(&:to_i)
11
+ range = Range.new(range.first, range.last)
12
+ parts.first.split("(") + [range]
13
+ end
14
+ # Running:
15
+ # rails g paper_trail:update_item_subtype Animal(species):1..4 Plant(genus):42..1337
16
+ # results in:
17
+ # # Versions of item_type "Animal" with IDs between 1 and 4 will be updated based on `species`
18
+ # # Versions of item_type "Plant" with IDs between 42 and 1337 will be updated based on `genus`
19
+ # hints = {"Animal"=>{1..4=>"species"}, "Plant"=>{42..1337=>"genus"}}
20
+ hint_descriptions = ""
21
+ hints = args.inject(Hash.new{|h, k| h[k] = {}}) do |s, v|
22
+ klass, column, range = parse_custom_entry(v)
23
+ hint_descriptions << " # Versions of item_type \"#{klass}\" with IDs between #{
24
+ range.first} and #{range.last} will be updated based on \`#{column}\`\n"
25
+ s[klass][range] = column
26
+ s
27
+ end
28
+
29
+ unless hints.empty?
30
+ "#{hint_descriptions} hints = #{hints.inspect}\n"
31
+ end
32
+ %>
33
+ # Find all ActiveRecord models mentioned in existing versions
34
+ changes = Hash.new { |h, k| h[k] = [] }
35
+ model_names = PaperTrail::Version.select(:item_type).distinct
36
+ model_names.map(&:item_type).each do |model_name|
37
+ hint = hints[model_name] if defined?(hints)
38
+ begin
39
+ klass = model_name.constantize
40
+ # Actually implements an inheritance_column? (Usually "type")
41
+ has_inheritance_column = klass.columns.map(&:name).include?(klass.inheritance_column)
42
+ # Find domain of types stored in PaperTrail versions
43
+ PaperTrail::Version.where(item_type: model_name, item_subtype: nil).select(:id, :object, :object_changes).each do |obj|
44
+ if (object_detail = PaperTrail.serializer.load(obj.object || obj.object_changes))
45
+ is_found = false
46
+ subtype_name = nil
47
+ hint&.each do |k, v|
48
+ if k === obj.id && (subtype_name = object_detail[v])
49
+ break
50
+ end
51
+ end
52
+ if subtype_name.nil? && has_inheritance_column
53
+ subtype_name = object_detail[klass.inheritance_column]
54
+ end
55
+ if subtype_name
56
+ subtype_name = subtype_name.last if subtype_name.is_a?(Array)
57
+ if subtype_name != model_name
58
+ changes[subtype_name] << obj.id
59
+ end
60
+ end
61
+ end
62
+ end
63
+ rescue NameError => ex
64
+ say "Skipping reference to #{model_name}", subitem: true
65
+ end
66
+ end
67
+ changes.each do |k, v|
68
+ # Update in blocks of up to 100 at a time
69
+ block_of_ids = []
70
+ id_count = 0
71
+ num_updated = 0
72
+ v.sort.each do |id|
73
+ block_of_ids << id
74
+ if (id_count += 1) % 100 == 0
75
+ num_updated += PaperTrail::Version.where(id: block_of_ids).update_all(item_subtype: k)
76
+ block_of_ids = []
77
+ end
78
+ end
79
+ num_updated += PaperTrail::Version.where(id: block_of_ids).update_all(item_subtype: k)
80
+ if num_updated > 0
81
+ say "Associated #{pluralize(num_updated, 'record')} to #{k}", subitem: true
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../migration_generator"
4
+
5
+ module PaperTrail
6
+ # Updates STI entries for PaperTrail
7
+ class UpdateItemSubtypeGenerator < MigrationGenerator
8
+ source_root File.expand_path("templates", __dir__)
9
+
10
+ desc "Generates (but does not run) a migration to update item_subtype for STI entries in an "\
11
+ "existing versions table."
12
+
13
+ def create_migration_file
14
+ add_paper_trail_migration("update_versions_for_item_subtype", sti_type_options: options)
15
+ end
16
+ end
17
+ end
@@ -57,46 +57,6 @@ module PaperTrail
57
57
  !!PaperTrail.config.enabled
58
58
  end
59
59
 
60
- # @deprecated
61
- def enabled_for_controller=(value)
62
- ::ActiveSupport::Deprecation.warn(
63
- "PaperTrail.enabled_for_controller= is deprecated, " \
64
- "use PaperTrail.request.enabled=",
65
- caller(1)
66
- )
67
- request.enabled = value
68
- end
69
-
70
- # @deprecated
71
- def enabled_for_controller?
72
- ::ActiveSupport::Deprecation.warn(
73
- "PaperTrail.enabled_for_controller? is deprecated, " \
74
- "use PaperTrail.request.enabled?",
75
- caller(1)
76
- )
77
- request.enabled?
78
- end
79
-
80
- # @deprecated
81
- def enabled_for_model(model, value)
82
- ::ActiveSupport::Deprecation.warn(
83
- "PaperTrail.enabled_for_model is deprecated, " \
84
- "use PaperTrail.request.enabled_for_model",
85
- caller(1)
86
- )
87
- request.enabled_for_model(model, value)
88
- end
89
-
90
- # @deprecated
91
- def enabled_for_model?(model)
92
- ::ActiveSupport::Deprecation.warn(
93
- "PaperTrail.enabled_for_model? is deprecated, " \
94
- "use PaperTrail.request.enabled_for_model?",
95
- caller(1)
96
- )
97
- request.enabled_for_model?(model)
98
- end
99
-
100
60
  # Returns PaperTrail's `::Gem::Version`, convenient for comparisons. This is
101
61
  # recommended over `::PaperTrail::VERSION::STRING`.
102
62
  #
@@ -137,53 +97,6 @@ module PaperTrail
137
97
  raise(E_TIMESTAMP_FIELD_CONFIG)
138
98
  end
139
99
 
140
- # @deprecated
141
- def whodunnit=(value)
142
- ::ActiveSupport::Deprecation.warn(
143
- "PaperTrail.whodunnit= is deprecated, use PaperTrail.request.whodunnit=",
144
- caller(1)
145
- )
146
- request.whodunnit = value
147
- end
148
-
149
- # @deprecated
150
- def whodunnit(value = nil, &block)
151
- if value.nil?
152
- ::ActiveSupport::Deprecation.warn(
153
- "PaperTrail.whodunnit is deprecated, use PaperTrail.request.whodunnit",
154
- caller(1)
155
- )
156
- request.whodunnit
157
- elsif block_given?
158
- ::ActiveSupport::Deprecation.warn(
159
- "Passing a block to PaperTrail.whodunnit is deprecated, " \
160
- 'use PaperTrail.request(whodunnit: "John") do .. end',
161
- caller(1)
162
- )
163
- request(whodunnit: value, &block)
164
- else
165
- raise ArgumentError, "Invalid arguments"
166
- end
167
- end
168
-
169
- # @deprecated
170
- def controller_info=(value)
171
- ::ActiveSupport::Deprecation.warn(
172
- "PaperTrail.controller_info= is deprecated, use PaperTrail.request.controller_info=",
173
- caller(1)
174
- )
175
- request.controller_info = value
176
- end
177
-
178
- # @deprecated
179
- def controller_info
180
- ::ActiveSupport::Deprecation.warn(
181
- "PaperTrail.controller_info is deprecated, use PaperTrail.request.controller_info",
182
- caller(1)
183
- )
184
- request.controller_info
185
- end
186
-
187
100
  # Set the PaperTrail serializer. This setting affects all threads.
188
101
  # @api public
189
102
  def serializer=(value)
@@ -228,7 +141,3 @@ if defined?(::Rails)
228
141
  else
229
142
  require "paper_trail/frameworks/active_record"
230
143
  end
231
-
232
- # https://github.com/paper-trail-gem/paper_trail/issues/1070
233
- # https://github.com/westonganger/paper_trail-association_tracking/issues/2
234
- require "paper_trail-association_tracking"
@@ -8,8 +8,18 @@ module PaperTrail
8
8
  # configuration can be found in `paper_trail.rb`, others in `controller.rb`.
9
9
  class Config
10
10
  include Singleton
11
- attr_accessor :serializer, :version_limit, :association_reify_error_behaviour,
12
- :object_changes_adapter
11
+
12
+ E_PT_AT_REMOVED = <<-EOS.squish
13
+ Association Tracking for PaperTrail has been extracted to a seperate gem.
14
+ Please add `paper_trail-association_tracking` to your Gemfile.
15
+ EOS
16
+
17
+ attr_accessor(
18
+ :association_reify_error_behaviour,
19
+ :object_changes_adapter,
20
+ :serializer,
21
+ :version_limit
22
+ )
13
23
 
14
24
  def initialize
15
25
  # Variables which affect all threads, whose access is synchronized.
@@ -28,5 +38,21 @@ module PaperTrail
28
38
  def enabled=(enable)
29
39
  @mutex.synchronize { @enabled = enable }
30
40
  end
41
+
42
+ # In PT 10, the paper_trail-association_tracking gem was changed from a
43
+ # runtime dependency to a development dependency. We raise an error about
44
+ # this for the people who don't read changelogs.
45
+ #
46
+ # We raise a generic RuntimeError instead of a specific PT error class
47
+ # because there is no known use case where someone would want to rescue
48
+ # this. If we think of such a use case in the future we can revisit this
49
+ # decision.
50
+ def track_associations=(_)
51
+ raise E_PT_AT_REMOVED
52
+ end
53
+
54
+ def track_associations?
55
+ raise E_PT_AT_REMOVED
56
+ end
31
57
  end
32
58
  end
@@ -0,0 +1,298 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PaperTrail
4
+ module Events
5
+ # We refer to times in the lifecycle of a record as "events". There are
6
+ # three events:
7
+ #
8
+ # - create
9
+ # - `after_create` we call `RecordTrail#record_create`
10
+ # - update
11
+ # - `after_update` we call `RecordTrail#record_update`
12
+ # - `after_touch` we call `RecordTrail#record_update`
13
+ # - `RecordTrail#save_with_version` calls `RecordTrail#record_update`
14
+ # - `RecordTrail#update_columns` is also referred to as an update, though
15
+ # it uses `RecordTrail#record_update_columns` rather than
16
+ # `RecordTrail#record_update`
17
+ # - destroy
18
+ # - `before_destroy` or `after_destroy` we call `RecordTrail#record_destroy`
19
+ #
20
+ # The value inserted into the `event` column of the versions table can also
21
+ # be overridden by the user, with `paper_trail_event`.
22
+ #
23
+ # @api private
24
+ class Base
25
+ RAILS_GTE_5_1 = ::ActiveRecord.gem_version >= ::Gem::Version.new("5.1.0.beta1")
26
+
27
+ # @api private
28
+ def initialize(record, in_after_callback)
29
+ @record = record
30
+ @in_after_callback = in_after_callback
31
+ end
32
+
33
+ # Determines whether it is appropriate to generate a new version
34
+ # instance. A timestamp-only update (e.g. only `updated_at` changed) is
35
+ # considered notable unless an ignored attribute was also changed.
36
+ #
37
+ # @api private
38
+ def changed_notably?
39
+ if ignored_attr_has_changed?
40
+ timestamps = @record.send(:timestamp_attributes_for_update_in_model).map(&:to_s)
41
+ (notably_changed - timestamps).any?
42
+ else
43
+ notably_changed.any?
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ # Rails 5.1 changed the API of `ActiveRecord::Dirty`. See
50
+ # https://github.com/paper-trail-gem/paper_trail/pull/899
51
+ #
52
+ # @api private
53
+ def attribute_changed_in_latest_version?(attr_name)
54
+ if @in_after_callback && RAILS_GTE_5_1
55
+ @record.saved_change_to_attribute?(attr_name.to_s)
56
+ else
57
+ @record.attribute_changed?(attr_name.to_s)
58
+ end
59
+ end
60
+
61
+ # @api private
62
+ def attributes_before_change(is_touch)
63
+ Hash[@record.attributes.map do |k, v|
64
+ if @record.class.column_names.include?(k)
65
+ [k, attribute_in_previous_version(k, is_touch)]
66
+ else
67
+ [k, v]
68
+ end
69
+ end]
70
+ end
71
+
72
+ # Rails 5.1 changed the API of `ActiveRecord::Dirty`. See
73
+ # https://github.com/paper-trail-gem/paper_trail/pull/899
74
+ #
75
+ # Event can be any of the three (create, update, destroy).
76
+ #
77
+ # @api private
78
+ def attribute_in_previous_version(attr_name, is_touch)
79
+ if RAILS_GTE_5_1
80
+ if @in_after_callback && !is_touch
81
+ # For most events, we want the original value of the attribute, before
82
+ # the last save.
83
+ @record.attribute_before_last_save(attr_name.to_s)
84
+ else
85
+ # We are either performing a `record_destroy` or a
86
+ # `record_update(is_touch: true)`.
87
+ @record.attribute_in_database(attr_name.to_s)
88
+ end
89
+ else
90
+ @record.attribute_was(attr_name.to_s)
91
+ end
92
+ end
93
+
94
+ # @api private
95
+ def changed_and_not_ignored
96
+ ignore = @record.paper_trail_options[:ignore].dup
97
+ # Remove Hash arguments and then evaluate whether the attributes (the
98
+ # keys of the hash) should also get pushed into the collection.
99
+ ignore.delete_if do |obj|
100
+ obj.is_a?(Hash) &&
101
+ obj.each { |attr, condition|
102
+ ignore << attr if condition.respond_to?(:call) && condition.call(@record)
103
+ }
104
+ end
105
+ skip = @record.paper_trail_options[:skip]
106
+ (changed_in_latest_version - ignore) - skip
107
+ end
108
+
109
+ # Rails 5.1 changed the API of `ActiveRecord::Dirty`. See
110
+ # https://github.com/paper-trail-gem/paper_trail/pull/899
111
+ #
112
+ # @api private
113
+ def changed_in_latest_version
114
+ if @in_after_callback && RAILS_GTE_5_1
115
+ @record.saved_changes.keys
116
+ else
117
+ @record.changed
118
+ end
119
+ end
120
+
121
+ # @api private
122
+ def prepare_object_changes(changes)
123
+ changes = serialize_object_changes(changes)
124
+ changes = recordable_object_changes(changes)
125
+ changes
126
+ end
127
+
128
+ # @api private
129
+ def serialize_object_changes(changes)
130
+ AttributeSerializers::ObjectChangesAttribute.
131
+ new(@record.class).
132
+ serialize(changes)
133
+ changes.to_hash
134
+ end
135
+
136
+ # @api private
137
+ def notable_changes
138
+ changes_in_latest_version.delete_if { |k, _v|
139
+ !notably_changed.include?(k)
140
+ }
141
+ end
142
+
143
+ # Rails 5.1 changed the API of `ActiveRecord::Dirty`. See
144
+ # https://github.com/paper-trail-gem/paper_trail/pull/899
145
+ #
146
+ # @api private
147
+ def changes_in_latest_version
148
+ if @in_after_callback && RAILS_GTE_5_1
149
+ @record.saved_changes
150
+ else
151
+ @record.changes
152
+ end
153
+ end
154
+
155
+ # An attributed is "ignored" if it is listed in the `:ignore` option
156
+ # and/or the `:skip` option. Returns true if an ignored attribute has
157
+ # changed.
158
+ #
159
+ # @api private
160
+ def ignored_attr_has_changed?
161
+ ignored = @record.paper_trail_options[:ignore] + @record.paper_trail_options[:skip]
162
+ ignored.any? && (changed_in_latest_version & ignored).any?
163
+ end
164
+
165
+ # PT 10 has a new optional column, `item_subtype`
166
+ #
167
+ # @api private
168
+ def merge_item_subtype_into(data)
169
+ if @record.class.paper_trail.version_class.columns_hash.key?("item_subtype")
170
+ data.merge!(item_subtype: @record.class.name)
171
+ end
172
+ end
173
+
174
+ # Updates `data` from the model's `meta` option and from `controller_info`.
175
+ # Metadata is always recorded; that means all three events (create, update,
176
+ # destroy) and `update_columns`.
177
+ #
178
+ # @api private
179
+ def merge_metadata_into(data)
180
+ merge_metadata_from_model_into(data)
181
+ merge_metadata_from_controller_into(data)
182
+ end
183
+
184
+ # Updates `data` from `controller_info`.
185
+ #
186
+ # @api private
187
+ def merge_metadata_from_controller_into(data)
188
+ data.merge(PaperTrail.request.controller_info || {})
189
+ end
190
+
191
+ # Updates `data` from the model's `meta` option.
192
+ #
193
+ # @api private
194
+ def merge_metadata_from_model_into(data)
195
+ @record.paper_trail_options[:meta].each do |k, v|
196
+ data[k] = model_metadatum(v, data[:event])
197
+ end
198
+ end
199
+
200
+ # Given a `value` from the model's `meta` option, returns an object to be
201
+ # persisted. The `value` can be a simple scalar value, but it can also
202
+ # be a symbol that names a model method, or even a Proc.
203
+ #
204
+ # @api private
205
+ def model_metadatum(value, event)
206
+ if value.respond_to?(:call)
207
+ value.call(@record)
208
+ elsif value.is_a?(Symbol) && @record.respond_to?(value, true)
209
+ # If it is an attribute that is changing in an existing object,
210
+ # be sure to grab the current version.
211
+ if event != "create" &&
212
+ @record.has_attribute?(value) &&
213
+ attribute_changed_in_latest_version?(value)
214
+ attribute_in_previous_version(value, false)
215
+ else
216
+ @record.send(value)
217
+ end
218
+ else
219
+ value
220
+ end
221
+ end
222
+
223
+ # @api private
224
+ def notably_changed
225
+ only = @record.paper_trail_options[:only].dup
226
+ # Remove Hash arguments and then evaluate whether the attributes (the
227
+ # keys of the hash) should also get pushed into the collection.
228
+ only.delete_if do |obj|
229
+ obj.is_a?(Hash) &&
230
+ obj.each { |attr, condition|
231
+ only << attr if condition.respond_to?(:call) && condition.call(@record)
232
+ }
233
+ end
234
+ only.empty? ? changed_and_not_ignored : (changed_and_not_ignored & only)
235
+ end
236
+
237
+ # Returns hash of attributes (with appropriate attributes serialized),
238
+ # omitting attributes to be skipped.
239
+ #
240
+ # @api private
241
+ def object_attrs_for_paper_trail(is_touch)
242
+ attrs = attributes_before_change(is_touch).
243
+ except(*@record.paper_trail_options[:skip])
244
+ AttributeSerializers::ObjectAttribute.new(@record.class).serialize(attrs)
245
+ attrs
246
+ end
247
+
248
+ # Returns an object which can be assigned to the `object_changes`
249
+ # attribute of a nascent version record. If the `object_changes` column is
250
+ # a postgres `json` column, then a hash can be used in the assignment,
251
+ # otherwise the column is a `text` column, and we must perform the
252
+ # serialization here, using `PaperTrail.serializer`.
253
+ #
254
+ # @api private
255
+ def recordable_object_changes(changes)
256
+ if PaperTrail.config.object_changes_adapter&.respond_to?(:diff)
257
+ changes = PaperTrail.config.object_changes_adapter.diff(changes)
258
+ end
259
+
260
+ if @record.class.paper_trail.version_class.object_changes_col_is_json?
261
+ changes
262
+ else
263
+ PaperTrail.serializer.dump(changes)
264
+ end
265
+ end
266
+
267
+ # Returns a boolean indicating whether to store serialized version diffs
268
+ # in the `object_changes` column of the version record.
269
+ #
270
+ # @api private
271
+ def record_object_changes?
272
+ @record.class.paper_trail.version_class.column_names.include?("object_changes")
273
+ end
274
+
275
+ # Returns a boolean indicating whether to store the original object during save.
276
+ #
277
+ # @api private
278
+ def record_object?
279
+ @record.class.paper_trail.version_class.column_names.include?("object")
280
+ end
281
+
282
+ # Returns an object which can be assigned to the `object` attribute of a
283
+ # nascent version record. If the `object` column is a postgres `json`
284
+ # column, then a hash can be used in the assignment, otherwise the column
285
+ # is a `text` column, and we must perform the serialization here, using
286
+ # `PaperTrail.serializer`.
287
+ #
288
+ # @api private
289
+ def recordable_object(is_touch)
290
+ if @record.class.paper_trail.version_class.object_col_is_json?
291
+ object_attrs_for_paper_trail(is_touch)
292
+ else
293
+ PaperTrail.serializer.dump(object_attrs_for_paper_trail(is_touch))
294
+ end
295
+ end
296
+ end
297
+ end
298
+ end