couch_potato 1.14.0 → 1.16.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: dd4dede2152745db1f3f0bf7ff3a94dfea3c7eb427beb02292034866dd7dac22
4
- data.tar.gz: 29ca77512b6bdeebcdd31847deac4957792946519dda4f658f60dd90c9dd83e4
3
+ metadata.gz: 3fd11db6b5bb37ef9b0d12115ae4586f8f0ec8afff586e7a2b381e1839af0196
4
+ data.tar.gz: 7f03e40ec9297bb669f28559e251b72f5b8e849010b8861d992c75c5ed7737a3
5
5
  SHA512:
6
- metadata.gz: 21728691cd7eb4b23bd6600899655cc7fa1dbb6a15a6d40c669b53941fab30bf38cb8a95fa9a3e8f85e05d44af116f58cd20a424eedd60d417972db893c07aff
7
- data.tar.gz: d2d26373afd0bcce487b35703be3bd7bd08cc7e4e108f98e8279bf81c41715b30f5590fdf8080c4b136ec95e356c857b5c50bef123b6971e67294e79cdeb180a
6
+ metadata.gz: 72bc1831a2b773d3f64682f1fbe20fb10acccad204fd057e92676c53fe03f5f56b462c02905b3f3ee52ddb6fa640f6dfe6a05a3f1e1aeb8e750eaecb76922a38
7
+ data.tar.gz: 4a49349375cabe65b465c99336e3c0b05564d0b720faa6ae4d956633809ea52d6a73119118c7bc923d0ffde0aefd5af93f3fbdc587fed89696942b05ce421025
data/CHANGES.md CHANGED
@@ -1,5 +1,15 @@
1
1
  ## Changes
2
2
 
3
+ # 1.16.0
4
+
5
+ - add payload to ActiveSupport instrumentation calls
6
+ - only notify load.cached when there are cached documents
7
+
8
+ # 1.15.0
9
+
10
+ - cache loading multiple documents
11
+ - keep the database cache when switching to another database and back to the original one
12
+
3
13
  # 1.14.0
4
14
 
5
15
  - 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,10 +145,12 @@ 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
- ActiveSupport::Notifications.instrument('couch_potato.load.cached') do
153
+ ActiveSupport::Notifications.instrument('couch_potato.load.cached', id: id, doc: cached) do
150
154
  cached
151
155
  end
152
156
  else
@@ -159,6 +163,26 @@ 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
+ cached_docs_by_id = cache&.slice(*ids) || {}
172
+ if cached_docs_by_id.any?
173
+ ActiveSupport::Notifications.instrument('couch_potato.load.cached', ids: cached_docs_by_id.keys, docs: cached_docs_by_id.values) do
174
+ cached_docs_by_id
175
+ end
176
+ end
177
+ if cache
178
+ uncached_ids.each do |id|
179
+ doc = uncached_docs_by_id[id]
180
+ cache[id] = doc if doc
181
+ end
182
+ end
183
+ ids.filter_map { |id| (cached_docs_by_id[id]) || uncached_docs_by_id[id] }
184
+ end
185
+
162
186
  # loads one or more documents by its id(s)
163
187
  # behaves like #load except it raises a CouchPotato::NotFound if any of the documents could not be found
164
188
  def load!(id)
@@ -176,6 +200,8 @@ module CouchPotato
176
200
  # returns the underlying CouchRest::Database instance
177
201
  attr_reader :couchrest_database
178
202
 
203
+ attr_accessor :switched_databases
204
+
179
205
  # returns a new database instance connected to the CouchDB database
180
206
  # with the given name. the name is passed through the
181
207
  # additional_databases configuration to resolve it to a database
@@ -183,21 +209,29 @@ module CouchPotato
183
209
  # if the current database has a cache, the new database will receive
184
210
  # a cleared copy of it.
185
211
  def switch_to(database_name)
186
- resolved_database_name = CouchPotato.resolve_database_name(database_name)
187
- self
212
+ self.switched_databases ||= {}
213
+ if (db = switched_databases[database_name || :default])
214
+ return db
215
+ end
216
+ switched_databases[name || :default] ||= self
217
+
218
+
219
+ resolved_database_name = CouchPotato.resolve_database_name(database_name) unless database_name == :default
220
+ couchrest_database = resolved_database_name ? CouchPotato.couchrest_database_for_name(resolved_database_name) : CouchPotato.couchrest_database
221
+ new_db = self
188
222
  .class
