couch_potato 1.14.0 → 1.15.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dd4dede2152745db1f3f0bf7ff3a94dfea3c7eb427beb02292034866dd7dac22
4
- data.tar.gz: 29ca77512b6bdeebcdd31847deac4957792946519dda4f658f60dd90c9dd83e4
3
+ metadata.gz: ffcaae6b2815d237b48cc3e4ad553f66920fe4df2c84c59ae38177260949ba6b
4
+ data.tar.gz: bd266a1033fa83fcba822a19e3de86b2c1aac2d80ba2dd1d3aef0920d4819d8f
5
5
  SHA512:
6
- metadata.gz: 21728691cd7eb4b23bd6600899655cc7fa1dbb6a15a6d40c669b53941fab30bf38cb8a95fa9a3e8f85e05d44af116f58cd20a424eedd60d417972db893c07aff
7
- data.tar.gz: d2d26373afd0bcce487b35703be3bd7bd08cc7e4e108f98e8279bf81c41715b30f5590fdf8080c4b136ec95e356c857b5c50bef123b6971e67294e79cdeb180a
6
+ metadata.gz: 16f3e1748e3b3314f1cf3d658fb175dd90c5e7c7cd717ab42477961c364bac527b262642a29f208c4aad200e9a31ce7470c0a538db8bf8d641e13da92f85debb
7
+ data.tar.gz: '089f1c9e5dc7238b659a4d1b2d0f3d341a084fe52f6d9c2d058ea8440f197f7d64b6f5a9d78c8cebbd19b131aede038329ab6c0f79b503d220f1f20d2e7d8a20'
data/CHANGES.md CHANGED
@@ -1,5 +1,10 @@
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
+
3
8
  # 1.14.0
4
9
 
5
10
  - add database_collection to models to help avoid n+1 requests
@@ -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
@@ -248,14 +279,10 @@ module CouchPotato
248
279
  raise "Can't load a document without an id (got nil)" if id.nil?
249
280
 
250
281
  ActiveSupport::Notifications.instrument('couch_potato.load') do
251
- if id.is_a?(Array)
252
- bulk_load id
253
- else
254
- instance = couchrest_database.get(id)
255
- instance.database = self if instance
256
- instance
257
- end
258
- end
282
+ instance = couchrest_database.get(id)
283
+ instance.database = self if instance
284
+ instance
285
+ end
259
286
  end
260
287
 
261
288
  def view_cache_id(spec)
@@ -294,11 +321,13 @@ module CouchPotato
294
321
  def bulk_load(ids)
295
322
  return [] if ids.empty?
296
323
 
297
- response = couchrest_database.bulk_load ids
298
- docs = response['rows'].map { |row| row['doc'] }.compact
299
- docs.each do |doc|
300
- doc.database = self if doc.respond_to?(:database=)
301
- doc.database_collection = docs if doc.respond_to?(:database_collection=)
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
302
331
  end
303
332
  end
304
333
 
@@ -1,4 +1,4 @@
1
1
  module CouchPotato
2
- VERSION = '1.14.0'.freeze
2
+ VERSION = '1.15.0'.freeze
3
3
  RSPEC_VERSION = '4.1.0'.freeze
4
4
  end
@@ -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
@@ -72,8 +72,8 @@ describe CouchPotato::Database, 'load' do
72
72
  end
73
73
 
74
74
  context 'when several ids given' do
75
- let(:doc1) { DbTestUser.new }
76
- let(:doc2) { DbTestUser.new }
75
+ let(:doc1) { DbTestUser.new(id: '1') }
76
+ let(:doc2) { DbTestUser.new(id: '2') }
77
77
  let(:response) do
78
78
  { 'rows' => [{ 'doc' => nil }, { 'doc' => doc1 }, { 'doc' => doc2 }] }
79
79
  end
@@ -98,7 +98,7 @@ describe CouchPotato::Database, 'load' do
98
98
  end
99
99
 
100
100
  it 'does not write itself to a document that has no database= method' do
101
- doc1 = double(:doc1)
101
+ doc1 = double(:doc1, id: '1')
102
102
  allow(doc1).to receive(:respond_to?) { false }
103
103
  allow(couchrest_db).to receive(:bulk_load) do
104
104
  { 'rows' => [{ 'doc' => doc1 }] }
@@ -116,7 +116,7 @@ describe CouchPotato::Database, 'load' do
116
116
  end
117
117
 
118
118
  it 'does not set database_collection on a document that has no database_collection= method' do
119
- doc1 = double(:doc1)
119
+ doc1 = double(:doc1, id: '1')
120
120
  allow(doc1).to receive(:respond_to?) { false }
121
121
  allow(couchrest_db).to receive(:bulk_load) do
122
122
  { 'rows' => [{ 'doc' => doc1 }] }
@@ -571,6 +571,14 @@ describe CouchPotato::Database, '#switch_to' do
571
571
 
572
572
  expect(new_db.cache).to be_nil
573
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
574
582
  end
575
583
 
576
584
  describe CouchPotato::Database, '#switch_to_default' do
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.14.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-02-25 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