mongo_trails 17.0.0 → 17.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 47992865a22a08835378021027077b45f22379edf107657c58ceef30a7434af8
4
- data.tar.gz: c6f4f9b31d6ea8cb9aee967a82eacb9fe25b65b8dcec66bb106825a705ed5c13
3
+ metadata.gz: 56dbb9f210528411b5b75400afafe8cedb67a22c9fe815534cf5702ffde9dfc0
4
+ data.tar.gz: 1b7e64ade5beddbaedb94c28ce4643373314994776e94a67e4af4b2340773b53
5
5
  SHA512:
6
- metadata.gz: eed6a890f004082f77f1cff771e27e0dbf5644d658e396081eea482198eaefbbecaed03eebe79c3f9ded6bc13df2d5c9d88dc117cd31c6beb4fec261351b71de
7
- data.tar.gz: edad164d6a99840e7ac132cbf6d4497a8ff116dd131b2d02e1a1f1f82102b187978b993cef5974962ecbd0fbb6b8abd9602c134a10ab092e5e097126dee6e02b
6
+ metadata.gz: 87875ed778feadc9e4d0b36716e12beb84fddf893aae69b222140cbaccf2dfb9ad600c2ef12da86d76c5c1340b25308ba1c138e13d07cfa11ca23f2a10175159
7
+ data.tar.gz: f034ef6b1babac2f7952d12865730e9bf7cbc1c5bcd00f8f1e570d5ad7ae2773c83aaec8167e40d3341b2670d709854ae8973191de018e63ee13343f3b5883d8
data/README.md CHANGED
@@ -43,6 +43,21 @@ Done!
43
43
  `PaperTrail.config.sidekiq_worker = PaperTrail::WriteVersionWorker` - Worker class
44
44
  `PaperTrail.config.sidekiq_options = { queue: :default }` - Options for Sidekiq
45
45
 
46
+ `PaperTrail.config.enable_integer_id = true` - Give every version a sequential `integer_id` from a per-collection counter, and order previous/next versions by it. With `false` no counter is used, `integer_id` stays empty and versions are ordered by `created_at`. The first write to a collection with no counter scans the whole collection for its highest `integer_id`, which is expensive on large collections (and on DocumentDB, where no index serves it).
47
+
48
+ `PaperTrail.config.on_write_error = nil` - A callable receiving `(error, version)` when a version write fails because MongoDB is unreachable or drops the connection (`MongoTrails::Version::WRITE_ERRORS`). Inside a transaction versions are written from the after-commit callbacks, so a raising write skips every after-commit callback behind it; a handler lets them run. Without a handler the error raises. Other errors always raise.
49
+
50
+ ```ruby
51
+ PaperTrail.config.on_write_error = lambda do |error, version|
52
+ ErrorReporter.report(error, item_type: version.item_type, item_id: version.item_id)
53
+ version.enqueue_write unless PaperTrail.config.enable_sidekiq # retry through Sidekiq
54
+ end
55
+ ```
56
+
57
+ `PaperTrail::WriteVersionWorker` rebuilds the queued version with `MongoTrails::Version.rebuild` (its `_id`, `created_at` and metadata are kept, `#initialize` is not run again), and treats a version whose `_id` is already stored as written, so a retried write is safe.
58
+
59
+ `MongoTrails::Version.bulk_insert(versions)` - Inserts many versions in one `insert_many`, allocating their `integer_id`s in one counter update when `enable_integer_id` is on.
60
+
46
61
  ## Releasing
47
62
 
48
63
  Update `MongoTrails::VERSION` and all appraisal lockfiles, then validate and release:
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- mongo_trails (17.0.0)
4
+ mongo_trails (17.1.0)
5
5
  mongoid (>= 7, < 10)
6
6
  ostruct
7
7
  paper_trail (>= 16, < 18)
