elastic_searchable 2.0.1 → 2.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -28,4 +28,5 @@ Gem::Specification.new do |s|
28
28
  s.add_development_dependency(%q<pry>, ["0.9.6.2"])
29
29
  s.add_development_dependency(%q<shoulda>, ["2.11.3"])
30
30
  s.add_development_dependency(%q<mocha>, ["0.10.0"])
31
+ s.add_development_dependency(%q<pry>, ["0.9.9.3"])
31
32
  end
@@ -1,79 +1,282 @@
1
1
  require 'active_record'
2
2
  require 'backgrounded'
3
- require 'elastic_searchable/queries'
4
- require 'elastic_searchable/callbacks'
5
- require 'elastic_searchable/index'
6
3
  require 'elastic_searchable/paginator'
7
4
 
8
5
  module ElasticSearchable
9
6
  module ActiveRecordExtensions
10
- # Valid options:
11
- # :type (optional) configue type to store data in. default to model table name
12
- # :mapping (optional) configure field properties for this model (ex: skip analyzer for field)
13
- # :if (optional) reference symbol/proc condition to only index when condition is true
14
- # :unless (optional) reference symbol/proc condition to skip indexing when condition is true
15
- # :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)
16
- #
17
- # Available callbacks:
18
- # after_index
19
- # called after the object is indexed in elasticsearch
20
- # (optional) :on => :create/:update can be used to only fire callback when object is created or updated
21
- #
22
- # after_percolate
23
- # called after object is indexed in elasticsearch
24
- # only fires if the update index call returns a non-empty set of registered percolations
25
- # use the "percolations" instance method from within callback to inspect what percolations were returned
26
- def elastic_searchable(options = {})
27
- cattr_accessor :elastic_options
28
- self.elastic_options = options.symbolize_keys.merge(:unless => Array.wrap(options[:unless]).push(:elasticsearch_offline?))
29
-
30
- if self.elastic_options[:index_options]
31
- ActiveSupport::Deprecation.warn ":index_options has been deprecated. Use ElasticSearchable.index_settings instead.", caller
32
- end
33
- if self.elastic_options[:index]
34
- ActiveSupport::Deprecation.warn ":index has been deprecated. Use ElasticSearchable.index_name instead.", caller
35
- end
7
+ extend ActiveSupport::Concern
36
8
 
37
- extend ElasticSearchable::Indexing::ClassMethods
38
- extend ElasticSearchable::Queries
9
+ included do
10
+ define_model_callbacks :index, :percolate, :only => :after
11
+ end
39
12
 
40
- include ElasticSearchable::Indexing::InstanceMethods
41
- include ElasticSearchable::Callbacks::InstanceMethods
13
+ module ClassMethods
14
+ # Valid options:
15
+ # :type (optional) configue type to store data in. default to model table name
16
+ # :mapping (optional) configure field properties for this model (ex: skip analyzer for field)
17
+ # :if (optional) reference symbol/proc condition to only index when condition is true
18
+ # :unless (optional) reference symbol/proc condition to skip indexing when condition is true
19
+ # :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
+ #
21
+ # after_percolate
22
+ # called after object is indexed in elasticsearch
23
+ # only fires if the update index call returns a non-empty set of registered percolations
24
+ # use the "percolations" instance method from within callback to inspect what percolations were returned
25
+ def elastic_searchable(options = {})
26
+ include ElasticSearchable::ActiveRecordExtensions::LocalMethods
42
27
 
43
- backgrounded :update_index_on_create => ElasticSearchable::Callbacks.backgrounded_options, :update_index_on_update => ElasticSearchable::Callbacks.backgrounded_options
44
- class << self
45
- backgrounded :delete_id_from_index => ElasticSearchable::Callbacks.backgrounded_options
46
- end
28
+ cattr_accessor :elastic_options
29
+ self.elastic_options = options.symbolize_keys.merge(:unless => Array.wrap(options[:unless]).push(:elasticsearch_offline?))
30
+ attr_reader :hit
31
+ attr_accessor :index_lifecycle
47
32
 