189
- .new(CouchPotato.couchrest_database_for_name(resolved_database_name), name: database_name)
223
+ .new(couchrest_database, name: database_name == :default ? nil : database_name)
190
224
  .tap(&copy_clear_cache_proc)
225
+
226
+ new_db.switched_databases = switched_databases
227
+ new_db
191
228
  end
192
229
 
193
230
  # returns a new database instance connected to the default CouchDB database.
194
231
  # if the current database has a cache, the new database will receive
195
232
  # a cleared copy of it.
196
233
  def switch_to_default
197
- self
198
- .class
199
- .new(CouchPotato.couchrest_database)
200
- .tap(&copy_clear_cache_proc)
234
+ switch_to(:default)
201
235
  end
202
236
 
203
237
  private
@@ -247,15 +281,13 @@ module CouchPotato
247
281
  def load_document_without_caching(id)
248
282
  raise "Can't load a document without an id (got nil)" if id.nil?
249
283
 
250
- 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
284
+ payload = {id: id}
285
+ ActiveSupport::Notifications.instrument('couch_potato.load', payload) do
286
+ instance = couchrest_database.get(id)
287
+ instance.database = self if instance
288
+ payload[:doc] = instance
289
+ instance
290
+ end
259
291
  end
260
292
 
261
293
  def view_cache_id(spec)
@@ -294,11 +326,15 @@ module CouchPotato
294
326
  def bulk_load(ids)
295
327
  return [] if ids.empty?
296
328
 
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=)
329
+ payload = {ids: ids}
330
+ ActiveSupport::Notifications.instrument('couch_potato.load', payload) do
331
+ response = couchrest_database.bulk_load ids
332
+ docs = response["rows"].map { |row| row["doc"] }.compact
333
+ payload[:docs] = docs
334
+ docs.each do |doc|
335
+ doc.database = self if doc.respond_to?(:database=)
336
+ doc.database_collection = docs if doc.respond_to?(:database_collection=)
337
+ end
302
338
  end
303
339
  end
304
340
 
@@ -1,12 +1,16 @@
1
1
  module CouchPotato
2
- module GhostAttributes #:nodoc:
3
- def method_missing(name, *args)
4
- if(value = _document && _document[name.to_s])
2
+ module GhostAttributes # :nodoc:
3
+ def method_missing(name, *)
4
+ if (value = _document && _document[name.to_s])
5
5
  value
6
6
  else
7
7
  super
8
8
  end
9
9
  end
10
+
11
+ def respond_to_missing?(name, *)
12
+ _document && _document[name.to_s]
13
+ end
10
14
  end
11
15
  end
12
16
 
@@ -1,4 +1,4 @@
1
1
  module CouchPotato
2
- VERSION = '1.14.0'.freeze
2
+ VERSION = '1.16.0'.freeze
3
3
  RSPEC_VERSION = '4.1.0'.freeze
4
4
  end
@@ -18,41 +18,144 @@ 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
23
-
24
- db.load_document '1'
25
- db.load_document '1'
21
+ after(:each) do
22
+ ActiveSupport::Notifications.unsubscribe(@subscriber) if @subscriber
26
23
  end
27
24
 
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
25
+ context 'for a single document' do
26
+ it 'gets an object from the cache the 2nd time via #load_documemt' do
27
+ expect(couchrest_db).to receive(:get).with('1').exactly(1).times
30
28
 
31
- db.load '1'
32
- db.load '1'
33
- end
29
+ db.load_document '1'
30
+ db.load_document '1'
31
+ end
34
32
 
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
33
+ it 'gets an object from the cache the 2nd time via #load' do
34
+ expect(couchrest_db).to receive(:get).with('1').exactly(1).times
37
35
 
38
- db.load! '1'
39
- db.load! '1'
40
- end
36
+ db.load '1'
37
+ db.load '1'
38
+ end
41
39
 
42
- it 'returns the correct object' do
43
- doc = double(:doc, 'database=': nil)
44
- allow(couchrest_db).to receive_messages(get: doc)
40
+ it 'gets an object from the cache the 2nd time via #load!' do
41
+ expect(couchrest_db).to receive(:get).with('1').exactly(1).times
45
42
 
