paper_trail 5.2.3 → 11.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 +5 -5
- data/lib/generators/paper_trail/install/USAGE +3 -0
- data/lib/generators/paper_trail/install/install_generator.rb +75 -0
- data/lib/generators/paper_trail/{templates/add_object_changes_to_versions.rb → install/templates/add_object_changes_to_versions.rb.erb} +1 -1
- data/lib/generators/paper_trail/install/templates/create_versions.rb.erb +36 -0
- data/lib/generators/paper_trail/migration_generator.rb +38 -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 +82 -130
- data/lib/paper_trail/attribute_serializers/attribute_serializer_factory.rb +27 -0
- data/lib/paper_trail/attribute_serializers/cast_attribute_serializer.rb +15 -44
- data/lib/paper_trail/attribute_serializers/object_attribute.rb +2 -0
- data/lib/paper_trail/attribute_serializers/object_changes_attribute.rb +2 -0
- data/lib/paper_trail/cleaner.rb +3 -1
- data/lib/paper_trail/compatibility.rb +51 -0
- data/lib/paper_trail/config.rb +11 -49
- data/lib/paper_trail/events/base.rb +323 -0
- data/lib/paper_trail/events/create.rb +32 -0
- data/lib/paper_trail/events/destroy.rb +42 -0
- data/lib/paper_trail/events/update.rb +60 -0
- data/lib/paper_trail/frameworks/active_record.rb +2 -1
- data/lib/paper_trail/frameworks/active_record/models/paper_trail/version.rb +8 -3
- data/lib/paper_trail/frameworks/cucumber.rb +5 -3
- data/lib/paper_trail/frameworks/rails.rb +2 -0
- data/lib/paper_trail/frameworks/rails/controller.rb +33 -43
- data/lib/paper_trail/frameworks/rails/engine.rb +34 -1
- data/lib/paper_trail/frameworks/rspec.rb +17 -4
- data/lib/paper_trail/frameworks/rspec/helpers.rb +2 -0
- data/lib/paper_trail/has_paper_trail.rb +22 -310
- data/lib/paper_trail/model_config.rb +157 -109
- data/lib/paper_trail/queries/versions/where_object.rb +65 -0
- data/lib/paper_trail/queries/versions/where_object_changes.rb +75 -0
- data/lib/paper_trail/record_history.rb +3 -9
- data/lib/paper_trail/record_trail.rb +169 -319
- data/lib/paper_trail/reifier.rb +53 -374
- data/lib/paper_trail/request.rb +166 -0
- data/lib/paper_trail/serializers/json.rb +9 -10
- data/lib/paper_trail/serializers/yaml.rb +15 -28
- data/lib/paper_trail/type_serializers/postgres_array_serializer.rb +48 -0
- data/lib/paper_trail/version_concern.rb +160 -155
- data/lib/paper_trail/version_number.rb +12 -4
- metadata +77 -372
- data/.github/CONTRIBUTING.md +0 -109
- data/.github/ISSUE_TEMPLATE.md +0 -13
- data/.gitignore +0 -23
- data/.rspec +0 -2
- data/.rubocop.yml +0 -99
- data/.rubocop_todo.yml +0 -22
- data/.travis.yml +0 -41
- data/Appraisals +0 -38
- data/CHANGELOG.md +0 -560
- data/Gemfile +0 -2
- data/MIT-LICENSE +0 -20
- data/README.md +0 -1654
- data/Rakefile +0 -30
- data/doc/bug_report_template.rb +0 -69
- data/doc/warning_about_not_setting_whodunnit.md +0 -32
- data/gemfiles/ar3.gemfile +0 -19
- data/gemfiles/ar4.gemfile +0 -8
- data/gemfiles/ar5.gemfile +0 -9
- data/lib/generators/paper_trail/USAGE +0 -2
- data/lib/generators/paper_trail/default_initializer.rb +0 -0
- data/lib/generators/paper_trail/install_generator.rb +0 -57
- data/lib/generators/paper_trail/templates/add_transaction_id_column_to_versions.rb +0 -13
- data/lib/generators/paper_trail/templates/create_version_associations.rb +0 -22
- data/lib/generators/paper_trail/templates/create_versions.rb +0 -80
- data/lib/paper_trail/attribute_serializers/legacy_active_record_shim.rb +0 -48
- data/lib/paper_trail/frameworks/active_record/models/paper_trail/version_association.rb +0 -11
- data/lib/paper_trail/frameworks/sinatra.rb +0 -40
- data/lib/paper_trail/version_association_concern.rb +0 -17
- data/paper_trail.gemspec +0 -56
- data/spec/generators/install_generator_spec.rb +0 -66
- data/spec/generators/paper_trail/templates/create_versions_spec.rb +0 -51
- data/spec/models/animal_spec.rb +0 -36
- data/spec/models/boolit_spec.rb +0 -48
- data/spec/models/callback_modifier_spec.rb +0 -96
- data/spec/models/car_spec.rb +0 -13
- data/spec/models/custom_primary_key_record_spec.rb +0 -18
- data/spec/models/fluxor_spec.rb +0 -17
- data/spec/models/gadget_spec.rb +0 -68
- data/spec/models/joined_version_spec.rb +0 -47
- data/spec/models/json_version_spec.rb +0 -102
- data/spec/models/kitchen/banana_spec.rb +0 -14
- data/spec/models/not_on_update_spec.rb +0 -22
- data/spec/models/post_with_status_spec.rb +0 -50
- data/spec/models/skipper_spec.rb +0 -46
- data/spec/models/thing_spec.rb +0 -11
- data/spec/models/truck_spec.rb +0 -5
- data/spec/models/vehicle_spec.rb +0 -5
- data/spec/models/version_spec.rb +0 -272
- data/spec/models/widget_spec.rb +0 -343
- data/spec/modules/paper_trail_spec.rb +0 -27
- data/spec/modules/version_concern_spec.rb +0 -31
- data/spec/modules/version_number_spec.rb +0 -43
- data/spec/paper_trail/config_spec.rb +0 -33
- data/spec/paper_trail_spec.rb +0 -79
- data/spec/rails_helper.rb +0 -34
- data/spec/requests/articles_spec.rb +0 -34
- data/spec/spec_helper.rb +0 -114
- data/spec/support/alt_db_init.rb +0 -54
- data/test/custom_json_serializer.rb +0 -13
- data/test/dummy/Rakefile +0 -7
- data/test/dummy/app/controllers/application_controller.rb +0 -33
- data/test/dummy/app/controllers/articles_controller.rb +0 -20
- data/test/dummy/app/controllers/test_controller.rb +0 -5
- data/test/dummy/app/controllers/widgets_controller.rb +0 -32
- data/test/dummy/app/helpers/application_helper.rb +0 -2
- data/test/dummy/app/models/animal.rb +0 -6
- data/test/dummy/app/models/article.rb +0 -24
- data/test/dummy/app/models/authorship.rb +0 -5
- data/test/dummy/app/models/bar_habtm.rb +0 -4
- data/test/dummy/app/models/book.rb +0 -9
- data/test/dummy/app/models/boolit.rb +0 -4
- data/test/dummy/app/models/callback_modifier.rb +0 -45
- data/test/dummy/app/models/car.rb +0 -3
- data/test/dummy/app/models/cat.rb +0 -2
- data/test/dummy/app/models/chapter.rb +0 -9
- data/test/dummy/app/models/citation.rb +0 -5
- data/test/dummy/app/models/custom_primary_key_record.rb +0 -13
- data/test/dummy/app/models/customer.rb +0 -4
- data/test/dummy/app/models/document.rb +0 -4
- data/test/dummy/app/models/dog.rb +0 -2
- data/test/dummy/app/models/editor.rb +0 -4
- data/test/dummy/app/models/editorship.rb +0 -5
- data/test/dummy/app/models/elephant.rb +0 -3
- data/test/dummy/app/models/fluxor.rb +0 -3
- data/test/dummy/app/models/foo_habtm.rb +0 -5
- data/test/dummy/app/models/foo_widget.rb +0 -2
- data/test/dummy/app/models/fruit.rb +0 -5
- data/test/dummy/app/models/gadget.rb +0 -3
- data/test/dummy/app/models/kitchen/banana.rb +0 -5
- data/test/dummy/app/models/legacy_widget.rb +0 -4
- data/test/dummy/app/models/line_item.rb +0 -4
- data/test/dummy/app/models/not_on_update.rb +0 -4
- data/test/dummy/app/models/order.rb +0 -5
- data/test/dummy/app/models/paragraph.rb +0 -5
- data/test/dummy/app/models/person.rb +0 -38
- data/test/dummy/app/models/post.rb +0 -3
- data/test/dummy/app/models/post_with_status.rb +0 -8
- data/test/dummy/app/models/protected_widget.rb +0 -3
- data/test/dummy/app/models/quotation.rb +0 -5
- data/test/dummy/app/models/section.rb +0 -6
- data/test/dummy/app/models/skipper.rb +0 -6
- data/test/dummy/app/models/song.rb +0 -41
- data/test/dummy/app/models/thing.rb +0 -3
- data/test/dummy/app/models/translation.rb +0 -4
- data/test/dummy/app/models/truck.rb +0 -4
- data/test/dummy/app/models/vehicle.rb +0 -4
- data/test/dummy/app/models/whatchamajigger.rb +0 -4
- data/test/dummy/app/models/widget.rb +0 -16
- data/test/dummy/app/models/wotsit.rb +0 -8
- data/test/dummy/app/versions/custom_primary_key_record_version.rb +0 -3
- data/test/dummy/app/versions/joined_version.rb +0 -6
- data/test/dummy/app/versions/json_version.rb +0 -3
- data/test/dummy/app/versions/kitchen/banana_version.rb +0 -5
- data/test/dummy/app/versions/post_version.rb +0 -3
- data/test/dummy/app/views/layouts/application.html.erb +0 -14
- data/test/dummy/config.ru +0 -4
- data/test/dummy/config/application.rb +0 -80
- data/test/dummy/config/boot.rb +0 -10
- data/test/dummy/config/database.mysql.yml +0 -19
- data/test/dummy/config/database.postgres.yml +0 -15
- data/test/dummy/config/database.sqlite.yml +0 -15
- data/test/dummy/config/environment.rb +0 -5
- data/test/dummy/config/environments/development.rb +0 -41
- data/test/dummy/config/environments/production.rb +0 -74
- data/test/dummy/config/environments/test.rb +0 -51
- data/test/dummy/config/initializers/backtrace_silencers.rb +0 -9
- data/test/dummy/config/initializers/inflections.rb +0 -10
- data/test/dummy/config/initializers/mime_types.rb +0 -5
- data/test/dummy/config/initializers/paper_trail.rb +0 -9
- data/test/dummy/config/initializers/secret_token.rb +0 -9
- data/test/dummy/config/initializers/session_store.rb +0 -8
- data/test/dummy/config/locales/en.yml +0 -5
- data/test/dummy/config/routes.rb +0 -4
- data/test/dummy/db/migrate/20110208155312_set_up_test_tables.rb +0 -361
- data/test/dummy/db/schema.rb +0 -288
- data/test/dummy/script/rails +0 -8
- data/test/functional/controller_test.rb +0 -90
- data/test/functional/enabled_for_controller_test.rb +0 -28
- data/test/functional/modular_sinatra_test.rb +0 -46
- data/test/functional/sinatra_test.rb +0 -51
- data/test/functional/thread_safety_test.rb +0 -46
- data/test/test_helper.rb +0 -127
- data/test/time_travel_helper.rb +0 -1
- data/test/unit/associations_test.rb +0 -1016
- data/test/unit/cleaner_test.rb +0 -188
- data/test/unit/inheritance_column_test.rb +0 -43
- data/test/unit/model_test.rb +0 -1489
- data/test/unit/protected_attrs_test.rb +0 -52
- data/test/unit/serializer_test.rb +0 -119
- data/test/unit/serializers/json_test.rb +0 -95
- data/test/unit/serializers/mixin_json_test.rb +0 -37
- data/test/unit/serializers/mixin_yaml_test.rb +0 -53
- data/test/unit/serializers/yaml_test.rb +0 -54
- data/test/unit/timestamp_test.rb +0 -41
- data/test/unit/version_test.rb +0 -119
data/lib/paper_trail/reifier.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "paper_trail/attribute_serializers/object_attribute"
|
2
4
|
|
3
5
|
module PaperTrail
|
@@ -10,39 +12,9 @@ module PaperTrail
|
|
10
12
|
def reify(version, options)
|
11
13
|
options = apply_defaults_to(options, version)
|
12
14
|
attrs = version.object_deserialized
|
13
|
-
|
14
|
-
# Normally a polymorphic belongs_to relationship allows us to get the
|
15
|
-
# object we belong to by calling, in this case, `item`. However this
|
16
|
-
# returns nil if `item` has been destroyed, and we need to be able to
|
17
|
-
# retrieve destroyed objects.
|
18
|
-
#
|
19
|
-
# In this situation we constantize the `item_type` to get hold of the
|
20
|
-
# class...except when the stored object's attributes include a `type`
|
21
|
-
# key. If this is the case, the object we belong to is using single
|
22
|
-
# table inheritance and the `item_type` will be the base class, not the
|
23
|
-
# actual subclass. If `type` is present but empty, the class is the base
|
24
|
-
# class.
|
25
|
-
if options[:dup] != true && version.item
|
26
|
-
model = version.item
|
27
|
-
if options[:unversioned_attributes] == :nil
|
28
|
-
init_unversioned_attrs(attrs, model)
|
29
|
-
end
|
30
|
-
else
|
31
|
-
klass = version_reification_class(version, attrs)
|
32
|
-
# The `dup` option always returns a new object, otherwise we should
|
33
|
-
# attempt to look for the item outside of default scope(s).
|
34
|
-
find_cond = { klass.primary_key => version.item_id }
|
35
|
-
if options[:dup] || (item_found = klass.unscoped.where(find_cond).first).nil?
|
36
|
-
model = klass.new
|
37
|
-
elsif options[:unversioned_attributes] == :nil
|
38
|
-
model = item_found
|
39
|
-
init_unversioned_attrs(attrs, model)
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
15
|
+
model = init_model(attrs, options, version)
|
43
16
|
reify_attributes(model, version, attrs)
|
44
17
|
model.send "#{model.class.version_association_name}=", version
|
45
|
-
reify_associations(model, options, version)
|
46
18
|
model
|
47
19
|
end
|
48
20
|
|
@@ -63,71 +35,41 @@ module PaperTrail
|
|
63
35
|
}.merge(options)
|
64
36
|
end
|
65
37
|
|
66
|
-
#
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
yield assoc
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
# Examine the `source_reflection`, i.e. the "source" of `assoc` the
|
75
|
-
# `ThroughReflection`. The source can be a `BelongsToReflection`
|
76
|
-
# or a `HasManyReflection`.
|
38
|
+
# Initialize a model object suitable for reifying `version` into. Does
|
39
|
+
# not perform reification, merely instantiates the appropriate model
|
40
|
+
# class and, if specified by `options[:unversioned_attributes]`, sets
|
41
|
+
# unversioned attributes to `nil`.
|
77
42
|
#
|
78
|
-
#
|
79
|
-
#
|
43
|
+
# Normally a polymorphic belongs_to relationship allows us to get the
|
44
|
+
# object we belong to by calling, in this case, `item`. However this
|
45
|
+
# returns nil if `item` has been destroyed, and we need to be able to
|
46
|
+
# retrieve destroyed objects.
|
80
47
|
#
|
81
|
-
#
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
48
|
+
# In this situation we constantize the `item_type` to get hold of the
|
49
|
+
# class...except when the stored object's attributes include a `type`
|
50
|
+
# key. If this is the case, the object we belong to is using single
|
51
|
+
# table inheritance (STI) and the `item_type` will be the base class,
|
52
|
+
# not the actual subclass. If `type` is present but empty, the class is
|
53
|
+
# the base class.
|
54
|
+
def init_model(attrs, options, version)
|
55
|
+
klass = version_reification_class(version, attrs)
|
56
|
+
|
57
|
+
# The `dup` option and destroyed version always returns a new object,
|
58
|
+
# otherwise we should attempt to load item or to look for the item
|
59
|
+
# outside of default scope(s).
|
60
|
+
model = if options[:dup] == true || version.event == "destroy"
|
61
|
+
klass.new
|
62
|
+
else
|
63
|
+
find_cond = { klass.primary_key => version.item_id }
|
64
|
+
|
65
|
+
version.item || klass.unscoped.where(find_cond).first || klass.new
|
66
|
+
end
|
93
67
|
|
94
|
-
|
95
|
-
|
96
|
-
through_collection.each do |through_model|
|
97
|
-
reify_has_manys(transaction_id, through_model, options)
|
68
|
+
if options[:unversioned_attributes] == :nil && !model.new_record?
|
69
|
+
init_unversioned_attrs(attrs, model)
|
98
70
|
end
|
99
71
|
|
100
|
-
|
101
|
-
# been reified, but not the final, "target" part. To continue our
|
102
|
-
# example, `model.sections` (including `model.sections.paragraphs`)
|
103
|
-
# has been loaded. However, the final "target" part of the
|
104
|
-
# association, that is, `model.paragraphs`, has not been loaded. So,
|
105
|
-
# we do that now.
|
106
|
-
through_collection.flat_map { |through_model|
|
107
|
-
through_model.public_send(assoc.name.to_sym).to_a
|
108
|
-
}
|
109
|
-
end
|
110
|
-
|
111
|
-
# @api private
|
112
|
-
def hmt_collection_through_belongs_to(through_collection, assoc, options, transaction_id)
|
113
|
-
collection_keys = through_collection.map { |through_model|
|
114
|
-
through_model.send(assoc.source_reflection.foreign_key)
|
115
|
-
}
|
116
|
-
version_id_subquery = assoc.klass.paper_trail.version_class.
|
117
|
-
select("MIN(id)").
|
118
|
-
where("item_type = ?", assoc.class_name).
|
119
|
-
where("item_id IN (?)", collection_keys).
|
120
|
-
where(
|
121
|
-
"created_at >= ? OR transaction_id = ?",
|
122
|
-
options[:version_at],
|
123
|
-
transaction_id
|
124
|
-
).
|
125
|
-
group("item_id").
|
126
|
-
to_sql
|
127
|
-
versions = versions_by_id(assoc.klass, version_id_subquery)
|
128
|
-
collection = Array.new assoc.klass.where(assoc.klass.primary_key => collection_keys)
|
129
|
-
prepare_array_for_has_many(collection, options, versions)
|
130
|
-
collection
|
72
|
+
model
|
131
73
|
end
|
132
74
|
|
133
75
|
# Look for attributes that exist in `model` and not in this version.
|
@@ -137,268 +79,32 @@ module PaperTrail
|
|
137
79
|
(model.attribute_names - attrs.keys).each { |k| attrs[k] = nil }
|
138
80
|
end
|
139
81
|
|
140
|
-
#
|
141
|
-
#
|
82
|
+
# Reify onto `model` an attribute named `k` with value `v` from `version`.
|
83
|
+
#
|
84
|
+
# `ObjectAttribute#deserialize` will return the mapped enum value and in
|
85
|
+
# Rails < 5, the []= uses the integer type caster from the column
|
86
|
+
# definition (in general) and thus will turn a (usually) string to 0
|
87
|
+
# instead of the correct value.
|
88
|
+
#
|
142
89
|
# @api private
|
143
|
-
def
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
90
|
+
def reify_attribute(k, v, model, version)
|
91
|
+
if model.has_attribute?(k)
|
92
|
+
model[k.to_sym] = v
|
93
|
+
elsif model.respond_to?("#{k}=")
|
94
|
+
model.send("#{k}=", v)
|
95
|
+
elsif version.logger
|
96
|
+
version.logger.warn(
|
97
|
+
"Attribute #{k} does not exist on #{version.item_type} (Version id: #{version.id})."
|
98
|
+
)
|
99
|
+
end
|
151
100
|
end
|
152
101
|
|
153
|
-
#
|
154
|
-
# record from the point in time identified by `transaction_id` or `version_at`.
|
102
|
+
# Reify onto `model` all the attributes of `version`.
|
155
103
|
# @api private
|
156
|
-
def load_version_for_has_one(assoc, model, transaction_id, version_at)
|
157
|
-
version_table_name = model.class.paper_trail.version_class.table_name
|
158
|
-
model.class.paper_trail.version_class.joins(:version_associations).
|
159
|
-
where("version_associations.foreign_key_name = ?", assoc.foreign_key).
|
160
|
-
where("version_associations.foreign_key_id = ?", model.id).
|
161
|
-
where("#{version_table_name}.item_type = ?", assoc.class_name).
|
162
|
-
where("created_at >= ? OR transaction_id = ?", version_at, transaction_id).
|
163
|
-
order("#{version_table_name}.id ASC").
|
164
|
-
first
|
165
|
-
end
|
166
|
-
|
167
|
-
# Set all the attributes in this version on the model.
|
168
104
|
def reify_attributes(model, version, attrs)
|
169
|
-
enums = model.class.respond_to?(:defined_enums) ? model.class.defined_enums : {}
|
170
105
|
AttributeSerializers::ObjectAttribute.new(model.class).deserialize(attrs)
|
171
106
|
attrs.each do |k, v|
|
172
|
-
|
173
|
-
# and in Rails < 5, the []= uses the integer type caster from the column
|
174
|
-
# definition (in general) and thus will turn a (usually) string to 0 instead
|
175
|
-
# of the correct value
|
176
|
-
is_enum_without_type_caster = ::ActiveRecord::VERSION::MAJOR < 5 && enums.key?(k)
|
177
|
-
|
178
|
-
if model.has_attribute?(k) && !is_enum_without_type_caster
|
179
|
-
model[k.to_sym] = v
|
180
|
-
elsif model.respond_to?("#{k}=")
|
181
|
-
model.send("#{k}=", v)
|
182
|
-
else
|
183
|
-
version.logger.warn(
|
184
|
-
"Attribute #{k} does not exist on #{version.item_type} (Version id: #{version.id})."
|
185
|
-
)
|
186
|
-
end
|
187
|
-
end
|
188
|
-
end
|
189
|
-
|
190
|
-
# Replaces each record in `array` with its reified version, if present
|
191
|
-
# in `versions`.
|
192
|
-
#
|
193
|
-
# @api private
|
194
|
-
# @param array - The collection to be modified.
|
195
|
-
# @param options
|
196
|
-
# @param versions - A `Hash` mapping IDs to `Version`s
|
197
|
-
# @return nil - Always returns `nil`
|
198
|
-
#
|
199
|
-
# Once modified by this method, `array` will be assigned to the
|
200
|
-
# AR association currently being reified.
|
201
|
-
#
|
202
|
-
def prepare_array_for_has_many(array, options, versions)
|
203
|
-
# Iterate each child to replace it with the previous value if there is
|
204
|
-
# a version after the timestamp.
|
205
|
-
array.map! do |record|
|
206
|
-
if (version = versions.delete(record.id)).nil?
|
207
|
-
record
|
208
|
-
elsif version.event == "create"
|
209
|
-
options[:mark_for_destruction] ? record.tap(&:mark_for_destruction) : nil
|
210
|
-
else
|
211
|
-
version.reify(
|
212
|
-
options.merge(
|
213
|
-
has_many: false,
|
214
|
-
has_one: false,
|
215
|
-
belongs_to: false,
|
216
|
-
has_and_belongs_to_many: false
|
217
|
-
)
|
218
|
-
)
|
219
|
-
end
|
220
|
-
end
|
221
|
-
|
222
|
-
# Reify the rest of the versions and add them to the collection, these
|
223
|
-
# versions are for those that have been removed from the live
|
224
|
-
# associations.
|
225
|
-
array.concat(
|
226
|
-
versions.values.map { |v|
|
227
|
-
v.reify(
|
228
|
-
options.merge(
|
229
|
-
has_many: false,
|
230
|
-
has_one: false,
|
231
|
-
belongs_to: false,
|
232
|
-
has_and_belongs_to_many: false
|
233
|
-
)
|
234
|
-
)
|
235
|
-
}
|
236
|
-
)
|
237
|
-
|
238
|
-
array.compact!
|
239
|
-
|
240
|
-
nil
|
241
|
-
end
|
242
|
-
|
243
|
-
def reify_associations(model, options, version)
|
244
|
-
reify_has_ones version.transaction_id, model, options if options[:has_one]
|
245
|
-
|
246
|
-
reify_belongs_tos version.transaction_id, model, options if options[:belongs_to]
|
247
|
-
|
248
|
-
reify_has_manys version.transaction_id, model, options if options[:has_many]
|
249
|
-
|
250
|
-
if options[:has_and_belongs_to_many]
|
251
|
-
reify_has_and_belongs_to_many version.transaction_id, model, options
|
252
|
-
end
|
253
|
-
end
|
254
|
-
|
255
|
-
# Restore the `model`'s has_one associations as they were when this
|
256
|
-
# version was superseded by the next (because that's what the user was
|
257
|
-
# looking at when they made the change).
|
258
|
-
def reify_has_ones(transaction_id, model, options = {})
|
259
|
-
associations = model.class.reflect_on_all_associations(:has_one)
|
260
|
-
each_enabled_association(associations) do |assoc|
|
261
|
-
version = load_version_for_has_one(assoc, model, transaction_id, options[:version_at])
|
262
|
-
next unless version
|
263
|
-
if version.event == "create"
|
264
|
-
if options[:mark_for_destruction]
|
265
|
-
model.send(assoc.name).mark_for_destruction if model.send(assoc.name, true)
|
266
|
-
else
|
267
|
-
model.paper_trail.appear_as_new_record do
|
268
|
-
model.send "#{assoc.name}=", nil
|
269
|
-
end
|
270
|
-
end
|
271
|
-
else
|
272
|
-
child = 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
|
-
model.paper_trail.appear_as_new_record do
|
281
|
-
without_persisting(child) do
|
282
|
-
model.send "#{assoc.name}=", child
|
283
|
-
end
|
284
|
-
end
|
285
|
-
end
|
286
|
-
end
|
287
|
-
end
|
288
|
-
|
289
|
-
def reify_belongs_tos(transaction_id, model, options = {})
|
290
|
-
associations = model.class.reflect_on_all_associations(:belongs_to)
|
291
|
-
each_enabled_association(associations) do |assoc|
|
292
|
-
collection_key = model.send(assoc.association_foreign_key)
|
293
|
-
version = assoc.klass.paper_trail.version_class.
|
294
|
-
where("item_type = ?", assoc.class_name).
|
295
|
-
where("item_id = ?", collection_key).
|
296
|
-
where("created_at >= ? OR transaction_id = ?", options[:version_at], transaction_id).
|
297
|
-
order("id").limit(1).first
|
298
|
-
|
299
|
-
collection = if version.nil?
|
300
|
-
assoc.klass.where(assoc.klass.primary_key => collection_key).first
|
301
|
-
else
|
302
|
-
version.reify(
|
303
|
-
options.merge(
|
304
|
-
has_many: false,
|
305
|
-
has_one: false,
|
306
|
-
belongs_to: false,
|
307
|
-
has_and_belongs_to_many: false
|
308
|
-
)
|
309
|
-
)
|
310
|
-
end
|
311
|
-
|
312
|
-
model.send("#{assoc.name}=".to_sym, collection)
|
313
|
-
end
|
314
|
-
end
|
315
|
-
|
316
|
-
# Restore the `model`'s has_many associations as they were at version_at
|
317
|
-
# timestamp We lookup the first child versions after version_at timestamp or
|
318
|
-
# in same transaction.
|
319
|
-
def reify_has_manys(transaction_id, model, options = {})
|
320
|
-
assoc_has_many_through, assoc_has_many_directly =
|
321
|
-
model.class.reflect_on_all_associations(:has_many).
|
322
|
-
partition { |assoc| assoc.options[:through] }
|
323
|
-
reify_has_many_directly(transaction_id, assoc_has_many_directly, model, options)
|
324
|
-
reify_has_many_through(transaction_id, assoc_has_many_through, model, options)
|
325
|
-
end
|
326
|
-
|
327
|
-
# Restore the `model`'s has_many associations not associated through
|
328
|
-
# another association.
|
329
|
-
def reify_has_many_directly(transaction_id, associations, model, options = {})
|
330
|
-
version_table_name = model.class.paper_trail.version_class.table_name
|
331
|
-
each_enabled_association(associations) do |assoc|
|
332
|
-
version_id_subquery = PaperTrail::VersionAssociation.
|
333
|
-
joins(model.class.version_association_name).
|
334
|
-
select("MIN(version_id)").
|
335
|
-
where("foreign_key_name = ?", assoc.foreign_key).
|
336
|
-
where("foreign_key_id = ?", model.id).
|
337
|
-
where("#{version_table_name}.item_type = ?", assoc.class_name).
|
338
|
-
where("created_at >= ? OR transaction_id = ?", options[:version_at], transaction_id).
|
339
|
-
group("item_id").
|
340
|
-
to_sql
|
341
|
-
versions = versions_by_id(model.class, version_id_subquery)
|
342
|
-
collection = Array.new model.send(assoc.name).reload # to avoid cache
|
343
|
-
prepare_array_for_has_many(collection, options, versions)
|
344
|
-
model.send(assoc.name).proxy_association.target = collection
|
345
|
-
end
|
346
|
-
end
|
347
|
-
|
348
|
-
# Restore the `model`'s has_many associations through another association.
|
349
|
-
# This must be called after the direct has_manys have been reified
|
350
|
-
# (reify_has_many_directly).
|
351
|
-
def reify_has_many_through(transaction_id, associations, model, options = {})
|
352
|
-
each_enabled_association(associations) do |assoc|
|
353
|
-
# Load the collection of through-models. For example, if `model` is a
|
354
|
-
# Chapter, having many Paragraphs through Sections, then
|
355
|
-
# `through_collection` will contain Sections.
|
356
|
-
through_collection = model.send(assoc.options[:through])
|
357
|
-
|
358
|
-
# Now, given the collection of "through" models (e.g. sections), load
|
359
|
-
# the collection of "target" models (e.g. paragraphs)
|
360
|
-
collection = hmt_collection(through_collection, assoc, options, transaction_id)
|
361
|
-
|
362
|
-
# Finally, assign the `collection` of "target" models, e.g. to
|
363
|
-
# `model.paragraphs`.
|
364
|
-
model.send(assoc.name).proxy_association.target = collection
|
365
|
-
end
|
366
|
-
end
|
367
|
-
|
368
|
-
def reify_has_and_belongs_to_many(transaction_id, model, options = {})
|
369
|
-
model.class.reflect_on_all_associations(:has_and_belongs_to_many).each do |assoc|
|
370
|
-
papertrail_enabled = assoc.klass.paper_trail.enabled?
|
371
|
-
next unless
|
372
|
-
model.class.paper_trail_save_join_tables.include?(assoc.name) ||
|
373
|
-
papertrail_enabled
|
374
|
-
|
375
|
-
version_ids = PaperTrail::VersionAssociation.
|
376
|
-
where("foreign_key_name = ?", assoc.name).
|
377
|
-
where("version_id = ?", transaction_id).
|
378
|
-
pluck(:foreign_key_id)
|
379
|
-
|
380
|
-
model.send(assoc.name).proxy_association.target =
|
381
|
-
version_ids.map do |id|
|
382
|
-
if papertrail_enabled
|
383
|
-
version = load_version_for_habtm(
|
384
|
-
assoc,
|
385
|
-
id,
|
386
|
-
transaction_id,
|
387
|
-
options[:version_at]
|
388
|
-
)
|
389
|
-
if version
|
390
|
-
next version.reify(
|
391
|
-
options.merge(
|
392
|
-
has_many: false,
|
393
|
-
has_one: false,
|
394
|
-
belongs_to: false,
|
395
|
-
has_and_belongs_to_many: false
|
396
|
-
)
|
397
|
-
)
|
398
|
-
end
|
399
|
-
end
|
400
|
-
assoc.klass.where(assoc.klass.primary_key => id).first
|
401
|
-
end
|
107
|
+
reify_attribute(k, v, model, version)
|
402
108
|
end
|
403
109
|
end
|
404
110
|
|
@@ -412,40 +118,13 @@ module PaperTrail
|
|
412
118
|
# this method returns the constant `Animal`. You can see this particular
|
413
119
|
# example in action in `spec/models/animal_spec.rb`.
|
414
120
|
#
|
121
|
+
# TODO: Duplication: similar `constantize` in VersionConcern#version_limit
|
415
122
|
def version_reification_class(version, attrs)
|
416
123
|
inheritance_column_name = version.item_type.constantize.inheritance_column
|
417
124
|
inher_col_value = attrs[inheritance_column_name]
|
418
125
|
class_name = inher_col_value.blank? ? version.item_type : inher_col_value
|
419
126
|
class_name.constantize
|
420
127
|
end
|
421
|
-
|
422
|
-
# Given a SQL fragment that identifies the IDs of version records,
|
423
|
-
# returns a `Hash` mapping those IDs to `Version`s.
|
424
|
-
#
|
425
|
-
# @api private
|
426
|
-
# @param klass - An ActiveRecord class.
|
427
|
-
# @param version_id_subquery - String. A SQL subquery that selects
|
428
|
-
# the IDs of version records.
|
429
|
-
# @return A `Hash` mapping IDs to `Version`s
|
430
|
-
#
|
431
|
-
def versions_by_id(klass, version_id_subquery)
|
432
|
-
klass.
|
433
|
-
paper_trail.version_class.
|
434
|
-
where("id IN (#{version_id_subquery})").
|
435
|
-
inject({}) { |a, e| a.merge!(e.item_id => e) }
|
436
|
-
end
|
437
|
-
|
438
|
-
# Temporarily suppress #save so we can reassociate with the reified
|
439
|
-
# master of a has_one relationship. Since ActiveRecord 5 the related
|
440
|
-
# object is saved when it is assigned to the association. ActiveRecord
|
441
|
-
# 5 also happens to be the first version that provides #suppress.
|
442
|
-
def without_persisting(record)
|
443
|
-
if record.class.respond_to? :suppress
|
444
|
-
record.class.suppress { yield }
|
445
|
-
else
|
446
|
-
yield
|
447
|
-
end
|
448
|
-
end
|
449
128
|
end
|
450
129
|
end
|
451
130
|
end
|