48
- attr_reader :hit # the hit json for this result
49
- attr_accessor :index_lifecycle, :percolations
50
- define_model_callbacks :index, :percolate, :only => :after
51
- after_commit :update_index_on_create_backgrounded, :if => :should_index?, :on => :create
52
- after_commit :update_index_on_update_backgrounded, :if => :should_index?, :on => :update
53
- after_commit :delete_from_index, :unless => :elasticsearch_offline?, :on => :destroy
54
-
55
- class_eval do
56
- # retuns list of percolation matches found during indexing
57
- def percolations
58
- @percolations || []
33
+ if self.elastic_options[:index_options]
34
+ ActiveSupport::Deprecation.warn ":index_options has been deprecated. Use ElasticSearchable.index_settings instead.", caller
35
+ end
36
+ if self.elastic_options[:index]
37
+ ActiveSupport::Deprecation.warn ":index has been deprecated. Use ElasticSearchable.index_name instead.", caller
59
38
  end
60
39
 
40
+ backgrounded :update_index_on_create => ElasticSearchable.backgrounded_options, :update_index_on_update => ElasticSearchable.backgrounded_options
61
41
  class << self
62
- # override default after_index callback definition to support :on option
63
- # see ActiveRecord::Transactions::ClassMethods#after_commit for example
64
- def after_index(*args, &block)
65
- options = args.last
66
- if options.is_a?(Hash) && options[:on]
67
- options[:if] = Array.wrap(options[:if])
68
- options[:if] << "self.index_lifecycle == :#{options[:on]}"
42
+ backgrounded :delete_id_from_index => ElasticSearchable.backgrounded_options
43
+ end
44
+
45
+ after_commit :update_index_on_create_backgrounded, :if => :should_index?, :on => :create
46
+ after_commit :update_index_on_update_backgrounded, :if => :should_index?, :on => :update
47
+ after_commit :delete_from_index, :unless => :elasticsearch_offline?, :on => :destroy
48
+ end
49
+ end
50
+
51
+ module LocalMethods
52
+ extend ActiveSupport::Concern
53
+
54
+ module ClassMethods
55
+ PER_PAGE_DEFAULT = 20
56
+
57
+ # Available callback method after indexing is complete
58
+ # called after the object is indexed in elasticsearch
59
+ # (optional) :on => :create/:update can be used to only fire callback when object is created or updated
60
+ # override default after_index callback definition to support :on option
61
+ # see ActiveRecord::Transactions::ClassMethods#after_commit for example
62
+ def after_index(*args, &block)
63
+ options = args.last
64
+ if options.is_a?(Hash) && options[:on]
65
+ options[:if] = Array.wrap(options[:if])
66
+ options[:if] << "self.index_lifecycle == :#{options[:on]}"
67
+ end
68
+ set_callback(:index, :after, *args, &block)
69
+ end
70
+
71
+ # default number of search results for this model
72
+ # can be overridden by implementing classes
73
+ def per_page
74
+ PER_PAGE_DEFAULT
75
+ end
76
+
77
+ # reindex all records using bulk api
78
+ # see http://www.elasticsearch.org/guide/reference/api/bulk.html
79
+ # options:
80
+ # :scope - scope to use for looking up records to reindex. defaults to self (all)
81
+ # :page - page/batch to begin indexing at. defaults to 1
82
+ # :per_page - number of records to index per batch. defaults to 1000
83
+ #
84
+ # TODO: move this to AREL relation to remove the options scope param
85
+ def reindex(options = {})
86
+ self.create_mapping
87
+ options.reverse_merge! :page => 1, :per_page => 1000
88
+ scope = options.delete(:scope) || self
89
+ page = options[:page]
90
+ per_page = options[:per_page]
91
+ records = scope.limit(per_page).offset(per_page * (page -1)).all
92
+ while records.any? do
93
+ ElasticSearchable.logger.debug "reindexing batch ##{page}..."
94
+ actions = []
95
+ records.each do |record|
96
+ next unless record.should_index?
97
+ begin
98
+ doc = ElasticSearchable.encode_json(record.as_json_for_index)
99
+ actions << ElasticSearchable.encode_json({:index => {'_index' => ElasticSearchable.index_name, '_type' => index_type, '_id' => record.id}})
100
+ actions << doc
101
+ rescue => e
102
+ ElasticSearchable.logger.warn "Unable to bulk index record: #{record.inspect} [#{e.message}]"
103
+ end
104
+ end
105
+ begin
106
+ ElasticSearchable.request(:put, '/_bulk', :body => "\n#{actions.join("\n")}\n") if actions.any?
107
+ rescue ElasticError => e
108
+ ElasticSearchable.logger.warn "Error indexing batch ##{page}: #{e.message}"
109
+ ElasticSearchable.logger.warn e
69
110
  end