46
- db.load_document '1'
47
- expect(db.load_document('1')).to eql(doc)
43
+ db.load! '1'
44
+ db.load! '1'
45
+ end
46
+
47
+ it 'returns the correct object' do
48
+ doc = double(:doc, 'database=': nil)
49
+ allow(couchrest_db).to receive_messages(get: doc)
50
+
51
+ db.load_document '1'
52
+ expect(db.load_document('1')).to eql(doc)
53
+ end
54
+
55
+ it 'instruments the load call' do
56
+ doc = double("doc").as_null_object
57
+ allow(couchrest_db).to receive(:get).and_return(doc)
58
+ events = []
59
+ @subscriber = ActiveSupport::Notifications.subscribe(
60
+ 'couch_potato.load.cached'
61
+ ) do |event|
62
+ events << event
63
+ end
64
+
65
+ db.load("1")
66
+ db.load("1")
67
+
68
+ expect(events.size).to eq(1)
69
+ expect(events.first.payload).to eq(
70
+ {
71
+ id: "1",
72
+ doc: doc
73
+ }
74
+ )
75
+
76
+ end
48
77
  end
49
78
 
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
79
+ context 'for multiple documents' do
80
+ let(:doc1) { double(:doc1, 'database=': nil, id: '1') }
81
+ let(:doc2) { double(:doc12, 'database=': nil, id: '2') }
82
+
83
+ it 'only loads uncached documents' do
84
+ allow(couchrest_db).to receive(:bulk_load).with(['1']).and_return('rows' => [{'doc' => doc1}])
85
+ allow(couchrest_db).to receive(:bulk_load).with(['2']).and_return('rows' => [{'doc' => doc2}])
86
+
87
+
88
+ db.load_document(['1'])
89
+ db.load_document(['1', '2'])
90
+
91
+ expect(couchrest_db).to have_received(:bulk_load).with(['1']).exactly(1).times
92
+ expect(couchrest_db).to have_received(:bulk_load).with(['2']).exactly(1).times
93
+ end
94
+
95
+ it 'instruments the load call' do
96
+ allow(couchrest_db).to receive(:bulk_load).with(['1'])
97
+ .and_return('rows' => [{'doc' => doc1}])
98
+ allow(couchrest_db).to receive(:bulk_load).with(['2'])
99
+ .and_return('rows' => [{'doc' => doc2}])
100
+ events = []
101
+ @subscriber = ActiveSupport::Notifications.subscribe(
102
+ 'couch_potato.load.cached'
103
+ ) do |event|
104
+ events << event
105
+ end
106
+
107
+
108
+ db.load_document(['1'])
109
+ db.load_document(['1', '2'])
110
+
111
+ expect(events.size).to eq(1)
112
+ expect(events.first.payload).to eq(
113
+ {
114
+ ids: ["1"],
115
+ docs: [doc1]
116
+ }
117
+ )
118
+ end
119
+
120
+ it 'loads nothing if all documents are cached' do
121
+ allow(couchrest_db).to receive(:bulk_load).with(['1', '2'])
122
+ .and_return('rows' => [{'doc' => doc1}, {'doc' => doc2}])
123
+
124
+ db.load_document(['1', '2'])
125
+ db.load_document(['1', '2'])
126
+
127
+ expect(couchrest_db).to have_received(:bulk_load).with(['1', '2']).exactly(1).times
128
+ end
129
+
130
+ it 'returns all requested documents' do
131
+ allow(couchrest_db).to receive(:bulk_load).with(['1']).and_return('rows' => [{'doc' => doc1}])
132
+ allow(couchrest_db).to receive(:bulk_load).with(['2']).and_return('rows' => [{'doc' => doc2}])
133
+
134
+
135
+ db.load_document(['1'])
136
+ result = db.load_document(['1', '2'])
137
+
138
+ expect(result).to eql([doc1, doc2])
139
+ end
140
+
141
+ it 'does not cache documents that do not respond to id' do
142
+ doc1 = {
143
+ 'id' => '1',
144
+ }
145
+ doc2 = {
146
+ 'id' => '2',
147
+ }
148
+ allow(couchrest_db).to receive(:bulk_load).with(['1', '2'])
149
+ .and_return('rows' => [{'doc' => doc1}, {'doc' => doc2}])
150
+
151
+ db.load_document(['1', '2'])
152
+ db.load_document(['1', '2'])
153
+
154
+ expect(couchrest_db).to have_received(:bulk_load).with(['1', '2']).exactly(2).times
155
+ end
156
+ end
53
157
 
