couch_potato 1.14.0 → 1.16.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: 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