couch_potato 1.13.0 → 1.15.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: c36fc0534a7564b1fcf3b2a59ca91e3e4e97452c2a74d77e30540eb4b7617211
4
- data.tar.gz: b1583818a743459e8ab86d88350f7ad5c6953c058c3b49c8b90da50610bdd197
3
+ metadata.gz: ffcaae6b2815d237b48cc3e4ad553f66920fe4df2c84c59ae38177260949ba6b
4
+ data.tar.gz: bd266a1033fa83fcba822a19e3de86b2c1aac2d80ba2dd1d3aef0920d4819d8f
5
5
  SHA512:
6
- metadata.gz: ceffb27b13b2cf9825cd5fcf507bb0e13a6ed9b5caa612fd1f5f27ba98934eb9dae34c4f4dc70835352a9176bc4562e49da12d489b925ebbeb37a3c9eaa964f4
7
- data.tar.gz: c5e01a2d0d83ea11b14a618f218fe3ff66af35be893a4d74644089938b852045c52a8e1d8ecf31b53e70396737f9db0a8849ef0fbacd843c89137ec4d8bfc0e2
6
+ metadata.gz: 16f3e1748e3b3314f1cf3d658fb175dd90c5e7c7cd717ab42477961c364bac527b262642a29f208c4aad200e9a31ce7470c0a538db8bf8d641e13da92f85debb
7
+ data.tar.gz: '089f1c9e5dc7238b659a4d1b2d0f3d341a084fe52f6d9c2d058ea8440f197f7d64b6f5a9d78c8cebbd19b131aede038329ab6c0f79b503d220f1f20d2e7d8a20'
data/.gitignore CHANGED
@@ -7,3 +7,4 @@ pkg
7
7
 
8
8
  .ruby-version
9
9
  .tool-versions
10
+ .vscode/settings.json
data/CHANGES.md CHANGED
@@ -1,5 +1,14 @@
1
1
  ## Changes
2
2
 
3
+ # 1.15.0
4
+
5
+ - cache loading multiple documents
6
+ - keep the database cache when switching to another database and back to the original one
7
+
8
+ # 1.14.0
9
+
10
+ - add database_collection to models to help avoid n+1 requests
11
+
3
12
  # 1.13.0
4
13
 
5
14
  - add Ruby 3.2 support
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'active_support/core_ext/enumerable'
4
+
3
5
  module CouchPotato
4
6
  class Database
5
7
  class ValidationsFailedError < ::StandardError; end
@@ -143,7 +145,9 @@ module CouchPotato
143
145
  # returns nil if the single document could not be found. when passing an array and some documents
144
146
  # could not be found these are omitted from the returned array
145
147
  def load_document(id)
146
- cached = cache && id.is_a?(String) && cache[id]
148
+ return load_documents(id) if id.is_a?(Array)
149
+
150
+ cached = cache && cache[id]
147
151
  if cache
148
152
  if cached
149
153
  ActiveSupport::Notifications.instrument('couch_potato.load.cached') do
@@ -159,6 +163,23 @@ module CouchPotato
159
163
  end
160
164
  alias load load_document
161
165
 
166
+ def load_documents(ids)
167
+ return [] if ids.empty?
168
+
169
+ uncached_ids = ids - (cache&.keys || [])
170
+ uncached_docs_by_id = bulk_load(uncached_ids).index_by {|doc| doc.id if doc.respond_to?(:id) }
171
+ if cache
172
+ uncached_ids.each do |id|
173
+ doc = uncached_docs_by_id[id]
174
+ cache[id] = doc if doc
175
+ end
176
+ end
177
+ cached_docs_by_id = ActiveSupport::Notifications.instrument('couch_potato.load.cached') do
178
+ cache&.slice(*ids) || {}
179
+ end
180
+ ids.filter_map { |id| (cached_docs_by_id[id]) || uncached_docs_by_id[id] }
181
+ end
182
+
162
183
  # loads one or more documents by its id(s)
163
184
  # behaves like #load except it raises a CouchPotato::NotFound if any of the documents could not be found
164
185
  def load!(id)
@@ -176,6 +197,8 @@ module CouchPotato
176
197
  # returns the underlying CouchRest::Database instance
