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 +4 -4
- data/lib/generators/paper_trail/install/USAGE +3 -0
- data/lib/generators/paper_trail/install/install_generator.rb +72 -0
- data/lib/generators/paper_trail/{templates → install/templates}/add_object_changes_to_versions.rb.erb +0 -0
- data/lib/generators/paper_trail/{templates → install/templates}/create_versions.rb.erb +1 -1
- data/lib/generators/paper_trail/migration_generator.rb +37 -0
- data/lib/generators/paper_trail/update_item_subtype/USAGE +4 -0
- data/lib/generators/paper_trail/update_item_subtype/templates/update_versions_for_item_subtype.rb.erb +85 -0
- data/lib/generators/paper_trail/update_item_subtype/update_item_subtype_generator.rb +17 -0
- data/lib/paper_trail.rb +0 -91
- data/lib/paper_trail/config.rb +28 -2
- data/lib/paper_trail/events/base.rb +298 -0
- data/lib/paper_trail/events/create.rb +31 -0
- data/lib/paper_trail/events/destroy.rb +34 -0
- data/lib/paper_trail/events/update.rb +59 -0
- data/lib/paper_trail/has_paper_trail.rb +0 -2
- data/lib/paper_trail/model_config.rb +12 -42
- data/lib/paper_trail/queries/versions/where_object.rb +4 -1
- data/lib/paper_trail/queries/versions/where_object_changes.rb +1 -1
- data/lib/paper_trail/record_trail.rb +65 -412
- data/lib/paper_trail/version_concern.rb +59 -34
- data/lib/paper_trail/version_number.rb +2 -2
- metadata +38 -29
- data/lib/generators/paper_trail/USAGE +0 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b53e84ebfeca4497e5ec413afd5db52751dcec8865d6b3282468fcdad0d8d5d8
|
4
|
+
data.tar.gz: a78df196db71dfa88c60709f22ea194f54902c66ff4a1fe90c4e5dabf89d6c74
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2716b1c5ec7a22b549f2a932d1d9f34a53437cb8c4f862ce3dc7e881d71903fd568187efdd144226822444e8c19ecb0df8e92152899f87fd91d71e6571707f44
|
7
|
+
data.tar.gz: 0354eebcdc0994e107e02440b665d74bbe7e644b02629cf681542624691a01b691206accc7f47b081db2e0d58672ee394e1ebbffe75b9bb5410608a18acae631
|
@@ -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
|
File without changes
|
@@ -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,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
|
data/lib/paper_trail.rb
CHANGED
@@ -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"
|
data/lib/paper_trail/config.rb
CHANGED
@@ -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
|
-
|
12
|
-
|
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
|