elastic_searchable 0.7.0 → 0.7.1
Sign up to get free protection for your applications and to get access to all the features.
- data/CONTRIBUTORS.txt +6 -0
- data/LICENSE.txt +19 -17
- data/README.md +51 -0
- data/lib/elastic_searchable/active_record_extensions.rb +1 -2
- data/lib/elastic_searchable/index.rb +6 -6
- data/lib/elastic_searchable/queries.rb +18 -2
- data/lib/elastic_searchable/version.rb +1 -1
- data/lib/elastic_searchable.rb +23 -25
- data/test/test_elastic_searchable.rb +67 -2
- metadata +6 -5
- data/README.rdoc +0 -41
data/CONTRIBUTORS.txt
ADDED
data/LICENSE.txt
CHANGED
@@ -1,20 +1,22 @@
|
|
1
|
-
|
1
|
+
The MIT License
|
2
2
|
|
3
|
-
|
4
|
-
a copy of this software and associated documentation files (the
|
5
|
-
"Software"), to deal in the Software without restriction, including
|
6
|
-
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
-
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
-
permit persons to whom the Software is furnished to do so, subject to
|
9
|
-
the following conditions:
|
3
|
+
Copyright (c) 2011 Socialcast, Inc
|
10
4
|
|
11
|
-
|
12
|
-
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
13
22
|
|
14
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
-
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
-
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
-
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
-
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
-
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
-
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
# elastic_searchable
|
2
|
+
|
3
|
+
Integrate the elasticsearch library into Rails.
|
4
|
+
|
5
|
+
## Usage
|
6
|
+
|
7
|
+
```ruby
|
8
|
+
class Blog < ActiveRecord::Base
|
9
|
+
elastic_searchable
|
10
|
+
end
|
11
|
+
|
12
|
+
results = Blog.search 'foo'
|
13
|
+
```
|
14
|
+
|
15
|
+
## Features
|
16
|
+
|
17
|
+
* fast. fast! FAST! 30% faster than rubberband on average.
|
18
|
+
* active record callbacks automatically keep search index up to date as your data changes
|
19
|
+
* out of the box background indexing of data using backgrounded. Don't lock up a foreground process waiting on a background job!
|
20
|
+
* integrates with will_paginate library for easy pagination of search results
|
21
|
+
|
22
|
+
## Installation
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
# Bundler Gemfile
|
26
|
+
gem 'elastic_searchable'
|
27
|
+
```
|
28
|
+
|
29
|
+
## Configuration
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
# config/initializers/elastic_searchable.rb
|
33
|
+
# (optional) customize elasticsearch host
|
34
|
+
# default is localhost:9200
|
35
|
+
ElasticSearchable.base_uri = 'server:9200'
|
36
|
+
```
|
37
|
+
|
38
|
+
## Contributing
|
39
|
+
|
40
|
+
* Fork the project
|
41
|
+
* Fix the issue
|
42
|
+
* Add unit tests
|
43
|
+
* Submit pull request on github
|
44
|
+
|
45
|
+
See CONTRIBUTORS.txt for list of project contributors
|
46
|
+
|
47
|
+
## Copyright
|
48
|
+
|
49
|
+
Copyright (c) 2011 Socialcast, Inc.
|
50
|
+
See LICENSE.txt for further details.
|
51
|
+
|
@@ -7,8 +7,6 @@ require 'elastic_searchable/index'
|
|
7
7
|
|
8
8
|
module ElasticSearchable
|
9
9
|
module ActiveRecordExtensions
|
10
|
-
attr_accessor :elastic_options
|
11
|
-
|
12
10
|
# Valid options:
|
13
11
|
# :index (optional) configure index to store data in. default to ElasticSearchable.default_index
|
14
12
|
# :type (optional) configue type to store data in. default to model table name
|
@@ -19,6 +17,7 @@ module ElasticSearchable
|
|
19
17
|
# :json (optional) configure the json document to be indexed (see http://api.rubyonrails.org/classes/ActiveModel/Serializers/JSON.html#method-i-as_json for available options)
|
20
18
|
def elastic_searchable(options = {})
|
21
19
|
options.symbolize_keys!
|
20
|
+
cattr_accessor :elastic_options
|
22
21
|
self.elastic_options = options.merge(:unless => Array.wrap(options[:unless]).push(&:elasticsearch_offline?))
|
23
22
|
|
24
23
|
extend ElasticSearchable::Indexing::ClassMethods
|
@@ -13,7 +13,7 @@ module ElasticSearchable
|
|
13
13
|
# http://www.elasticsearch.com/docs/elasticsearch/rest_api/admin/indices/put_mapping/
|
14
14
|
def update_index_mapping
|
15
15
|
if mapping = self.elastic_options[:mapping]
|
16
|
-
ElasticSearchable.request :put, index_type_path('_mapping'), :body => {index_type => mapping}
|
16
|
+
ElasticSearchable.request :put, index_type_path('_mapping'), :body => {index_type => mapping}
|
17
17
|
end
|
18
18
|
end
|
19
19
|
|
@@ -23,7 +23,7 @@ module ElasticSearchable
|
|
23
23
|
options = {}
|
24
24
|
options.merge! :settings => self.elastic_options[:index_options] if self.elastic_options[:index_options]
|
25
25
|
options.merge! :mappings => {index_type => self.elastic_options[:mapping]} if self.elastic_options[:mapping]
|
26
|
-
ElasticSearchable.request :put, index_path, :body => options
|
26
|
+
ElasticSearchable.request :put, index_path, :body => options
|
27
27
|
end
|
28
28
|
|
29
29
|
# explicitly refresh the index, making all operations performed since the last refresh
|
@@ -77,8 +77,8 @@ module ElasticSearchable
|
|
77
77
|
records.each do |record|
|
78
78
|
next unless record.should_index?
|
79
79
|
begin
|
80
|
-
doc = record.as_json_for_index
|
81
|
-
actions << {:index => {'_index' => index_name, '_type' => index_type, '_id' => record.id}}
|
80
|
+
doc = ElasticSearchable.encode_json(record.as_json_for_index)
|
81
|
+
actions << ElasticSearchable.encode_json({:index => {'_index' => index_name, '_type' => index_type, '_id' => record.id}})
|
82
82
|
actions << doc
|
83
83
|
rescue => e
|
84
84
|
ElasticSearchable.logger.warn "Unable to bulk index record: #{record.inspect} [#{e.message}]"
|
@@ -112,7 +112,7 @@ module ElasticSearchable
|
|
112
112
|
def reindex(lifecycle = nil)
|
113
113
|
query = {}
|
114
114
|
query.merge! :percolate => "*" if self.class.elastic_options[:percolate]
|
115
|
-
response = ElasticSearchable.request :put, self.class.index_type_path(self.id), :query => query, :body => self.as_json_for_index
|
115
|
+
response = ElasticSearchable.request :put, self.class.index_type_path(self.id), :query => query, :body => self.as_json_for_index
|
116
116
|
|
117
117
|
self.run_callbacks("after_index_on_#{lifecycle}".to_sym) if lifecycle
|
118
118
|
self.run_callbacks(:after_index)
|
@@ -135,7 +135,7 @@ module ElasticSearchable
|
|
135
135
|
# can be done automatically when indexing using :percolate => true config option
|
136
136
|
# http://www.elasticsearch.org/blog/2011/02/08/percolator.html
|
137
137
|
def percolate
|
138
|
-
response = ElasticSearchable.request :get, self.class.index_type_path('_percolate'), :body => {:doc => self.as_json_for_index}
|
138
|
+
response = ElasticSearchable.request :get, self.class.index_type_path('_percolate'), :body => {:doc => self.as_json_for_index}
|
139
139
|
response['matches']
|
140
140
|
end
|
141
141
|
|
@@ -13,11 +13,27 @@ module ElasticSearchable
|
|
13
13
|
def search(query, options = {})
|
14
14
|
page = (options.delete(:page) || 1).to_i
|
15
15
|
options[:fields] ||= '_id'
|
16
|
-
options[:q] ||= query
|
17
16
|
options[:size] ||= per_page_for_search(options)
|
18
17
|
options[:from] ||= options[:size] * (page - 1)
|
18
|
+
if query.is_a?(Hash)
|
19
|
+
options[:query] = query
|
20
|
+
else
|
21
|
+
options[:query] = {
|
22
|
+
:query_string => {
|
23
|
+
:query => query,
|
24
|
+
:default_operator => options.delete(:default_operator)
|
25
|
+
}
|
26
|
+
}
|
27
|
+
end
|
28
|
+
query = {}
|
29
|
+
case sort = options.delete(:sort)
|
30
|
+
when Array,Hash
|
31
|
+
options[:sort] = sort
|
32
|
+
when String
|
33
|
+
query[:sort] = sort
|
34
|
+
end
|
19
35
|
|
20
|
-
response = ElasticSearchable.request :get, index_type_path('_search'), :query => options
|
36
|
+
response = ElasticSearchable.request :get, index_type_path('_search'), :query => query, :body => options
|
21
37
|
hits = response['hits']
|
22
38
|
ids = hits['hits'].collect {|h| h['_id'].to_i }
|
23
39
|
results = self.find(ids).sort_by {|result| ids.index(result.id) }
|
data/lib/elastic_searchable.rb
CHANGED
@@ -3,48 +3,38 @@ require 'logger'
|
|
3
3
|
require 'elastic_searchable/active_record_extensions'
|
4
4
|
|
5
5
|
module ElasticSearchable
|
6
|
+
DEFAULT_INDEX = 'elastic_searchable'
|
6
7
|
include HTTParty
|
7
8
|
format :json
|
8
9
|
base_uri 'localhost:9200'
|
9
|
-
#debug_output
|
10
10
|
|
11
11
|
class ElasticError < StandardError; end
|
12
12
|
class << self
|
13
|
-
|
14
|
-
# one index can hold many object 'types'
|
15
|
-
@@default_index = nil
|
16
|
-
def default_index=(index)
|
17
|
-
@@default_index = index
|
18
|
-
end
|
19
|
-
def default_index
|
20
|
-
@@default_index || 'elastic_searchable'
|
21
|
-
end
|
22
|
-
|
23
|
-
@@logger = Logger.new(STDOUT)
|
24
|
-
@@logger.level = Logger::INFO
|
25
|
-
def logger=(logger)
|
26
|
-
@@logger = logger
|
27
|
-
end
|
28
|
-
def logger
|
29
|
-
@@logger
|
30
|
-
end
|
13
|
+
attr_accessor :logger, :default_index, :offline
|
31
14
|
|
32
15
|
# execute a block of work without reindexing objects
|
33
|
-
@@offline = false
|
34
|
-
def offline?
|
35
|
-
@@offline
|
36
|
-
end
|
37
16
|
def offline(&block)
|
38
|
-
|
17
|
+
offline = true
|
39
18
|
yield
|
40
19
|
ensure
|
41
|
-
|
20
|
+
offline = false
|
21
|
+
end
|
22
|
+
def offline?
|
23
|
+
!!offline
|
24
|
+
end
|
25
|
+
# encapsulate encoding hash into json string
|
26
|
+
# support Yajl encoder if installed
|
27
|
+
def encode_json(options = {})
|
28
|
+
defined?(Yajl) ? Yajl::Encoder.encode(options) : ActiveSupport::JSON.encode(options)
|
42
29
|
end
|
43
30
|
# perform a request to the elasticsearch server
|
44
31
|
# configuration:
|
45
32
|
# ElasticSearchable.base_uri 'host:port' controls where to send request to
|
46
33
|
# ElasticSearchable.debug_output outputs all http traffic to console
|
47
34
|
def request(method, url, options = {})
|
35
|
+
options.merge! :headers => {'Content-Type' => 'application/json'}
|
36
|
+
options.merge! :body => ElasticSearchable.encode_json(options[:body]) if options[:body]
|
37
|
+
|
48
38
|
response = self.send(method, url, options)
|
49
39
|
logger.debug "elasticsearch request: #{method} #{url} #{"took #{response['took']}ms" if response['took']}"
|
50
40
|
validate_response response
|
@@ -61,3 +51,11 @@ module ElasticSearchable
|
|
61
51
|
end
|
62
52
|
end
|
63
53
|
|
54
|
+
# configure default logger to standard out with info log level
|
55
|
+
ElasticSearchable.logger = Logger.new STDOUT
|
56
|
+
ElasticSearchable.logger.level = Logger::INFO
|
57
|
+
|
58
|
+
# configure default index to be elastic_searchable
|
59
|
+
# one index can hold many object 'types'
|
60
|
+
ElasticSearchable.default_index = ElasticSearchable::DEFAULT_INDEX
|
61
|
+
|
@@ -6,10 +6,19 @@ class TestElasticSearchable < Test::Unit::TestCase
|
|
6
6
|
end
|
7
7
|
ElasticSearchable.debug_output
|
8
8
|
|
9
|
+
context 'non elastic activerecord class' do
|
10
|
+
class Cat < ActiveRecord::Base
|
11
|
+
end
|
12
|
+
should 'not respond to elastic_options' do
|
13
|
+
assert !Cat.respond_to?(:elastic_options)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
9
17
|
class Post < ActiveRecord::Base
|
10
18
|
elastic_searchable :index_options => {'number_of_replicas' => 0, 'number_of_shards' => 1}
|
11
19
|
after_index :indexed
|
12
20
|
after_index_on_create :indexed_on_create
|
21
|
+
after_index_on_update :indexed_on_update
|
13
22
|
def indexed
|
14
23
|
@indexed = true
|
15
24
|
end
|
@@ -22,6 +31,12 @@ class TestElasticSearchable < Test::Unit::TestCase
|
|
22
31
|
def indexed_on_create?
|
23
32
|
@indexed_on_create
|
24
33
|
end
|
34
|
+
def indexed_on_update
|
35
|
+
@indexed_on_update = true
|
36
|
+
end
|
37
|
+
def indexed_on_update?
|
38
|
+
@indexed_on_update
|
39
|
+
end
|
25
40
|
end
|
26
41
|
context 'activerecord class with default elastic_searchable config' do
|
27
42
|
setup do
|
@@ -64,6 +79,24 @@ class TestElasticSearchable < Test::Unit::TestCase
|
|
64
79
|
should 'have fired after_index_on_create callback' do
|
65
80
|
assert @post.indexed_on_create?
|
66
81
|
end
|
82
|
+
should 'not have fired after_index_on_update callback' do
|
83
|
+
assert !@post.indexed_on_update?
|
84
|
+
end
|
85
|
+
context 'Model.update' do
|
86
|
+
setup do
|
87
|
+
@post.title = 'baz'
|
88
|
+
@post.save
|
89
|
+
end
|
90
|
+
should 'have fired after_index callback' do
|
91
|
+
assert @post.indexed?
|
92
|
+
end
|
93
|
+
should 'not have fired after_index_on_create callback' do
|
94
|
+
assert !@post.indexed_on_create?
|
95
|
+
end
|
96
|
+
should 'have fired after_index_on_update callback' do
|
97
|
+
assert @post.indexed_on_update?
|
98
|
+
end
|
99
|
+
end
|
67
100
|
end
|
68
101
|
|
69
102
|
context 'Model.create within ElasticSearchable.offline block' do
|
@@ -139,6 +172,28 @@ class TestElasticSearchable < Test::Unit::TestCase
|
|
139
172
|
assert_nil @results.next_page
|
140
173
|
end
|
141
174
|
end
|
175
|
+
|
176
|
+
context 'searching for results using a query Hash' do
|
177
|
+
setup do
|
178
|
+
@results = Post.search({
|
179
|
+
:filtered => {
|
180
|
+
:query => {
|
181
|
+
:term => {:title => 'foo'},
|
182
|
+
},
|
183
|
+
:filter => {
|
184
|
+
:or => [
|
185
|
+
{:term => {:body => 'second'}},
|
186
|
+
{:term => {:body => 'third'}}
|
187
|
+
]
|
188
|
+
}
|
189
|
+
}
|
190
|
+
})
|
191
|
+
end
|
192
|
+
should 'find only the object which ' do
|
193
|
+
assert_does_not_contain @results, @first_post
|
194
|
+
assert_contains @results, @second_post
|
195
|
+
end
|
196
|
+
end
|
142
197
|
|
143
198
|
context 'searching for second page using will_paginate params' do
|
144
199
|
setup do
|
@@ -167,6 +222,16 @@ class TestElasticSearchable < Test::Unit::TestCase
|
|
167
222
|
assert_equal @first_post, @results.last
|
168
223
|
end
|
169
224
|
end
|
225
|
+
|
226
|
+
context 'advanced sort options' do
|
227
|
+
setup do
|
228
|
+
@results = Post.search 'foo', :sort => [{:id => 'desc'}]
|
229
|
+
end
|
230
|
+
should 'sort results correctly' do
|
231
|
+
assert_equal @second_post, @results.first
|
232
|
+
assert_equal @first_post, @results.last
|
233
|
+
end
|
234
|
+
end
|
170
235
|
|
171
236
|
context 'destroying one object' do
|
172
237
|
setup do
|
@@ -273,7 +338,7 @@ class TestElasticSearchable < Test::Unit::TestCase
|
|
273
338
|
ElasticSearchable.default_index = 'my_new_index'
|
274
339
|
end
|
275
340
|
teardown do
|
276
|
-
ElasticSearchable.default_index =
|
341
|
+
ElasticSearchable.default_index = ElasticSearchable::DEFAULT_INDEX
|
277
342
|
end
|
278
343
|
should 'change default index' do
|
279
344
|
assert_equal 'my_new_index', ElasticSearchable.default_index
|
@@ -296,7 +361,7 @@ class TestElasticSearchable < Test::Unit::TestCase
|
|
296
361
|
end
|
297
362
|
context "when index has configured percolation" do
|
298
363
|
setup do
|
299
|
-
ElasticSearchable.request :put, '/_percolator/elastic_searchable/myfilter', :body => {:query => {:query_string => {:query => 'foo' }}}
|
364
|
+
ElasticSearchable.request :put, '/_percolator/elastic_searchable/myfilter', :body => {:query => {:query_string => {:query => 'foo' }}}
|
300
365
|
ElasticSearchable.request :post, '/_percolator/_refresh'
|
301
366
|
end
|
302
367
|
context 'creating an object that matches the percolation' do
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: elastic_searchable
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 1
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 7
|
9
|
-
-
|
10
|
-
version: 0.7.
|
9
|
+
- 1
|
10
|
+
version: 0.7.1
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Ryan Sonnek
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2011-04-
|
18
|
+
date: 2011-04-28 00:00:00 Z
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
21
21
|
name: activerecord
|
@@ -197,9 +197,10 @@ extra_rdoc_files: []
|
|
197
197
|
files:
|
198
198
|
- .document
|
199
199
|
- .gitignore
|
200
|
+
- CONTRIBUTORS.txt
|
200
201
|
- Gemfile
|
201
202
|
- LICENSE.txt
|
202
|
-
- README.
|
203
|
+
- README.md
|
203
204
|
- Rakefile
|
204
205
|
- elastic_searchable.gemspec
|
205
206
|
- lib/elastic_searchable.rb
|
data/README.rdoc
DELETED
@@ -1,41 +0,0 @@
|
|
1
|
-
= elastic_searchable
|
2
|
-
|
3
|
-
Integrate the elasticsearch library into Rails.
|
4
|
-
|
5
|
-
== Usage
|
6
|
-
class Blog < ActiveRecord::Base
|
7
|
-
elastic_searchable
|
8
|
-
end
|
9
|
-
|
10
|
-
results = Blog.search 'foo'
|
11
|
-
|
12
|
-
== Features
|
13
|
-
|
14
|
-
* fast. fast! FAST! 30% faster than rubberband on average.
|
15
|
-
* active record callbacks automatically keep search index up to date as your data changes
|
16
|
-
* out of the box background indexing of data using backgrounded. Don't lock up a foreground process waiting on a background job!
|
17
|
-
* integrates with will_paginate library for easy pagination of search results
|
18
|
-
|
19
|
-
== Installation
|
20
|
-
#Gemfile
|
21
|
-
gem 'elastic_searchable'
|
22
|
-
|
23
|
-
== Configuration
|
24
|
-
|
25
|
-
#config/initializers/elastic_searchable.rb
|
26
|
-
#customize elasticsearch host
|
27
|
-
#defaults to localhost:9200
|
28
|
-
ElasticSearchable.base_uri = 'server:9200'
|
29
|
-
|
30
|
-
== Contributing
|
31
|
-
|
32
|
-
* Fork the project
|
33
|
-
* Fix the issue
|
34
|
-
* Add unit tests
|
35
|
-
* Submit pull request on github
|
36
|
-
|
37
|
-
== Copyright
|
38
|
-
|
39
|
-
Copyright (c) 2011 Ryan Sonnek. See LICENSE.txt for
|
40
|
-
further details.
|
41
|
-
|