177
198
  attr_reader :couchrest_database
178
199
 
200
+ attr_accessor :switched_databases
201
+
179
202
  # returns a new database instance connected to the CouchDB database
180
203
  # with the given name. the name is passed through the
181
204
  # additional_databases configuration to resolve it to a database
@@ -183,21 +206,29 @@ module CouchPotato
183
206
  # if the current database has a cache, the new database will receive
184
207
  # a cleared copy of it.
185
208
  def switch_to(database_name)
186
- resolved_database_name = CouchPotato.resolve_database_name(database_name)
187
- self
209
+ self.switched_databases ||= {}
210
+ if (db = switched_databases[database_name || :default])
211
+ return db
212
+ end
213
+ switched_databases[name || :default] ||= self
214
+
215
+
216
+ resolved_database_name = CouchPotato.resolve_database_name(database_name) unless database_name == :default
217
+ couchrest_database = resolved_database_name ? CouchPotato.couchrest_database_for_name(resolved_database_name) : CouchPotato.couchrest_database
218
+ new_db = self
188
219
  .class
189
- .new(CouchPotato.couchrest_database_for_name(resolved_database_name), name: database_name)
220
+ .new(couchrest_database, name: database_name == :default ? nil : database_name)
190
221
  .tap(&copy_clear_cache_proc)
222
+
223
+ new_db.switched_databases = switched_databases
224
+ new_db
191
225
  end
192
226
 
193
227
  # returns a new database instance connected to the default CouchDB database.
194
228
  # if the current database has a cache, the new database will receive
195
229
  # a cleared copy of it.
196
230
  def switch_to_default
197
- self
198
- .class
199
- .new(CouchPotato.couchrest_database)
200
- .tap(&copy_clear_cache_proc)
231
+ switch_to(:default)
201
232
  end
202
233
 
203
234
  private
@@ -224,6 +255,7 @@ module CouchPotato
224
255
  elsif processed_results.respond_to?(:each)
225
256
  processed_results.each do |document|
226
257
  document.database = self if document.respond_to?(:database=)
258
+ document.database_collection = processed_results if document.respond_to?(:database_collection=)
227
259
  end
228
260
  end
229
261
  processed_results
@@ -247,14 +279,10 @@ module CouchPotato
247
279
  raise "Can't load a document without an id (got nil)" if id.nil?
248
280
 
249
281
  ActiveSupport::Notifications.instrument('couch_potato.load') do
250
- if id.is_a?(Array)
251
- bulk_load id
252
- else
253
- instance = couchrest_database.get(id)
254
- instance.database = self if instance
255
- instance
256
- end
257
- end
282
+ instance = couchrest_database.get(id)
283
+ instance.database = self if instance
284
+ instance
285
+ end
258
286
  end
259
287
 
260
288
  def view_cache_id(spec)
@@ -293,10 +321,13 @@ module CouchPotato
293
321
  def bulk_load(ids)
294
322
  return [] if ids.empty?
295
323
 
296
- response = couchrest_database.bulk_load ids
297
- docs = response['rows'].map { |row| row['doc'] }.compact
298
- docs.each do |doc|
299
- doc.database = self if doc.respond_to?(:database=)
324
+ ActiveSupport::Notifications.instrument('couch_potato.load') do
325
+ response = couchrest_database.bulk_load ids
326
+ docs = response['rows'].map { |row| row['doc'] }.compact
327
+ docs.each do |doc|
328
+ doc.database = self if doc.respond_to?(:database=)
329
+ doc.database_collection = docs if doc.respond_to?(:database_collection=)
330
+ end
300
331
  end
301
332
  end
302
333
 
@@ -27,7 +27,7 @@ module CouchPotato
27
27
  ForbiddenAttributesProtection, Revisions
28
28
  base.send :include, Validation
29
29
  base.class_eval do
30
- attr_accessor :_id, :_rev, :_deleted, :database
30
+ attr_accessor :_id, :_rev, :_deleted, :database, :database_collection
31
31
  alias_method :id, :_id
32
32
  alias_method :id=, :_id=
33
33
 
@@ -1,4 +1,4 @@
1
1
  module CouchPotato
