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 +4 -4
- data/CHANGES.md +10 -0
- data/lib/couch_potato/database.rb +59 -23
- data/lib/couch_potato/persistence/ghost_attributes.rb +7 -3
- data/lib/couch_potato/version.rb +1 -1
- data/spec/unit/caching_spec.rb +128 -25
- data/spec/unit/database_spec.rb +57 -4
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3fd11db6b5bb37ef9b0d12115ae4586f8f0ec8afff586e7a2b381e1839af0196
|
4
|
+
data.tar.gz: 7f03e40ec9297bb669f28559e251b72f5b8e849010b8861d992c75c5ed7737a3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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
|
-
|
187
|
-
|
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(
|
223
|
+
.new(couchrest_database, name: database_name == :default ? nil : database_name)
|
190
224
|
.tap(©_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
|
-
|
198
|
-
.class
|
199
|
-
.new(CouchPotato.couchrest_database)
|
200
|
-
.tap(©_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
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
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
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
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
|
3
|
-
def method_missing(name, *
|
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
|
|
data/lib/couch_potato/version.rb
CHANGED
data/spec/unit/caching_spec.rb
CHANGED
@@ -18,41 +18,144 @@ RSpec.describe 'database caching' do
|
|
18
18
|
{}
|
19
19
|
end
|
20
20
|
|
21
|
-
|
22
|
-
|
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
|
-
|
29
|
-
|
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
|
-
|
32
|
-
|
33
|
-
|
29
|
+
db.load_document '1'
|
30
|
+
db.load_document '1'
|
31
|
+
end
|
34
32
|
|
35
|
-
|
36
|
-
|
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
|
-
|
39
|
-
|
40
|
-
|
36
|
+
db.load '1'
|
37
|
+
db.load '1'
|
38
|
+
end
|
41
39
|
|
42
|
-
|
43
|
-
|
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
|
-
|
47
|
-
|
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
|
-
|
51
|
-
|
52
|
-
|
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
|
-
|
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
|
data/spec/unit/database_spec.rb
CHANGED
@@ -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.
|
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
|
11
|
+
date: 2024-04-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activemodel
|