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 +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
|