54
- db.load_document ['1']
55
- db.load_document ['1']
158
+ context 'when switching the database' do
56
159
  end
57
160
 
58
161
  it 'clears the cache when destroying a document via #destroy_document' do
@@ -42,6 +42,11 @@ describe CouchPotato::Database, 'load' do
42
42
  let(:couchrest_db) { double('couchrest db', info: nil).as_null_object }
43
43
  let(:db) { CouchPotato::Database.new couchrest_db }
44
44
 
45
+
46
+ after(:each) do
47
+ ActiveSupport::Notifications.unsubscribe(@subscriber) if @subscriber
48
+ end
49
+
45
50
  it 'should raise an exception if nil given' do
46
51
  expect do
47
52
  db.load nil
@@ -72,8 +77,8 @@ describe CouchPotato::Database, 'load' do
72
77
  end
73
78
 
74
79
  context 'when several ids given' do
75
- let(:doc1) { DbTestUser.new }
76
- let(:doc2) { DbTestUser.new }
80
+ let(:doc1) { DbTestUser.new(id: '1') }
81
+ let(:doc2) { DbTestUser.new(id: '2') }
77
82
  let(:response) do
78
83
  { 'rows' => [{ 'doc' => nil }, { 'doc' => doc1 }, { 'doc' => doc2 }] }
79
84
  end
@@ -98,7 +103,7 @@ describe CouchPotato::Database, 'load' do
98
103
  end
99
104
 
100
105
  it 'does not write itself to a document that has no database= method' do
101
- doc1 = double(:doc1)
106
+ doc1 = double(:doc1, id: '1')
102
107
  allow(doc1).to receive(:respond_to?) { false }
103
108
  allow(couchrest_db).to receive(:bulk_load) do
104
109
  { 'rows' => [{ 'doc' => doc1 }] }
@@ -116,7 +121,7 @@ describe CouchPotato::Database, 'load' do
116
121
  end
117
122
 
118
123
  it 'does not set database_collection on a document that has no database_collection= method' do
119
- doc1 = double(:doc1)
124
+ doc1 = double(:doc1, id: '1')
120
125
  allow(doc1).to receive(:respond_to?) { false }
121
126
  allow(couchrest_db).to receive(:bulk_load) do
122
127
  { 'rows' => [{ 'doc' => doc1 }] }
@@ -130,6 +135,46 @@ describe CouchPotato::Database, 'load' do
130
135
  it 'returns an empty array when passing an empty array' do
131
136
  expect(db.load([])).to eq([])
132
137
  end
138
+
139
+ it 'instruments the load call' do
140
+ events = []
141
+ @subscriber = ActiveSupport::Notifications.subscribe(
142
+ 'couch_potato.load'
143
+ ) do |event|
144
+ events << event
145
+ end
146
+
147
+ db.load(["1", "2"])
148
+
149
+ expect(events.size).to eq(1)
150
+ expect(events.first.payload).to eq(
151
+ {
152
+ ids: ["1", "2"],
153
+ docs: [doc1, doc2]
154
+ }
155
+ )
156
+ end
157
+ end
158
+
159
+ it 'instruments the load call' do
160
+ doc = double("doc").as_null_object
161
+ allow(couchrest_db).to receive(:get).and_return(doc)
162
+ events = []
163
+ @subscriber = ActiveSupport::Notifications.subscribe(
164
+ 'couch_potato.load'
165
+ ) do |event|
166
+ events << event
167
+ end
168
+
169
+ db.load("1")
170
+
171
+ expect(events.size).to eq(1)
172
+ expect(events.first.payload).to eq(
173
+ {
174
+ id: "1",
175
+ doc: doc
176
+ }
177
+ )
133
178
  end
134
179
  end
135
180
 
@@ -571,6 +616,14 @@ describe CouchPotato::Database, '#switch_to' do
571
616
 
572
617
  expect(new_db.cache).to be_nil
573
618
  end
619
+
620
+ it 're-uses the original cache when switching back to the original database' do
621
+ db.cache = { key: 'value' }
622
+ new_db = db.switch_to('db2')
623
+ original_db = new_db.switch_to_default
624
+
625
+ expect(original_db.cache).to have_key(:key)
626
+ end
574
627
  end
575
628
 
576
629
  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.16.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-04-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemodel