Skip to content

Commit 7bcd91d

Browse files
committed
[STORE] Added the Search module
The module provides an interface for getting objects from the repository based on a search query. The results are wrapped in the `Response::Results` instance, which proxies methods to the `results` property.
1 parent 41a3695 commit 7bcd91d

File tree

7 files changed

+256
-0
lines changed

7 files changed

+256
-0
lines changed

elasticsearch-persistence/elasticsearch-persistence.gemspec

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ Gem::Specification.new do |s|
2323

2424
s.add_dependency "elasticsearch", '> 0.4'
2525
s.add_dependency "active_support"
26+
s.add_dependency "hashie"
2627

2728
s.add_development_dependency "bundler", "~> 1.5"
2829
s.add_development_dependency "rake"

elasticsearch-persistence/lib/elasticsearch/persistence.rb

+3
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
require 'elasticsearch'
2+
require 'hashie'
23

34
require 'active_support/inflector'
45

56
require 'elasticsearch/persistence/version'
67

78
require 'elasticsearch/persistence/client'
9+
require 'elasticsearch/persistence/repository/response/results'
810
require 'elasticsearch/persistence/repository/naming'
911
require 'elasticsearch/persistence/repository/serialize'
1012
require 'elasticsearch/persistence/repository/store'
1113
require 'elasticsearch/persistence/repository/find'
14+
require 'elasticsearch/persistence/repository/search'
1215
require 'elasticsearch/persistence/repository'
1316

1417
require 'elasticsearch/persistence/repository/class'

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

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ module Repository
77
include Elasticsearch::Persistence::Repository::Serialize
88
include Elasticsearch::Persistence::Repository::Store
99
include Elasticsearch::Persistence::Repository::Find
10+
include Elasticsearch::Persistence::Repository::Search
1011

1112
def new(options={}, &block)
1213
Elasticsearch::Persistence::Repository::Class.new options, &block
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
module Elasticsearch
2+
module Persistence
3+
module Repository
4+
module Response
5+
6+
class Results
7+
include Enumerable
8+
9+
attr_reader :repository, :response, :response
10+
11+
def initialize(repository, response, options={})
12+
@repository = repository
13+
@response = Hashie::Mash.new(response)
14+
@options = options
15+
end
16+
17+
def method_missing(method_name, *arguments, &block)
18+
results.respond_to?(method_name) ? results.__send__(method_name, *arguments, &block) : super
19+
end
20+
21+
def respond_to?(method_name, include_private = false)
22+
results.respond_to?(method_name) || super
23+
end
24+
25+
def total
26+
response['hits']['total']
27+
end
28+
29+
def max_score
30+
response['hits']['max_score']
31+
end
32+
33+
# Yields [object, hit] pairs to the block
34+
#
35+
def each_with_hit(&block)
36+
results.zip(response['hits']['hits']).each(&block)
37+
end
38+
39+
# Yields [object, hit] pairs and returns the result
40+
#
41+
def map_with_hit(&block)
42+
results.zip(response['hits']['hits']).map(&block)
43+
end
44+
45+
def results
46+
@results ||= response['hits']['hits'].map do |document|
47+
repository.deserialize(document.to_hash)
48+
end
49+
end
50+
end
51+
end
52+
end
53+
end
54+
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
module Elasticsearch
2+
module Persistence
3+
module Repository
4+
5+
module Search
6+
def search(query_or_definition, options={})
7+
type = (klass ? __get_type_from_class(klass) : nil )
8+
case
9+
when query_or_definition.respond_to?(:to_hash)
10+
response = client.search( { index: 'test', type: type, body: query_or_definition.to_hash }.merge(options) )
11+
when query_or_definition.is_a?(String)
12+
response = client.search( { index: 'test', type: type, q: query_or_definition }.merge(options) )
13+
else
14+
raise ArgumentError, "[!] Pass the search definition as a Hash-like object or pass the query as a String" +
15+
" -- #{query_or_definition.class} given."
16+
end
17+
Response::Results.new(self, response)
18+
end
19+
end
20+
21+
end
22+
end
23+
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
require 'test_helper'
2+
3+
class Elasticsearch::Persistence::RepositoryResponseResultsTest < Test::Unit::TestCase
4+
include Elasticsearch::Persistence
5+
class MyDocument; end
6+
7+
context "Response results" do
8+
setup do
9+
@repository = Repository.new
10+
11+
@response = { "took" => 2,
12+
"timed_out" => false,
13+
"_shards" => {"total" => 5, "successful" => 5, "failed" => 0},
14+
"hits" =>
15+
{ "total" => 2,
16+
"max_score" => 0.19,
17+
"hits" =>
18+
[{"_index" => "test",
19+
"_type" => "note",
20+
"_id" => "1",
21+
"_score" => 0.19,
22+
"_source" => {"id" => 1, "title" => "Test 1"}},
23+
24+
{"_index" => "test",
25+
"_type" => "note",
26+
"_id" => "2",
27+
"_score" => 0.19,
28+
"_source" => {"id" => 2, "title" => "Test 2"}}
29+
]
30+
}
31+
}
32+
33+
@shoulda_subject = Repository::Response::Results.new @repository, @response
34+
end
35+
36+
should "provide the access to the repository" do
37+
assert_instance_of Repository::Class, subject.repository
38+
end
39+
40+
should "provide the access to the response" do
41+
assert_equal 5, subject.response['_shards']['total']
42+
end
43+
44+
should "wrap the response in Hashie::Mash" do
45+
assert_equal 5, subject.response._shards.total
46+
end
47+
48+
should "return the total" do
49+
assert_equal 2, subject.total
50+
end
51+
52+
should "return the max_score" do
53+
assert_equal 0.19, subject.max_score
54+
end
55+
56+
should "delegate methods to results" do
57+
subject.repository
58+
.expects(:deserialize)
59+
.twice
60+
.returns(MyDocument.new)
61+
62+
assert_equal 2, subject.size
63+
assert_respond_to subject, :each
64+
end
65+
66+
should "yield each object with hit" do
67+
@shoulda_subject = Repository::Response::Results.new \
68+
@repository,
69+
{ 'hits' => { 'hits' => [{'_id' => '1', 'foo' => 'bar'}] } }
70+
71+
subject.repository
72+
.expects(:deserialize)
73+
.returns('FOO')
74+
75+
subject.each_with_hit do |object, hit|
76+
assert_equal 'FOO', object
77+
assert_equal 'bar', hit.foo
78+
end
79+
end
80+
81+
should "map objects and hits" do
82+
@shoulda_subject = Repository::Response::Results.new \
83+
@repository,
84+
{ 'hits' => { 'hits' => [{'_id' => '1', 'foo' => 'bar'}] } }
85+
86+
subject.repository
87+
.expects(:deserialize)
88+
.returns('FOO')
89+
90+
assert_equal ['FOO---bar'], subject.map_with_hit { |object, hit| "#{object}---#{hit.foo}" }
91+
end
92+
end
93+
94+
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
require 'test_helper'
2+
3+
class Elasticsearch::Persistence::RepositorySearchTest < Test::Unit::TestCase
4+
class MyDocument; end
5+
6+
context "The repository search" do
7+
setup do
8+
@shoulda_subject = Class.new() { include Elasticsearch::Persistence::Repository::Search }.new
9+
10+
@client = mock
11+
@shoulda_subject.stubs(:klass).returns(nil)
12+
@shoulda_subject.stubs(:client).returns(@client)
13+
end
14+
15+
should "search in type based on klass" do
16+
subject.expects(:klass).returns(MyDocument).at_least_once
17+
subject.expects(:__get_type_from_class).with(MyDocument).returns('my_document')
18+
19+
@client.expects(:search).with do |arguments|
20+
assert_equal 'test', arguments[:index]
21+
assert_equal 'my_document', arguments[:type]
22+
23+
assert_equal({foo: 'bar'}, arguments[:body])
24+
end
25+
26+
subject.search foo: 'bar'
27+
end
28+
29+
should "search across all types" do
30+
subject.expects(:klass).returns(nil).at_least_once
31+
subject.expects(:__get_type_from_class).never
32+
33+
@client.expects(:search).with do |arguments|
34+
assert_equal 'test', arguments[:index]
35+
assert_equal nil, arguments[:type]
36+
37+
assert_equal({foo: 'bar'}, arguments[:body])
38+
end
39+
40+
assert_instance_of Elasticsearch::Persistence::Repository::Response::Results,
41+
subject.search(foo: 'bar')
42+
end
43+
44+
should "pass options to the client" do
45+
subject.expects(:klass).returns(nil).at_least_once
46+
subject.expects(:__get_type_from_class).never
47+
48+
@client.expects(:search).twice.with do |arguments|
49+
assert_equal 'bambam', arguments[:routing]
50+
end
51+
52+
assert_instance_of Elasticsearch::Persistence::Repository::Response::Results,
53+
subject.search( {foo: 'bar'}, { routing: 'bambam' } )
54+
assert_instance_of Elasticsearch::Persistence::Repository::Response::Results,
55+
subject.search( 'foobar', { routing: 'bambam' } )
56+
end
57+
58+
should "search with simple search" do
59+
subject.expects(:klass).returns(nil).at_least_once
60+
subject.expects(:__get_type_from_class).never
61+
62+
@client.expects(:search).with do |arguments|
63+
assert_equal 'foobar', arguments[:q]
64+
end
65+
66+
assert_instance_of Elasticsearch::Persistence::Repository::Response::Results,
67+
subject.search('foobar')
68+
end
69+
70+
should "raise error for incorrect search definitions" do
71+
subject.expects(:klass).returns(nil).at_least_once
72+
subject.expects(:__get_type_from_class).never
73+
74+
assert_raise ArgumentError do
75+
subject.search 123
76+
end
77+
end
78+
end
79+
80+
end

0 commit comments

Comments
 (0)