elastomer-client 0.3.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.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +8 -0
  3. data/.ruby-version +1 -0
  4. data/CHANGELOG.md +4 -0
  5. data/Gemfile +5 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +108 -0
  8. data/Rakefile +9 -0
  9. data/docs/notifications.md +71 -0
  10. data/elastomer-client.gemspec +30 -0
  11. data/lib/elastomer/client.rb +307 -0
  12. data/lib/elastomer/client/bulk.rb +257 -0
  13. data/lib/elastomer/client/cluster.rb +208 -0
  14. data/lib/elastomer/client/docs.rb +432 -0
  15. data/lib/elastomer/client/errors.rb +51 -0
  16. data/lib/elastomer/client/index.rb +407 -0
  17. data/lib/elastomer/client/multi_search.rb +115 -0
  18. data/lib/elastomer/client/nodes.rb +87 -0
  19. data/lib/elastomer/client/scan.rb +161 -0
  20. data/lib/elastomer/client/template.rb +85 -0
  21. data/lib/elastomer/client/warmer.rb +96 -0
  22. data/lib/elastomer/core_ext/time.rb +7 -0
  23. data/lib/elastomer/middleware/encode_json.rb +51 -0
  24. data/lib/elastomer/middleware/opaque_id.rb +69 -0
  25. data/lib/elastomer/middleware/parse_json.rb +39 -0
  26. data/lib/elastomer/notifications.rb +83 -0
  27. data/lib/elastomer/version.rb +7 -0
  28. data/script/bootstrap +16 -0
  29. data/script/cibuild +28 -0
  30. data/script/console +9 -0
  31. data/script/testsuite +10 -0
  32. data/test/assertions.rb +74 -0
  33. data/test/client/bulk_test.rb +226 -0
  34. data/test/client/cluster_test.rb +113 -0
  35. data/test/client/docs_test.rb +394 -0
  36. data/test/client/index_test.rb +244 -0
  37. data/test/client/multi_search_test.rb +129 -0
  38. data/test/client/nodes_test.rb +35 -0
  39. data/test/client/scan_test.rb +84 -0
  40. data/test/client/stubbed_client_tests.rb +40 -0
  41. data/test/client/template_test.rb +33 -0
  42. data/test/client/warmer_test.rb +56 -0
  43. data/test/client_test.rb +86 -0
  44. data/test/core_ext/time_test.rb +46 -0
  45. data/test/middleware/encode_json_test.rb +53 -0
  46. data/test/middleware/opaque_id_test.rb +39 -0
  47. data/test/middleware/parse_json_test.rb +54 -0
  48. data/test/test_helper.rb +94 -0
  49. metadata +210 -0
