Skip to content

Commit 41a3695

Browse files
committed
[STORE] Added the Find module
The module provides method to find one or multiple documents and to check for documents existence. The methods return `deserialize`-d Ruby objects based on `klass` or the document Elasticsearch `_type`. Missing documents are kept in the resulting Array as `nil` objects.
1 parent 5938ef0 commit 41a3695

File tree

4 files changed

+356
-0
lines changed

4 files changed

+356
-0
lines changed

elasticsearch-persistence/lib/elasticsearch/persistence.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
require 'elasticsearch/persistence/repository/naming'
99
require 'elasticsearch/persistence/repository/serialize'
1010
require 'elasticsearch/persistence/repository/store'
11+
require 'elasticsearch/persistence/repository/find'
1112
require 'elasticsearch/persistence/repository'
1213

1314
require 'elasticsearch/persistence/repository/class'

elasticsearch-persistence/lib/elasticsearch/persistence/repository.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ module Repository
66
include Elasticsearch::Persistence::Repository::Naming
77
include Elasticsearch::Persistence::Repository::Serialize
88
include Elasticsearch::Persistence::Repository::Store
9+
include Elasticsearch::Persistence::Repository::Find
910

1011
def new(options={}, &block)
1112
Elasticsearch::Persistence::Repository::Class.new options, &block
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
module Elasticsearch
2+
module Persistence
3+
module Repository
4+
class DocumentNotFound < StandardError; end
5+
6+
module Find
7+
def find(*args)
8+
options = args.last.is_a?(Hash) ? args.pop : {}
9+
ids = args
10+
11+
if args.size == 1
12+
id = args.pop
13+
id.is_a?(Array) ? __find_many(id, options) : __find_one(id, options)
14+
else
15+
__find_many args, options
16+
end
17+
end
18+
19+
def exists?(id, options={})
20+
type = (klass ? __get_type_from_class(klass) : '_all')
21+
client.exists( { index: 'test', type: type, id: id }.merge(options) )
22+
end
23+
24+
def __find_one(id, options={})
25+
type = (klass ? __get_type_from_class(klass) : '_all')
26+
document = client.get( { index: 'test', type: type, id: id }.merge(options) )
27+
28+
deserialize(document)
29+
rescue Elasticsearch::Transport::Transport::Errors::NotFound => e
30+
raise DocumentNotFound, e.message, caller
31+
end
32+
33+
def __find_many(ids, options={})
34+
type = (klass ? __get_type_from_class(klass) : '_all')
35+
documents = client.mget( { index: 'test', type: type, body: { ids: ids } }.merge(options) )
36+
37+
documents['docs'].map { |document| document['found'] ? deserialize(document) : nil }
38+
end
39+
end
40+
41+
end
42+
end
43+
end
Lines changed: 311 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,311 @@
1+
require 'test_helper'
2+
3+
class Elasticsearch::Persistence::RepositoryFindTest < Test::Unit::TestCase
4+
class MyDocument; end
5+
6+
context "The repository" do
7+
setup do
8+
@shoulda_subject = Class.new() { include Elasticsearch::Persistence::Repository::Find }.new
9+
10+
@client = mock
11+
@shoulda_subject.stubs(:klass).returns(nil)
12+
@shoulda_subject.stubs(:client).returns(@client)
13+
end
14+
15+
context "find method" do
16+
should "find one document when passed a single, literal ID" do
17+
subject.expects(:__find_one).with(1, {})
18+
subject.find(1)
19+
end
20+
21+
should "find multiple documents when passed multiple IDs" do
22+
subject.expects(:__find_many).with([1, 2], {})
23+
subject.find(1, 2)
24+
end
25+
26+
should "find multiple documents when passed an array of IDs" do
27+
subject.expects(:__find_many).with([1, 2], {})
28+
subject.find([1, 2])
29+
end
30+
31+
should "pass the options" do
32+
subject.expects(:__find_one).with(1, { foo: 'bar' })
33+
subject.find(1, foo: 'bar')
34+
35+
subject.expects(:__find_many).with([1, 2], { foo: 'bar' })
36+
subject.find([1, 2], foo: 'bar')
37+
38+
subject.expects(:__find_many).with([1, 2], { foo: 'bar' })
39+
subject.find(1, 2, foo: 'bar')
40+
end
41+
end
42+
43+
context "'exists?' method" do
44+
should "return false when the document does not exist" do
45+
@client.expects(:exists).returns(false)
46+
assert_equal false, subject.exists?('1')
47+
end
48+
49+
should "return whether document for klass exists" do
50+
subject.expects(:klass).returns(MyDocument).at_least_once
51+
subject.expects(:__get_type_from_class).with(MyDocument).returns('my_document')
52+
53+
@client
54+
.expects(:exists)
55+
.with do |arguments|
56+
assert_equal 'my_document', arguments[:type]
57+
assert_equal '1', arguments[:id]
58+
end
59+
.returns(true)
60+
61+
assert_equal true, subject.exists?('1')
62+
end
63+
64+
should "return whether document exists" do
65+
subject.expects(:klass).returns(nil)
66+
subject.expects(:__get_type_from_class).never
67+
68+
@client
69+
.expects(:exists)
70+
.with do |arguments|
71+
assert_equal '_all', arguments[:type]
72+
assert_equal '1', arguments[:id]
73+
end
74+
.returns(true)
75+
76+
assert_equal true, subject.exists?('1')
77+
end
78+
79+
should "pass options to the client" do
80+
@client.expects(:exists).with do |arguments|
81+
assert_equal 'bambam', arguments[:routing]
82+
end
83+
84+
subject.exists? '1', routing: 'bambam'
85+
end
86+
end
87+
88+
context "'__find_one' method" do
89+
should "find document based on klass and return a deserialized object" do
90+
subject.expects(:klass).returns(MyDocument).at_least_once
91+
subject.expects(:__get_type_from_class).with(MyDocument).returns('my_document')
92+
93+
subject.expects(:deserialize).with({'_source' => {'foo' => 'bar'}}).returns(MyDocument.new)
94+
95+
@client
96+
.expects(:get)
97+
.with do |arguments|
98+
assert_equal 'my_document', arguments[:type]
99+
assert_equal '1', arguments[:id]
100+
end
101+
.returns({'_source' => { 'foo' => 'bar' }})
102+
103+
assert_instance_of MyDocument, subject.__find_one('1')
104+
end
105+
106+
should "find document and return a deserialized object" do
107+
subject.expects(:klass).returns(nil).at_least_once
108+
subject.expects(:__get_type_from_class).never
109+
110+
subject.expects(:deserialize).with({'_source' => {'foo' => 'bar'}}).returns(MyDocument.new)
111+
112+
@client
113+
.expects(:get)
114+
.with do |arguments|
115+
assert_equal '_all', arguments[:type]
116+
assert_equal '1', arguments[:id]
117+
end
118+
.returns({'_source' => { 'foo' => 'bar' }})
119+
120+
assert_instance_of MyDocument, subject.__find_one('1')
121+
end
122+
123+
should "raise DocumentNotFound exception when the document cannot be found" do
124+
subject.expects(:klass).returns(nil).at_least_once
125+
126+
subject.expects(:deserialize).never
127+
128+
@client
129+
.expects(:get)
130+
.raises(Elasticsearch::Transport::Transport::Errors::NotFound)
131+
132+
assert_raise Elasticsearch::Persistence::Repository::DocumentNotFound do
133+
subject.__find_one('foobar')
134+
end
135+
end
136+
137+
should "pass other exceptions" do
138+
subject.expects(:klass).returns(nil).at_least_once
139+
140+
subject.expects(:deserialize).never
141+
142+
@client
143+
.expects(:get)
144+
.raises(RuntimeError)
145+
146+
assert_raise RuntimeError do
147+
subject.__find_one('foobar')
148+
end
149+
end
150+
151+
should "pass options to the client" do
152+
subject.expects(:klass).returns(nil).at_least_once
153+
subject.expects(:deserialize)
154+
155+
@client
156+
.expects(:get)
157+
.with do |arguments|
158+
assert_equal 'bambam', arguments[:routing]
159+
end
160+
.returns({'_source' => { 'foo' => 'bar' }})
161+
162+
subject.__find_one '1', routing: 'bambam'
163+
end
164+
end
165+
166+
context "'__find_many' method" do
167+
setup do
168+
@response = {"docs"=>
169+
[ {"_index"=>"test",
170+
"_type"=>"note",
171+
"_id"=>"1",
172+
"_version"=>1,
173+
"found"=>true,
174+
"_source"=>{"id"=>"1", "title"=>"Test 1"}},
175+
176+
{"_index"=>"test",
177+
"_type"=>"note",
178+
"_id"=>"2",
179+
"_version"=>1,
180+
"found"=>true,
181+
"_source"=>{"id"=>"2", "title"=>"Test 2"}}
182+
]}
183+
end
184+
185+
should "find documents based on klass and return an Array of deserialized objects" do
186+
subject.expects(:klass).returns(MyDocument).at_least_once
187+
subject.expects(:__get_type_from_class).with(MyDocument).returns('my_document')
188+
189+
subject
190+
.expects(:deserialize)
191+
.with(@response['docs'][0])
192+
.returns(MyDocument.new)
193+
194+
subject
195+
.expects(:deserialize)
196+
.with(@response['docs'][1])
197+
.returns(MyDocument.new)
198+
199+
@client
200+
.expects(:mget)
201+
.with do |arguments|
202+
assert_equal 'my_document', arguments[:type]
203+
assert_equal ['1', '2'], arguments[:body][:ids]
204+
end
205+
.returns(@response)
206+
207+
results = subject.__find_many(['1', '2'])
208+
assert_instance_of MyDocument, results[0]
209+
assert_instance_of MyDocument, results[1]
210+
end
211+
212+
should "find documents and return an Array of deserialized objects" do
213+
subject.expects(:klass).returns(nil).at_least_once
214+
subject.expects(:__get_type_from_class).never
215+
216+
subject
217+
.expects(:deserialize)
218+
.with(@response['docs'][0])
219+
.returns(MyDocument.new)
220+
221+
subject
222+
.expects(:deserialize)
223+
.with(@response['docs'][1])
224+
.returns(MyDocument.new)
225+
226+
@client
227+
.expects(:mget)
228+
.with do |arguments|
229+
assert_equal '_all', arguments[:type]
230+
assert_equal ['1', '2'], arguments[:body][:ids]
231+
end
232+
.returns(@response)
233+
234+
results = subject.__find_many(['1', '2'])
235+
236+
assert_equal 2, results.size
237+
238+
assert_instance_of MyDocument, results[0]
239+
assert_instance_of MyDocument, results[1]
240+
end
241+
242+
should "find keep missing documents in the result as nil" do
243+
@response = {"docs"=>
244+
[ {"_index"=>"test",
245+
"_type"=>"note",
246+
"_id"=>"1",
247+
"_version"=>1,
248+
"found"=>true,
249+
"_source"=>{"id"=>"1", "title"=>"Test 1"}},
250+
251+
{"_index"=>"test",
252+
"_type"=>"note",
253+
"_id"=>"3",
254+
"_version"=>1,
255+
"found"=>false},
256+
257+
{"_index"=>"test",
258+
"_type"=>"note",
259+
"_id"=>"2",
260+
"_version"=>1,
261+
"found"=>true,
262+
"_source"=>{"id"=>"2", "title"=>"Test 2"}}
263+
]}
264+
265+
subject.expects(:klass).returns(MyDocument).at_least_once
266+
subject.expects(:__get_type_from_class).with(MyDocument).returns('my_document')
267+
268+
subject
269+
.expects(:deserialize)
270+
.with(@response['docs'][0])
271+
.returns(MyDocument.new)
272+
273+
subject
274+
.expects(:deserialize)
275+
.with(@response['docs'][2])
276+
.returns(MyDocument.new)
277+
278+
@client
279+
.expects(:mget)
280+
.with do |arguments|
281+
assert_equal 'my_document', arguments[:type]
282+
assert_equal ['1', '3', '2'], arguments[:body][:ids]
283+
end
284+
.returns(@response)
285+
286+
results = subject.__find_many(['1', '3', '2'])
287+
288+
assert_equal 3, results.size
289+
290+
assert_instance_of MyDocument, results[0]
291+
assert_instance_of NilClass, results[1]
292+
assert_instance_of MyDocument, results[2]
293+
end
294+
295+
should "pass options to the client" do
296+
subject.expects(:klass).returns(nil).at_least_once
297+
subject.expects(:deserialize).twice
298+
299+
@client
300+
.expects(:mget)
301+
.with do |arguments|
302+
assert_equal 'bambam', arguments[:routing]
303+
end
304+
.returns(@response)
305+
306+
subject.__find_many ['1', '2'], routing: 'bambam'
307+
end
308+
end
309+
310+
end
311+
end

0 commit comments

Comments
 (0)