elasticsearch-rails2 0.0.1
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.
- data/.gitignore +17 -0
- data/.travis.yml +3 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +201 -0
- data/Rakefile +11 -0
- data/elasticsearch-rails2.gemspec +31 -0
- data/lib/elasticsearch/rails2.rb +63 -0
- data/lib/elasticsearch/rails2/client.rb +58 -0
- data/lib/elasticsearch/rails2/configuration.rb +39 -0
- data/lib/elasticsearch/rails2/naming.rb +125 -0
- data/lib/elasticsearch/rails2/response.rb +77 -0
- data/lib/elasticsearch/rails2/response/result.rb +62 -0
- data/lib/elasticsearch/rails2/response/results.rb +52 -0
- data/lib/elasticsearch/rails2/searching.rb +120 -0
- data/lib/elasticsearch/rails2/version.rb +5 -0
- data/spec/client_spec.rb +38 -0
- data/spec/naming_spec.rb +88 -0
- data/spec/rails2_spec.rb +53 -0
- data/spec/response/result_spec.rb +89 -0
- data/spec/response/results_spec.rb +33 -0
- data/spec/response_spec.rb +13 -0
- data/spec/searching_search_request_spec.rb +62 -0
- data/spec/searching_spec.rb +36 -0
- data/spec/spec_helper.rb +27 -0
- data/spec/support/active_record.rb +3 -0
- data/tasks/rspec.rake +3 -0
- metadata +198 -0
@@ -0,0 +1,125 @@
|
|
1
|
+
module Elasticsearch
|
2
|
+
|
3
|
+
module Rails2
|
4
|
+
# Provides methods for getting and setting index name
|
5
|
+
#
|
6
|
+
module Naming
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
|
10
|
+
# Get or set the name of the index
|
11
|
+
#
|
12
|
+
# @example Set the index name for the `Building` model
|
13
|
+
#
|
14
|
+
# class Building
|
15
|
+
# index_name "buildings-#{Rails.env}"
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# @example Set the index name for the `Building` model and re-evaluate it on each call
|
19
|
+
#
|
20
|
+
# class Building
|
21
|
+
# index_name { "buildings-#{Time.now.year}" }
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# @example Directly set the index name for the `Building` model
|
25
|
+
#
|
26
|
+
# Building.index_name "buildings-#{Rails.env}"
|
27
|
+
#
|
28
|
+
#
|
29
|
+
def index_name name=nil, &block
|
30
|
+
if name || block_given?
|
31
|
+
return (@index_name = name || block)
|
32
|
+
end
|
33
|
+
|
34
|
+
if @index_name.respond_to?(:call)
|
35
|
+
@index_name.call
|
36
|
+
else
|
37
|
+
@index_name || Elasticsearch::Rails2.index_name || "#{self.model_name.collection}_index"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Set the index name
|
42
|
+
#
|
43
|
+
# @see index_name
|
44
|
+
def index_name=(name)
|
45
|
+
@index_name = name
|
46
|
+
end
|
47
|
+
|
48
|
+
# Get or set the document type
|
49
|
+
#
|
50
|
+
# @example Set the document type for the `Building` model
|
51
|
+
#
|
52
|
+
# class Building
|
53
|
+
# document_type "my-building"
|
54
|
+
# end
|
55
|
+
#
|
56
|
+
# @example Directly set the document type for the `Building` model
|
57
|
+
#
|
58
|
+
# Building.document_type "my-building"
|
59
|
+
#
|
60
|
+
def document_type name=nil
|
61
|
+
@document_type = name || @document_type || self.model_name.collection
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
# Set the document type
|
66
|
+
#
|
67
|
+
# @see document_type
|
68
|
+
#
|
69
|
+
def document_type=(name)
|
70
|
+
@document_type = name
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
module InstanceMethods
|
75
|
+
|
76
|
+
# Get or set the index name for the model instance
|
77
|
+
#
|
78
|
+
# @example Set the index name for an instance of the `Building` model
|
79
|
+
#
|
80
|
+
# @building.index_name "buildings-#{@building.sourceid}"
|
81
|
+
#
|
82
|
+
#
|
83
|
+
def index_name name=nil, &block
|
84
|
+
if name || block_given?
|
85
|
+
return (@index_name = name || block)
|
86
|
+
end
|
87
|
+
|
88
|
+
if @index_name.respond_to?(:call)
|
89
|
+
@index_name.call
|
90
|
+
else
|
91
|
+
@index_name || self.class.index_name
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# Set the index name
|
96
|
+
#
|
97
|
+
# @see index_name
|
98
|
+
def index_name=(name)
|
99
|
+
@index_name = name
|
100
|
+
end
|
101
|
+
|
102
|
+
# Get or set the document type
|
103
|
+
#
|
104
|
+
# @example Set the document type for an instance of the `Building` model
|
105
|
+
#
|
106
|
+
# @bulding.document_type "my-building"
|
107
|
+
#
|
108
|
+
#
|
109
|
+
def document_type name=nil
|
110
|
+
@document_type = name || @document_type || self.class.document_type
|
111
|
+
end
|
112
|
+
|
113
|
+
|
114
|
+
# Set the document type
|
115
|
+
#
|
116
|
+
# @see document_type
|
117
|
+
#
|
118
|
+
def document_type=(name)
|
119
|
+
@document_type = name
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module Elasticsearch
|
2
|
+
module Rails2
|
3
|
+
# Contains modules and classes for wrapping the response from Elasticsearch
|
4
|
+
#
|
5
|
+
module Response
|
6
|
+
|
7
|
+
# Encapsulate the response returned from the Elasticsearch client
|
8
|
+
#
|
9
|
+
# Implements Enumerable and forwards its methods to the {#results} object.
|
10
|
+
#
|
11
|
+
class Response
|
12
|
+
attr_reader :klass, :search, :response,
|
13
|
+
:took, :timed_out, :shards
|
14
|
+
|
15
|
+
include Enumerable
|
16
|
+
|
17
|
+
delegate :each, :empty?, :size, :slice, :[], :to_ary, to: :results
|
18
|
+
|
19
|
+
def initialize(klass, search, options={})
|
20
|
+
@klass = klass
|
21
|
+
@search = search
|
22
|
+
end
|
23
|
+
|
24
|
+
# Returns the Elasticsearch response
|
25
|
+
#
|
26
|
+
# @return [Hash]
|
27
|
+
#
|
28
|
+
def response
|
29
|
+
@response ||= begin
|
30
|
+
Hashie::Mash.new(search.execute!)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Returns the collection of "hits" from Elasticsearch
|
35
|
+
#
|
36
|
+
# @return [Results]
|
37
|
+
#
|
38
|
+
def results
|
39
|
+
@results ||= Results.new(klass, self)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Returns the collection of records from the database
|
43
|
+
#
|
44
|
+
# @return [Records]
|
45
|
+
#
|
46
|
+
def records
|
47
|
+
@records ||= Records.new(klass, self)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Returns the "took" time
|
51
|
+
#
|
52
|
+
def took
|
53
|
+
response['took']
|
54
|
+
end
|
55
|
+
|
56
|
+
# Returns whether the response timed out
|
57
|
+
#
|
58
|
+
def timed_out
|
59
|
+
response['timed_out']
|
60
|
+
end
|
61
|
+
|
62
|
+
# Returns the statistics on shards
|
63
|
+
#
|
64
|
+
def shards
|
65
|
+
Hashie::Mash.new(response['_shards'])
|
66
|
+
end
|
67
|
+
|
68
|
+
# Returns whether the response scroll id
|
69
|
+
#
|
70
|
+
def scroll_id
|
71
|
+
response['_scroll_id']
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module Elasticsearch
|
2
|
+
module Rails2
|
3
|
+
module Response
|
4
|
+
|
5
|
+
# Encapsulates the "hit" returned from the Elasticsearch client
|
6
|
+
#
|
7
|
+
# Wraps the raw Hash with in a `Hashie::Mash` instance, providing
|
8
|
+
# access to the Hash properties by calling Ruby methods.
|
9
|
+
#
|
10
|
+
# @see https://github.com/intridea/hashie
|
11
|
+
#
|
12
|
+
class Result
|
13
|
+
|
14
|
+
# @param attributes [Hash] A Hash with document properties
|
15
|
+
#
|
16
|
+
def initialize(attributes={})
|
17
|
+
@result = Hashie::Mash.new(attributes)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Return document `_id` as `id`
|
21
|
+
#
|
22
|
+
def id
|
23
|
+
@result['_id']
|
24
|
+
end
|
25
|
+
|
26
|
+
# Return document `_type` as `_type`
|
27
|
+
#
|
28
|
+
def type
|
29
|
+
@result['_type']
|
30
|
+
end
|
31
|
+
|
32
|
+
# Delegate methods to `@result` or `@result._source`
|
33
|
+
#
|
34
|
+
def method_missing(name, *arguments)
|
35
|
+
case
|
36
|
+
when name.to_s.end_with?('?')
|
37
|
+
@result.__send__(name, *arguments) || ( @result._source && @result._source.__send__(name, *arguments) )
|
38
|
+
when @result.respond_to?(name)
|
39
|
+
@result.__send__ name, *arguments
|
40
|
+
when @result._source && @result._source.respond_to?(name)
|
41
|
+
@result._source.__send__ name, *arguments
|
42
|
+
else
|
43
|
+
super
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Respond to methods from `@result` or `@result._source`
|
48
|
+
#
|
49
|
+
def respond_to?(method_name, include_private = false)
|
50
|
+
@result.respond_to?(method_name.to_sym) || \
|
51
|
+
@result._source && @result._source.respond_to?(method_name.to_sym) || \
|
52
|
+
super
|
53
|
+
end
|
54
|
+
|
55
|
+
def as_json(options={})
|
56
|
+
@result.as_json(options)
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Elasticsearch
|
2
|
+
module Rails2
|
3
|
+
module Response
|
4
|
+
|
5
|
+
# Encapsulates the collection of documents returned from Elasticsearch
|
6
|
+
#
|
7
|
+
# Implements Enumerable and forwards its methods to the {#results} object.
|
8
|
+
#
|
9
|
+
class Results
|
10
|
+
attr_reader :klass, :response
|
11
|
+
|
12
|
+
include Enumerable
|
13
|
+
|
14
|
+
delegate :each, :empty?, :size, :slice, :[], :to_a, :to_ary, to: :results
|
15
|
+
|
16
|
+
# @param klass [Class] The name of the model class
|
17
|
+
# @param response [Hash] The full response returned from Elasticsearch client
|
18
|
+
# @param options [Hash] Optional parameters
|
19
|
+
#
|
20
|
+
def initialize(klass, response, options={})
|
21
|
+
@klass = klass
|
22
|
+
@response = response
|
23
|
+
end
|
24
|
+
|
25
|
+
# Returns the {Results} collection
|
26
|
+
#
|
27
|
+
def results
|
28
|
+
@results = response.response['hits']['hits'].map { |hit| Result.new(hit) }
|
29
|
+
end
|
30
|
+
|
31
|
+
# Returns the total number of hits
|
32
|
+
#
|
33
|
+
def total
|
34
|
+
response.response['hits']['total']
|
35
|
+
end
|
36
|
+
|
37
|
+
# Returns the max_score
|
38
|
+
#
|
39
|
+
def max_score
|
40
|
+
response.response['hits']['max_score']
|
41
|
+
end
|
42
|
+
|
43
|
+
# Returns the hit IDs
|
44
|
+
#
|
45
|
+
def ids
|
46
|
+
response.response['hits']['hits'].map { |hit| hit['_id'] }
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
module Elasticsearch
|
2
|
+
module Rails2
|
3
|
+
|
4
|
+
# Contains functionality related to searching.
|
5
|
+
#
|
6
|
+
module Searching
|
7
|
+
|
8
|
+
# Wraps a search request definition
|
9
|
+
#
|
10
|
+
class SearchRequest
|
11
|
+
attr_reader :klass, :definition, :options
|
12
|
+
|
13
|
+
# @param klass [Class] The class of the model
|
14
|
+
# @param query_or_payload [String,Hash,Object] The search request definition
|
15
|
+
# (string, JSON, Hash, or object responding to `to_hash`)
|
16
|
+
# @param options [Hash] Optional parameters to be passed to the Elasticsearch client
|
17
|
+
#
|
18
|
+
def initialize(klass, query_or_payload, options={})
|
19
|
+
@klass = klass
|
20
|
+
@options = options
|
21
|
+
|
22
|
+
__index_name = options[:index] || klass.index_name
|
23
|
+
__document_type = options[:type] || klass.document_type
|
24
|
+
|
25
|
+
case
|
26
|
+
# search query: ...
|
27
|
+
when query_or_payload.respond_to?(:to_hash)
|
28
|
+
body = query_or_payload.to_hash
|
29
|
+
|
30
|
+
# search '{ "query" : ... }'
|
31
|
+
when query_or_payload.is_a?(String) && query_or_payload =~ /^\s*{/
|
32
|
+
body = query_or_payload
|
33
|
+
|
34
|
+
# search '...'
|
35
|
+
else
|
36
|
+
q = query_or_payload
|
37
|
+
end
|
38
|
+
|
39
|
+
if body
|
40
|
+
@definition = { index: __index_name, type: __document_type, body: body }.update options
|
41
|
+
else
|
42
|
+
@definition = { index: __index_name, type: __document_type, q: q }.update options
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Performs the request and returns the response from client
|
47
|
+
#
|
48
|
+
# @return [Hash] The response from Elasticsearch
|
49
|
+
#
|
50
|
+
def execute!
|
51
|
+
klass.client.search(@definition)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
module ClassMethods
|
56
|
+
|
57
|
+
# Provides a `search` method for the model to easily search within an index/type
|
58
|
+
# corresponding to the model settings.
|
59
|
+
#
|
60
|
+
# @param query_or_payload [String,Hash,Object] The search request definition
|
61
|
+
# (string, JSON, Hash, or object responding to `to_hash`)
|
62
|
+
# @param options [Hash] Optional parameters to be passed to the Elasticsearch client
|
63
|
+
#
|
64
|
+
# @return [Elasticsearch::Rails2::Response]
|
65
|
+
#
|
66
|
+
# @example Simple search in `Article`
|
67
|
+
#
|
68
|
+
# Article.search 'foo'
|
69
|
+
#
|
70
|
+
# @example Search using a search definition as a Hash
|
71
|
+
#
|
72
|
+
# response = Article.search \
|
73
|
+
# query: {
|
74
|
+
# match: {
|
75
|
+
# title: 'foo'
|
76
|
+
# }
|
77
|
+
# },
|
78
|
+
# highlight: {
|
79
|
+
# fields: {
|
80
|
+
# title: {}
|
81
|
+
# }
|
82
|
+
# }
|
83
|
+
#
|
84
|
+
# response.results.first.title
|
85
|
+
# # => "Foo"
|
86
|
+
#
|
87
|
+
# response.results.first.highlight.title
|
88
|
+
# # => ["<em>Foo</em>"]
|
89
|
+
#
|
90
|
+
# response.records.first.title
|
91
|
+
# # Article Load (0.2ms) SELECT "articles".* FROM "articles" WHERE "articles"."id" IN (1, 3)
|
92
|
+
# # => "Foo"
|
93
|
+
#
|
94
|
+
# @example Search using a search definition as a JSON string
|
95
|
+
#
|
96
|
+
# Article.search '{"query" : { "match_all" : {} }}'
|
97
|
+
#
|
98
|
+
def search(query_or_payload, options={})
|
99
|
+
search = SearchRequest.new(self, query_or_payload, options)
|
100
|
+
Response::Response.new(self, search)
|
101
|
+
end
|
102
|
+
|
103
|
+
# Scan and scroll all ids
|
104
|
+
# Useful to do a SQL query with IN(...) operator
|
105
|
+
#
|
106
|
+
def scan_all_ids(query_or_payload, options={})
|
107
|
+
ids = []
|
108
|
+
scroll = options[:scroll]
|
109
|
+
search_response = search(query_or_payload, options.update(search_type: 'scan'))
|
110
|
+
response = search_response.response
|
111
|
+
while response = client.scroll(scroll_id: response['_scroll_id'], scroll: scroll) and !response['hits']['hits'].empty? do
|
112
|
+
response['hits']['hits'].each { |r| ids << r['_id']}
|
113
|
+
end
|
114
|
+
ids
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|