70
- set_callback(:index, :after, *args, &block)
111
+
112
+ page += 1
113
+ records = scope.limit(per_page).offset(per_page* (page-1)).all
71
114
  end
115
+ end
116
+
117
+ # search returns a will_paginate collection of ActiveRecord objects for the search results
118
+ # supported options:
119
+ # :page - page of results to search for
120
+ # :per_page - number of results per page
121
+ #
122
+ # http://www.elasticsearch.com/docs/elasticsearch/rest_api/search/
123
+ def search(query, options = {})
124
+ page = (options.delete(:page) || 1).to_i
125
+ options[:fields] ||= '_id'
126
+ options[:size] ||= per_page_for_search(options)
127
+ options[:from] ||= options[:size] * (page - 1)
128
+ options[:query] ||= if query.is_a?(Hash)
129
+ query
130
+ else
131
+ {}.tap do |q|
132
+ q[:query_string] = { :query => query }
133
+ q[:query_string][:default_operator] = options.delete(:default_operator) if options.has_key?(:default_operator)
134
+ end
135
+ end
136
+ query = {}
137
+ case sort = options.delete(:sort)
138
+ when Array,Hash
139
+ options[:sort] = sort
140
+ when String
141
+ query[:sort] = sort
142
+ end
143
+
144
+ response = ElasticSearchable.request :get, index_mapping_path('_search'), :query => query, :json_body => options
145
+ hits = response['hits']
146
+ ids = hits['hits'].collect {|h| h['_id'].to_i }
147
+ results = self.find(ids).sort_by {|result| ids.index(result.id) }
148
+
149
+ results.each do |result|
150
+ result.instance_variable_set '@hit', hits['hits'][ids.index(result.id)]
151
+ end
152
+
153
+ ElasticSearchable::Paginator.handler.new(results, page, options[:size], hits['total'])
154
+ end
72
155
 
