paper_trail 9.2.0 → 10.0.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 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