2
- VERSION = '1.13.0'.freeze
2
+ VERSION = '1.15.0'.freeze
3
3
  RSPEC_VERSION = '4.1.0'.freeze
4
4
  end
@@ -73,9 +73,11 @@ module CouchPotato
73
73
  end
74
74
 
75
75
  def docs
76
+ all_docs = rows.map { |r| r['doc'] }
76
77
  rows.map do |row|
77
78
  doc = row['doc']
78
79
  doc.database = database if doc.respond_to?(:database=)
80
+ doc.database_collection = all_docs if doc.respond_to?(:database_collection=)
79
81
  doc
80
82
  end
81
83
  end
data/lib/couch_potato.rb CHANGED
@@ -54,7 +54,7 @@ module CouchPotato
54
54
  def self.use(database_name)
55
55
  resolved_database_name = resolve_database_name(database_name)
56
56
  Thread.current[:__couch_potato_databases] ||= {}
57
- Thread.current[:__couch_potato_databases][resolved_database_name] ||= Database.new(couchrest_database_for_name!(resolved_database_name), name: database_name)
57
+ Thread.current[:__couch_potato_databases][resolved_database_name] ||= Database.new(couchrest_database_for_name(resolved_database_name), name: database_name)
58
58
  end
59
59
 
60
60
  # resolves a name to a database name/full url configured under additional databases
data/spec/create_spec.rb CHANGED
@@ -34,14 +34,24 @@ describe "create" do
34
34
  end
35
35
 
36
36
  describe "multi-db" do
37
- TEST_DBS = ['comment_a', 'comment_b', 'comment_c']
37
+ let(:test_dbs) do
38
+ ['comment_a', 'comment_b', 'comment_c'].map do |name|
39
+ if ENV['DATABASE']
40
+ uri = URI.parse(ENV['DATABASE'])
41
+ uri.path = "/#{name}"
42
+ uri.to_s
43
+ else
44
+ name
45
+ end
46
+ end
47
+ end
38
48
 
39
49
  before(:each) do
40
- TEST_DBS.each { |db_name| CouchPotato.couchrest_database_for_name(db_name).recreate! }
50
+ test_dbs.each { |db_name| CouchPotato.couchrest_database_for_name(db_name).recreate! }
41
51
  end
42
52
 
43
53
  it "should create documents in multiple dbs" do
44
- TEST_DBS.each do |db_name|
54
+ test_dbs.each do |db_name|
45
55
  @comment = Comment.new(:title => 'my_title')
46
56
  CouchPotato.with_database(db_name) do |couch|
47
57
  couch.save_document! @comment
@@ -18,41 +18,92 @@ RSpec.describe 'database caching' do
18
18
  {}
19
19
  end
20
20
 
21
- it 'gets an object from the cache the 2nd time via #load_documemt' do
22
- expect(couchrest_db).to receive(:get).with('1').exactly(1).times
21
+ context 'for a single document' do
22
+ it 'gets an object from the cache the 2nd time via #load_documemt' do
23
+ expect(couchrest_db).to receive(:get).with('1').exactly(1).times
23
24
 
24
- db.load_document '1'
25
- db.load_document '1'
26
- end
25
+ db.load_document '1'
26
+ db.load_document '1'
27
+ end
27
28
 
28
- it 'gets an object from the cache the 2nd time via #load' do
29
- expect(couchrest_db).to receive(:get).with('1').exactly(1).times
29
+ it 'gets an object from the cache the 2nd time via #load' do
30
+ expect(couchrest_db).to receive(:get).with('1').exactly(1).times
30
31
 
31
- db.load '1'
32
- db.load '1'
33
- end
32
+ db.load '1'
33
+ db.load '1'
34
+ end
34
35
 
35
- it 'gets an object from the cache the 2nd time via #load!' do
36
- expect(couchrest_db).to receive(:get).with('1').exactly(1).times
36
+ it 'gets an object from the cache the 2nd time via #load!' do
37
+ expect(couchrest_db).to receive(:get).with('1').exactly(1).times
37
38
 
38
- db.load! '1'
39
- db.load! '1'
40
- end
39
+ db.load! '1'
40
+ db.load! '1'
41
+ end
41
42
 