@@ -0,0 +1,432 @@
1
+
2
+ module Elastomer
3
+ class Client
4
+
5
+ # Provides access to document-level API commands.
6
+ #
7
+ # name - The name of the index as a String
8
+ # type - The document type as a String
9
+ #
10
+ # Returns a Docs instance.
11
+ def docs( name, type = nil )
12
+ Docs.new self, name, type
13
+ end
14
+
15
+
16
+ class Docs
17
+ # Create a new document client for making API requests that pertain to
18
+ # the indexing and searching of documents in a search index.
19
+ #
20
+ # client - Elastomer::Client used for HTTP requests to the server
21
+ # name - The name of the index as a String
22
+ # type - The document type as a String
23
+ #
24
+ def initialize( client, name, type = nil )
25
+ @client = client
26
+ @name = @client.assert_param_presence(name, 'index name')
27
+ @type = @client.assert_param_presence(type, 'document type') unless type.nil?
28
+ end
29
+
30
+ attr_reader :client, :name, :type
31
+
32
+ # Adds or updates a document in the index, making it searchable.
33
+ # See http://www.elasticsearch.org/guide/reference/api/index_/
34
+ #
35
+ # document - The document (as a Hash or JSON encoded String) to add to the index
36
+ # params - Parameters Hash
37
+ #
38
+ # Returns the response body as a Hash
39
+ def index( document, params = {} )
40
+ overrides = from_document(document)
41
+ params = update_params(params, overrides)
42
+ params[:action] = 'docs.index'
43
+
44
+ params.delete(:id) if params[:id].nil? || params[:id].to_s =~ /\A\s*\z/
45
+
46
+ response =
47
+ if params[:id]
48
+ client.put '/{index}/{type}/{id}', params
49
+ else
50
+ client.post '/{index}/{type}', params
51
+ end
52
+
53
+ response.body
54
+ end
55
+ alias :add :index
56
+
57
+ # Delete a document from the index based on the document ID. The :id is
58
+ # provided as part of the params hash.
59
+ #
60
+ # See http://www.elasticsearch.org/guide/reference/api/delete/
61
+ #
62
+ # params - Parameters Hash
63
+ #
64
+ # Returns the response body as a Hash
65
+ def delete( params = {} )
66
+ response = client.delete '/{index}/{type}/{id}', update_params(params, :action => 'docs.delete')
67
+ response.body
68
+ end
69
+
70
+ # Retrieve a document from the index based on its ID. The :id is
71
+ # provided as part of the params hash.
72
+ #
73
+ # See http://www.elasticsearch.org/guide/reference/api/get/
74
+ #
75
+ # params - Parameters Hash
76
+ #
77
+ # Returns the response body as a Hash
78
+ def get( params = {} )
79
+ response = client.get '/{index}/{type}/{id}', update_params(params, :action => 'docs.get')
80
+ response.body
81
+ end
82
+
83
+ # Retrieve the document source from the index based on the ID and type.
84
+ # The :id is provided as part of the params hash.
85
+ #
86
+ # See http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/docs-get.html#_source
87
+ #
88
+ # params - Parameters Hash
89
+ #
90
+ # Returns the response body as a Hash
91
+ def source( params = {} )
92
+ response = client.get '/{index}/{type}/{id}/_source', update_params(params, :action => 'docs.source')
93
+ response.body
94
+ end
95
+
96
+ # Allows to get multiple documents based on an index, type, and id (and possibly routing).
97
+ # See http://www.elasticsearch.org/guide/reference/api/multi-get/
98
+ #
99
+ # docs - The Hash describing the documents to get
100
+ # params - Parameters Hash
101
+ #
102
+ # Returns the response body as a Hash
103
+ def multi_get( docs, params = {} )
104
+ overrides = from_document(docs)
105
+ overrides[:action] = 'docs.multi_get'
106
+
107
+ response = client.get '{/index}{/type}{/id}/_mget', update_params(params, overrides)
108
+ response.body
109
+ end
110
+
111
+ # Update a document based on a script provided.
112
+ # See http://www.elasticsearch.org/guide/reference/api/update/
113
+ #
114
+ # script - The script (as a Hash) used to update the document in place
115
+ # params - Parameters Hash
116
+ #
117
+ # Returns the response body as a Hash
118
+ def update( script, params = {} )
119
+ overrides = from_document(script)
120
+ overrides[:action] = 'docs.update'
121
+
122
+ response = client.post '/{index}/{type}/{id}/_update', update_params(params, overrides)
123
+ response.body
124
+ end
125
+
126
+ # Allows you to execute a search query and get back search hits that
127
+ # match the query. This method supports both the "request body" query
128
+ # and the "URI request" query. When using the request body semantics,
129
+ # the query hash must contain the :query key. Otherwise we assume a URI
130
+ # request is being made.
131
+ #
132
+ # See http://www.elasticsearch.org/guide/reference/api/search/
133
+ #
134
+ # query - The query body as a Hash
135
+ # params - Parameters Hash
136
+ #
137
+ # Examples
138
+ #
139
+ # # request body query
140
+ # search({:query => {:match_all => {}}}, :type => 'tweet')
141
+ #
142
+ # # same thing but using the URI request method
143
+ # search(:q => '*:*', :type => 'tweet')
144
+ #
145
+ # Returns the response body as a hash
146
+ def search( query, params = nil )
147
+ query, params = extract_params(query) if params.nil?
148
+
149
+ response = client.get '/{index}{/type}/_search', update_params(params, :body => query, :action => 'docs.search')
150
+ response.body
151
+ end
152
+
153
+ # Executes a search query, but instead of returning results, returns
154
+ # the number of documents matched. This method supports both the
155
+ # "request body" query and the "URI request" query. When using the
156
+ # request body semantics, the query hash must contain the :query key.
157
+ # Otherwise we assume a URI request is being made.
158
+ #
159
+ # See http://www.elasticsearch.org/guide/reference/api/count/
160
+ #
161
+ # query - The query body as a Hash
162
+ # params - Parameters Hash
163
+ #
164
+ # Examples
165
+ #
166
+ # # request body query
167
+ # count({:match_all => {}}, :type => 'tweet')
168
+ #
169
+ # # same thing but using the URI request method
170
+ # count(:q => '*:*', :type => 'tweet')
171
+ #
172
+ # Returns the response body as a Hash
173
+ def count(query, params = nil)
174
+ query, params = extract_params(query) if params.nil?
175
+
176
+ response = client.get '/{index}{/type}/_count', update_params(params, :body => query)
177
+ response.body
178
+ end
179
+
180
+ # Delete documents from one or more indices and one or more types based
181
+ # on a query. This method supports both the "request body" query and the
182
+ # "URI request" query. When using the request body semantics, the query
183
+ # hash must contain the :query key. Otherwise we assume a URI request is
184
+ # being made.
185
+ #
186
+ # See http://www.elasticsearch.org/guide/reference/api/delete-by-query/
187
+ #
188
+ # query - The query body as a Hash
189
+ # params - Parameters Hash
190
+ #
191
+ # Examples
192
+ #
193
+ # # request body query
194
+ # delete_by_query({:query => {:match_all => {}}}, :type => 'tweet')
195
+ #
196
+ # # same thing but using the URI request method
197
+ # delete_by_query(:q => '*:*', :type => 'tweet')
198
+ #
199
+ # Returns the response body as a hash
200
+ def delete_by_query( query, params = nil )
201
+ query, params = extract_params(query) if params.nil?
202
+
203
+ response = client.delete '/{index}{/type}/_query', update_params(params, :body => query, :action => 'docs.delete_by_query')
204
+ response.body
205
+ end
206
+
207
+ =begin
208
+ Percolate
209
+ =end
210
+
211
+ # Search for documents similar to a specific document. The document
212
+ # :id is provided as part of the params hash. If the _all field is
213
+ # not enabled, :mlt_fields must be passed. A query cannot be present
214
+ # in the query body, but other fields like :size and :facets are
215
+ # allowed.
216
+ #
217
+ # See http://www.elasticsearch.org/guide/reference/api/more-like-this/
218
+ #
219
+ # params - Parameters Hash
220
+ #
221
+ # Examples
222
+ #
223
+ # more_like_this(:mlt_fields => "title", :min_term_freq => 1, :type => "doc1", :id => 1)
224
+ #
225
+ # # with query hash
226
+ # more_like_this({:from => 5, :size => 10}, :mlt_fields => "title",
227
+ # :min_term_freq => 1, :type => "doc1", :id => 1)
228
+ #
229
+ # Returns the response body as a hash
230
+ def more_like_this(query, params = nil)
231
+ query, params = extract_params(query) if params.nil?
232
+
233
+ response = client.get '/{index}/{type}/{id}/_mlt', update_params(params, :body => query, :action => 'docs.more_like_this')
234
+ response.body
235
+ end
236
+
237
+ # Compute a score explanation for a query and a specific document. This
238
+ # can give useful feedback about why a document matched or didn't match
239
+ # a query. The document :id is provided as part of the params hash.
240
+ #
241
+ # See http://www.elasticsearch.org/guide/reference/api/explain/
242
+ #
243
+ # query - The query body as a Hash
244
+ # params - Parameters Hash
245
+ #
246
+ # Examples
247
+ #
248
+ # explain({:query => {:term => {"message" => "search"}}}, :id => 1)
249
+ #
250
+ # explain(:q => "message:search", :id => 1)
251
+ #
252
+ # Returns the response body as a hash
253
+ def explain(query, params = nil)
254
+ query, params = extract_params(query) if params.nil?
255
+
256
+ response = client.get '/{index}/{type}/{id}/_explain', update_params(params, :body => query, :action => 'docs.explain')
257
+ response.body
258
+ end
259
+
260
+ # Validate a potentially expensive query before running it. The
261
+ # :explain parameter can be used to get detailed information about
262
+ # why a query failed.
263
+ #
264
+ # See http://www.elasticsearch.org/guide/reference/api/validate/
265
+ #
266
+ # query - The query body as a Hash
267
+ # params - Parameters Hash
268
+ #
269
+ # Examples
270
+ #
271
+ # # request body query
272
+ # validate(:query_string => {:query => "*:*"})
273
+ #
274
+ # # same thing but using the URI query parameter
275
+ # validate({:q => "post_date:foo"}, :explain => true)
276
+ #
277
+ # Returns the response body as a hash
278
+ def validate(query, params = nil)
279
+ query, params = extract_params(query) if params.nil?
280
+
281
+ response = client.get '/{index}{/type}/_validate/query', update_params(params, :body => query, :action => 'docs.validate')
282
+ response.body
283
+ end
284
+
285
+ # Perform bulk indexing and/or delete operations. The current index name
286
+ # and document type will be passed to the bulk API call as part of the
287
+ # request parameters.
288
+ #
289
+ # params - Parameters Hash that will be passed to the bulk API call.
290
+ # block - Required block that is used to accumulate bulk API operations.
291
+ # All the operations will be passed to the search cluster via a
292
+ # single API request.
293
+ #
294
+ # Yields a Bulk instance for building bulk API call bodies.
295
+ #
296
+ # Examples
297
+ #
298
+ # docs.bulk do |b|
299
+ # b.index( document1 )
300
+ # b.index( document2 )
301
+ # b.delete( document3 )
302
+ # ...
303
+ # end
304
+ #
305
+ # Returns the response body as a Hash
306
+ def bulk( params = {}, &block )
307
+ raise 'a block is required' if block.nil?
308
+
309
+ params = {:index => self.name, :type => self.type}.merge params
310
+ client.bulk params, &block
311
+ end
312
+
313
+ # Create a new Scan instance for scrolling all results from a `query`.
314
+ # The Scan will be scoped to the current index and document type.
315
+ #
316
+ # query - The query to scan as a Hash or a JSON encoded String
317
+ # opts - Options Hash
318
+ # :index - the name of the index to search
319
+ # :type - the document type to search
320
+ # :scroll - the keep alive time of the scrolling request (5 minutes by default)
321
+ # :size - the number of documents per shard to fetch per scroll
322
+ #
323
+ # Examples
324
+ #
325
+ # scan = docs.scan('{"query":{"match_all":{}}}')
326
+ # scan.each_document do |document|
327
+ # document['_id']
328
+ # document['_source']
329
+ # end
330
+ #
331
+ # Returns a new Scan instance
332
+ def scan( query, opts = {} )
333
+ opts = {:index => name, :type => type}.merge opts
334
+ client.scan query, opts
335
+ end
336
+
337
+ # Execute an array of searches in bulk. Results are returned in an
338
+ # array in the order the queries were sent. The current index name
339
+ # and document type will be passed to the multi_search API call as
340
+ # part of the request parameters.
341
+ #
342
+ # See http://www.elasticsearch.org/guide/reference/api/multi-search/
343
+ #
344
+ # params - Parameters Hash that will be passed to the API call.
345
+ # block - Required block that is used to accumulate searches.
346
+ # All the operations will be passed to the search cluster
347
+ # via a single API request.
348
+ #
349
+ # Yields a MultiSearch instance for building multi_search API call
350
+ # bodies.
351
+ #
352
+ # Examples
353
+ #
354
+ # docs.multi_search do |m|
355
+ # m.search({:query => {:match_all => {}}, :search_type => :count)
356
+ # m.search({:query => {:field => {"foo" => "bar"}}})
357
+ # ...
358
+ # end
359
+ #
360
+ # Returns the response body as a Hash
361
+ def multi_search(params = {}, &block)
362
+ raise 'a block is required' if block.nil?
363
+
364
+ params = {:index => self.name, :type => self.type}.merge params
365
+ client.multi_search params, &block
366
+ end
367
+
368
+ # Internal: Given a `document` generate an options hash that will
369
+ # override parameters based on the content of the document. The document
370
+ # will be returned as the value of the :body key.
371
+ #
372
+ # We only extract information from the document if it is given as a
373
+ # Hash. We do not parse JSON encoded Strings.
374
+ #
375
+ # document - A document Hash or JSON encoded String.
376
+ #
377
+ # Returns an options Hash extracted from the document.
378
+ def from_document( document )
379
+ opts = {:body => document}
380
+
381
+ unless String === document
382
+ %w[_id _type _routing _parent _ttl _timestamp _retry_on_conflict].each do |field|
383
+ key = field.sub(/^_/, '').to_sym
384
+
385
+ opts[key] = document.delete field if document.key? field
386
+ opts[key] = document.delete field.to_sym if document.key? field.to_sym
387
+ end
388
+ end
389
+
390
+ opts
391
+ end
392
+
393
+ # Internal: Add default parameters to the `params` Hash and then apply
394
+ # `overrides` to the params if any are given.
395
+ #
396
+ # params - Parameters Hash
397
+ # overrides - Optional parameter overrides as a Hash
398
+ #
399
+ # Returns a new params Hash.
400
+ def update_params( params, overrides = nil )
401
+ h = defaults.update params
402
+ h.update overrides unless overrides.nil?
403
+ h[:routing] = h[:routing].join(',') if Array === h[:routing]
404
+ h
405
+ end
406
+
407
+ # Internal: Returns a Hash containing default parameters.
408
+ def defaults
409
+ { :index => name, :type => type }
410
+ end
411
+
412
+ # Internal: Allow params to be passed as the first argument to
413
+ # methods that take both an optional query hash and params.
414
+ #
415
+ # query - query hash OR params hash
416
+ # params - params hash OR nil if no query
417
+ #
418
+ # Returns an array of the query (possibly nil) and params Hash.
419
+ def extract_params(query, params=nil)
420
+ if params.nil?
421
+ if query.key? :query
422
+ params = {}
423
+ else
424
+ params, query = query, nil
425
+ end
426
+ end
427
+ [query, params]
428
+ end
429
+
430
+ end # Docs
431
+ end # Client
432
+ end # Elastomer
@@ -0,0 +1,51 @@
1
+
2
+ module Elastomer
3
+
4
+ # Parent class for all Elastomer errors.
5
+ Error = Class.new StandardError
6
+
7
+ class Client
8
+
9
+ # General error response from client requests.
10
+ class Error < ::Elastomer::Error
11
+
12
+ # Construct a new Error from the given response object or a message
13
+ # String. If a response object is given, the error message will be
14
+ # extracted from the response body.
15
+ #
16
+ # response - Faraday::Response object or a simple error message String
17
+ def initialize( response )
18
+ if response.respond_to? :body
19
+ message = Hash === response.body && response.body['error'] || response.body.to_s
20
+ else
21
+ message, response = response.to_s, nil
22
+ end
23
+
24
+ @status = response.nil? ? nil : response.status
25
+
26
+ super message
27
+ end
28
+
29
+ # Returns the status code from the `response` or nil if the Error was not
30
+ # created with a response.
31
+ attr_reader :status
32
+
33
+ end # Error
34
+
35
+ # Timeout specific error class.
36
+ class TimeoutError < ::Elastomer::Error
37
+
38
+ # Wrap a Farday TimeoutError with our own class and include the HTTP
39
+ # path where the error originated.
40
+ #
41
+ # exception - The originating Faraday::Error::TimeoutError
42
+ # path - The path portion of the HTTP request
43
+ #
44
+ def initialize( exception, path )
45
+ super "#{exception.message}: #{path}"
46
+ set_backtrace exception.backtrace
47
+ end
48
+ end # TimeoutError
49
+
50
+ end # Client
51
+ end # Elastomer