paper_trail 6.0.2 → 7.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/.github/CONTRIBUTING.md +20 -0
- data/.rubocop.yml +30 -2
- data/.rubocop_todo.yml +20 -0
- data/.travis.yml +3 -5
- data/Appraisals +5 -6
- data/CHANGELOG.md +33 -0
- data/README.md +43 -81
- data/Rakefile +1 -1
- data/doc/bug_report_template.rb +4 -2
- data/gemfiles/ar_4.0.gemfile +7 -0
- data/gemfiles/ar_4.2.gemfile +0 -1
- data/lib/generators/paper_trail/templates/create_version_associations.rb +1 -1
- data/lib/generators/paper_trail/templates/create_versions.rb +1 -1
- data/lib/paper_trail.rb +7 -9
- data/lib/paper_trail/config.rb +0 -15
- data/lib/paper_trail/frameworks/rspec.rb +8 -2
- data/lib/paper_trail/model_config.rb +6 -2
- data/lib/paper_trail/record_trail.rb +3 -1
- data/lib/paper_trail/reifier.rb +43 -354
- data/lib/paper_trail/reifiers/belongs_to.rb +48 -0
- data/lib/paper_trail/reifiers/has_and_belongs_to_many.rb +50 -0
- data/lib/paper_trail/reifiers/has_many.rb +110 -0
- data/lib/paper_trail/reifiers/has_many_through.rb +90 -0
- data/lib/paper_trail/reifiers/has_one.rb +76 -0
- data/lib/paper_trail/serializers/yaml.rb +2 -25
- data/lib/paper_trail/version_concern.rb +5 -5
- data/lib/paper_trail/version_number.rb +7 -3
- data/paper_trail.gemspec +7 -34
- data/spec/controllers/articles_controller_spec.rb +1 -1
- data/spec/generators/install_generator_spec.rb +40 -34
- data/spec/models/animal_spec.rb +50 -25
- data/spec/models/boolit_spec.rb +8 -7
- data/spec/models/callback_modifier_spec.rb +13 -13
- data/spec/models/document_spec.rb +21 -0
- data/spec/models/gadget_spec.rb +35 -39
- data/spec/models/joined_version_spec.rb +4 -4
- data/spec/models/json_version_spec.rb +14 -15
- data/spec/models/not_on_update_spec.rb +1 -1
- data/spec/models/post_with_status_spec.rb +2 -2
- data/spec/models/skipper_spec.rb +4 -4
- data/spec/models/thing_spec.rb +1 -1
- data/spec/models/truck_spec.rb +1 -1
- data/spec/models/vehicle_spec.rb +1 -1
- data/spec/models/version_spec.rb +152 -168
- data/spec/models/widget_spec.rb +170 -196
- data/spec/modules/paper_trail_spec.rb +3 -3
- data/spec/modules/version_concern_spec.rb +5 -8
- data/spec/modules/version_number_spec.rb +11 -36
- data/spec/paper_trail/cleaner_spec.rb +152 -0
- data/spec/paper_trail/config_spec.rb +1 -1
- data/spec/paper_trail/serializers/custom_yaml_serializer_spec.rb +45 -0
- data/spec/paper_trail/serializers/json_spec.rb +57 -0
- data/spec/paper_trail/version_limit_spec.rb +55 -0
- data/spec/paper_trail_spec.rb +45 -32
- data/spec/requests/articles_spec.rb +4 -4
- data/test/dummy/app/models/custom_primary_key_record.rb +4 -2
- data/test/dummy/app/models/document.rb +1 -1
- data/test/dummy/app/models/not_on_update.rb +1 -1
- data/test/dummy/app/models/on/create.rb +6 -0
- data/test/dummy/app/models/on/destroy.rb +6 -0
- data/test/dummy/app/models/on/empty_array.rb +6 -0
- data/test/dummy/app/models/on/update.rb +6 -0
- data/test/dummy/app/models/person.rb +1 -0
- data/test/dummy/app/models/song.rb +19 -28
- data/test/dummy/config/application.rb +10 -43
- data/test/dummy/config/routes.rb +1 -1
- data/test/dummy/db/migrate/20110208155312_set_up_test_tables.rb +25 -51
- data/test/dummy/db/schema.rb +29 -19
- data/test/test_helper.rb +0 -16
- data/test/unit/associations_test.rb +81 -81
- data/test/unit/model_test.rb +48 -131
- data/test/unit/serializer_test.rb +34 -45
- data/test/unit/serializers/mixin_json_test.rb +3 -1
- data/test/unit/serializers/yaml_test.rb +1 -5
- metadata +44 -19
- data/lib/paper_trail/frameworks/sinatra.rb +0 -40
- data/test/functional/modular_sinatra_test.rb +0 -46
- data/test/functional/sinatra_test.rb +0 -51
- data/test/unit/cleaner_test.rb +0 -151
- data/test/unit/inheritance_column_test.rb +0 -41
- data/test/unit/serializers/json_test.rb +0 -95
- data/test/unit/serializers/mixin_yaml_test.rb +0 -53
data/Rakefile
CHANGED
data/doc/bug_report_template.rb
CHANGED
@@ -34,7 +34,7 @@ ActiveRecord::Schema.define do
|
|
34
34
|
t.integer :transaction_id
|
35
35
|
t.datetime :created_at
|
36
36
|
end
|
37
|
-
add_index :versions,
|
37
|
+
add_index :versions, %i(item_type item_id)
|
38
38
|
add_index :versions, [:transaction_id]
|
39
39
|
|
40
40
|
create_table :version_associations do |t|
|
@@ -43,7 +43,7 @@ ActiveRecord::Schema.define do
|
|
43
43
|
t.integer :foreign_key_id
|
44
44
|
end
|
45
45
|
add_index :version_associations, [:version_id]
|
46
|
-
add_index :version_associations,
|
46
|
+
add_index :version_associations, %i(foreign_key_name foreign_key_id),
|
47
47
|
name: "index_version_associations_on_foreign_key"
|
48
48
|
end
|
49
49
|
ActiveRecord::Base.logger = Logger.new(STDOUT)
|
@@ -67,3 +67,5 @@ class BugTest < ActiveSupport::TestCase
|
|
67
67
|
}
|
68
68
|
end
|
69
69
|
end
|
70
|
+
|
71
|
+
# STEP SIX: Run this script using `ruby my_bug_report.rb`
|
data/gemfiles/ar_4.2.gemfile
CHANGED
@@ -9,7 +9,7 @@ class CreateVersionAssociations < ActiveRecord::Migration
|
|
9
9
|
end
|
10
10
|
add_index :version_associations, [:version_id]
|
11
11
|
add_index :version_associations,
|
12
|
-
|
12
|
+
%i(foreign_key_name foreign_key_id),
|
13
13
|
name: "index_version_associations_on_foreign_key"
|
14
14
|
end
|
15
15
|
|
data/lib/paper_trail.rb
CHANGED
@@ -34,14 +34,6 @@ module PaperTrail
|
|
34
34
|
!!PaperTrail.config.enabled
|
35
35
|
end
|
36
36
|
|
37
|
-
def serialized_attributes?
|
38
|
-
ActiveSupport::Deprecation.warn(
|
39
|
-
"PaperTrail.serialized_attributes? is deprecated without replacement " +
|
40
|
-
"and always returns false."
|
41
|
-
)
|
42
|
-
false
|
43
|
-
end
|
44
|
-
|
45
37
|
# Sets whether PaperTrail is enabled or disabled for the current request.
|
46
38
|
# @api public
|
47
39
|
def enabled_for_controller=(value)
|
@@ -70,6 +62,13 @@ module PaperTrail
|
|
70
62
|
!!paper_trail_store.fetch(:"enabled_for_#{model}", true)
|
71
63
|
end
|
72
64
|
|
65
|
+
# Returns a `::Gem::Version`, convenient for comparisons. This is
|
66
|
+
# recommended over `::PaperTrail::VERSION::STRING`.
|
67
|
+
# @api public
|
68
|
+
def gem_version
|
69
|
+
::Gem::Version.new(VERSION::STRING)
|
70
|
+
end
|
71
|
+
|
73
72
|
# Set the field which records when a version was created.
|
74
73
|
# @api public
|
75
74
|
def timestamp_field=(_field_name)
|
@@ -163,7 +162,6 @@ ActiveSupport.on_load(:active_record) do
|
|
163
162
|
end
|
164
163
|
|
165
164
|
# Require frameworks
|
166
|
-
require "paper_trail/frameworks/sinatra"
|
167
165
|
if defined?(::Rails) && ActiveRecord::VERSION::STRING >= "3.2"
|
168
166
|
require "paper_trail/frameworks/rails"
|
169
167
|
else
|
data/lib/paper_trail/config.rb
CHANGED
@@ -18,21 +18,6 @@ module PaperTrail
|
|
18
18
|
@serializer = PaperTrail::Serializers::YAML
|
19
19
|
end
|
20
20
|
|
21
|
-
def serialized_attributes
|
22
|
-
ActiveSupport::Deprecation.warn(
|
23
|
-
"PaperTrail.config.serialized_attributes is deprecated without " +
|
24
|
-
"replacement and always returns false."
|
25
|
-
)
|
26
|
-
false
|
27
|
-
end
|
28
|
-
|
29
|
-
def serialized_attributes=(_)
|
30
|
-
ActiveSupport::Deprecation.warn(
|
31
|
-
"PaperTrail.config.serialized_attributes= is deprecated without " +
|
32
|
-
"replacement and no longer has any effect."
|
33
|
-
)
|
34
|
-
end
|
35
|
-
|
36
21
|
# Previously, we checked `PaperTrail::VersionAssociation.table_exists?`
|
37
22
|
# here, but that proved to be problematic in situations when the database
|
38
23
|
# connection had not been established, or when the database does not exist
|
@@ -25,10 +25,16 @@ end
|
|
25
25
|
|
26
26
|
RSpec::Matchers.define :have_a_version_with do |attributes|
|
27
27
|
# check if the model has a version with the specified attributes
|
28
|
-
match
|
28
|
+
match do |actual|
|
29
|
+
versions_association = actual.class.versions_association_name
|
30
|
+
actual.send(versions_association).where_object(attributes).any?
|
31
|
+
end
|
29
32
|
end
|
30
33
|
|
31
34
|
RSpec::Matchers.define :have_a_version_with_changes do |attributes|
|
32
35
|
# check if the model has a version changes with the specified attributes
|
33
|
-
match
|
36
|
+
match do |actual|
|
37
|
+
versions_association = actual.class.versions_association_name
|
38
|
+
actual.send(versions_association).where_object_changes(attributes).any?
|
39
|
+
end
|
34
40
|
end
|
@@ -76,10 +76,14 @@ module PaperTrail
|
|
76
76
|
# "class attributes", instance methods, and more.
|
77
77
|
# @api private
|
78
78
|
def setup(options = {})
|
79
|
-
options[:on] ||=
|
79
|
+
options[:on] ||= %i(create update destroy)
|
80
80
|
options[:on] = Array(options[:on]) # Support single symbol
|
81
81
|
@model_class.send :include, ::PaperTrail::Model::InstanceMethods
|
82
82
|
if ::ActiveRecord::VERSION::STRING < "4.2"
|
83
|
+
::ActiveSupport::Deprecation.warn(
|
84
|
+
"Your version of ActiveRecord (< 4.2) has reached EOL. PaperTrail " \
|
85
|
+
"will soon drop support. Please upgrade ActiveRecord ASAP."
|
86
|
+
)
|
83
87
|
@model_class.send :extend, AttributeSerializers::LegacyActiveRecordShim
|
84
88
|
end
|
85
89
|
setup_options(options)
|
@@ -163,7 +167,7 @@ module PaperTrail
|
|
163
167
|
@model_class.class_attribute :paper_trail_options
|
164
168
|
@model_class.paper_trail_options = options.dup
|
165
169
|
|
166
|
-
|
170
|
+
%i(ignore skip only).each do |k|
|
167
171
|
@model_class.paper_trail_options[k] = [@model_class.paper_trail_options[k]].
|
168
172
|
flatten.
|
169
173
|
compact.
|
@@ -474,7 +474,9 @@ module PaperTrail
|
|
474
474
|
if @in_after_callback && RAILS_GTE_5_1
|
475
475
|
@record.attribute_before_last_save(attr_name.to_s)
|
476
476
|
else
|
477
|
-
|
477
|
+
# TODO: after dropping support for rails 4.0, remove send, because
|
478
|
+
# attribute_was is no longer private.
|
479
|
+
@record.send(:attribute_was, attr_name.to_s)
|
478
480
|
end
|
479
481
|
end
|
480
482
|
|
data/lib/paper_trail/reifier.rb
CHANGED
@@ -1,4 +1,9 @@
|
|
1
1
|
require "paper_trail/attribute_serializers/object_attribute"
|
2
|
+
require "paper_trail/reifiers/belongs_to"
|
3
|
+
require "paper_trail/reifiers/has_and_belongs_to_many"
|
4
|
+
require "paper_trail/reifiers/has_many"
|
5
|
+
require "paper_trail/reifiers/has_many_through"
|
6
|
+
require "paper_trail/reifiers/has_one"
|
2
7
|
|
3
8
|
module PaperTrail
|
4
9
|
# Given a version record and some options, builds a new model object.
|
@@ -17,6 +22,18 @@ module PaperTrail
|
|
17
22
|
model
|
18
23
|
end
|
19
24
|
|
25
|
+
# Restore the `model`'s has_many associations as they were at version_at
|
26
|
+
# timestamp We lookup the first child versions after version_at timestamp or
|
27
|
+
# in same transaction.
|
28
|
+
# @api private
|
29
|
+
def reify_has_manys(transaction_id, model, options = {})
|
30
|
+
assoc_has_many_through, assoc_has_many_directly =
|
31
|
+
model.class.reflect_on_all_associations(:has_many).
|
32
|
+
partition { |assoc| assoc.options[:through] }
|
33
|
+
reify_has_many_associations(transaction_id, assoc_has_many_directly, model, options)
|
34
|
+
reify_has_many_through_associations(transaction_id, assoc_has_many_through, model, options)
|
35
|
+
end
|
36
|
+
|
20
37
|
private
|
21
38
|
|
22
39
|
# Given a hash of `options` for `.reify`, return a new hash with default
|
@@ -79,54 +96,6 @@ module PaperTrail
|
|
79
96
|
model
|
80
97
|
end
|
81
98
|
|
82
|
-
# Examine the `source_reflection`, i.e. the "source" of `assoc` the
|
83
|
-
# `ThroughReflection`. The source can be a `BelongsToReflection`
|
84
|
-
# or a `HasManyReflection`.
|
85
|
-
#
|
86
|
-
# If the association is a has_many association again, then call
|
87
|
-
# reify_has_manys for each record in `through_collection`.
|
88
|
-
#
|
89
|
-
# @api private
|
90
|
-
def hmt_collection(through_collection, assoc, options, transaction_id)
|
91
|
-
if !assoc.source_reflection.belongs_to? && through_collection.present?
|
92
|
-
hmt_collection_through_has_many(
|
93
|
-
through_collection, assoc, options, transaction_id
|
94
|
-
)
|
95
|
-
else
|
96
|
-
hmt_collection_through_belongs_to(
|
97
|
-
through_collection, assoc, options, transaction_id
|
98
|
-
)
|
99
|
-
end
|
100
|
-
end
|
101
|
-
|
102
|
-
# @api private
|
103
|
-
def hmt_collection_through_has_many(through_collection, assoc, options, transaction_id)
|
104
|
-
through_collection.each do |through_model|
|
105
|
-
reify_has_manys(transaction_id, through_model, options)
|
106
|
-
end
|
107
|
-
|
108
|
-
# At this point, the "through" part of the association chain has
|
109
|
-
# been reified, but not the final, "target" part. To continue our
|
110
|
-
# example, `model.sections` (including `model.sections.paragraphs`)
|
111
|
-
# has been loaded. However, the final "target" part of the
|
112
|
-
# association, that is, `model.paragraphs`, has not been loaded. So,
|
113
|
-
# we do that now.
|
114
|
-
through_collection.flat_map { |through_model|
|
115
|
-
through_model.public_send(assoc.name.to_sym).to_a
|
116
|
-
}
|
117
|
-
end
|
118
|
-
|
119
|
-
# @api private
|
120
|
-
def hmt_collection_through_belongs_to(through_collection, assoc, options, tx_id)
|
121
|
-
ids = through_collection.map { |through_model|
|
122
|
-
through_model.send(assoc.source_reflection.foreign_key)
|
123
|
-
}
|
124
|
-
versions = load_versions_for_hmt_association(assoc, ids, tx_id, options[:version_at])
|
125
|
-
collection = Array.new assoc.klass.where(assoc.klass.primary_key => ids)
|
126
|
-
prepare_array_for_has_many(collection, options, versions)
|
127
|
-
collection
|
128
|
-
end
|
129
|
-
|
130
99
|
# Look for attributes that exist in `model` and not in this version.
|
131
100
|
# These attributes should be set to nil. Modifies `attrs`.
|
132
101
|
# @api private
|
@@ -134,171 +103,35 @@ module PaperTrail
|
|
134
103
|
(model.attribute_names - attrs.keys).each { |k| attrs[k] = nil }
|
135
104
|
end
|
136
105
|
|
137
|
-
#
|
138
|
-
#
|
106
|
+
# Reify onto `model` an attribute named `k` with value `v` from `version`.
|
107
|
+
#
|
108
|
+
# `ObjectAttribute#deserialize` will return the mapped enum value and in
|
109
|
+
# Rails < 5, the []= uses the integer type caster from the column
|
110
|
+
# definition (in general) and thus will turn a (usually) string to 0
|
111
|
+
# instead of the correct value.
|
112
|
+
#
|
139
113
|
# @api private
|
140
|
-
def
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
)
|
114
|
+
def reify_attribute(k, v, model, version)
|
115
|
+
enums = model.class.respond_to?(:defined_enums) ? model.class.defined_enums : {}
|
116
|
+
is_enum_without_type_caster = ::ActiveRecord::VERSION::MAJOR < 5 && enums.key?(k)
|
117
|
+
if model.has_attribute?(k) && !is_enum_without_type_caster
|
118
|
+
model[k.to_sym] = v
|
119
|
+
elsif model.respond_to?("#{k}=")
|
120
|
+
model.send("#{k}=", v)
|
121
|
+
elsif version.logger
|
122
|
+
version.logger.warn(
|
123
|
+
"Attribute #{k} does not exist on #{version.item_type} (Version id: #{version.id})."
|
151
124
|
)
|
152
125
|
end
|
153
126
|
end
|
154
127
|
|
155
|
-
#
|
156
|
-
# from the point in time identified by `transaction_id` or `version_at`.
|
157
|
-
# @api private
|
158
|
-
def load_version_for_bt_association(assoc, id, transaction_id, version_at)
|
159
|
-
assoc.klass.paper_trail.version_class.
|
160
|
-
where("item_type = ?", assoc.class_name).
|
161
|
-
where("item_id = ?", id).
|
162
|
-
where("created_at >= ? OR transaction_id = ?", version_at, transaction_id).
|
163
|
-
order("id").limit(1).first
|
164
|
-
end
|
165
|
-
|
166
|
-
# Given a HABTM association `assoc` and an `id`, return a version record
|
167
|
-
# from the point in time identified by `transaction_id` or `version_at`.
|
168
|
-
# @api private
|
169
|
-
def load_version_for_habtm(assoc, id, transaction_id, version_at)
|
170
|
-
assoc.klass.paper_trail.version_class.
|
171
|
-
where("item_type = ?", assoc.klass.name).
|
172
|
-
where("item_id = ?", id).
|
173
|
-
where("created_at >= ? OR transaction_id = ?", version_at, transaction_id).
|
174
|
-
order("id").
|
175
|
-
limit(1).
|
176
|
-
first
|
177
|
-
end
|
178
|
-
|
179
|
-
# Given a has-one association `assoc` on `model`, return the version
|
180
|
-
# record from the point in time identified by `transaction_id` or `version_at`.
|
181
|
-
# @api private
|
182
|
-
def load_version_for_has_one(assoc, model, transaction_id, version_at)
|
183
|
-
version_table_name = model.class.paper_trail.version_class.table_name
|
184
|
-
model.class.paper_trail.version_class.joins(:version_associations).
|
185
|
-
where("version_associations.foreign_key_name = ?", assoc.foreign_key).
|
186
|
-
where("version_associations.foreign_key_id = ?", model.id).
|
187
|
-
where("#{version_table_name}.item_type = ?", assoc.class_name).
|
188
|
-
where("created_at >= ? OR transaction_id = ?", version_at, transaction_id).
|
189
|
-
order("#{version_table_name}.id ASC").
|
190
|
-
first
|
191
|
-
end
|
192
|
-
|
193
|
-
# Given a `has_many` association on `model`, return the version records
|
194
|
-
# from the point in time identified by `tx_id` or `version_at`.
|
128
|
+
# Reify onto `model` all the attributes of `version`.
|
195
129
|
# @api private
|
196
|
-
def load_versions_for_hm_association(assoc, model, version_table, tx_id, version_at)
|
197
|
-
version_id_subquery = ::PaperTrail::VersionAssociation.
|
198
|
-
joins(model.class.version_association_name).
|
199
|
-
select("MIN(version_id)").
|
200
|
-
where("foreign_key_name = ?", assoc.foreign_key).
|
201
|
-
where("foreign_key_id = ?", model.id).
|
202
|
-
where("#{version_table}.item_type = ?", assoc.class_name).
|
203
|
-
where("created_at >= ? OR transaction_id = ?", version_at, tx_id).
|
204
|
-
group("item_id").
|
205
|
-
to_sql
|
206
|
-
versions_by_id(model.class, version_id_subquery)
|
207
|
-
end
|
208
|
-
|
209
|
-
# Given a `has_many(through:)` association and an array of `ids`, return
|
210
|
-
# the version records from the point in time identified by `tx_id` or
|
211
|
-
# `version_at`.
|
212
|
-
# @api private
|
213
|
-
def load_versions_for_hmt_association(assoc, ids, tx_id, version_at)
|
214
|
-
version_id_subquery = assoc.klass.paper_trail.version_class.
|
215
|
-
select("MIN(id)").
|
216
|
-
where("item_type = ?", assoc.class_name).
|
217
|
-
where("item_id IN (?)", ids).
|
218
|
-
where(
|
219
|
-
"created_at >= ? OR transaction_id = ?",
|
220
|
-
version_at,
|
221
|
-
tx_id
|
222
|
-
).
|
223
|
-
group("item_id").
|
224
|
-
to_sql
|
225
|
-
versions_by_id(assoc.klass, version_id_subquery)
|
226
|
-
end
|
227
|
-
|
228
|
-
# Set all the attributes in this version on the model.
|
229
130
|
def reify_attributes(model, version, attrs)
|
230
|
-
enums = model.class.respond_to?(:defined_enums) ? model.class.defined_enums : {}
|
231
131
|
AttributeSerializers::ObjectAttribute.new(model.class).deserialize(attrs)
|
232
132
|
attrs.each do |k, v|
|
233
|
-
|
234
|
-
# and in Rails < 5, the []= uses the integer type caster from the column
|
235
|
-
# definition (in general) and thus will turn a (usually) string to 0 instead
|
236
|
-
# of the correct value
|
237
|
-
is_enum_without_type_caster = ::ActiveRecord::VERSION::MAJOR < 5 && enums.key?(k)
|
238
|
-
|
239
|
-
if model.has_attribute?(k) && !is_enum_without_type_caster
|
240
|
-
model[k.to_sym] = v
|
241
|
-
elsif model.respond_to?("#{k}=")
|
242
|
-
model.send("#{k}=", v)
|
243
|
-
elsif version.logger
|
244
|
-
version.logger.warn(
|
245
|
-
"Attribute #{k} does not exist on #{version.item_type} (Version id: #{version.id})."
|
246
|
-
)
|
247
|
-
end
|
248
|
-
end
|
249
|
-
end
|
250
|
-
|
251
|
-
# Replaces each record in `array` with its reified version, if present
|
252
|
-
# in `versions`.
|
253
|
-
#
|
254
|
-
# @api private
|
255
|
-
# @param array - The collection to be modified.
|
256
|
-
# @param options
|
257
|
-
# @param versions - A `Hash` mapping IDs to `Version`s
|
258
|
-
# @return nil - Always returns `nil`
|
259
|
-
#
|
260
|
-
# Once modified by this method, `array` will be assigned to the
|
261
|
-
# AR association currently being reified.
|
262
|
-
#
|
263
|
-
def prepare_array_for_has_many(array, options, versions)
|
264
|
-
# Iterate each child to replace it with the previous value if there is
|
265
|
-
# a version after the timestamp.
|
266
|
-
array.map! do |record|
|
267
|
-
if (version = versions.delete(record.id)).nil?
|
268
|
-
record
|
269
|
-
elsif version.event == "create"
|
270
|
-
options[:mark_for_destruction] ? record.tap(&:mark_for_destruction) : nil
|
271
|
-
else
|
272
|
-
version.reify(
|
273
|
-
options.merge(
|
274
|
-
has_many: false,
|
275
|
-
has_one: false,
|
276
|
-
belongs_to: false,
|
277
|
-
has_and_belongs_to_many: false
|
278
|
-
)
|
279
|
-
)
|
280
|
-
end
|
133
|
+
reify_attribute(k, v, model, version)
|
281
134
|
end
|
282
|
-
|
283
|
-
# Reify the rest of the versions and add them to the collection, these
|
284
|
-
# versions are for those that have been removed from the live
|
285
|
-
# associations.
|
286
|
-
array.concat(
|
287
|
-
versions.values.map { |v|
|
288
|
-
v.reify(
|
289
|
-
options.merge(
|
290
|
-
has_many: false,
|
291
|
-
has_one: false,
|
292
|
-
belongs_to: false,
|
293
|
-
has_and_belongs_to_many: false
|
294
|
-
)
|
295
|
-
)
|
296
|
-
}
|
297
|
-
)
|
298
|
-
|
299
|
-
array.compact!
|
300
|
-
|
301
|
-
nil
|
302
135
|
end
|
303
136
|
|
304
137
|
# @api private
|
@@ -317,36 +150,6 @@ module PaperTrail
|
|
317
150
|
end
|
318
151
|
end
|
319
152
|
|
320
|
-
# Reify a single `has_one` association of `model`.
|
321
|
-
# @api private
|
322
|
-
def reify_has_one_association(assoc, model, options, transaction_id)
|
323
|
-
version = load_version_for_has_one(assoc, model, transaction_id, options[:version_at])
|
324
|
-
return unless version
|
325
|
-
if version.event == "create"
|
326
|
-
if options[:mark_for_destruction]
|
327
|
-
model.send(assoc.name).mark_for_destruction if model.send(assoc.name, true)
|
328
|
-
else
|
329
|
-
model.paper_trail.appear_as_new_record do
|
330
|
-
model.send "#{assoc.name}=", nil
|
331
|
-
end
|
332
|
-
end
|
333
|
-
else
|
334
|
-
child = version.reify(
|
335
|
-
options.merge(
|
336
|
-
has_many: false,
|
337
|
-
has_one: false,
|
338
|
-
belongs_to: false,
|
339
|
-
has_and_belongs_to_many: false
|
340
|
-
)
|
341
|
-
)
|
342
|
-
model.paper_trail.appear_as_new_record do
|
343
|
-
without_persisting(child) do
|
344
|
-
model.send "#{assoc.name}=", child
|
345
|
-
end
|
346
|
-
end
|
347
|
-
end
|
348
|
-
end
|
349
|
-
|
350
153
|
# Restore the `model`'s has_one associations as they were when this
|
351
154
|
# version was superseded by the next (because that's what the user was
|
352
155
|
# looking at when they made the change).
|
@@ -354,130 +157,44 @@ module PaperTrail
|
|
354
157
|
def reify_has_one_associations(transaction_id, model, options = {})
|
355
158
|
associations = model.class.reflect_on_all_associations(:has_one)
|
356
159
|
each_enabled_association(associations) do |assoc|
|
357
|
-
|
160
|
+
Reifiers::HasOne.reify(assoc, model, options, transaction_id)
|
358
161
|
end
|
359
162
|
end
|
360
163
|
|
361
|
-
# Reify a single `belongs_to` association of `model`.
|
362
|
-
# @api private
|
363
|
-
def reify_belongs_to_association(assoc, model, options, transaction_id)
|
364
|
-
id = model.send(assoc.association_foreign_key)
|
365
|
-
version = load_version_for_bt_association(assoc, id, transaction_id, options[:version_at])
|
366
|
-
record = load_record_for_bt_association(assoc, id, options, version)
|
367
|
-
model.send("#{assoc.name}=".to_sym, record)
|
368
|
-
end
|
369
|
-
|
370
164
|
# Reify all `belongs_to` associations of `model`.
|
371
165
|
# @api private
|
372
166
|
def reify_belongs_to_associations(transaction_id, model, options = {})
|
373
167
|
associations = model.class.reflect_on_all_associations(:belongs_to)
|
374
168
|
each_enabled_association(associations) do |assoc|
|
375
|
-
|
169
|
+
Reifiers::BelongsTo.reify(assoc, model, options, transaction_id)
|
376
170
|
end
|
377
171
|
end
|
378
172
|
|
379
|
-
# Restore the `model`'s has_many associations as they were at version_at
|
380
|
-
# timestamp We lookup the first child versions after version_at timestamp or
|
381
|
-
# in same transaction.
|
382
|
-
def reify_has_manys(transaction_id, model, options = {})
|
383
|
-
assoc_has_many_through, assoc_has_many_directly =
|
384
|
-
model.class.reflect_on_all_associations(:has_many).
|
385
|
-
partition { |assoc| assoc.options[:through] }
|
386
|
-
reify_has_many_associations(transaction_id, assoc_has_many_directly, model, options)
|
387
|
-
reify_has_many_through_associations(transaction_id, assoc_has_many_through, model, options)
|
388
|
-
end
|
389
|
-
|
390
|
-
# Reify a single, direct (not `through`) `has_many` association of `model`.
|
391
|
-
# @api private
|
392
|
-
def reify_has_many_association(assoc, model, options, transaction_id, version_table_name)
|
393
|
-
versions = load_versions_for_hm_association(
|
394
|
-
assoc,
|
395
|
-
model,
|
396
|
-
version_table_name,
|
397
|
-
transaction_id,
|
398
|
-
options[:version_at]
|
399
|
-
)
|
400
|
-
collection = Array.new model.send(assoc.name).reload # to avoid cache
|
401
|
-
prepare_array_for_has_many(collection, options, versions)
|
402
|
-
model.send(assoc.name).proxy_association.target = collection
|
403
|
-
end
|
404
|
-
|
405
173
|
# Reify all direct (not `through`) `has_many` associations of `model`.
|
406
174
|
# @api private
|
407
175
|
def reify_has_many_associations(transaction_id, associations, model, options = {})
|
408
176
|
version_table_name = model.class.paper_trail.version_class.table_name
|
409
177
|
each_enabled_association(associations) do |assoc|
|
410
|
-
|
178
|
+
Reifiers::HasMany.reify(assoc, model, options, transaction_id, version_table_name)
|
411
179
|
end
|
412
180
|
end
|
413
181
|
|
414
|
-
# Reify a single HMT association of `model`.
|
415
|
-
# @api private
|
416
|
-
def reify_has_many_through_association(assoc, model, options, transaction_id)
|
417
|
-
# Load the collection of through-models. For example, if `model` is a
|
418
|
-
# Chapter, having many Paragraphs through Sections, then
|
419
|
-
# `through_collection` will contain Sections.
|
420
|
-
through_collection = model.send(assoc.options[:through])
|
421
|
-
|
422
|
-
# Now, given the collection of "through" models (e.g. sections), load
|
423
|
-
# the collection of "target" models (e.g. paragraphs)
|
424
|
-
collection = hmt_collection(through_collection, assoc, options, transaction_id)
|
425
|
-
|
426
|
-
# Finally, assign the `collection` of "target" models, e.g. to
|
427
|
-
# `model.paragraphs`.
|
428
|
-
model.send(assoc.name).proxy_association.target = collection
|
429
|
-
end
|
430
|
-
|
431
182
|
# Reify all HMT associations of `model`. This must be called after the
|
432
183
|
# direct (non-`through`) has_manys have been reified.
|
433
184
|
# @api private
|
434
185
|
def reify_has_many_through_associations(transaction_id, associations, model, options = {})
|
435
186
|
each_enabled_association(associations) do |assoc|
|
436
|
-
|
187
|
+
Reifiers::HasManyThrough.reify(assoc, model, options, transaction_id)
|
437
188
|
end
|
438
189
|
end
|
439
190
|
|
440
|
-
# Reify a single HABTM association of `model`.
|
441
|
-
# @api private
|
442
|
-
def reify_habtm_association(assoc, model, options, papertrail_enabled, transaction_id)
|
443
|
-
version_ids = PaperTrail::VersionAssociation.
|
444
|
-
where("foreign_key_name = ?", assoc.name).
|
445
|
-
where("version_id = ?", transaction_id).
|
446
|
-
pluck(:foreign_key_id)
|
447
|
-
|
448
|
-
model.send(assoc.name).proxy_association.target =
|
449
|
-
version_ids.map do |id|
|
450
|
-
if papertrail_enabled
|
451
|
-
version = load_version_for_habtm(
|
452
|
-
assoc,
|
453
|
-
id,
|
454
|
-
transaction_id,
|
455
|
-
options[:version_at]
|
456
|
-
)
|
457
|
-
if version
|
458
|
-
next version.reify(
|
459
|
-
options.merge(
|
460
|
-
has_many: false,
|
461
|
-
has_one: false,
|
462
|
-
belongs_to: false,
|
463
|
-
has_and_belongs_to_many: false
|
464
|
-
)
|
465
|
-
)
|
466
|
-
end
|
467
|
-
end
|
468
|
-
assoc.klass.where(assoc.klass.primary_key => id).first
|
469
|
-
end
|
470
|
-
end
|
471
|
-
|
472
191
|
# Reify all HABTM associations of `model`.
|
473
192
|
# @api private
|
474
193
|
def reify_habtm_associations(transaction_id, model, options = {})
|
475
194
|
model.class.reflect_on_all_associations(:has_and_belongs_to_many).each do |assoc|
|
476
|
-
|
477
|
-
next unless
|
478
|
-
|
479
|
-
papertrail_enabled
|
480
|
-
reify_habtm_association(assoc, model, options, papertrail_enabled, transaction_id)
|
195
|
+
pt_enabled = assoc.klass.paper_trail.enabled?
|
196
|
+
next unless model.class.paper_trail_save_join_tables.include?(assoc.name) || pt_enabled
|
197
|
+
Reifiers::HasAndBelongsToMany.reify(pt_enabled, assoc, model, options, transaction_id)
|
481
198
|
end
|
482
199
|
end
|
483
200
|
|
@@ -497,34 +214,6 @@ module PaperTrail
|
|
497
214
|
class_name = inher_col_value.blank? ? version.item_type : inher_col_value
|
498
215
|
class_name.constantize
|
499
216
|
end
|
500
|
-
|
501
|
-
# Given a SQL fragment that identifies the IDs of version records,
|
502
|
-
# returns a `Hash` mapping those IDs to `Version`s.
|
503
|
-
#
|
504
|
-
# @api private
|
505
|
-
# @param klass - An ActiveRecord class.
|
506
|
-
# @param version_id_subquery - String. A SQL subquery that selects
|
507
|
-
# the IDs of version records.
|
508
|
-
# @return A `Hash` mapping IDs to `Version`s
|
509
|
-
#
|
510
|
-
def versions_by_id(klass, version_id_subquery)
|
511
|
-
klass.
|
512
|
-
paper_trail.version_class.
|
513
|
-
where("id IN (#{version_id_subquery})").
|
514
|
-
inject({}) { |a, e| a.merge!(e.item_id => e) }
|
515
|
-
end
|
516
|
-
|
517
|
-
# Temporarily suppress #save so we can reassociate with the reified
|
518
|
-
# master of a has_one relationship. Since ActiveRecord 5 the related
|
519
|
-
# object is saved when it is assigned to the association. ActiveRecord
|
520
|
-
# 5 also happens to be the first version that provides #suppress.
|
521
|
-
def without_persisting(record)
|
522
|
-
if record.class.respond_to? :suppress
|
523
|
-
record.class.suppress { yield }
|
524
|
-
else
|
525
|
-
yield
|
526
|
-
end
|
527
|
-
end
|
528
217
|
end
|
529
218
|
end
|
530
219
|
end
|