42
- it 'returns the correct object' do
43
- doc = double(:doc, 'database=': nil)
44
- allow(couchrest_db).to receive_messages(get: doc)
43
+ it 'returns the correct object' do
44
+ doc = double(:doc, 'database=': nil)
45
+ allow(couchrest_db).to receive_messages(get: doc)
45
46
 
46
- db.load_document '1'
47
- expect(db.load_document('1')).to eql(doc)
47
+ db.load_document '1'
48
+ expect(db.load_document('1')).to eql(doc)
49
+ end
48
50
  end
49
51
 
50
- it 'does not cache bulk loads' do
51
- allow(couchrest_db).to receive_messages(bulk_load: {'rows' => []})
52
- expect(couchrest_db).to receive(:bulk_load).with(['1']).exactly(2).times
52
+ context 'for multiple documents' do
53
+ let(:doc1) { double(:doc1, 'database=': nil, id: '1') }
54
+ let(:doc2) { double(:doc12, 'database=': nil, id: '2') }
55
+
56
+ it 'only loads uncached documents' do
57
+ allow(couchrest_db).to receive(:bulk_load).with(['1']).and_return('rows' => [{'doc' => doc1}])
58
+ allow(couchrest_db).to receive(:bulk_load).with(['2']).and_return('rows' => [{'doc' => doc2}])
59
+
60
+
61
+ db.load_document(['1'])
62
+ db.load_document(['1', '2'])
63
+
64
+ expect(couchrest_db).to have_received(:bulk_load).with(['1']).exactly(1).times
65
+ expect(couchrest_db).to have_received(:bulk_load).with(['2']).exactly(1).times
66
+ end
67
+
68
+ it 'loads nothing if all documents are cached' do
69
+ allow(couchrest_db).to receive(:bulk_load).with(['1', '2'])
70
+ .and_return('rows' => [{'doc' => doc1}, {'doc' => doc2}])
71
+
72
+ db.load_document(['1', '2'])
73
+ db.load_document(['1', '2'])
74
+
75
+ expect(couchrest_db).to have_received(:bulk_load).with(['1', '2']).exactly(1).times
76
+ end
77
+
78
+ it 'returns all requested documents' do
79
+ allow(couchrest_db).to receive(:bulk_load).with(['1']).and_return('rows' => [{'doc' => doc1}])
80
+ allow(couchrest_db).to receive(:bulk_load).with(['2']).and_return('rows' => [{'doc' => doc2}])
81
+
82
+
83
+ db.load_document(['1'])
84
+ result = db.load_document(['1', '2'])
85
+
86
+ expect(result).to eql([doc1, doc2])
87
+ end
88
+
89
+ it 'does not cache documents that do not respond to id' do
90
+ doc1 = {
91
+ 'id' => '1',
92
+ }
93
+ doc2 = {
94
+ 'id' => '2',
95
+ }
96
+ allow(couchrest_db).to receive(:bulk_load).with(['1', '2'])
97
+ .and_return('rows' => [{'doc' => doc1}, {'doc' => doc1}])
98
+
99
+ db.load_document(['1', '2'])
100
+ db.load_document(['1', '2'])
101
+
102
+ expect(couchrest_db).to have_received(:bulk_load).with(['1', '2']).exactly(2).times
103
+ end
104
+ end
53
105
 
54
- db.load_document ['1']
55
- db.load_document ['1']
106
+ context 'when switching the database' do
56
107
  end
57
108
 
58
109
  it 'clears the cache when destroying a document via #destroy_document' do
@@ -56,14 +56,24 @@ describe CouchPotato::Database, 'load' do
56
56
  db.load '1'
57
57
  end
58
58
 
59
+ it 'does not set database_collection on the model' do
60
+ user = double('user', 'database_collection=': nil).as_null_object
61
+ allow(DbTestUser).to receive(:new).and_return(user)
62
+ allow(couchrest_db).to receive(:get).and_return DbTestUser.json_create({ JSON.create_id => 'DbTestUser' })
63
+
64
+ db.load '1'
65
+
66
+ expect(user).not_to have_received(:database_collection=).with(db)
67
+ end
68
+
59
69
  it 'should load namespaced models' do