156
+ def index_type
157
+ self.elastic_options[:type] || self.table_name
158
+ end
159
+
160
+ # helper method to generate elasticsearch url for this object type
161
+ def index_mapping_path(action = nil)
162
+ ElasticSearchable.request_path [index_type, action].compact.join('/')
163
+ end
164
+
165
+ # delete all documents of this type in the index
166
+ # http://www.elasticsearch.com/docs/elasticsearch/rest_api/admin/indices/delete_mapping/
167
+ def delete_mapping
168
+ ElasticSearchable.request :delete, index_mapping_path
169
+ end
170
+
171
+ # configure the index for this type
172
+ # http://www.elasticsearch.com/docs/elasticsearch/rest_api/admin/indices/put_mapping/
173
+ def create_mapping
174
+ return unless self.elastic_options[:mapping]
175
+ ElasticSearchable.request :put, index_mapping_path('_mapping'), :json_body => {index_type => self.elastic_options[:mapping]}
176
+ end
177
+
178
+ # delete one record from the index
179
+ # http://www.elasticsearch.com/docs/elasticsearch/rest_api/delete/
180
+ def delete_id_from_index(id)
181
+ ElasticSearchable.request :delete, index_mapping_path(id)
182
+ rescue ElasticSearchable::ElasticError => e
183
+ ElasticSearchable.logger.warn e
184
+ end
185
+
186
+ private
187
+ # determine the number of search results per page
188
+ # supports will_paginate configuration by using:
189
+ # Model.per_page
190
+ # Model.max_per_page
191
+ def per_page_for_search(options = {})
192
+ per_page = (options.delete(:per_page) || self.per_page).to_i
193
+ per_page = [per_page, self.max_per_page].min if self.respond_to?(:max_per_page)
194
+ per_page
195
+ end
196
+ end
197
+
198
+ # retuns list of percolation matches found during indexing
199
+ # usable when the model is configured with an :after_index callback
200
+ def percolations
201
+ @percolations || []
202
+ end
203
+
204
+ # reindex the object in elasticsearch
205
+ # fires after_index callbacks after operation is complete
206
+ # see http://www.elasticsearch.org/guide/reference/api/index_.html
207
+ def reindex(lifecycle = nil)
208
+ query = {}
209
+ query[:percolate] = "*" if _percolate_callbacks.any?
210
+ response = ElasticSearchable.request :put, self.class.index_mapping_path(self.id), :query => query, :json_body => self.as_json_for_index
211
+
212
+ self.index_lifecycle = lifecycle ? lifecycle.to_sym : nil
213
+ _run_index_callbacks
214
+
215
+ @percolations = response['matches'] || []
216
+ _run_percolate_callbacks if @percolations.any?
217
+ end
218
+
219
+ # document to index in elasticsearch
220
+ # can be overridden by implementing class to customize the content
221
+ def as_json_for_index
222
+ original_include_root_in_json = self.class.include_root_in_json
223
+ self.class.include_root_in_json = false
224
+ return self.as_json self.class.elastic_options[:json]
225
+ ensure
226
+ self.class.include_root_in_json = original_include_root_in_json
227
+ end
228
+
229
+ # flag to tell if this instance should be indexed
230
+ def should_index?
231
+ [self.class.elastic_options[:if]].flatten.compact.all? { |m| evaluate_elastic_condition(m) } &&
232
+ ![self.class.elastic_options[:unless]].flatten.compact.any? { |m| evaluate_elastic_condition(m) }
233
+ end
234
+
235
+ # percolate this object to see what registered searches match
236
+ # can be done on transient/non-persisted objects!
237
+ # can be done automatically when indexing using :percolate => true config option
238
+ # http://www.elasticsearch.org/blog/2011/02/08/percolator.html
239
+ def percolate(percolator_query = nil)
240
+ body = {:doc => self.as_json_for_index}
241
+ body[:query] = percolator_query if percolator_query
242
+ response = ElasticSearchable.request :get, self.class.index_mapping_path('_percolate'), :json_body => body
243
+ @percolations = response['matches'] || []
244
+ end
245
+
246
+ private
247
+ def delete_from_index
248
+ self.class.delete_id_from_index_backgrounded self.id
249
+ end
250
+ def update_index_on_create
251
+ reindex :create
252
+ end
253
+ def update_index_on_update
254
+ reindex :update
255
+ end
256
+ def elasticsearch_offline?
257
+ ElasticSearchable.offline?
258
+ end
259
+ # ripped from activesupport
260
+ def evaluate_elastic_condition(method)
261
+ case method
262
+ when Symbol
263
+ self.send method
264
+ when String
265
+ eval(method, self.instance_eval { binding })
266
+ when Proc, Method
267
+ method.call
268
+ else
269
+ if method.respond_to?(kind)
270
+ method.send kind
271
+ else
272
+ raise ArgumentError,
273
+ "Callbacks must be a symbol denoting the method to call, a string to be evaluated, " +
274
+ "a block to be invoked, or an object responding to the callback method."
275
+ end
73
276
  end
74
277
  end
75
278
  end
76
279
  end
77
280
  end
78
281
 
79
- ActiveRecord::Base.send(:extend, ElasticSearchable::ActiveRecordExtensions)
282
+ ActiveRecord::Base.send(:include, ElasticSearchable::ActiveRecordExtensions)
@@ -1,3 +1,3 @@
1
1
  module ElasticSearchable
2
- VERSION = '2.0.1'
2
+ VERSION = '2.0.2'
3
3
  end
