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 ADDED
@@ -0,0 +1,6 @@
1
+ Ryan Sonnek - Original Author
2
+
3
+
4
+ Complete list of contributors:
5
+ https://github.com/socialcast/elastic_searchable/contributors
6
+
data/LICENSE.txt CHANGED
@@ -1,20 +1,22 @@
1
- Copyright (c) 2011 Ryan Sonnek
1
+ The MIT License
2
2
 
3
- Permission is hereby granted, free of charge, to any person obtaining
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
- The above copyright notice and this permission notice shall be
12
- included in all copies or substantial portions of the Software.
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}.to_json
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.to_json
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.to_json
81
- actions << {:index => {'_index' => index_name, '_type' => index_type, '_id' => record.id}}.to_json
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.to_json
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}.to_json
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) }
@@ -1,4 +1,4 @@
1
1
  module ElasticSearchable
2
- VERSION = '0.7.0'
2
+ VERSION = '0.7.1'
3
3
  end
4
4
 
@@ -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
- # setup the default index to use
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
- @@offline = true
17
+ offline = true
39
18
  yield
40
19
  ensure
41
- @@offline = false
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 = nil
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' }}}.to_json
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: 3
4
+ hash: 1
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 7
9
- - 0
10
- version: 0.7.0
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-14 00:00:00 Z
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.rdoc
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
-