@@ -172,7 +172,7 @@ CHECKSUMS
172
172
  mcp (0.8.0) sha256=ae8bd146bb8e168852866fd26f805f52744f6326afb3211e073f78a95e0c34fb
173
173
  minitest (5.27.0) sha256=2d3b17f8a36fe7801c1adcffdbc38233b938eb0b4966e97a6739055a45fa77d5
174
174
  mongo (2.25.0) sha256=b4663884d23523d24f50c579846956444410f939093e8c2a95fa4b76bb929053
175
- mongo_trails (17.0.0)
175
+ mongo_trails (17.1.0)
176
176
  mongoid (9.1.0) sha256=ef7b50f4c84e636850c3431bc3c92f98481f4e1d147ff06a1c21242d0b242a9e
177
177
  ostruct (0.6.3) sha256=95a2ed4a4bd1d190784e666b47b2d3f078e4a9efda2fccf18f84ddc6538ed912
178
178
  paper_trail (17.0.0) sha256=1c2842061d3874ca7015908e821e2aa14f9b982af2acb2a7974713bf79021c85
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- mongo_trails (17.0.0)
4
+ mongo_trails (17.1.0)
5
5
  mongoid (>= 7, < 10)
6
6
  ostruct
7
7
  paper_trail (>= 16, < 18)
@@ -172,7 +172,7 @@ CHECKSUMS
172
172
  mcp (0.8.0) sha256=ae8bd146bb8e168852866fd26f805f52744f6326afb3211e073f78a95e0c34fb
173
173
  minitest (5.27.0) sha256=2d3b17f8a36fe7801c1adcffdbc38233b938eb0b4966e97a6739055a45fa77d5
174
174
  mongo (2.25.0) sha256=b4663884d23523d24f50c579846956444410f939093e8c2a95fa4b76bb929053
175
- mongo_trails (17.0.0)
175
+ mongo_trails (17.1.0)
176
176
  mongoid (9.1.0) sha256=ef7b50f4c84e636850c3431bc3c92f98481f4e1d147ff06a1c21242d0b242a9e
177
177
  ostruct (0.6.3) sha256=95a2ed4a4bd1d190784e666b47b2d3f078e4a9efda2fccf18f84ddc6538ed912
178
178
  paper_trail (17.0.0) sha256=1c2842061d3874ca7015908e821e2aa14f9b982af2acb2a7974713bf79021c85
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- mongo_trails (17.0.0)
4
+ mongo_trails (17.1.0)
5
5
  mongoid (>= 7, < 10)
6
6
  ostruct
7
7
  paper_trail (>= 16, < 18)
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- mongo_trails (17.0.0)
4
+ mongo_trails (17.1.0)
5
5
  mongoid (>= 7, < 10)
6
6
  ostruct
7
7
  paper_trail (>= 16, < 18)
@@ -175,7 +175,7 @@ CHECKSUMS
175
175
  mcp (0.8.0) sha256=ae8bd146bb8e168852866fd26f805f52744f6326afb3211e073f78a95e0c34fb
176
176
  minitest (5.27.0) sha256=2d3b17f8a36fe7801c1adcffdbc38233b938eb0b4966e97a6739055a45fa77d5
177
177
  mongo (2.25.0) sha256=b4663884d23523d24f50c579846956444410f939093e8c2a95fa4b76bb929053
178
- mongo_trails (17.0.0)
178
+ mongo_trails (17.1.0)
179
179
  mongoid (9.1.0) sha256=ef7b50f4c84e636850c3431bc3c92f98481f4e1d147ff06a1c21242d0b242a9e
180
180
  ostruct (0.6.3) sha256=95a2ed4a4bd1d190784e666b47b2d3f078e4a9efda2fccf18f84ddc6538ed912
181
181
  paper_trail (16.0.0) sha256=e9b9f0fb1b8b590c8231cfa931b282ba92f90e066e393930a5e1c61ae4c5019d
