mongo_trails 10.3.1 → 14.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/workflows/test.yml +53 -0
- data/.gitignore +2 -0
- data/.rubocop.yml +19 -0
- data/.ruby-version +1 -0
- data/Appraisals +10 -4
- data/Gemfile +8 -1
- data/README.md +27 -6
- data/Rakefile +17 -0
- data/gemfiles/rails_7.gemfile +15 -0
- data/gemfiles/rails_7.gemfile.lock +212 -0
- data/gemfiles/rails_8.gemfile +15 -0
- data/gemfiles/rails_8.gemfile.lock +213 -0
- data/gemfiles/rails_8_1.gemfile +15 -0
- data/gemfiles/rails_8_1.gemfile.lock +143 -0
- data/lib/mongo_trails/callback_propagation.rb +77 -0
- data/lib/mongo_trails/config.rb +7 -24
- data/lib/mongo_trails/events/base.rb +5 -308
- data/lib/mongo_trails/model_config.rb +1 -232
- data/lib/mongo_trails/mongo_support/config.rb +7 -1
- data/lib/mongo_trails/mongo_support/criteria.rb +7 -0
- data/lib/mongo_trails/mongo_support/version.rb +242 -19
- data/lib/mongo_trails/mongo_support/version_commit_wrap.rb +37 -0
- data/lib/mongo_trails/mongo_support/write_version_worker.rb +11 -0
- data/lib/mongo_trails/record_trail.rb +21 -262
- data/lib/mongo_trails/version.rb +5 -0
- data/lib/mongo_trails/version_concern.rb +4 -293
- data/lib/mongo_trails.rb +13 -149
- data/mongo_trails.gemspec +19 -20
- metadata +50 -85
- data/.travis.yml +0 -13
- data/Gemfile.lock +0 -62
- data/gemfiles/rails_5.gemfile +0 -9
- data/gemfiles/rails_5.gemfile.lock +0 -63
- data/gemfiles/rails_6.gemfile +0 -9
- data/gemfiles/rails_6.gemfile.lock +0 -63
- data/lib/mongo_trails/attribute_serializers/README.md +0 -10
- data/lib/mongo_trails/attribute_serializers/attribute_serializer_factory.rb +0 -27
- data/lib/mongo_trails/attribute_serializers/cast_attribute_serializer.rb +0 -51
- data/lib/mongo_trails/attribute_serializers/object_attribute.rb +0 -41
- data/lib/mongo_trails/attribute_serializers/object_changes_attribute.rb +0 -44
- data/lib/mongo_trails/cleaner.rb +0 -60
- data/lib/mongo_trails/compatibility.rb +0 -51
- data/lib/mongo_trails/events/create.rb +0 -32
- data/lib/mongo_trails/events/destroy.rb +0 -42
- data/lib/mongo_trails/events/update.rb +0 -60
- data/lib/mongo_trails/frameworks/cucumber.rb +0 -33
- data/lib/mongo_trails/frameworks/rails/controller.rb +0 -109
- data/lib/mongo_trails/frameworks/rails/engine.rb +0 -43
- data/lib/mongo_trails/frameworks/rails.rb +0 -4
- data/lib/mongo_trails/frameworks/rspec/helpers.rb +0 -29
- data/lib/mongo_trails/frameworks/rspec.rb +0 -43
- data/lib/mongo_trails/has_paper_trail.rb +0 -86
- data/lib/mongo_trails/queries/versions/where_object.rb +0 -65
- data/lib/mongo_trails/queries/versions/where_object_changes.rb +0 -75
- data/lib/mongo_trails/record_history.rb +0 -51
- data/lib/mongo_trails/reifier.rb +0 -130
- data/lib/mongo_trails/request.rb +0 -166
- data/lib/mongo_trails/serializers/json.rb +0 -46
- data/lib/mongo_trails/serializers/yaml.rb +0 -43
- data/lib/mongo_trails/type_serializers/postgres_array_serializer.rb +0 -48
- data/lib/mongo_trails/version_number.rb +0 -23
|
@@ -1,178 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module PaperTrail
|
|
4
|
-
# Configures an ActiveRecord model, mostly at application boot time, but also
|
|
5
|
-
# sometimes mid-request, with methods like enable/disable.
|
|
6
4
|
class ModelConfig
|
|
7
|
-
|
|
8
|
-
paper_trail.on_destroy(:after) is incompatible with ActiveRecord's
|
|
9
|
-
belongs_to_required_by_default. Use on_destroy(:before)
|
|
10
|
-
or disable belongs_to_required_by_default.
|
|
11
|
-
STR
|
|
12
|
-
E_HPT_ABSTRACT_CLASS = <<~STR.squish.freeze
|
|
13
|
-
An application model (%s) has been configured to use PaperTrail (via
|
|
14
|
-
`has_paper_trail`), but the version model it has been told to use (%s) is
|
|
15
|
-
an `abstract_class`. This could happen when an advanced feature called
|
|
16
|
-
Custom Version Classes (http://bit.ly/2G4ch0G) is misconfigured. When all
|
|
17
|
-
version classes are custom, PaperTrail::Version is configured to be an
|
|
18
|
-
`abstract_class`. This is fine, but all application models must be
|
|
19
|
-
configured to use concrete (not abstract) version models.
|
|
20
|
-
STR
|
|
21
|
-
E_MODEL_LIMIT_REQUIRES_ITEM_SUBTYPE = <<~STR.squish.freeze
|
|
22
|
-
To use PaperTrail's per-model limit in your %s model, you must have an
|
|
23
|
-
item_subtype column in your versions table. See documentation sections
|
|
24
|
-
2.e.1 Per-model limit, and 4.b.1 The optional item_subtype column.
|
|
25
|
-
STR
|
|
26
|
-
DPR_PASSING_ASSOC_NAME_DIRECTLY_TO_VERSIONS_OPTION = <<~STR.squish
|
|
27
|
-
Passing versions association name as `has_paper_trail versions: %{versions_name}`
|
|
28
|
-
is deprecated. Use `has_paper_trail versions: {name: %{versions_name}}` instead.
|
|
29
|
-
The hash you pass to `versions:` is now passed directly to `has_many`.
|
|
30
|
-
STR
|
|
31
|
-
DPR_CLASS_NAME_OPTION = <<~STR.squish
|
|
32
|
-
Passing Version class name as `has_paper_trail class_name: %{class_name}`
|
|
33
|
-
is deprecated. Use `has_paper_trail versions: {class_name: %{class_name}}`
|
|
34
|
-
instead. The hash you pass to `versions:` is now passed directly to `has_many`.
|
|
35
|
-
STR
|
|
36
|
-
|
|
37
|
-
def initialize(model_class)
|
|
38
|
-
@model_class = model_class
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
# Adds a callback that records a version after a "create" event.
|
|
42
|
-
#
|
|
43
|
-
# @api public
|
|
44
|
-
def on_create
|
|
45
|
-
@model_class.after_create { |r|
|
|
46
|
-
r.paper_trail.record_create if r.paper_trail.save_version?
|
|
47
|
-
}
|
|
48
|
-
return if @model_class.paper_trail_options[:on].include?(:create)
|
|
49
|
-
@model_class.paper_trail_options[:on] << :create
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
# Adds a callback that records a version before or after a "destroy" event.
|
|
53
|
-
#
|
|
54
|
-
# @api public
|
|
55
|
-
def on_destroy(recording_order = "before")
|
|
56
|
-
unless %w[after before].include?(recording_order.to_s)
|
|
57
|
-
raise ArgumentError, 'recording order can only be "after" or "before"'
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
if recording_order.to_s == "after" && cannot_record_after_destroy?
|
|
61
|
-
raise E_CANNOT_RECORD_AFTER_DESTROY
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
@model_class.send(
|
|
65
|
-
"#{recording_order}_destroy",
|
|
66
|
-
lambda do |r|
|
|
67
|
-
return unless r.paper_trail.save_version?
|
|
68
|
-
r.paper_trail.record_destroy(recording_order)
|
|
69
|
-
end
|
|
70
|
-
)
|
|
71
|
-
|
|
72
|
-
return if @model_class.paper_trail_options[:on].include?(:destroy)
|
|
73
|
-
@model_class.paper_trail_options[:on] << :destroy
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
# Adds a callback that records a version after an "update" event.
|
|
77
|
-
#
|
|
78
|
-
# @api public
|
|
79
|
-
def on_update
|
|
80
|
-
@model_class.before_save { |r|
|
|
81
|
-
r.paper_trail.reset_timestamp_attrs_for_update_if_needed
|
|
82
|
-
}
|
|
83
|
-
@model_class.after_update { |r|
|
|
84
|
-
if r.paper_trail.save_version?
|
|
85
|
-
r.paper_trail.record_update(
|
|
86
|
-
force: false,
|
|
87
|
-
in_after_callback: true,
|
|
88
|
-
is_touch: false
|
|
89
|
-
)
|
|
90
|
-
end
|
|
91
|
-
}
|
|
92
|
-
@model_class.after_update { |r|
|
|
93
|
-
r.paper_trail.clear_version_instance
|
|
94
|
-
}
|
|
95
|
-
return if @model_class.paper_trail_options[:on].include?(:update)
|
|
96
|
-
@model_class.paper_trail_options[:on] << :update
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
# Adds a callback that records a version after a "touch" event.
|
|
100
|
-
# @api public
|
|
101
|
-
def on_touch
|
|
102
|
-
@model_class.after_touch { |r|
|
|
103
|
-
r.paper_trail.record_update(
|
|
104
|
-
force: true,
|
|
105
|
-
in_after_callback: true,
|
|
106
|
-
is_touch: true
|
|
107
|
-
)
|
|
108
|
-
}
|
|
109
|
-
end
|
|
110
|
-
|
|
111
|
-
# Set up `@model_class` for PaperTrail. Installs callbacks, associations,
|
|
112
|
-
# "class attributes", instance methods, and more.
|
|
113
|
-
# @api private
|
|
114
|
-
def setup(options = {})
|
|
115
|
-
options[:on] ||= %i[create update destroy touch]
|
|
116
|
-
options[:on] = Array(options[:on]) # Support single symbol
|
|
117
|
-
@model_class.send :include, ::PaperTrail::Model::InstanceMethods
|
|
118
|
-
setup_options(options)
|
|
119
|
-
setup_associations(options)
|
|
120
|
-
check_presence_of_item_subtype_column(options)
|
|
121
|
-
@model_class.after_rollback { paper_trail.clear_rolled_back_versions }
|
|
122
|
-
setup_callbacks_from_options options[:on]
|
|
123
|
-
end
|
|
124
|
-
|
|
125
|
-
def version_class
|
|
126
|
-
@_version_class ||= @model_class.version_class_name.constantize
|
|
127
|
-
end
|
|
128
|
-
|
|
129
|
-
private
|
|
130
|
-
|
|
131
|
-
# Raises an error if the provided class is an `abstract_class`.
|
|
132
|
-
# @api private
|
|
133
|
-
def assert_concrete_activerecord_class(class_name)
|
|
134
|
-
if class_name.constantize.abstract_class?
|
|
135
|
-
raise format(E_HPT_ABSTRACT_CLASS, @model_class, class_name)
|
|
136
|
-
end
|
|
137
|
-
end
|
|
138
|
-
|
|
139
|
-
def cannot_record_after_destroy?
|
|
140
|
-
::ActiveRecord::Base.belongs_to_required_by_default
|
|
141
|
-
end
|
|
142
|
-
|
|
143
|
-
# Some options require the presence of the `item_subtype` column. Currently
|
|
144
|
-
# only `limit`, but in the future there may be others.
|
|
145
|
-
#
|
|
146
|
-
# @api private
|
|
147
|
-
def check_presence_of_item_subtype_column(options)
|
|
148
|
-
return unless options.key?(:limit)
|
|
149
|
-
return if version_class.item_subtype_column_present?
|
|
150
|
-
raise format(E_MODEL_LIMIT_REQUIRES_ITEM_SUBTYPE, @model_class.name)
|
|
151
|
-
end
|
|
152
|
-
|
|
153
|
-
def check_version_class_name(options)
|
|
154
|
-
# @api private - `version_class_name`
|
|
155
|
-
@model_class.class_attribute :version_class_name
|
|
156
|
-
if options[:class_name]
|
|
157
|
-
::ActiveSupport::Deprecation.warn(
|
|
158
|
-
format(
|
|
159
|
-
DPR_CLASS_NAME_OPTION,
|
|
160
|
-
class_name: options[:class_name].inspect
|
|
161
|
-
),
|
|
162
|
-
caller(1)
|
|
163
|
-
)
|
|
164
|
-
options[:versions][:class_name] = options[:class_name]
|
|
165
|
-
end
|
|
166
|
-
@model_class.version_class_name = options[:versions][:class_name] || "PaperTrail::Version"
|
|
167
|
-
end
|
|
168
|
-
|
|
169
|
-
def check_versions_association_name(options)
|
|
170
|
-
# @api private - versions_association_name
|
|
171
|
-
@model_class.class_attribute :versions_association_name
|
|
172
|
-
@model_class.versions_association_name = options[:versions][:name] || :versions
|
|
173
|
-
end
|
|
174
|
-
|
|
175
|
-
def define_has_many_mongo_versions(options)
|
|
5
|
+
def define_has_many_versions(options)
|
|
176
6
|
options = ensure_versions_option_is_hash(options)
|
|
177
7
|
check_version_class_name(options)
|
|
178
8
|
check_versions_association_name(options)
|
|
@@ -184,66 +14,5 @@ module PaperTrail
|
|
|
184
14
|
end
|
|
185
15
|
RUBY
|
|
186
16
|
end
|
|
187
|
-
|
|
188
|
-
def ensure_versions_option_is_hash(options)
|
|
189
|
-
unless options[:versions].is_a?(Hash)
|
|
190
|
-
if options[:versions]
|
|
191
|
-
::ActiveSupport::Deprecation.warn(
|
|
192
|
-
format(
|
|
193
|
-
DPR_PASSING_ASSOC_NAME_DIRECTLY_TO_VERSIONS_OPTION,
|
|
194
|
-
versions_name: options[:versions].inspect
|
|
195
|
-
),
|
|
196
|
-
caller(1)
|
|
197
|
-
)
|
|
198
|
-
end
|
|
199
|
-
options[:versions] = {
|
|
200
|
-
name: options[:versions]
|
|
201
|
-
}
|
|
202
|
-
end
|
|
203
|
-
options
|
|
204
|
-
end
|
|
205
|
-
|
|
206
|
-
def get_versions_scope(options)
|
|
207
|
-
options[:versions][:scope] || -> { order(model.timestamp_sort_order) }
|
|
208
|
-
end
|
|
209
|
-
|
|
210
|
-
def setup_associations(options)
|
|
211
|
-
# @api private - version_association_name
|
|
212
|
-
@model_class.class_attribute :version_association_name
|
|
213
|
-
@model_class.version_association_name = options[:version] || :version
|
|
214
|
-
|
|
215
|
-
# The version this instance was reified from.
|
|
216
|
-
# @api public
|
|
217
|
-
@model_class.send :attr_accessor, @model_class.version_association_name
|
|
218
|
-
|
|
219
|
-
# @api public - paper_trail_event
|
|
220
|
-
@model_class.send :attr_accessor, :paper_trail_event
|
|
221
|
-
|
|
222
|
-
define_has_many_mongo_versions(options)
|
|
223
|
-
end
|
|
224
|
-
|
|
225
|
-
def setup_callbacks_from_options(options_on = [])
|
|
226
|
-
options_on.each do |event|
|
|
227
|
-
public_send("on_#{event}")
|
|
228
|
-
end
|
|
229
|
-
end
|
|
230
|
-
|
|
231
|
-
def setup_options(options)
|
|
232
|
-
# @api public - paper_trail_options - Let's encourage plugins to use eg.
|
|
233
|
-
# `paper_trail_options[:versions][:class_name]` rather than
|
|
234
|
-
# `version_class_name` because the former is documented and the latter is
|
|
235
|
-
# not.
|
|
236
|
-
@model_class.class_attribute :paper_trail_options
|
|
237
|
-
@model_class.paper_trail_options = options.dup
|
|
238
|
-
|
|
239
|
-
%i[ignore skip only].each do |k|
|
|
240
|
-
@model_class.paper_trail_options[k] = [@model_class.paper_trail_options[k]].
|
|
241
|
-
flatten.
|
|
242
|
-
compact.
|
|
243
|
-
map { |attr| attr.is_a?(Hash) ? attr.stringify_keys : attr.to_s }
|
|
244
|
-
end
|
|
245
|
-
|
|
246
|
-
@model_class.paper_trail_options[:meta] ||= {}
|
|
247
|
-
end
|
|
248
17
|
end
|
|
249
18
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'mongoid'
|
|
2
4
|
|
|
3
5
|
Mongoid.configure do |config|
|
|
@@ -6,4 +8,8 @@ Mongoid.configure do |config|
|
|
|
6
8
|
config.log_level = :error
|
|
7
9
|
end
|
|
8
10
|
|
|
9
|
-
|
|
11
|
+
if defined?(Mongo::QueryCache)
|
|
12
|
+
Mongo::QueryCache.enabled = false
|
|
13
|
+
elsif defined?(Mongoid::QueryCache)
|
|
14
|
+
Mongoid::QueryCache.enabled = false
|
|
15
|
+
end
|
|
@@ -1,20 +1,129 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'mongoid'
|
|
4
|
+
require 'time'
|
|
5
|
+
require 'mongo_trails/mongo_support/version_commit_wrap'
|
|
6
|
+
|
|
7
|
+
begin
|
|
8
|
+
require 'sidekiq'
|
|
9
|
+
require 'mongo_trails/mongo_support/write_version_worker'
|
|
10
|
+
require 'mongo_trails/mongo_support/criteria'
|
|
11
|
+
rescue LoadError
|
|
12
|
+
# Continue without Sidekiq
|
|
13
|
+
end
|
|
3
14
|
|
|
4
15
|
class AutoIncrementCounters
|
|
5
16
|
include Mongoid::Document
|
|
17
|
+
|
|
18
|
+
store_in collection: 'auto_increment_counters'
|
|
19
|
+
|
|
20
|
+
field :sequence, type: Integer, default: 0
|
|
6
21
|
end
|
|
7
22
|
|
|
8
|
-
|
|
23
|
+
PaperTrail.config.has_paper_trail_defaults = { versions: { class_name: 'MongoTrails::Version' } }
|
|
24
|
+
|
|
25
|
+
module MongoTrails
|
|
9
26
|
class Version
|
|
27
|
+
class << self
|
|
28
|
+
def find(id)
|
|
29
|
+
find_by(integer_id: id)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def incrementing_fields
|
|
33
|
+
@incrementing_fields ||= {
|
|
34
|
+
integer_id: {
|
|
35
|
+
scope: -> { MongoTrails::Version.prefix_map },
|
|
36
|
+
step: 1
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def prefix_map
|
|
42
|
+
(PaperTrail.config.mongo_prefix.is_a?(Proc) && PaperTrail.config.mongo_prefix.call) || 'paper_trail'
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def next_integer_id
|
|
46
|
+
next_integer_ids(1).first
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def next_integer_ids(count, scope: prefix_map)
|
|
50
|
+
return [] if count.to_i <= 0
|
|
51
|
+
|
|
52
|
+
counter_id = counter_key(scope)
|
|
53
|
+
ensure_counter_initialized(counter_id, scope)
|
|
54
|
+
|
|
55
|
+
counter = AutoIncrementCounters.collection.find(_id: counter_id).find_one_and_update(
|
|
56
|
+
{ '$inc' => { sequence: count } },
|
|
57
|
+
upsert: true,
|
|
58
|
+
return_document: :after
|
|
59
|
+
)
|
|
60
|
+
max_integer_id = counter.fetch('sequence')
|
|
61
|
+
|
|
62
|
+
((max_integer_id - count + 1)..max_integer_id).to_a
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def bulk_insert_attributes(version)
|
|
66
|
+
version = version.is_a?(self) ? version : new(version)
|
|
67
|
+
attrs = version.attributes.dup
|
|
68
|
+
attrs.delete('_id') if attrs['_id'].nil?
|
|
69
|
+
attrs
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def table_name; end
|
|
73
|
+
|
|
74
|
+
def abstract_class?
|
|
75
|
+
false
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def columns_hash
|
|
79
|
+
fields
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def column_names
|
|
83
|
+
fields.keys
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def belongs_to(_name, _options = {}, &block); end
|
|
87
|
+
|
|
88
|
+
def validates_presence_of(_name); end
|
|
89
|
+
|
|
90
|
+
def after_create(_name); end
|
|
91
|
+
|
|
92
|
+
private
|
|
93
|
+
|
|
94
|
+
def counter_key(scope)
|
|
95
|
+
"#{name}:#{scope || prefix_map}"
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def ensure_counter_initialized(counter_id, _scope)
|
|
99
|
+
existing_counter = AutoIncrementCounters.collection.find(_id: counter_id).first
|
|
100
|
+
return if existing_counter.present?
|
|
101
|
+
|
|
102
|
+
max_id = maximum_existing_integer_id
|
|
103
|
+
|
|
104
|
+
AutoIncrementCounters.collection.find(_id: counter_id).find_one_and_update(
|
|
105
|
+
{ '$setOnInsert' => { sequence: max_id } },
|
|
106
|
+
upsert: true,
|
|
107
|
+
return_document: :after
|
|
108
|
+
)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def maximum_existing_integer_id
|
|
112
|
+
pipeline = [
|
|
113
|
+
{ '$match' => {} },
|
|
114
|
+
{ '$group' => { _id: nil, max_integer_id: { '$max' => '$integer_id' } } }
|
|
115
|
+
]
|
|
116
|
+
Version.collection.aggregate(pipeline).first&.dig('max_integer_id').to_i
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
10
120
|
include PaperTrail::VersionConcern
|
|
11
121
|
include Mongoid::Document
|
|
12
|
-
include Mongoid::Autoinc
|
|
13
122
|
|
|
14
|
-
store_in collection: ->
|
|
123
|
+
store_in collection: -> { "#{MongoTrails::Version.prefix_map}_versions" }
|
|
15
124
|
|
|
16
125
|
field :item_type, type: String
|
|
17
|
-
field :item_id, type:
|
|
126
|
+
field :item_id, type: String
|
|
18
127
|
field :event, type: String
|
|
19
128
|
field :whodunnit, type: String
|
|
20
129
|
field :object, type: Hash
|
|
@@ -22,35 +131,149 @@ module PaperTrail
|
|
|
22
131
|
field :created_at, type: DateTime
|
|
23
132
|
field :integer_id, type: Integer
|
|
24
133
|
|
|
25
|
-
|
|
134
|
+
index({ item_type: -1, item_id: -1 }, { background: true })
|
|
26
135
|
|
|
27
|
-
|
|
28
|
-
def reset
|
|
29
|
-
Mongoid::QueryCache.clear_cache
|
|
30
|
-
end
|
|
136
|
+
before_create :assign_integer_id
|
|
31
137
|
|
|
32
|
-
|
|
33
|
-
find_by(integer_id: id)
|
|
34
|
-
end
|
|
138
|
+
attr_accessor :mongo_trails_source_item
|
|
35
139
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
140
|
+
def save_version
|
|
141
|
+
schedule_version_persistence(bang: false)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def save_version!
|
|
145
|
+
schedule_version_persistence(bang: true)
|
|
39
146
|
end
|
|
40
147
|
|
|
41
148
|
def initialize(data)
|
|
149
|
+
data = data.to_h.deep_symbolize_keys
|
|
42
150
|
item = data.delete(:item)
|
|
43
151
|
if item.present?
|
|
44
152
|
data[:item_type] = item.class.name
|
|
45
153
|
data[:item_id] = item.id
|
|
46
154
|
end
|
|
47
|
-
data[:created_at] =
|
|
155
|
+
data[:created_at] = normalize_created_at(data[:created_at])
|
|
48
156
|
|
|
49
157
|
super
|
|
50
158
|
end
|
|
51
159
|
|
|
52
160
|
def item
|
|
53
|
-
item_type.constantize.
|
|
161
|
+
item_type.constantize.find_by(id: item_id)
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def object=(value)
|
|
165
|
+
super(escape_value(value))
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def object
|
|
169
|
+
unescape_value(super)
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def object_changes
|
|
173
|
+
unescape_value(super)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def object_changes=(value)
|
|
177
|
+
super(escape_value(value))
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def unescape_value(value)
|
|
181
|
+
value&.deep_transform_keys { |key| parser.unescape(force_utf8(key)) }
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def escape_value(value)
|
|
185
|
+
value&.deep_transform_keys { |key| parser.escape(force_utf8(key.to_s), /[$.]/) }
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
private
|
|
189
|
+
|
|
190
|
+
def schedule_version_persistence(bang:)
|
|
191
|
+
connection = transaction_connection
|
|
192
|
+
return persist_version(self, bang:) unless connection.transaction_open?
|
|
193
|
+
unless mongo_trails_source_item
|
|
194
|
+
return VersionCommitWrap.new { persist_version(self, bang:) }.add_to_transaction(connection)
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
if (propagation = callback_propagation)
|
|
198
|
+
merge_callback_propagation(propagation, connection:, bang:)
|
|
199
|
+
else
|
|
200
|
+
register_callback_propagation(connection:, bang:)
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def transaction_connection
|
|
205
|
+
mongo_trails_source_item ? mongo_trails_source_item.class.connection : ActiveRecord::Base.connection
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def callback_propagation
|
|
209
|
+
return callback_propagations[event] unless event == 'update'
|
|
210
|
+
|
|
211
|
+
callback_propagations[event] || callback_propagations['create']
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def callback_propagations
|
|
215
|
+
mongo_trails_source_item.instance_variable_get(:@mongo_trails_callback_propagations) ||
|
|
216
|
+
mongo_trails_source_item.instance_variable_set(:@mongo_trails_callback_propagations, {})
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def register_callback_propagation(connection:, bang:)
|
|
220
|
+
propagation = CallbackPropagation.new(mongo_trails_source_item, self, bang:)
|
|
221
|
+
callback_propagations[event] = propagation
|
|
222
|
+
VersionCommitWrap.new(rollback: -> { propagation.clear }) { propagation.persist }.add_to_transaction(connection)
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def merge_callback_propagation(propagation, connection:, bang:)
|
|
226
|
+
snapshot = propagation.snapshot
|
|
227
|
+
VersionCommitWrap.new(rollback: -> { propagation.restore!(snapshot) }) {}.add_to_transaction(connection)
|
|
228
|
+
propagation.merge!(self, bang:)
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
def assign_integer_id
|
|
232
|
+
self.integer_id ||= self.class.next_integer_id
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def normalize_created_at(value)
|
|
236
|
+
return value if value.is_a?(Time) || value.is_a?(DateTime)
|
|
237
|
+
return current_time if value.nil?
|
|
238
|
+
|
|
239
|
+
parse_time(value)
|
|
240
|
+
rescue ArgumentError
|
|
241
|
+
current_time
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def persist_version(version, bang: false)
|
|
245
|
+
return version.send(:async_save!) if sidekiq_enabled?
|
|
246
|
+
|
|
247
|
+
bang ? version.save! : version.save
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def parse_time(value)
|
|
251
|
+
time_zone&.parse(value.to_s) || Time.parse(value.to_s)
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
def current_time = time_zone&.now || Time.now
|
|
255
|
+
def time_zone = Time.zone
|
|
256
|
+
|
|
257
|
+
def sidekiq_enabled?
|
|
258
|
+
defined?(Sidekiq) && PaperTrail.config.enable_sidekiq
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def force_utf8(value)
|
|
262
|
+
value.respond_to?(:force_encoding) ? value.dup.force_encoding('UTF-8') : value
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
def parser
|
|
266
|
+
@parser ||= URI::Parser.new
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
def async_save!
|
|
270
|
+
worker = defined?(PaperTrail.config.sidekiq_worker.queue) ? PaperTrail.config.sidekiq_worker : PaperTrail::WriteVersionWorker
|
|
271
|
+
args = attributes.as_json
|
|
272
|
+
if worker == PaperTrail::WriteVersionWorker
|
|
273
|
+
worker.set(PaperTrail.config.sidekiq_options).perform_async(args)
|
|
274
|
+
else
|
|
275
|
+
worker.perform_async(args)
|
|
276
|
+
end
|
|
54
277
|
end
|
|
55
278
|
end
|
|
56
279
|
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MongoTrails
|
|
4
|
+
class VersionCommitWrap
|
|
5
|
+
def initialize(rollback: nil, &callback)
|
|
6
|
+
@callback = callback
|
|
7
|
+
@rollback = rollback
|
|
8
|
+
@executed = false
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def has_transactional_callbacks? # rubocop:disable Naming/PredicatePrefix
|
|
12
|
+
true
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def trigger_transactional_callbacks?
|
|
16
|
+
true
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def before_committed!; end
|
|
20
|
+
|
|
21
|
+
def rolledback!(*)
|
|
22
|
+
@executed = true
|
|
23
|
+
@rollback&.call
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def committed!(**)
|
|
27
|
+
return if @executed
|
|
28
|
+
|
|
29
|
+
@executed = true
|
|
30
|
+
@callback.call
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def add_to_transaction(connection = ActiveRecord::Base.connection)
|
|
34
|
+
connection.add_transaction_record(self)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|