@@ -43,6 +43,11 @@ module ElasticSearchable
43
43
  response
44
44
  end
45
45
 
46
+ # options for automatic active record indexing
47
+ def backgrounded_options
48
+ {:queue => 'elasticsearch'}
49
+ end
50
+
46
51
  # escape lucene special characters
47
52
  def escape_query(string)
48
53
  string.to_s.gsub(/([\(\)\[\]\{\}\?\\\"!\^\+\-\*:~])/,'\\\\\1')
@@ -92,4 +97,3 @@ ElasticSearchable.logger.level = Logger::INFO
92
97
  # configure default index to be elastic_searchable
93
98
  # one index can hold many object 'types'
94
99
  ElasticSearchable.index_name = 'elastic_searchable'
95
-
@@ -388,6 +388,7 @@ class TestElasticSearchable < Test::Unit::TestCase
388
388
  end
389
389
  context "when index has configured percolation" do
390
390
  setup do
391
+ ElasticSearchable.request :delete, '/_percolator'
391
392
  ElasticSearchable.request :put, '/_percolator/elastic_searchable/myfilter', :json_body => {:query => {:query_string => {:query => 'foo' }}}
392
393
  ElasticSearchable.request :post, '/_percolator/_refresh'
393
394
  end
@@ -416,6 +417,7 @@ class TestElasticSearchable < Test::Unit::TestCase
416
417
  end
417
418
  context "with multiple percolators" do
418
419
  setup do
420
+ ElasticSearchable.request :delete, '/_percolator'
419
421
  ElasticSearchable.request :put, '/_percolator/elastic_searchable/greenfilter', :json_body => { :color => 'green', :query => {:query_string => {:query => 'foo' }}}
420
422
  ElasticSearchable.request :put, '/_percolator/elastic_searchable/bluefilter', :json_body => { :color => 'blue', :query => {:query_string => {:query => 'foo' }}}
421
423
  ElasticSearchable.request :post, '/_percolator/_refresh'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: elastic_searchable
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.1
4
+ version: 2.0.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -13,7 +13,7 @@ date: 2012-04-23 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activerecord
16
- requirement: &2151868260 !ruby/object:Gem::Requirement
16
+ requirement: &2151881900 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: 3.0.5
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *2151868260
24
+ version_requirements: *2151881900
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: httparty
27
- requirement: &2151867400 !ruby/object:Gem::Requirement
27
+ requirement: &2151881240 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: 0.6.0
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *2151867400
35
+ version_requirements: *2151881240
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: backgrounded
38
- requirement: &2151866680 !ruby/object:Gem::Requirement
38
+ requirement: &2151880600 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: 0.7.0
44
44
  type: :runtime
45
45
  prerelease: false
46
- version_requirements: *2151866680
46
+ version_requirements: *2151880600
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: multi_json
49
- requirement: &2151866000 !ruby/object:Gem::Requirement
49
+ requirement: &2151880000 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ! '>='
@@ -54,10 +54,10 @@ dependencies:
54
54
  version: 1.0.0
55
55
  type: :runtime
56
56
  prerelease: false
57
- version_requirements: *2151866000
57
+ version_requirements: *2151880000
58
58
  - !ruby/object:Gem::Dependency
59
59
  name: rake
60
- requirement: &2151865340 !ruby/object:Gem::Requirement
60
+ requirement: &2151879320 !ruby/object:Gem::Requirement
61
61
  none: false
62
62
  requirements:
63
63
  - - =
@@ -65,10 +65,10 @@ dependencies:
65
65
  version: 0.9.2.2
66
66
  type: :development
67
67
  prerelease: false
68
- version_requirements: *2151865340
68
+ version_requirements: *2151879320
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: sqlite3
71
- requirement: &2151864760 !ruby/object:Gem::Requirement
71
+ requirement: &2151894960 !ruby/object:Gem::Requirement
72
72
  none: false
73
73
  requirements:
74
74
  - - =
@@ -76,10 +76,10 @@ dependencies:
76
76
  version: 1.3.4
77
77
  type: :development
78
78
  prerelease: false
79
- version_requirements: *2151864760
79
+ version_requirements: *2151894960
80
80
  - !ruby/object:Gem::Dependency
81
81
  name: pry
82
- requirement: &2151864020 !ruby/object:Gem::Requirement
82
+ requirement: &2151894160 !ruby/object:Gem::Requirement
83
83
  none: false
84
84
  requirements:
85
85
  - - =
@@ -87,10 +87,10 @@ dependencies:
87
87
  version: 0.9.6.2
88
88
  type: :development
89
89
  prerelease: false
90
- version_requirements: *2151864020
90
+ version_requirements: *2151894160
91
91
  - !ruby/object:Gem::Dependency
92
92
  name: shoulda
93
- requirement: &2151863260 !ruby/object:Gem::Requirement
93
+ requirement: &2151893480 !ruby/object:Gem::Requirement
94
94
  none: false
95
95
  requirements:
96
96
  - - =
@@ -98,10 +98,10 @@ dependencies:
98
98
  version: 2.11.3
99
99
  type: :development
100
100
  prerelease: false
101
- version_requirements: *2151863260
101
+ version_requirements: *2151893480
102
102
  - !ruby/object:Gem::Dependency
103
103
  name: mocha
104
- requirement: &2151862580 !ruby/object:Gem::Requirement
104
+ requirement: &2151892820 !ruby/object:Gem::Requirement
105
105
  none: false
106
106
  requirements:
107
107
  - - =
@@ -109,7 +109,18 @@ dependencies:
109
109
  version: 0.10.0
110
110
  type: :development
111
111
  prerelease: false
112
- version_requirements: *2151862580
112
+ version_requirements: *2151892820
113
+ - !ruby/object:Gem::Dependency
114
+ name: pry
115
+ requirement: &2151892120 !ruby/object:Gem::Requirement
116
+ none: false
117
+ requirements:
118
+ - - =
119
+ - !ruby/object:Gem::Version
120
+ version: 0.9.9.3
121
+ type: :development
122
+ prerelease: false
123
+ version_requirements: *2151892120
113
124
  description: integrate the elastic search engine with rails
114
125
  email:
115
126
  - ryan@codecrate.com
@@ -128,12 +139,9 @@ files:
128
139
  - elastic_searchable.gemspec
129
140
  - lib/elastic_searchable.rb
130
141
  - lib/elastic_searchable/active_record_extensions.rb
131
- - lib/elastic_searchable/callbacks.rb
132
- - lib/elastic_searchable/index.rb
133
142
  - lib/elastic_searchable/pagination/kaminari.rb
134
143
  - lib/elastic_searchable/pagination/will_paginate.rb
135
144
  - lib/elastic_searchable/paginator.rb
136
- - lib/elastic_searchable/queries.rb
137
145
  - lib/elastic_searchable/version.rb
138
146
  - test/database.yml
139
147
  - test/helper.rb
@@ -153,7 +161,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
153
161
  version: '0'
154
162
  segments:
155
163
  - 0
156
- hash: 808032034448504633
164
+ hash: 2660925478965356473
157
165
  required_rubygems_version: !ruby/object:Gem::Requirement
158
166
  none: false
159
167
  requirements:
@@ -162,7 +170,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
162
170
  version: '0'
163
171
  segments:
164
172
  - 0
165
- hash: 808032034448504633
173
+ hash: 2660925478965356473
166
174
  requirements: []
167
175
  rubyforge_project: elastic_searchable
168
176
  rubygems_version: 1.8.15
@@ -1,22 +0,0 @@
1
- module ElasticSearchable
2
- module Callbacks
3
- class << self
4
- def backgrounded_options
5
- {:queue => 'elasticsearch'}
6
- end
7
- end
8
-
9
- module InstanceMethods
10
- private
11
- def delete_from_index
12
- self.class.delete_id_from_index_backgrounded self.id
13
- end
14
- def update_index_on_create
15
- reindex :create
16
- end
17
- def update_index_on_update
18
- reindex :update
19
- end
20
- end
21
- end
22
- end
@@ -1,140 +0,0 @@
1
- module ElasticSearchable
2
- module Indexing
3
- module ClassMethods
4
- # delete all documents of this type in the index
5
- # http://www.elasticsearch.com/docs/elasticsearch/rest_api/admin/indices/delete_mapping/
6
- def delete_mapping
7
- ElasticSearchable.request :delete, index_mapping_path
8
- end
9
-
10
- # configure the index for this type
11
- # http://www.elasticsearch.com/docs/elasticsearch/rest_api/admin/indices/put_mapping/
12
- def create_mapping
13
- return unless self.elastic_options[:mapping]
14
- ElasticSearchable.request :put, index_mapping_path('_mapping'), :json_body => {index_type => self.elastic_options[:mapping]}
15
- end
16
-
17
- # delete one record from the index
18
- # http://www.elasticsearch.com/docs/elasticsearch/rest_api/delete/
19
- def delete_id_from_index(id)
20
- ElasticSearchable.request :delete, index_mapping_path(id)
21
- rescue ElasticSearchable::ElasticError => e
22
- ElasticSearchable.logger.warn e
23
- end
24
-
25
- # helper method to generate elasticsearch url for this object type
26
- def index_mapping_path(action = nil)
27
- ElasticSearchable.request_path [index_type, action].compact.join('/')
28
- end
29
-
30
- # reindex all records using bulk api
31
- # see http://www.elasticsearch.org/guide/reference/api/bulk.html
32
- # options:
33
- # :scope - scope to use for looking up records to reindex. defaults to self (all)
34
- # :page - page/batch to begin indexing at. defaults to 1
35
- # :per_page - number of records to index per batch. defaults to 1000
36
- #
37
- # TODO: move this to AREL relation to remove the options scope param
38
- def reindex(options = {})
39
- self.create_mapping
40
- options.reverse_merge! :page => 1, :per_page => 1000
41
- scope = options.delete(:scope) || self
42
- page = options[:page]
43
- per_page = options[:per_page]
44
- records = scope.limit(per_page).offset(per_page * (page -1)).all
45
- while records.any? do
46
- ElasticSearchable.logger.debug "reindexing batch ##{page}..."
47
- actions = []
48
- records.each do |record|
49
- next unless record.should_index?
50
- begin
51
- doc = ElasticSearchable.encode_json(record.as_json_for_index)
52
- actions << ElasticSearchable.encode_json({:index => {'_index' => ElasticSearchable.index_name, '_type' => index_type, '_id' => record.id}})
53
- actions << doc
54
- rescue => e
55
- ElasticSearchable.logger.warn "Unable to bulk index record: #{record.inspect} [#{e.message}]"
56
- end
57
- end
58
- begin
59
- ElasticSearchable.request(:put, '/_bulk', :body => "\n#{actions.join("\n")}\n") if actions.any?
60
- rescue ElasticError => e
61
- ElasticSearchable.logger.warn "Error indexing batch ##{page}: #{e.message}"
62
- ElasticSearchable.logger.warn e
63
- end
64
-
65
- page += 1
66
- records = scope.limit(per_page).offset(per_page* (page-1)).all
67
- end
68
- end
69
-
70
- private
71
- def index_type
72
- self.elastic_options[:type] || self.table_name
73
- end
74
- end
75
-
76
- module InstanceMethods
77
- # reindex the object in elasticsearch
78
- # fires after_index callbacks after operation is complete
79
- # see http://www.elasticsearch.org/guide/reference/api/index_.html
80
- def reindex(lifecycle = nil)
81
- query = {}
82
- query[:percolate] = "*" if _percolate_callbacks.any?
83
- response = ElasticSearchable.request :put, self.class.index_mapping_path(self.id), :query => query, :json_body => self.as_json_for_index
84
-
85
- self.index_lifecycle = lifecycle ? lifecycle.to_sym : nil
86
- _run_index_callbacks
87
-
88
- self.percolations = response['matches'] || []
89
- _run_percolate_callbacks if self.percolations.any?
90
- end
91
- # document to index in elasticsearch
92
- def as_json_for_index
93
- original_include_root_in_json = self.class.include_root_in_json
94
- self.class.include_root_in_json = false
95
- return self.as_json self.class.elastic_options[:json]
96
- ensure
97
- self.class.include_root_in_json = original_include_root_in_json
98
- end
99
- def should_index?
100
- [self.class.elastic_options[:if]].flatten.compact.all? { |m| evaluate_elastic_condition(m) } &&
101
- ![self.class.elastic_options[:unless]].flatten.compact.any? { |m| evaluate_elastic_condition(m) }
102
- end
103
- # percolate this object to see what registered searches match
104
- # can be done on transient/non-persisted objects!
105
- # can be done automatically when indexing using :percolate => true config option
106
- # http://www.elasticsearch.org/blog/2011/02/08/percolator.html
107
- def percolate(percolator_query = nil)
108
- body = {:doc => self.as_json_for_index}
109
- body[:query] = percolator_query if percolator_query
110
- response = ElasticSearchable.request :get, self.class.index_mapping_path('_percolate'), :json_body => body
111
- self.percolations = response['matches'] || []
112
- self.percolations
113
- end
114
-
115
- private
116
- def elasticsearch_offline?
117
- ElasticSearchable.offline?
118
- end
119
- # ripped from activesupport
120
- def evaluate_elastic_condition(method)
121
- case method
122
- when Symbol
123
- self.send method
124
- when String
125
- eval(method, self.instance_eval { binding })
126
- when Proc, Method
127
- method.call
128
- else
129
- if method.respond_to?(kind)
130
- method.send kind
131
- else
132
- raise ArgumentError,
133
- "Callbacks must be a symbol denoting the method to call, a string to be evaluated, " +
134
- "a block to be invoked, or an object responding to the callback method."
135
- end
136
- end
137
- end
138
- end
139
- end
140
- end
@@ -1,59 +0,0 @@
1
- module ElasticSearchable
2
- module Queries
3
- PER_PAGE_DEFAULT = 20
4
-
5
- def per_page
6
- PER_PAGE_DEFAULT
7
- end
8
-
9
- # search returns a will_paginate collection of ActiveRecord objects for the search results
10
- # supported options:
11
- # :page - page of results to search for
12
- # :per_page - number of results per page
13
- #
14
- # http://www.elasticsearch.com/docs/elasticsearch/rest_api/search/
15
- def search(query, options = {})
16
- page = (options.delete(:page) || 1).to_i
17
- options[:fields] ||= '_id'
18
- options[:size] ||= per_page_for_search(options)
19
- options[:from] ||= options[:size] * (page - 1)
20
- options[:query] ||= if query.is_a?(Hash)
21
- query
22
- else
23
- {}.tap do |q|
24
- q[:query_string] = { :query => query }
25
- q[:query_string][:default_operator] = options.delete(:default_operator) if options.has_key?(:default_operator)
26
- end
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
35
-
36
- response = ElasticSearchable.request :get, index_mapping_path('_search'), :query => query, :json_body => options
37
- hits = response['hits']
38
- ids = hits['hits'].collect {|h| h['_id'].to_i }
39
- results = self.find(ids).sort_by {|result| ids.index(result.id) }
40
-
41
- results.each do |result|
42
- result.instance_variable_set '@hit', hits['hits'][ids.index(result.id)]
43
- end
44
-
45
- ElasticSearchable::Paginator.handler.new(results, page, options[:size], hits['total'])
46
- end
47
-
48
- private
49
- # determine the number of search results per page
50
- # supports will_paginate configuration by using:
51
- # Model.per_page
52
- # Model.max_per_page
53
- def per_page_for_search(options = {})
54
- per_page = (options.delete(:per_page) || (self.respond_to?(:per_page) ? self.per_page : nil) || ElasticSearchable::Queries::PER_PAGE_DEFAULT).to_i
55
- per_page = [per_page, self.max_per_page].min if self.respond_to?(:max_per_page)
56
- per_page
57
- end
58
- end
59
- end