60
70
  allow(couchrest_db).to receive(:get).and_return Parent::Child.json_create({ JSON.create_id => 'Parent::Child' })
61
71
  expect(db.load('1').class).to eq(Parent::Child)
62
72
  end
63
73
 
64
74
  context 'when several ids given' do
65
- let(:doc1) { DbTestUser.new }
66
- let(:doc2) { DbTestUser.new }
75
+ let(:doc1) { DbTestUser.new(id: '1') }
76
+ let(:doc2) { DbTestUser.new(id: '2') }
67
77
  let(:response) do
68
78
  { 'rows' => [{ 'doc' => nil }, { 'doc' => doc1 }, { 'doc' => doc2 }] }
69
79
  end
@@ -88,8 +98,8 @@ describe CouchPotato::Database, 'load' do
88
98
  end
89
99
 
90
100
  it 'does not write itself to a document that has no database= method' do
91
- doc1 = double(:doc1)
92
- allow(doc1).to receive(:respond_to?).with(:database=) { false }
101
+ doc1 = double(:doc1, id: '1')
102
+ allow(doc1).to receive(:respond_to?) { false }
93
103
  allow(couchrest_db).to receive(:bulk_load) do
94
104
  { 'rows' => [{ 'doc' => doc1 }] }
95
105
  end
@@ -99,6 +109,24 @@ describe CouchPotato::Database, 'load' do
99
109
  db.load(['1'])
100
110
  end
101
111
 
112
+ it 'sets database_collection on each of the documents' do
113
+ db.load(%w[1 2]).each do |doc|
114
+ expect(doc.database_collection).to eql([doc1, doc2])
115
+ end
116
+ end
117
+
118
+ it 'does not set database_collection on a document that has no database_collection= method' do
119
+ doc1 = double(:doc1, id: '1')
120
+ allow(doc1).to receive(:respond_to?) { false }
121
+ allow(couchrest_db).to receive(:bulk_load) do
122
+ { 'rows' => [{ 'doc' => doc1 }] }
123
+ end
124
+
125
+ expect(doc1).not_to receive(:database_collection=)
126
+
127
+ db.load(['1'])
128
+ end
129
+
102
130
  it 'returns an empty array when passing an empty array' do
103
131
  expect(db.load([])).to eq([])
104
132
  end
@@ -336,7 +364,7 @@ describe CouchPotato::Database, 'view' do
336
364
  allow(CouchPotato::View::ViewQuery).to receive_messages(new: double('view query', query_view!: { 'rows' => [@result] }))
337
365
  end
338
366
 