@@ -8,7 +8,9 @@ module PaperTrail
8
8
  :enable_sidekiq,
9
9
  :sidekiq_worker,
10
10
  :sidekiq_options,
11
- :mongo_trails_config
11
+ :mongo_trails_config,
12
+ :enable_integer_id,
13
+ :on_write_error
12
14
  )
13
15
 
14
16
  def initialize
@@ -19,6 +21,8 @@ module PaperTrail
19
21
  @has_paper_trail_defaults = {}
20
22
  @enable_sidekiq = false
21
23
  @sidekiq_options = { queue: :default, retry: true, backtrace: false }
24
+ @enable_integer_id = true
25
+ @on_write_error = nil
22
26
  end
23
27
  end
24
28
  end
@@ -24,9 +24,35 @@ PaperTrail.config.has_paper_trail_defaults = { versions: { class_name: 'MongoTra
24
24
 
25
25
  module MongoTrails
26
26
  class Version
27
+ # Errors a version write can raise when MongoDB is unreachable or drops the connection. Only these reach
28
+ # PaperTrail.config.on_write_error; anything else (a validation error, a bug) still raises.
29
+ WRITE_ERRORS = [Mongo::Error, IOError, SystemCallError, Timeout::Error].freeze
30
+ DUPLICATE_KEY_ERROR_CODE = 11_000
31
+
27
32
  class << self
28
33
  def find(id)
29
- find_by(integer_id: id)
34
+ integer_id_enabled? ? find_by(integer_id: id) : super
35
+ end
36
+
37
+ def integer_id_enabled?
38
+ PaperTrail.config.enable_integer_id != false
39
+ end
40
+
41
+ # PaperTrail orders previous/next versions by primary key when it is an integer, which here means integer_id.
42
+ # Without integer_id they are ordered by created_at.
43
+ def primary_key_is_int?
44
+ integer_id_enabled?
45
+ end
46
+
47
+ # Rebuilds a version from the JSON of one that was already built (see #enqueue_write), keeping its _id,
48
+ # created_at and metadata. It does not run #initialize, so build-time defaults are not applied a second time.
49
+ def rebuild(attributes)
50
+ document = attributes.to_h.stringify_keys.to_h { |name, value| [name, from_json_value(name, value)] }
51
+ instantiate(document).tap { |version| version.new_record = true }
52
+ end
53
+
54
+ def duplicate_key_error?(error)
55
+ error.respond_to?(:code) && error.code == DUPLICATE_KEY_ERROR_CODE
30
56
  end
31
57
 
32
58
  def incrementing_fields
@@ -62,6 +88,17 @@ module MongoTrails
62
88
  ((max_integer_id - count + 1)..max_integer_id).to_a
63
89
  end
64
90
 
91
+ def bulk_insert(versions)
92
+ return [] if versions.empty?
93
+
94
+ documents = versions.map { |version| bulk_insert_attributes(version) }
95
+ if integer_id_enabled?
96
+ integer_ids = next_integer_ids(documents.count { |document| document['integer_id'].nil? })
97
+ documents.each { |document| document['integer_id'] ||= integer_ids.shift }
98
+ end
99
+ collection.insert_many(documents)
100
+ end
101
+
65
102
  def bulk_insert_attributes(version)
66
103
  version = version.is_a?(self) ? version : new(version)
67
104
  attrs = version.attributes.dup
@@ -91,6 +128,17 @@ module MongoTrails
91
128
 
92
129
  private
93
130
 
131
+ # as_json turns ObjectIds and times into strings; every other field type survives JSON as is.
132
+ def from_json_value(name, value)
133
+ return value unless value.is_a?(String)
134
+
135
+ type = fields[name]&.type
136
+ return BSON::ObjectId.legal?(value) ? BSON::ObjectId.from_string(value) : value if type == BSON::ObjectId
137
+ return (Time.zone || Time).parse(value) if [Time, DateTime].include?(type)
138
+
139
+ value
140
+ end
141
+
94
142
  def counter_key(scope)
95
143
  "#{name}:#{scope || prefix_map}"
96
144
  end
@@ -152,6 +200,17 @@ module MongoTrails
152
200
  schedule_version_persistence(bang: true)
153
201
  end
154
202
 
203
+ # Queues the write of this version for PaperTrail::WriteVersionWorker (or the configured sidekiq_worker).
204
+ def enqueue_write
205
+ worker = defined?(PaperTrail.config.sidekiq_worker.queue) ? PaperTrail.config.sidekiq_worker : PaperTrail::WriteVersionWorker
206
+ args = attributes.as_json
207
+ if worker == PaperTrail::WriteVersionWorker
208
+ worker.set(PaperTrail.config.sidekiq_options).perform_async(args)
209
+ else
210
+ worker.perform_async(args)
211
+ end
212
+ end
213
+
155
214
  def initialize(data)
156
215
  data = data.to_h.deep_symbolize_keys
157
216
  item = data.delete(:item)
@@ -236,6 +295,8 @@ module MongoTrails
236
295
  end
237
296
 
238
297
  def assign_integer_id
298
+ return unless self.class.integer_id_enabled?
299
+
239
300
  self.integer_id ||= self.class.next_integer_id
240
301
  end
241
302
 
@@ -248,10 +309,19 @@ module MongoTrails
248
309
  current_time
249
310
  end
250
311
 
312
+ # Inside a transaction this runs from VersionCommitWrap#committed!, among the source record's after-commit
313
+ # callbacks, so an error raised here skips every callback queued behind it. PaperTrail.config.on_write_error
314
+ # receives the connection errors instead of letting them raise; without a handler they raise as before.
251
315
  def persist_version(version, bang: false)
252
- return version.send(:async_save!) if sidekiq_enabled?
316
+ return version.enqueue_write if sidekiq_enabled?
253
317
 
254
318
  bang ? version.save! : version.save
319
+ rescue *WRITE_ERRORS => e
320
+ handler = PaperTrail.config.on_write_error
321
+ raise unless handler
322
+
323
+ handler.call(e, version)
324
+ nil
255
325
  end
256
326
 
257
327
  def parse_time(value)
@@ -273,14 +343,7 @@ module MongoTrails
273
343
  @parser ||= URI::Parser.new
274
344
  end
275
345
 
276
- def async_save!
277
- worker = defined?(PaperTrail.config.sidekiq_worker.queue) ? PaperTrail.config.sidekiq_worker : PaperTrail::WriteVersionWorker
278
- args = attributes.as_json
279
- if worker == PaperTrail::WriteVersionWorker
280
- worker.set(PaperTrail.config.sidekiq_options).perform_async(args)
281
- else
282
- worker.perform_async(args)
283
- end
284
- end
346
+ # Kept for callers of the old private name; use #enqueue_write.
347
+ alias async_save! enqueue_write
285
348
  end
286
349
  end
@@ -4,8 +4,12 @@ module PaperTrail
4
4
  class WriteVersionWorker
5
5
  include Sidekiq::Job
6
6
 
7
+ # The job carries a version that was already built, so it is rebuilt as is rather than built again. A retry after a
8
+ # write whose acknowledgement was lost finds its _id already stored: the version is written, so the job is done.
7
9
  def perform(obj)
8
- MongoTrails::Version.new(obj).save!
10
+ MongoTrails::Version.rebuild(obj).save!
11
+ rescue Mongo::Error::OperationFailure => e
12
+ raise unless MongoTrails::Version.duplicate_key_error?(e)
9
13
  end
10
14
  end
11
15
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MongoTrails
4
- VERSION = '17.0.0'
4
+ VERSION = '17.1.0'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mongo_trails
3
3
  version: !ruby/object:Gem::Version
4
- version: 17.0.0
4
+ version: 17.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alex Tsirel