elastic_searchable 0.7.3 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +7 -10
- data/LICENSE.txt +17 -19
- data/README.rdoc +44 -0
- data/VERSION +1 -0
- data/elastic_searchable.gemspec +3 -3
- data/lib/elastic_searchable/active_record.rb +67 -0
- data/lib/elastic_searchable/index.rb +30 -39
- data/lib/elastic_searchable/queries.rb +2 -18
- data/lib/elastic_searchable/version.rb +1 -1
- data/lib/elastic_searchable.rb +19 -31
- data/test/helper.rb +5 -2
- data/test/test_elastic_searchable.rb +81 -149
- metadata +28 -46
- data/CONTRIBUTORS.txt +0 -6
- data/README.md +0 -51
- data/lib/elastic_searchable/active_record_extensions.rb +0 -41
- data/test/setup_database.rb +0 -33
data/.gitignore
CHANGED
@@ -19,15 +19,6 @@ pkg
|
|
19
19
|
test/*.log
|
20
20
|
test/*.sqlite3
|
21
21
|
|
22
|
-
# For vim:
|
23
|
-
*.swp
|
24
|
-
|
25
|
-
# For MacOS:
|
26
|
-
.DS_Store
|
27
|
-
|
28
|
-
# git files
|
29
|
-
*.orig
|
30
|
-
|
31
22
|
# Have editor/IDE/OS specific files you need to ignore? Consider using a global gitignore:
|
32
23
|
#
|
33
24
|
# * Create a file at ~/.gitignore
|
@@ -39,6 +30,10 @@ test/*.sqlite3
|
|
39
30
|
#
|
40
31
|
# Not sure what to needs to be ignored for particular editors/OSes? Here's some ideas to get you started. (Remember, remove the leading # of the line)
|
41
32
|
#
|
33
|
+
# For MacOS:
|
34
|
+
#
|
35
|
+
#.DS_Store
|
36
|
+
#
|
42
37
|
# For TextMate
|
43
38
|
#*.tmproj
|
44
39
|
#tmtags
|
@@ -47,4 +42,6 @@ test/*.sqlite3
|
|
47
42
|
#*~
|
48
43
|
#\#*
|
49
44
|
#.\#*
|
50
|
-
|
45
|
+
#
|
46
|
+
# For vim:
|
47
|
+
#*.swp
|
data/LICENSE.txt
CHANGED
@@ -1,22 +1,20 @@
|
|
1
|
-
|
1
|
+
Copyright (c) 2011 Ryan Sonnek
|
2
2
|
|
3
|
-
|
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:
|
4
10
|
|
5
|
-
|
6
|
-
|
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.
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
22
13
|
|
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.rdoc
ADDED
@@ -0,0 +1,44 @@
|
|
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 to elastic_searchable
|
31
|
+
|
32
|
+
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
|
33
|
+
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
|
34
|
+
* Fork the project
|
35
|
+
* Start a feature/bugfix branch
|
36
|
+
* Commit and push until you are happy with your contribution
|
37
|
+
* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
|
38
|
+
* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
|
39
|
+
|
40
|
+
== Copyright
|
41
|
+
|
42
|
+
Copyright (c) 2011 Ryan Sonnek. See LICENSE.txt for
|
43
|
+
further details.
|
44
|
+
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.6.1
|
data/elastic_searchable.gemspec
CHANGED
@@ -14,18 +14,18 @@ Gem::Specification.new do |s|
|
|
14
14
|
|
15
15
|
s.rubyforge_project = "elastic_searchable"
|
16
16
|
|
17
|
-
s.add_runtime_dependency(%q<activerecord>, ["~>
|
17
|
+
s.add_runtime_dependency(%q<activerecord>, ["~> 3.0.0"])
|
18
18
|
s.add_runtime_dependency(%q<httparty>, ["~> 0.6.0"])
|
19
19
|
s.add_runtime_dependency(%q<backgrounded>, ["~> 0.7.0"])
|
20
20
|
s.add_runtime_dependency(%q<will_paginate>, ["~> 2.3.15"])
|
21
|
-
s.add_runtime_dependency(%q<larsklevan-after_commit>, ["~> 1.0.5"])
|
22
21
|
s.add_development_dependency(%q<shoulda>, [">= 0"])
|
23
22
|
s.add_development_dependency(%q<mocha>, [">= 0"])
|
24
|
-
s.add_development_dependency(%q<
|
23
|
+
s.add_development_dependency(%q<bundler>, [">= 0"])
|
25
24
|
s.add_development_dependency(%q<rcov>, [">= 0"])
|
26
25
|
s.add_development_dependency(%q<sqlite3-ruby>, ["~> 1.3.2"])
|
27
26
|
s.add_development_dependency(%q<ruby-debug>, [">= 0"])
|
28
27
|
|
28
|
+
|
29
29
|
s.files = `git ls-files`.split("\n")
|
30
30
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
31
31
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
require 'backgrounded'
|
3
|
+
require 'elastic_searchable/queries'
|
4
|
+
require 'elastic_searchable/callbacks'
|
5
|
+
require 'elastic_searchable/index'
|
6
|
+
|
7
|
+
module ElasticSearchable
|
8
|
+
module ActiveRecord
|
9
|
+
def self.included(base)
|
10
|
+
base.send :extend, ClassMethods
|
11
|
+
end
|
12
|
+
|
13
|
+
module ClassMethods
|
14
|
+
attr_accessor :elastic_options
|
15
|
+
|
16
|
+
# Valid options:
|
17
|
+
# :index (optional) configure index to store data in. default to ElasticSearchable.default_index
|
18
|
+
# :type (optional) configue type to store data in. default to model table name
|
19
|
+
# :index_options (optional) configure index properties (ex: tokenizer)
|
20
|
+
# :mapping (optional) configure field properties for this model (ex: skip analyzer for field)
|
21
|
+
# :if (optional) reference symbol/proc condition to only index when condition is true
|
22
|
+
# :unless (optional) reference symbol/proc condition to skip indexing when condition is true
|
23
|
+
# :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)
|
24
|
+
#
|
25
|
+
# Available callbacks:
|
26
|
+
# after_index
|
27
|
+
# called after the object is indexed in elasticsearch
|
28
|
+
# (optional) :on => :create/:update can be used to only fire callback when object is created or updated
|
29
|
+
#
|
30
|
+
# after_percolate
|
31
|
+
# called after object is indexed in elasticsearch
|
32
|
+
# only fires if the update index call returns a non-empty set of registered percolations
|
33
|
+
# use the "percolations" instance method from within callback to inspect what percolations were returned
|
34
|
+
def elastic_searchable(options = {})
|
35
|
+
options.symbolize_keys!
|
36
|
+
self.elastic_options = options
|
37
|
+
|
38
|
+
extend ElasticSearchable::Indexing::ClassMethods
|
39
|
+
extend ElasticSearchable::Queries
|
40
|
+
|
41
|
+
include ElasticSearchable::Indexing::InstanceMethods
|
42
|
+
include ElasticSearchable::Callbacks::InstanceMethods
|
43
|
+
|
44
|
+
backgrounded :update_index_on_create => ElasticSearchable::Callbacks.backgrounded_options, :update_index_on_update => ElasticSearchable::Callbacks.backgrounded_options
|
45
|
+
class << self
|
46
|
+
backgrounded :delete_id_from_index => ElasticSearchable::Callbacks.backgrounded_options
|
47
|
+
end
|
48
|
+
|
49
|
+
attr_accessor :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, :on => :destroy
|
54
|
+
end
|
55
|
+
# override default after_index callback definition to support :on option
|
56
|
+
# see ActiveRecord::Transactions::ClassMethods#after_commit for example
|
57
|
+
def after_index(*args, &block)
|
58
|
+
options = args.last
|
59
|
+
if options.is_a?(Hash) && options[:on]
|
60
|
+
options[:if] = Array.wrap(options[:if])
|
61
|
+
options[:if] << "@index_lifecycle == :#{options[:on]}"
|
62
|
+
end
|
63
|
+
set_callback(:index, :after, *args, &block)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -1,5 +1,3 @@
|
|
1
|
-
require 'will_paginate'
|
2
|
-
|
3
1
|
module ElasticSearchable
|
4
2
|
module Indexing
|
5
3
|
module ClassMethods
|
@@ -13,17 +11,16 @@ module ElasticSearchable
|
|
13
11
|
# http://www.elasticsearch.com/docs/elasticsearch/rest_api/admin/indices/put_mapping/
|
14
12
|
def update_index_mapping
|
15
13
|
if mapping = self.elastic_options[:mapping]
|
16
|
-
ElasticSearchable.request :put, index_type_path('_mapping'), :
|
14
|
+
ElasticSearchable.request :put, index_type_path('_mapping'), :body => {index_type => mapping}.to_json
|
17
15
|
end
|
18
16
|
end
|
19
17
|
|
20
18
|
# create the index
|
21
|
-
# http://www.elasticsearch.
|
19
|
+
# http://www.elasticsearch.com/docs/elasticsearch/rest_api/admin/indices/create_index/
|
22
20
|
def create_index
|
23
|
-
options =
|
24
|
-
|
25
|
-
|
26
|
-
ElasticSearchable.request :put, index_path, :json_body => options
|
21
|
+
options = self.elastic_options[:index_options] ? self.elastic_options[:index_options].to_json : ''
|
22
|
+
ElasticSearchable.request :put, index_path, :body => options
|
23
|
+
self.update_index_mapping
|
27
24
|
end
|
28
25
|
|
29
26
|
# explicitly refresh the index, making all operations performed since the last refresh
|
@@ -44,8 +41,6 @@ module ElasticSearchable
|
|
44
41
|
# http://www.elasticsearch.com/docs/elasticsearch/rest_api/delete/
|
45
42
|
def delete_id_from_index(id)
|
46
43
|
ElasticSearchable.request :delete, index_type_path(id)
|
47
|
-
rescue ElasticSearchable::ElasticError => e
|
48
|
-
ElasticSearchable.logger.warn e
|
49
44
|
end
|
50
45
|
|
51
46
|
# helper method to generate elasticsearch url for this object type
|
@@ -59,26 +54,26 @@ module ElasticSearchable
|
|
59
54
|
end
|
60
55
|
|
61
56
|
# reindex all records using bulk api
|
62
|
-
# see http://www.elasticsearch.org/guide/reference/api/bulk.html
|
63
57
|
# options:
|
64
|
-
# :scope - scope
|
65
|
-
# :
|
66
|
-
# :
|
58
|
+
# :scope - scope the find_in_batches to only a subset of records
|
59
|
+
# :batch - counter to start indexing at
|
60
|
+
# :include - passed to find_in_batches to hydrate objects
|
61
|
+
# see http://www.elasticsearch.org/guide/reference/api/bulk.html
|
67
62
|
def reindex(options = {})
|
68
63
|
self.update_index_mapping
|
69
|
-
options.
|
64
|
+
batch = options.delete(:batch) || 1
|
65
|
+
options[:batch_size] ||= 1000
|
66
|
+
options[:start] ||= (batch - 1) * options[:batch_size]
|
70
67
|
scope = options.delete(:scope) || self
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
ElasticSearchable.logger.debug "reindexing batch ##{records.current_page}..."
|
75
|
-
|
68
|
+
scope.find_in_batches(options) do |records|
|
69
|
+
ElasticSearchable.logger.info "reindexing batch ##{batch}..."
|
70
|
+
batch += 1
|
76
71
|
actions = []
|
77
72
|
records.each do |record|
|
78
73
|
next unless record.should_index?
|
79
74
|
begin
|
80
|
-
doc =
|
81
|
-
actions <<
|
75
|
+
doc = record.as_json_for_index.to_json
|
76
|
+
actions << {:index => {'_index' => index_name, '_type' => index_type, '_id' => record.id}}.to_json
|
82
77
|
actions << doc
|
83
78
|
rescue => e
|
84
79
|
ElasticSearchable.logger.warn "Unable to bulk index record: #{record.inspect} [#{e.message}]"
|
@@ -87,12 +82,9 @@ module ElasticSearchable
|
|
87
82
|
begin
|
88
83
|
ElasticSearchable.request(:put, '/_bulk', :body => "\n#{actions.join("\n")}\n") if actions.any?
|
89
84
|
rescue ElasticError => e
|
90
|
-
ElasticSearchable.logger.warn "Error indexing batch ##{
|
85
|
+
ElasticSearchable.logger.warn "Error indexing batch ##{batch}: #{e.message}"
|
91
86
|
ElasticSearchable.logger.warn e
|
92
87
|
end
|
93
|
-
|
94
|
-
options.merge! :page => (options[:page] + 1)
|
95
|
-
records = scope.paginate(options)
|
96
88
|
end
|
97
89
|
end
|
98
90
|
|
@@ -111,20 +103,22 @@ module ElasticSearchable
|
|
111
103
|
# see http://www.elasticsearch.org/guide/reference/api/index_.html
|
112
104
|
def reindex(lifecycle = nil)
|
113
105
|
query = {}
|
114
|
-
query.merge! :percolate => "*" if
|
115
|
-
response = ElasticSearchable.request :put, self.class.index_type_path(self.id), :query => query, :
|
106
|
+
query.merge! :percolate => "*" if _percolate_callbacks.any?
|
107
|
+
response = ElasticSearchable.request :put, self.class.index_type_path(self.id), :query => query, :body => self.as_json_for_index.to_json
|
116
108
|
|
117
|
-
|
118
|
-
|
109
|
+
@index_lifecycle = lifecycle ? lifecycle.to_sym : nil
|
110
|
+
_run_index_callbacks
|
119
111
|
|
120
|
-
|
121
|
-
|
122
|
-
self.send percolate_callback, matches if matches.any?
|
123
|
-
end
|
112
|
+
@percolations = response['matches']
|
113
|
+
_run_percolate_callbacks if @percolations.any?
|
124
114
|
end
|
125
115
|
# document to index in elasticsearch
|
126
116
|
def as_json_for_index
|
127
|
-
|
117
|
+
original_include_root_in_json = ::ActiveRecord::Base.include_root_in_json
|
118
|
+
::ActiveRecord::Base.include_root_in_json = false
|
119
|
+
return self.as_json self.class.elastic_options[:json]
|
120
|
+
ensure
|
121
|
+
::ActiveRecord::Base.include_root_in_json = original_include_root_in_json
|
128
122
|
end
|
129
123
|
def should_index?
|
130
124
|
[self.class.elastic_options[:if]].flatten.compact.all? { |m| evaluate_elastic_condition(m) } &&
|
@@ -135,14 +129,11 @@ module ElasticSearchable
|
|
135
129
|
# can be done automatically when indexing using :percolate => true config option
|
136
130
|
# http://www.elasticsearch.org/blog/2011/02/08/percolator.html
|
137
131
|
def percolate
|
138
|
-
response = ElasticSearchable.request :get, self.class.index_type_path('_percolate'), :
|
132
|
+
response = ElasticSearchable.request :get, self.class.index_type_path('_percolate'), :body => {:doc => self.as_json_for_index}.to_json
|
139
133
|
response['matches']
|
140
134
|
end
|
141
135
|
|
142
136
|
private
|
143
|
-
def elasticsearch_offline?
|
144
|
-
ElasticSearchable.offline?
|
145
|
-
end
|
146
137
|
# ripped from activesupport
|
147
138
|
def evaluate_elastic_condition(method)
|
148
139
|
case method
|
@@ -13,27 +13,11 @@ 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
|
16
17
|
options[:size] ||= per_page_for_search(options)
|
17
18
|
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
|
35
19
|
|
36
|
-
response = ElasticSearchable.request :get, index_type_path('_search'), :query =>
|
20
|
+
response = ElasticSearchable.request :get, index_type_path('_search'), :query => options
|
37
21
|
hits = response['hits']
|
38
22
|
ids = hits['hits'].collect {|h| h['_id'].to_i }
|
39
23
|
results = self.find(ids).sort_by {|result| ids.index(result.id) }
|
data/lib/elastic_searchable.rb
CHANGED
@@ -1,40 +1,35 @@
|
|
1
1
|
require 'httparty'
|
2
2
|
require 'logger'
|
3
|
-
require 'elastic_searchable/
|
3
|
+
require 'elastic_searchable/active_record'
|
4
4
|
|
5
5
|
module ElasticSearchable
|
6
|
-
DEFAULT_INDEX = 'elastic_searchable'
|
7
6
|
include HTTParty
|
8
7
|
format :json
|
9
8
|
base_uri 'localhost:9200'
|
9
|
+
#debug_output
|
10
10
|
|
11
11
|
class ElasticError < StandardError; end
|
12
12
|
class << self
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
def
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
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
21
|
end
|
22
|
-
|
23
|
-
|
22
|
+
|
23
|
+
@@logger = Logger.new(STDOUT)
|
24
|
+
@@logger.level = Logger::INFO
|
25
|
+
def logger=(logger)
|
26
|
+
@@logger = logger
|
24
27
|
end
|
25
|
-
|
26
|
-
|
27
|
-
def encode_json(options = {})
|
28
|
-
defined?(Yajl) ? Yajl::Encoder.encode(options) : ActiveSupport::JSON.encode(options)
|
28
|
+
def logger
|
29
|
+
@@logger
|
29
30
|
end
|
30
|
-
#
|
31
|
-
# configuration:
|
32
|
-
# ElasticSearchable.base_uri 'host:port' controls where to send request to
|
33
|
-
# ElasticSearchable.debug_output outputs all http traffic to console
|
31
|
+
#perform a request to the elasticsearch server
|
34
32
|
def request(method, url, options = {})
|
35
|
-
options.merge! :headers => {'Content-Type' => 'application/json'}
|
36
|
-
options.merge! :body => ElasticSearchable.encode_json(options[:json_body]) if options[:json_body]
|
37
|
-
|
38
33
|
response = self.send(method, url, options)
|
39
34
|
logger.debug "elasticsearch request: #{method} #{url} #{"took #{response['took']}ms" if response['took']}"
|
40
35
|
validate_response response
|
@@ -51,11 +46,4 @@ module ElasticSearchable
|
|
51
46
|
end
|
52
47
|
end
|
53
48
|
|
54
|
-
|
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
|
-
|
49
|
+
ActiveRecord::Base.send(:include, ElasticSearchable::ActiveRecord)
|
data/test/helper.rb
CHANGED
@@ -10,12 +10,15 @@ end
|
|
10
10
|
require 'test/unit'
|
11
11
|
require 'shoulda'
|
12
12
|
require 'mocha'
|
13
|
-
require
|
13
|
+
require "ruby-debug"
|
14
14
|
|
15
15
|
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
16
16
|
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
17
17
|
require 'elastic_searchable'
|
18
|
-
|
18
|
+
|
19
|
+
config = YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml'))
|
20
|
+
ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + "/debug.log")
|
21
|
+
ActiveRecord::Base.establish_connection(config[ENV['DB'] || 'sqlite'])
|
19
22
|
|
20
23
|
class Test::Unit::TestCase
|
21
24
|
def delete_index
|
@@ -1,24 +1,40 @@
|
|
1
1
|
require File.join(File.dirname(__FILE__), 'helper')
|
2
2
|
|
3
3
|
class TestElasticSearchable < Test::Unit::TestCase
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
4
|
+
ActiveRecord::Schema.define(:version => 1) do
|
5
|
+
create_table :posts, :force => true do |t|
|
6
|
+
t.column :title, :string
|
7
|
+
t.column :body, :string
|
8
|
+
end
|
9
|
+
create_table :blogs, :force => true do |t|
|
10
|
+
t.column :title, :string
|
11
|
+
t.column :body, :string
|
11
12
|
end
|
12
|
-
|
13
|
-
|
13
|
+
create_table :users, :force => true do |t|
|
14
|
+
t.column :name, :string
|
15
|
+
end
|
16
|
+
create_table :friends, :force => true do |t|
|
17
|
+
t.column :name, :string
|
18
|
+
t.column :favorite_color, :string
|
19
|
+
end
|
20
|
+
create_table :books, :force => true do |t|
|
21
|
+
t.column :title, :string
|
22
|
+
end
|
23
|
+
create_table :max_page_size_classes, :force => true do |t|
|
24
|
+
t.column :name, :string
|
14
25
|
end
|
15
26
|
end
|
16
27
|
|
28
|
+
def setup
|
29
|
+
delete_index
|
30
|
+
end
|
31
|
+
def teardown
|
32
|
+
delete_index
|
33
|
+
end
|
17
34
|
class Post < ActiveRecord::Base
|
18
|
-
elastic_searchable :index_options => {'
|
35
|
+
elastic_searchable :index_options => { "analysis.analyzer.default.tokenizer" => 'standard', "analysis.analyzer.default.filter" => ["standard", "lowercase", 'porterStem'] }
|
19
36
|
after_index :indexed
|
20
|
-
|
21
|
-
after_index_on_update :indexed_on_update
|
37
|
+
after_index :indexed_on_create, :on => :create
|
22
38
|
def indexed
|
23
39
|
@indexed = true
|
24
40
|
end
|
@@ -31,14 +47,8 @@ class TestElasticSearchable < Test::Unit::TestCase
|
|
31
47
|
def indexed_on_create?
|
32
48
|
@indexed_on_create
|
33
49
|
end
|
34
|
-
def indexed_on_update
|
35
|
-
@indexed_on_update = true
|
36
|
-
end
|
37
|
-
def indexed_on_update?
|
38
|
-
@indexed_on_update
|
39
|
-
end
|
40
50
|
end
|
41
|
-
context '
|
51
|
+
context 'Post class with default elastic_searchable config' do
|
42
52
|
setup do
|
43
53
|
@clazz = Post
|
44
54
|
end
|
@@ -50,7 +60,7 @@ class TestElasticSearchable < Test::Unit::TestCase
|
|
50
60
|
end
|
51
61
|
end
|
52
62
|
|
53
|
-
context '
|
63
|
+
context 'ElasticSearchable.request with invalid url' do
|
54
64
|
should 'raise error' do
|
55
65
|
assert_raises ElasticSearchable::ElasticError do
|
56
66
|
ElasticSearchable.request :get, '/elastic_searchable/foobar/notfound'
|
@@ -58,95 +68,66 @@ class TestElasticSearchable < Test::Unit::TestCase
|
|
58
68
|
end
|
59
69
|
end
|
60
70
|
|
61
|
-
context '
|
71
|
+
context 'Post.create_index' do
|
62
72
|
setup do
|
63
73
|
Post.create_index
|
64
|
-
Post.refresh_index
|
65
74
|
@status = ElasticSearchable.request :get, '/elastic_searchable/_status'
|
66
75
|
end
|
67
76
|
should 'have created index' do
|
68
77
|
assert @status['ok']
|
69
78
|
end
|
79
|
+
should 'have used custom index_options' do
|
80
|
+
expected = {
|
81
|
+
"index.number_of_replicas" => "1",
|
82
|
+
"index.number_of_shards" => "5",
|
83
|
+
"index.analysis.analyzer.default.tokenizer" => "standard",
|
84
|
+
"index.analysis.analyzer.default.filter.0" => "standard",
|
85
|
+
"index.analysis.analyzer.default.filter.1" => "lowercase",
|
86
|
+
"index.analysis.analyzer.default.filter.2" => "porterStem"
|
87
|
+
}
|
88
|
+
assert_equal expected, @status['indices']['elastic_searchable']['settings'], @status.inspect
|
89
|
+
end
|
70
90
|
end
|
71
91
|
|
72
|
-
context '
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
assert @post.indexed?
|
78
|
-
end
|
79
|
-
should 'have fired after_index_on_create callback' do
|
80
|
-
assert @post.indexed_on_create?
|
81
|
-
end
|
82
|
-
should 'not have fired after_index_on_update callback' do
|
83
|
-
assert !@post.indexed_on_update?
|
92
|
+
context 'deleting object that does not exist in search index' do
|
93
|
+
should 'raise error' do
|
94
|
+
assert_raises ElasticSearchable::ElasticError do
|
95
|
+
Post.delete_id_from_index 123
|
96
|
+
end
|
84
97
|
end
|
85
98
|
end
|
86
99
|
|
87
|
-
context '
|
100
|
+
context 'Post.create' do
|
88
101
|
setup do
|
89
|
-
Post.create :title => 'foo', :body =>
|
90
|
-
@post = Post.last
|
91
|
-
@post.title = 'baz'
|
92
|
-
@post.save
|
102
|
+
@post = Post.create :title => 'foo', :body => "bar"
|
93
103
|
end
|
94
104
|
should 'have fired after_index callback' do
|
95
105
|
assert @post.indexed?
|
96
106
|
end
|
97
|
-
should '
|
98
|
-
assert
|
99
|
-
end
|
100
|
-
should 'have fired after_index_on_update callback' do
|
101
|
-
assert @post.indexed_on_update?
|
102
|
-
end
|
103
|
-
end
|
104
|
-
|
105
|
-
context 'Model.create within ElasticSearchable.offline block' do
|
106
|
-
setup do
|
107
|
-
ElasticSearchable.offline do
|
108
|
-
@post = Post.create :title => 'foo', :body => "bar"
|
109
|
-
end
|
110
|
-
end
|
111
|
-
should 'not have fired after_index callback' do
|
112
|
-
assert !@post.indexed?
|
113
|
-
end
|
114
|
-
should 'not have fired after_index_on_create callback' do
|
115
|
-
assert !@post.indexed_on_create?
|
107
|
+
should 'have fired after_index_on_create callback' do
|
108
|
+
assert @post.indexed_on_create?
|
116
109
|
end
|
117
110
|
end
|
118
111
|
|
119
112
|
context 'with empty index when multiple database records' do
|
120
113
|
setup do
|
121
|
-
Post.delete_all
|
122
114
|
Post.create_index
|
123
115
|
@first_post = Post.create :title => 'foo', :body => "first bar"
|
124
116
|
@second_post = Post.create :title => 'foo', :body => "second bar"
|
125
|
-
Post.
|
126
|
-
Post.create_index
|
117
|
+
Post.clean_index
|
127
118
|
end
|
128
119
|
should 'not raise error if error occurs reindexing model' do
|
129
120
|
ElasticSearchable.expects(:request).raises(ElasticSearchable::ElasticError.new('faux error'))
|
130
|
-
|
131
|
-
Post.reindex
|
132
|
-
end
|
133
|
-
end
|
134
|
-
should 'not raise error if destroying one instance' do
|
135
|
-
Logger.any_instance.expects(:warn)
|
136
|
-
assert_nothing_raised do
|
137
|
-
@first_post.destroy
|
138
|
-
end
|
121
|
+
Post.reindex
|
139
122
|
end
|
140
|
-
context '
|
123
|
+
context 'Post.reindex' do
|
141
124
|
setup do
|
142
|
-
Post.reindex
|
125
|
+
Post.reindex
|
143
126
|
Post.refresh_index
|
144
127
|
end
|
145
128
|
should 'have reindexed both records' do
|
146
|
-
|
147
|
-
|
148
|
-
ElasticSearchable.request :get, "/elastic_searchable/posts/#{@second_post.id}"
|
149
|
-
end
|
129
|
+
ElasticSearchable.request :get, "/elastic_searchable/posts/#{@first_post.id}"
|
130
|
+
ElasticSearchable.request :get, "/elastic_searchable/posts/#{@second_post.id}"
|
150
131
|
end
|
151
132
|
end
|
152
133
|
end
|
@@ -168,33 +149,11 @@ class TestElasticSearchable < Test::Unit::TestCase
|
|
168
149
|
end
|
169
150
|
should 'be paginated' do
|
170
151
|
assert_equal 1, @results.current_page
|
171
|
-
assert_equal
|
152
|
+
assert_equal 20, @results.per_page
|
172
153
|
assert_nil @results.previous_page
|
173
154
|
assert_nil @results.next_page
|
174
155
|
end
|
175
156
|
end
|
176
|
-
|
177
|
-
context 'searching for results using a query Hash' do
|
178
|
-
setup do
|
179
|
-
@results = Post.search({
|
180
|
-
:filtered => {
|
181
|
-
:query => {
|
182
|
-
:term => {:title => 'foo'},
|
183
|
-
},
|
184
|
-
:filter => {
|
185
|
-
:or => [
|
186
|
-
{:term => {:body => 'second'}},
|
187
|
-
{:term => {:body => 'third'}}
|
188
|
-
]
|
189
|
-
}
|
190
|
-
}
|
191
|
-
})
|
192
|
-
end
|
193
|
-
should 'find only the object which ' do
|
194
|
-
assert_does_not_contain @results, @first_post
|
195
|
-
assert_contains @results, @second_post
|
196
|
-
end
|
197
|
-
end
|
198
157
|
|
199
158
|
context 'searching for second page using will_paginate params' do
|
200
159
|
setup do
|
@@ -223,16 +182,6 @@ class TestElasticSearchable < Test::Unit::TestCase
|
|
223
182
|
assert_equal @first_post, @results.last
|
224
183
|
end
|
225
184
|
end
|
226
|
-
|
227
|
-
context 'advanced sort options' do
|
228
|
-
setup do
|
229
|
-
@results = Post.search 'foo', :sort => [{:id => 'desc'}]
|
230
|
-
end
|
231
|
-
should 'sort results correctly' do
|
232
|
-
assert_equal @second_post, @results.first
|
233
|
-
assert_equal @first_post, @results.last
|
234
|
-
end
|
235
|
-
end
|
236
185
|
|
237
186
|
context 'destroying one object' do
|
238
187
|
setup do
|
@@ -248,7 +197,7 @@ class TestElasticSearchable < Test::Unit::TestCase
|
|
248
197
|
|
249
198
|
|
250
199
|
class Blog < ActiveRecord::Base
|
251
|
-
elastic_searchable :if => proc {|b| b.should_index? }
|
200
|
+
elastic_searchable :if => proc {|b| b.should_index? }
|
252
201
|
def should_index?
|
253
202
|
false
|
254
203
|
end
|
@@ -268,32 +217,15 @@ class TestElasticSearchable < Test::Unit::TestCase
|
|
268
217
|
end
|
269
218
|
|
270
219
|
class User < ActiveRecord::Base
|
271
|
-
elastic_searchable :
|
272
|
-
'number_of_replicas' => 0,
|
273
|
-
'number_of_shards' => 1,
|
274
|
-
"analysis.analyzer.default.tokenizer" => 'standard',
|
275
|
-
"analysis.analyzer.default.filter" => ["standard", "lowercase", 'porterStem']},
|
276
|
-
:mapping => {:properties => {:name => {:type => :string, :index => :not_analyzed}}}
|
220
|
+
elastic_searchable :mapping => {:properties => {:name => {:type => :string, :index => :not_analyzed}}}
|
277
221
|
end
|
278
|
-
context 'activerecord class with :
|
222
|
+
context 'activerecord class with :mapping=>{}' do
|
279
223
|
context 'creating index' do
|
280
224
|
setup do
|
281
225
|
User.create_index
|
282
|
-
|
283
|
-
should 'have used custom index_options' do
|
284
|
-
@status = ElasticSearchable.request :get, '/elastic_searchable/_status'
|
285
|
-
expected = {
|
286
|
-
"index.number_of_replicas" => "0",
|
287
|
-
"index.number_of_shards" => "1",
|
288
|
-
"index.analysis.analyzer.default.tokenizer" => "standard",
|
289
|
-
"index.analysis.analyzer.default.filter.0" => "standard",
|
290
|
-
"index.analysis.analyzer.default.filter.1" => "lowercase",
|
291
|
-
"index.analysis.analyzer.default.filter.2" => "porterStem"
|
292
|
-
}
|
293
|
-
assert_equal expected, @status['indices']['elastic_searchable']['settings'], @status.inspect
|
226
|
+
@status = ElasticSearchable.request :get, '/elastic_searchable/users/_mapping'
|
294
227
|
end
|
295
228
|
should 'have set mapping' do
|
296
|
-
@status = ElasticSearchable.request :get, '/elastic_searchable/users/_mapping'
|
297
229
|
expected = {
|
298
230
|
"users"=> {
|
299
231
|
"properties"=> {
|
@@ -307,27 +239,19 @@ class TestElasticSearchable < Test::Unit::TestCase
|
|
307
239
|
end
|
308
240
|
|
309
241
|
class Friend < ActiveRecord::Base
|
310
|
-
|
311
|
-
elastic_searchable :json => {:include => {:book => {:only => :title}}, :only => :name}, :index_options => {'number_of_replicas' => 0, 'number_of_shards' => 1}
|
242
|
+
elastic_searchable :json => {:only => [:name]}
|
312
243
|
end
|
313
|
-
context 'activerecord class with
|
244
|
+
context 'activerecord class with optiona :json config' do
|
314
245
|
context 'creating index' do
|
315
246
|
setup do
|
316
247
|
Friend.create_index
|
317
|
-
@
|
318
|
-
@friend = Friend.new :name => 'bob', :favorite_color => 'red'
|
319
|
-
@friend.book = @book
|
320
|
-
@friend.save!
|
248
|
+
@friend = Friend.create! :name => 'bob', :favorite_color => 'red'
|
321
249
|
Friend.refresh_index
|
322
250
|
end
|
323
251
|
should 'index json with configuration' do
|
324
252
|
@response = ElasticSearchable.request :get, "/elastic_searchable/friends/#{@friend.id}"
|
325
|
-
# should not index:
|
326
|
-
# friend.favorite_color
|
327
|
-
# book.isbn
|
328
253
|
expected = {
|
329
|
-
"name" => 'bob'
|
330
|
-
'book' => {'title' => 'another world'}
|
254
|
+
"name" => 'bob' #favorite_color should not be indexed
|
331
255
|
}
|
332
256
|
assert_equal expected, @response['_source'], @response.inspect
|
333
257
|
end
|
@@ -339,7 +263,7 @@ class TestElasticSearchable < Test::Unit::TestCase
|
|
339
263
|
ElasticSearchable.default_index = 'my_new_index'
|
340
264
|
end
|
341
265
|
teardown do
|
342
|
-
ElasticSearchable.default_index =
|
266
|
+
ElasticSearchable.default_index = nil
|
343
267
|
end
|
344
268
|
should 'change default index' do
|
345
269
|
assert_equal 'my_new_index', ElasticSearchable.default_index
|
@@ -347,24 +271,32 @@ class TestElasticSearchable < Test::Unit::TestCase
|
|
347
271
|
end
|
348
272
|
|
349
273
|
class Book < ActiveRecord::Base
|
350
|
-
elastic_searchable
|
351
|
-
|
352
|
-
|
274
|
+
elastic_searchable
|
275
|
+
after_percolate :on_percolated
|
276
|
+
def on_percolated
|
277
|
+
@percolated = percolations
|
353
278
|
end
|
354
279
|
def percolated
|
355
280
|
@percolated
|
356
281
|
end
|
357
282
|
end
|
358
|
-
context 'Book class with
|
283
|
+
context 'Book class with after_percolate callback' do
|
359
284
|
context 'with created index' do
|
360
285
|
setup do
|
361
286
|
Book.create_index
|
362
287
|
end
|
363
288
|
context "when index has configured percolation" do
|
364
289
|
setup do
|
365
|
-
ElasticSearchable.request :put, '/_percolator/elastic_searchable/myfilter', :
|
290
|
+
ElasticSearchable.request :put, '/_percolator/elastic_searchable/myfilter', :body => {:query => {:query_string => {:query => 'foo' }}}.to_json
|
366
291
|
ElasticSearchable.request :post, '/_percolator/_refresh'
|
367
292
|
end
|
293
|
+
context 'creating an object that does not match the percolation' do
|
294
|
+
setup do
|
295
|
+
Book.any_instance.expects(:on_percolated).never
|
296
|
+
@book = Book.create! :title => 'bar'
|
297
|
+
end
|
298
|
+
should 'not percolate the record' do end #see expectations
|
299
|
+
end
|
368
300
|
context 'creating an object that matches the percolation' do
|
369
301
|
setup do
|
370
302
|
@book = Book.create :title => "foo"
|
@@ -386,7 +318,7 @@ class TestElasticSearchable < Test::Unit::TestCase
|
|
386
318
|
end
|
387
319
|
|
388
320
|
class MaxPageSizeClass < ActiveRecord::Base
|
389
|
-
elastic_searchable
|
321
|
+
elastic_searchable
|
390
322
|
def self.max_per_page
|
391
323
|
1
|
392
324
|
end
|
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: 23
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
|
+
- 1
|
7
8
|
- 0
|
8
|
-
-
|
9
|
-
|
10
|
-
version: 0.7.3
|
9
|
+
- 0
|
10
|
+
version: 1.0.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Ryan Sonnek
|
@@ -15,7 +15,8 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2011-
|
18
|
+
date: 2011-03-30 00:00:00 -05:00
|
19
|
+
default_executable:
|
19
20
|
dependencies:
|
20
21
|
- !ruby/object:Gem::Dependency
|
21
22
|
name: activerecord
|
@@ -25,12 +26,12 @@ dependencies:
|
|
25
26
|
requirements:
|
26
27
|
- - ~>
|
27
28
|
- !ruby/object:Gem::Version
|
28
|
-
hash:
|
29
|
+
hash: 7
|
29
30
|
segments:
|
30
|
-
- 2
|
31
31
|
- 3
|
32
|
-
-
|
33
|
-
|
32
|
+
- 0
|
33
|
+
- 0
|
34
|
+
version: 3.0.0
|
34
35
|
type: :runtime
|
35
36
|
version_requirements: *id001
|
36
37
|
- !ruby/object:Gem::Dependency
|
@@ -82,23 +83,21 @@ dependencies:
|
|
82
83
|
type: :runtime
|
83
84
|
version_requirements: *id004
|
84
85
|
- !ruby/object:Gem::Dependency
|
85
|
-
name:
|
86
|
+
name: shoulda
|
86
87
|
prerelease: false
|
87
88
|
requirement: &id005 !ruby/object:Gem::Requirement
|
88
89
|
none: false
|
89
90
|
requirements:
|
90
|
-
- -
|
91
|
+
- - ">="
|
91
92
|
- !ruby/object:Gem::Version
|
92
|
-
hash:
|
93
|
+
hash: 3
|
93
94
|
segments:
|
94
|
-
- 1
|
95
95
|
- 0
|
96
|
-
|
97
|
-
|
98
|
-
type: :runtime
|
96
|
+
version: "0"
|
97
|
+
type: :development
|
99
98
|
version_requirements: *id005
|
100
99
|
- !ruby/object:Gem::Dependency
|
101
|
-
name:
|
100
|
+
name: mocha
|
102
101
|
prerelease: false
|
103
102
|
requirement: &id006 !ruby/object:Gem::Requirement
|
104
103
|
none: false
|
@@ -112,7 +111,7 @@ dependencies:
|
|
112
111
|
type: :development
|
113
112
|
version_requirements: *id006
|
114
113
|
- !ruby/object:Gem::Dependency
|
115
|
-
name:
|
114
|
+
name: bundler
|
116
115
|
prerelease: false
|
117
116
|
requirement: &id007 !ruby/object:Gem::Requirement
|
118
117
|
none: false
|
@@ -125,26 +124,10 @@ dependencies:
|
|
125
124
|
version: "0"
|
126
125
|
type: :development
|
127
126
|
version_requirements: *id007
|
128
|
-
- !ruby/object:Gem::Dependency
|
129
|
-
name: jeweler
|
130
|
-
prerelease: false
|
131
|
-
requirement: &id008 !ruby/object:Gem::Requirement
|
132
|
-
none: false
|
133
|
-
requirements:
|
134
|
-
- - ~>
|
135
|
-
- !ruby/object:Gem::Version
|
136
|
-
hash: 7
|
137
|
-
segments:
|
138
|
-
- 1
|
139
|
-
- 5
|
140
|
-
- 2
|
141
|
-
version: 1.5.2
|
142
|
-
type: :development
|
143
|
-
version_requirements: *id008
|
144
127
|
- !ruby/object:Gem::Dependency
|
145
128
|
name: rcov
|
146
129
|
prerelease: false
|
147
|
-
requirement: &
|
130
|
+
requirement: &id008 !ruby/object:Gem::Requirement
|
148
131
|
none: false
|
149
132
|
requirements:
|
150
133
|
- - ">="
|
@@ -154,11 +137,11 @@ dependencies:
|
|
154
137
|
- 0
|
155
138
|
version: "0"
|
156
139
|
type: :development
|
157
|
-
version_requirements: *
|
140
|
+
version_requirements: *id008
|
158
141
|
- !ruby/object:Gem::Dependency
|
159
142
|
name: sqlite3-ruby
|
160
143
|
prerelease: false
|
161
|
-
requirement: &
|
144
|
+
requirement: &id009 !ruby/object:Gem::Requirement
|
162
145
|
none: false
|
163
146
|
requirements:
|
164
147
|
- - ~>
|
@@ -170,11 +153,11 @@ dependencies:
|
|
170
153
|
- 2
|
171
154
|
version: 1.3.2
|
172
155
|
type: :development
|
173
|
-
version_requirements: *
|
156
|
+
version_requirements: *id009
|
174
157
|
- !ruby/object:Gem::Dependency
|
175
158
|
name: ruby-debug
|
176
159
|
prerelease: false
|
177
|
-
requirement: &
|
160
|
+
requirement: &id010 !ruby/object:Gem::Requirement
|
178
161
|
none: false
|
179
162
|
requirements:
|
180
163
|
- - ">="
|
@@ -184,7 +167,7 @@ dependencies:
|
|
184
167
|
- 0
|
185
168
|
version: "0"
|
186
169
|
type: :development
|
187
|
-
version_requirements: *
|
170
|
+
version_requirements: *id010
|
188
171
|
description: integrate the elastic search engine with rails
|
189
172
|
email:
|
190
173
|
- ryan@codecrate.com
|
@@ -197,22 +180,22 @@ extra_rdoc_files: []
|
|
197
180
|
files:
|
198
181
|
- .document
|
199
182
|
- .gitignore
|
200
|
-
- CONTRIBUTORS.txt
|
201
183
|
- Gemfile
|
202
184
|
- LICENSE.txt
|
203
|
-
- README.
|
185
|
+
- README.rdoc
|
204
186
|
- Rakefile
|
187
|
+
- VERSION
|
205
188
|
- elastic_searchable.gemspec
|
206
189
|
- lib/elastic_searchable.rb
|
207
|
-
- lib/elastic_searchable/
|
190
|
+
- lib/elastic_searchable/active_record.rb
|
208
191
|
- lib/elastic_searchable/callbacks.rb
|
209
192
|
- lib/elastic_searchable/index.rb
|
210
193
|
- lib/elastic_searchable/queries.rb
|
211
194
|
- lib/elastic_searchable/version.rb
|
212
195
|
- test/database.yml
|
213
196
|
- test/helper.rb
|
214
|
-
- test/setup_database.rb
|
215
197
|
- test/test_elastic_searchable.rb
|
198
|
+
has_rdoc: true
|
216
199
|
homepage: http://github.com/wireframe/elastic_searchable
|
217
200
|
licenses: []
|
218
201
|
|
@@ -242,12 +225,11 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
242
225
|
requirements: []
|
243
226
|
|
244
227
|
rubyforge_project: elastic_searchable
|
245
|
-
rubygems_version: 1.
|
228
|
+
rubygems_version: 1.6.2
|
246
229
|
signing_key:
|
247
230
|
specification_version: 3
|
248
231
|
summary: elastic search for activerecord
|
249
232
|
test_files:
|
250
233
|
- test/database.yml
|
251
234
|
- test/helper.rb
|
252
|
-
- test/setup_database.rb
|
253
235
|
- test/test_elastic_searchable.rb
|
data/CONTRIBUTORS.txt
DELETED
data/README.md
DELETED
@@ -1,51 +0,0 @@
|
|
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
|
-
|
@@ -1,41 +0,0 @@
|
|
1
|
-
require 'active_record'
|
2
|
-
require 'after_commit'
|
3
|
-
require 'backgrounded'
|
4
|
-
require 'elastic_searchable/queries'
|
5
|
-
require 'elastic_searchable/callbacks'
|
6
|
-
require 'elastic_searchable/index'
|
7
|
-
|
8
|
-
module ElasticSearchable
|
9
|
-
module ActiveRecordExtensions
|
10
|
-
# Valid options:
|
11
|
-
# :index (optional) configure index to store data in. default to ElasticSearchable.default_index
|
12
|
-
# :type (optional) configue type to store data in. default to model table name
|
13
|
-
# :index_options (optional) configure index properties (ex: tokenizer)
|
14
|
-
# :mapping (optional) configure field properties for this model (ex: skip analyzer for field)
|
15
|
-
# :if (optional) reference symbol/proc condition to only index when condition is true
|
16
|
-
# :unless (optional) reference symbol/proc condition to skip indexing when condition is true
|
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)
|
18
|
-
def elastic_searchable(options = {})
|
19
|
-
cattr_accessor :elastic_options
|
20
|
-
self.elastic_options = options.symbolize_keys.merge(:unless => Array.wrap(options[:unless]).push(:elasticsearch_offline?))
|
21
|
-
|
22
|
-
extend ElasticSearchable::Indexing::ClassMethods
|
23
|
-
extend ElasticSearchable::Queries
|
24
|
-
|
25
|
-
include ElasticSearchable::Indexing::InstanceMethods
|
26
|
-
include ElasticSearchable::Callbacks::InstanceMethods
|
27
|
-
|
28
|
-
backgrounded :update_index_on_create => ElasticSearchable::Callbacks.backgrounded_options, :update_index_on_update => ElasticSearchable::Callbacks.backgrounded_options
|
29
|
-
class << self
|
30
|
-
backgrounded :delete_id_from_index => ElasticSearchable::Callbacks.backgrounded_options
|
31
|
-
end
|
32
|
-
|
33
|
-
define_callbacks :after_index_on_create, :after_index_on_update, :after_index
|
34
|
-
after_commit_on_create :update_index_on_create_backgrounded, :if => :should_index?
|
35
|
-
after_commit_on_update :update_index_on_update_backgrounded, :if => :should_index?
|
36
|
-
after_commit_on_destroy :delete_from_index
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
ActiveRecord::Base.send(:extend, ElasticSearchable::ActiveRecordExtensions)
|
data/test/setup_database.rb
DELETED
@@ -1,33 +0,0 @@
|
|
1
|
-
config = YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml'))
|
2
|
-
ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + "/debug.log")
|
3
|
-
ActiveRecord::Base.establish_connection(config[ENV['DB'] || 'sqlite'])
|
4
|
-
|
5
|
-
ActiveRecord::Schema.define(:version => 1) do
|
6
|
-
create_table :posts, :force => true do |t|
|
7
|
-
t.column :title, :string
|
8
|
-
t.column :body, :string
|
9
|
-
t.column :name, :string
|
10
|
-
end
|
11
|
-
create_table :blogs, :force => true do |t|
|
12
|
-
t.column :title, :string
|
13
|
-
t.column :body, :string
|
14
|
-
end
|
15
|
-
create_table :users, :force => true do |t|
|
16
|
-
t.column :name, :string
|
17
|
-
end
|
18
|
-
create_table :friends, :force => true do |t|
|
19
|
-
t.column :name, :string
|
20
|
-
t.column :favorite_color, :string
|
21
|
-
t.belongs_to :book
|
22
|
-
end
|
23
|
-
create_table :books, :force => true do |t|
|
24
|
-
t.column :title, :string
|
25
|
-
t.column :isbn, :string
|
26
|
-
end
|
27
|
-
create_table :max_page_size_classes, :force => true do |t|
|
28
|
-
t.column :name, :string
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
WillPaginate.enable_activerecord
|
33
|
-
|