339
- it 'initialzes a view query with map/reduce/list/lib funtions' do
367
+ it 'initializes a view query with map/reduce/list/lib funtions' do
340
368
  allow(@spec).to receive_messages(design_document: 'design_doc', view_name: 'my_view',
341
369
  map_function: '<map_code>', reduce_function: '<reduce_code>',
342
370
  lib: { test: '<test_code>' },
@@ -355,7 +383,7 @@ describe CouchPotato::Database, 'view' do
355
383
  @db.view(@spec)
356
384
  end
357
385
 
358
- it 'initialzes a view query with map/reduce/list funtions' do
386
+ it 'initializes a view query with map/reduce/list funtions' do
359
387
  allow(@spec).to receive_messages(design_document: 'design_doc', view_name: 'my_view',
360
388
  map_function: '<map_code>', reduce_function: '<reduce_code>',
361
389
  lib: nil, list_name: 'my_list', list_function: '<list_code>',
@@ -374,7 +402,7 @@ describe CouchPotato::Database, 'view' do
374
402
  @db.view(@spec)
375
403
  end
376
404
 
377
- it 'initialzes a view query with only map/reduce/lib functions' do
405
+ it 'initializes a view query with only map/reduce/lib functions' do
378
406
  allow(@spec).to receive_messages(design_document: 'design_doc', view_name: 'my_view',
379
407
  map_function: '<map_code>', reduce_function: '<reduce_code>',
380
408
  list_name: nil, list_function: nil,
@@ -390,7 +418,7 @@ describe CouchPotato::Database, 'view' do
390
418
  @db.view(@spec)
391
419
  end
392
420
 
393
- it 'initialzes a view query with only map/reduce functions' do
421
+ it 'initializes a view query with only map/reduce functions' do
394
422
  allow(@spec).to receive_messages(design_document: 'design_doc', view_name: 'my_view',
395
423
  map_function: '<map_code>', reduce_function: '<reduce_code>',
396
424
  lib: nil, list_name: nil, list_function: nil)
@@ -405,18 +433,37 @@ describe CouchPotato::Database, 'view' do
405
433
  @db.view(@spec)
406
434
  end
407
435
 
408
- it 'sets itself on returned results that have an accessor' do
436
+ it 'sets itself on returned docs that have an accessor' do
437
+ allow(@result).to receive(:respond_to?).and_return(false)
409
438
  allow(@result).to receive(:respond_to?).with(:database=).and_return(true)
410
439
  expect(@result).to receive(:database=).with(@db)
411
440
  @db.view(@spec)
412
441
  end
413
442
 
414
- it "does not set itself on returned results that don't have an accessor" do
415
- allow(@result).to receive(:respond_to?).with(:database=).and_return(false)
443
+ it "does not set itself on returned docs that don't have an accessor" do
444
+ allow(@result).to receive(:respond_to?).and_return(false)
416
445
  expect(@result).not_to receive(:database=).with(@db)
417
446
  @db.view(@spec)
418
447
  end
419
448
 
449
+ it 'sets the result of the view call on each returned doc' do
450
+ allow(@result).to receive(:respond_to?).and_return(false)
451
+ allow(@result).to receive(:respond_to?).with(:database_collection=).and_return(true)
452
+ allow(@result).to receive(:database_collection=)
453
+
454
+ @db.view(@spec)
455
+
456
+ expect(@result).to have_received(:database_collection=).with([@result])
457
+ end
458
+
459
+ it "does not set the result of the view call on docs that don't have an accessor" do
460
+ allow(@result).to receive(:respond_to?).and_return(false)
461
+
462
+ @db.view(@spec)
463
+
464
+ expect(@result).not_to receive(:database_collection=).with([@result])
465
+ end
466
+
420
467
  it 'does not try to set itself on result sets that are not collections' do
421
468
  expect do
422
469
  allow(@spec).to receive_messages(process_results: 1)
@@ -524,6 +571,14 @@ describe CouchPotato::Database, '#switch_to' do
524
571
 
525
572
  expect(new_db.cache).to be_nil
526
573
  end
574
+
575
+ it 're-uses the original cache when switching back to the original database' do
576
+ db.cache = { key: 'value' }
577
+ new_db = db.switch_to('db2')
578
+ original_db = new_db.switch_to_default
579
+
580
+ expect(original_db.cache).to have_key(:key)
581
+ end
527
582
  end
528
583
 
529
584
  describe CouchPotato::Database, '#switch_to_default' do
@@ -15,3 +15,30 @@ RSpec.describe CouchPotato::View::FlexViewSpec::Results, '#reduce_count' do
15
15
  expect(result.reduce_count).to eq(0)
16
16
  end
17
17
  end
18
+
19
+ RSpec.describe CouchPotato::View::FlexViewSpec::Results, '#docs' do
20
+ it 'sets the database on each doc' do
21
+ db = double('db')
22
+ doc = double('doc', 'database=': nil)
23
+
24
+ result = CouchPotato::View::FlexViewSpec::Results.new 'rows' => [{ 'doc' => doc }]
25
+ result.database = db
26
+
27
+ result.docs
28
+
29
+ expect(doc).to have_received(:database=).with(db)
30
+ end
31
+
32
+ it 'sets all docs as database_collection on each doc' do
33
+ doc = double('doc', 'database_collection=': nil)
34
+
35
+ result = CouchPotato::View::FlexViewSpec::Results.new 'rows' => [{ 'doc' => doc }]
36
+
37
+ result.docs
38
+
39
+ expect(doc).to have_received(:database_collection=).with([doc])
40
+ end
41
+
42
+ it 'returns the docs' do
43
+ end
44
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: couch_potato
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.13.0
4
+ version: 1.15.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alexander Lang
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-01-18 00:00:00.000000000 Z
11
+ date: 2024-03-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemodel