elastictastic 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. data/LICENSE +19 -0
  2. data/README.md +326 -0
  3. data/lib/elastictastic/association.rb +21 -0
  4. data/lib/elastictastic/bulk_persistence_strategy.rb +70 -0
  5. data/lib/elastictastic/callbacks.rb +30 -0
  6. data/lib/elastictastic/child_collection_proxy.rb +56 -0
  7. data/lib/elastictastic/client.rb +101 -0
  8. data/lib/elastictastic/configuration.rb +35 -0
  9. data/lib/elastictastic/dirty.rb +130 -0
  10. data/lib/elastictastic/discrete_persistence_strategy.rb +52 -0
  11. data/lib/elastictastic/document.rb +98 -0
  12. data/lib/elastictastic/errors.rb +7 -0
  13. data/lib/elastictastic/field.rb +38 -0
  14. data/lib/elastictastic/index.rb +19 -0
  15. data/lib/elastictastic/mass_assignment_security.rb +15 -0
  16. data/lib/elastictastic/middleware.rb +119 -0
  17. data/lib/elastictastic/nested_document.rb +29 -0
  18. data/lib/elastictastic/new_relic_instrumentation.rb +26 -0
  19. data/lib/elastictastic/observer.rb +3 -0
  20. data/lib/elastictastic/observing.rb +21 -0
  21. data/lib/elastictastic/parent_child.rb +115 -0
  22. data/lib/elastictastic/persistence.rb +67 -0
  23. data/lib/elastictastic/properties.rb +236 -0
  24. data/lib/elastictastic/railtie.rb +35 -0
  25. data/lib/elastictastic/resource.rb +4 -0
  26. data/lib/elastictastic/scope.rb +283 -0
  27. data/lib/elastictastic/scope_builder.rb +32 -0
  28. data/lib/elastictastic/scoped.rb +20 -0
  29. data/lib/elastictastic/search.rb +180 -0
  30. data/lib/elastictastic/server_error.rb +15 -0
  31. data/lib/elastictastic/test_helpers.rb +172 -0
  32. data/lib/elastictastic/util.rb +63 -0
  33. data/lib/elastictastic/validations.rb +45 -0
  34. data/lib/elastictastic/version.rb +3 -0
  35. data/lib/elastictastic.rb +82 -0
  36. data/spec/environment.rb +6 -0
  37. data/spec/examples/active_model_lint_spec.rb +20 -0
  38. data/spec/examples/bulk_persistence_strategy_spec.rb +233 -0
  39. data/spec/examples/callbacks_spec.rb +96 -0
  40. data/spec/examples/dirty_spec.rb +238 -0
  41. data/spec/examples/document_spec.rb +600 -0
  42. data/spec/examples/mass_assignment_security_spec.rb +13 -0
  43. data/spec/examples/middleware_spec.rb +92 -0
  44. data/spec/examples/observing_spec.rb +141 -0
  45. data/spec/examples/parent_child_spec.rb +308 -0
  46. data/spec/examples/properties_spec.rb +92 -0
  47. data/spec/examples/scope_spec.rb +491 -0
  48. data/spec/examples/search_spec.rb +382 -0
  49. data/spec/examples/spec_helper.rb +15 -0
  50. data/spec/examples/validation_spec.rb +65 -0
  51. data/spec/models/author.rb +9 -0
  52. data/spec/models/blog.rb +5 -0
  53. data/spec/models/comment.rb +5 -0
  54. data/spec/models/post.rb +41 -0
  55. data/spec/models/post_observer.rb +11 -0
  56. data/spec/support/fakeweb_request_history.rb +13 -0
  57. metadata +227 -0
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ The MIT License (MIT)
2
+ Copyright (c) 2011 Mat Brown
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
5
+ this software and associated documentation files (the "Software"), to deal in
6
+ the Software without restriction, including without limitation the rights to
7
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
8
+ of the Software, and to permit persons to whom the Software is furnished to do
9
+ so, subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in all
12
+ copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
16
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
17
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
18
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
19
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,326 @@
1
+ # Elastictastic #
2
+
3
+ Elastictastic is an object-document mapper and lightweight API adapter for
4
+ [ElasticSearch](http://www.elasticsearch.org/). Elastictastic's primary use case
5
+ is to define model classes which use ElasticSearch as a primary
6
+ document-oriented data store, and to expose ElasticSearch's search functionality
7
+ to query for those models.
8
+
9
+ ## Dependencies ##
10
+
11
+ Elastictastic requires Ruby 1.9 and ActiveSupport 3. Elastictastic does not
12
+ require Rails, but if you do run Rails, Elastictastic will only work with Rails
13
+ 3.
14
+
15
+ You will also need a running ElasticSearch instance (or cluster). For local
16
+ development, you can easily [download](http://www.elasticsearch.org/download/)
17
+ and
18
+ [install](http://www.elasticsearch.org/guide/reference/setup/installation.html)
19
+ a copy, or your preferred package manager might have it available.
20
+
21
+ ## Installation ##
22
+
23
+ Just add it to your Gemfile:
24
+
25
+ ```ruby
26
+ gem 'elastictastic'
27
+ ```
28
+
29
+ ## Defining models ##
30
+
31
+ Elastictastic's setup DSL will be familiar to those who have used other
32
+ Ruby object-document mappers such as [Mongoid](http://mongoid.org/). Persisted
33
+ models mix in the `Elastictastic::Document` module, and fields are defined with
34
+ the `field` class macro:
35
+
36
+ ```ruby
37
+ class Post
38
+ field :title
39
+ end
40
+ ```
41
+
42
+ The `field` method can take options; the options available here are simply those
43
+ that are available in a
44
+ [field mapping](http://www.elasticsearch.org/guide/reference/mapping/core-types.html)
45
+ in ElasticSearch. Elastictastic is (mostly) agnostic to the options you pass in;
46
+ they're just used to generate the mapping for ElasticSearch.
47
+
48
+ By default, ElasticSearch assigns fields a `string` type. An example of how one
49
+ might define a field with some options:
50
+
51
+ ```ruby
52
+ class Post
53
+ include Elastictastic::Document
54
+
55
+ field :comments_count, :type => :integer, :store => 'yes'
56
+ end
57
+ ```
58
+
59
+ ### Multi-fields ###
60
+
61
+ ElasticSearch allows you to define
62
+ [multi-fields](http://www.elasticsearch.org/guide/reference/mapping/multi-field-type.html),
63
+ which index the same data in multiple ways. To define a multi-field in
64
+ Elastictastic, you may pass a block to the `field` macro, in which the alternate
65
+ fields are defined using the same DSL:
66
+
67
+ ```ruby
68
+ field :title, :type => 'string', :index => 'analyzed' do
69
+ field :unanalyzed, :type => 'string', :index => 'not_analyzed'
70
+ end
71
+ ```
72
+
73
+ The arguments passed to the outer `field` method are used for the default field
74
+ mapping; thus, the above is the same as the following:
75
+
76
+ ```ruby
77
+ field :title,
78
+ :type => 'multi_field',
79
+ :fields => {
80
+ :title => { :type => 'string', :index => 'analyzed' },
81
+ :unanalyzed => { :type => 'string', :index => 'not_analyzed' }
82
+ }
83
+ ```
84
+
85
+ ### Embedded Objects ###
86
+
87
+ ElasticSearch supports deep nesting of properties by way of
88
+ [object fields](http://www.elasticsearch.org/guide/reference/mapping/object-type.html).
89
+ To define embedded objects in your Elastictastic models, use the `embed` class
90
+ macro:
91
+
92
+ ```ruby
93
+ class Post
94
+ include Elastictastic::Document
95
+
96
+ embed :author
97
+ embed :recent_comments, :class_name => 'Comment'
98
+ end
99
+ ```
100
+
101
+ The class that's embedded should include the `Elastictastic::Resource` mixin,
102
+ which exposes the same configuration DSL as `Elastictastic::Document` but does
103
+ not give the class the functionality of a top-level persistent object:
104
+
105
+ ```ruby
106
+ class Author
107
+ include Elastictastic::Resource
108
+
109
+ field :name
110
+ field :email, :index => 'not_analyzed'
111
+ end
112
+ ```
113
+
114
+ ### Parent-child relationships ###
115
+
116
+ You may define
117
+ [parent-child relationships](http://www.elasticsearch.org/blog/2010/12/27/0.14.0-released.html)
118
+ for your documents using the `has_many` and `belongs_to` macros:
119
+
120
+ ```ruby
121
+ class Blog
122
+ include Elastictastic::Document
123
+
124
+ has_many :posts
125
+ end
126
+ ```
127
+
128
+ ```ruby
129
+ class Post
130
+ include Elastictastic::Document
131
+
132
+ belongs_to :blog
133
+ end
134
+ ```
135
+
136
+ Unlike in, say, ActiveRecord, an Elastictastic document can only specify one
137
+ parent (`belongs_to`) relationship. A document can have as many children
138
+ (`has_many`) as you would like.
139
+
140
+ The parent/child relationship has far-reaching consequences in ElasticSearch,
141
+ and as such you will generally interact with child documents via the parent's
142
+ association collection. For instance, this is the standard way to create a new
143
+ child instance:
144
+
145
+ ```ruby
146
+ post = blog.posts.new
147
+ ```
148
+
149
+ The above will return a new Post object whose parent is the `blog`; the
150
+ `blog.posts` collection will retain a reference to the transient `post`
151
+ instance, and will auto-save it when the `blog` is saved.
152
+
153
+ You may also create a child instance independently and then add it to a parent's
154
+ child collection; however, you must do so before saving the child instance, as
155
+ ElasticSeach requires types that define parents to have a parent. The following
156
+ code block has the same outcome as the previous one:
157
+
158
+ ```ruby
159
+ post = Post.new
160
+ blog.posts << post
161
+ ```
162
+
163
+ In most other respects, the `blog.posts` collection behaves the same as a
164
+ search scope (more on that below), except that enumeration methods (`#each`,
165
+ `#map`, etc.) will return unsaved child instances along with instances
166
+ persisted in ElasticSearch.
167
+
168
+ ### Syncing your mapping ###
169
+
170
+ Before you start creating documents with Elastictastic, you need to make
171
+ ElasticSearch aware of your document structure. To do this, use the
172
+ `sync_mapping` method:
173
+
174
+ ```ruby
175
+ Post.sync_mapping
176
+ ```
177
+
178
+ If you have a complex multi-index topology, you may want to consider using
179
+ [ElasticSearch templates](http://www.elasticsearch.org/guide/reference/api/admin-indices-templates.html)
180
+ to manage mappings and other index settings; Elastictastic doesn't provide any
181
+ explicit support for this at the moment, although you can use e.g.
182
+ `Post.mapping` to retrieve the mapping structure which you can then merge into
183
+ your template.
184
+
185
+ ### Reserved Attributes ###
186
+
187
+ All `Elastictastic::Document` models have an `id` and an `index` field, which
188
+ combine to define the full resource locator for the document in ElasticSearch.
189
+ You should not define fields or methods with these names. You may, however, set
190
+ the id explicitly on new (not yet saved) model instances.
191
+
192
+ ## Persistence ##
193
+
194
+ Elastictastic models are persisted the usual way, namely by calling `save`:
195
+
196
+ ```ruby
197
+ post = Post.new
198
+ post.title = 'You know, for search.'
199
+ post.save
200
+ ```
201
+
202
+ To retrieve a document from the data store, use `get`:
203
+
204
+ ```ruby
205
+ Post.find('123')
206
+ ```
207
+
208
+ You can look up multiple documents by ID:
209
+
210
+ ```ruby
211
+ Post.find('123', '456')
212
+ ```
213
+
214
+ You can also pass an array of IDs; the following will return a one-element
215
+ array:
216
+
217
+ ```ruby
218
+ Post.find(['123'])
219
+ ```
220
+
221
+ For child documents, you **must** perform GET requests using the parent's
222
+ association collection:
223
+
224
+ ```ruby
225
+ post = blog.posts.new
226
+ post.save
227
+
228
+ blog.posts.find(post.id) # this will return the post
229
+ Post.find(post.id) # but this won't!
230
+ ```
231
+
232
+ ### Specifying the index ###
233
+
234
+ Elastictastic defines a default index for your documents. If you're using Rails,
235
+ the default index is your application's name suffixed with the current
236
+ environment; outside of Rails, the default index is simply "default". You can
237
+ change this using the `default_index` configuration key.
238
+
239
+ When you want to work with documents in an index other than the default, use
240
+ the `in_index` class method:
241
+
242
+ ```ruby
243
+ new_post = Post.in_index('my_special_index').new # create in an index
244
+ post = Post.in_index('my_special_index').get('123') # retrieve from an index
245
+ ```
246
+
247
+ To retrieve documents from multiple indices at the same time, pass a hash into
248
+ `get` where the keys are index names and the values are the IDs you wish to
249
+ retrieve from that index:
250
+
251
+ ```ruby
252
+ Post.get('default' => ['123', '456'], 'my_special_index' => '789')
253
+ ```
254
+
255
+ ## Search ##
256
+
257
+ ElasticSearch is, above all, a search tool. Accordingly, aside from direct
258
+ lookup by ID, all retrieval of documents is done via the
259
+ [search API](http://www.elasticsearch.org/guide/reference/api/search/).
260
+ Elastictastic models have class methods corresponding to the top-level keys
261
+ in the ElasticSearch search API; you may chain these much as in ActiveRecord
262
+ or Mongoid:
263
+
264
+ ```ruby
265
+ Post.query(:query_string => { :query => 'pizza' }).facets(:cuisine => { :term => { :field => :tags }}).from(10).size(10)
266
+ # Generates { :query => { :query_string => { :query => 'pizza' }}, :facets => { :cuisine => { :term => { :field => :tags }}}, :from => 10, :size => 10 }
267
+ ```
268
+
269
+ Elastictastic also has an alternate block-based query builder, if you prefer:
270
+
271
+ ```ruby
272
+ Post.query do
273
+ query_string { query('pizza') }
274
+ end.facets { cuisine { term { field :tags }}}.from(10).size(10)
275
+ # Same effect as the previous example
276
+ ```
277
+
278
+ The scopes that are generated by the preceding calls act as collections of
279
+ matching documents; thus all the usual Enumerable methods are available:
280
+
281
+ ```ruby
282
+ Post.query(:query_string => { :query => 'pizza' }).each do |post|
283
+ puts post.title
284
+ end
285
+ ```
286
+
287
+ You may access other components of the response using hash-style access; this
288
+ will return a `Hashie::Mash` which allows hash-style or object-style access:
289
+
290
+ ```ruby
291
+ Post.facets(:cuisine => { :term => { :field => :tags }})['facets'].each_pair do |name, facet|
292
+ facet.terms.each { |term| puts "#{term.term}: #{term.count}" }
293
+ end
294
+ ```
295
+
296
+ You can also call `count` on a scope; this will give the total number of
297
+ documents matching the query.
298
+
299
+ In some situations, you may wish to access metadata about search results beyond
300
+ simply the result document. To do this, use the `#find_each` method, which
301
+ yields a `Hashie::Mash` containing the raw ElasticSearch hit object in the
302
+ second argument:
303
+
304
+ ```ruby
305
+ Post.highlight { fields(:title => {}) }.find_each do |post, hit|
306
+ puts "Post #{post.id} matched the query string in the title field: #{hit.highlight['title']}"
307
+ end
308
+ ```
309
+
310
+ Search scope also expose a #find_in_batches method, which also yields the raw
311
+ hit. The following code gives the same result as the previous example:
312
+
313
+ ```ruby
314
+ Post.highlight { fields(:title => {}) }.find_in_batches do |batch|
315
+ batch.each do |post, hit|
316
+ puts "Post #{post.id} matched the query string in the title field: #{hit.highlight['title']}"
317
+ end
318
+ end
319
+ ```
320
+
321
+ Both `find_each` and `find_in_batches` accept a :batch_size option.
322
+
323
+ ## License ##
324
+
325
+ Elastictastic is distributed under the MIT license. See the attached LICENSE
326
+ file for all the sordid details.
@@ -0,0 +1,21 @@
1
+ module Elastictastic
2
+ class Association
3
+ attr_reader :name, :options
4
+
5
+ def initialize(name, options = {})
6
+ @name, @options = name.to_s, options.symbolize_keys
7
+ end
8
+
9
+ def class_name
10
+ @options[:class_name] || @name.to_s.classify
11
+ end
12
+
13
+ def clazz
14
+ @clazz ||= class_name.constantize
15
+ end
16
+
17
+ def extract(instance)
18
+ instance.__send__(name)
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,70 @@
1
+ require 'stringio'
2
+
3
+ module Elastictastic
4
+ class BulkPersistenceStrategy
5
+ def initialize
6
+ @buffer = StringIO.new
7
+ @handlers = []
8
+ end
9
+
10
+ def create(instance, params = {})
11
+ if instance.pending_save?
12
+ raise Elastictastic::OperationNotAllowed,
13
+ "Can't re-save transient document with pending save in bulk operation"
14
+ end
15
+ instance.pending_save!
16
+ add(
17
+ { 'create' => bulk_identifier(instance) },
18
+ instance.elasticsearch_doc
19
+ ) do |response|
20
+ instance.id = response['create']['_id']
21
+ instance.persisted!
22
+ end
23
+ end
24
+
25
+ def update(instance)
26
+ instance.pending_save!
27
+ add(
28
+ { 'index' => bulk_identifier(instance) },
29
+ instance.elasticsearch_doc
30
+ )
31
+ end
32
+
33
+ def destroy(instance)
34
+ instance.pending_destroy!
35
+ add(:delete => bulk_identifier(instance)) do |response|
36
+ instance.transient!
37
+ end
38
+ end
39
+
40
+ def flush
41
+ return if @buffer.length.zero?
42
+
43
+ params = {}
44
+ params[:refresh] = true if Elastictastic.config.auto_refresh
45
+ response = Elastictastic.client.bulk(@buffer.string, params)
46
+
47
+ response['items'].each_with_index do |op_response, i|
48
+ handler = @handlers[i]
49
+ handler.call(op_response) if handler
50
+ end
51
+ response
52
+ end
53
+
54
+ private
55
+
56
+ def bulk_identifier(instance)
57
+ identifier = { :_index => instance.index.name, :_type => instance.class.type }
58
+ identifier['_id'] = instance.id if instance.id
59
+ identifier['parent'] = instance._parent_id if instance._parent_id
60
+ identifier
61
+ end
62
+
63
+ def add(*requests, &block)
64
+ requests.each do |request|
65
+ @buffer.puts(request.to_json)
66
+ end
67
+ @handlers << block
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,30 @@
1
+ module Elastictastic
2
+ module Callbacks
3
+ extend ActiveSupport::Concern
4
+
5
+ HOOKS = [:save, :create, :update, :destroy]
6
+
7
+ included do
8
+ extend ActiveModel::Callbacks
9
+ define_model_callbacks(*HOOKS)
10
+ end
11
+
12
+ module InstanceMethods
13
+ def save
14
+ run_callbacks(:save) { super }
15
+ end
16
+
17
+ def create
18
+ run_callbacks(:create) { super }
19
+ end
20
+
21
+ def update
22
+ run_callbacks(:update) { super }
23
+ end
24
+
25
+ def destroy
26
+ run_callbacks(:destroy) { super }
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,56 @@
1
+ module Elastictastic
2
+ class ChildCollectionProxy < Scope
3
+ attr_reader :parent, :transient_children
4
+
5
+ def initialize(association, parent)
6
+ super(
7
+ parent.index,
8
+ association.clazz,
9
+ Search.new(
10
+ 'query' => {
11
+ 'constant_score' => {
12
+ 'filter' => { 'term' => { '_parent' => parent.id }}
13
+ }
14
+ }
15
+ )
16
+ )
17
+ @parent = parent
18
+ @parent_collection = self
19
+ @transient_children = []
20
+ end
21
+
22
+ def initialize_instance(instance)
23
+ super
24
+ self << instance
25
+ end
26
+
27
+ def first
28
+ super || @transient_children.first
29
+ end
30
+
31
+ def each(&block)
32
+ if block
33
+ super
34
+ @transient_children.each(&block)
35
+ else
36
+ ::Enumerator.new(self, :each)
37
+ end
38
+ end
39
+
40
+ def persisted!(child)
41
+ @transient_children.delete(child)
42
+ end
43
+
44
+ def <<(child)
45
+ child.parent_collection = self
46
+ @transient_children << child
47
+ self
48
+ end
49
+
50
+ private
51
+
52
+ def params_for_find
53
+ super.merge('routing' => @parent.id)
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,101 @@
1
+ require 'faraday'
2
+
3
+ module Elastictastic
4
+ class Client
5
+ attr_reader :connection
6
+
7
+ def initialize(config)
8
+ builder = Faraday::Builder.new do |builder|
9
+ builder.use Middleware::RaiseServerErrors
10
+ builder.use Middleware::JsonEncodeBody
11
+ builder.use Middleware::JsonDecodeResponse
12
+ if config.logger
13
+ builder.use Middleware::LogRequests, config.logger
14
+ end
15
+ end
16
+ if config.hosts.length == 1
17
+ builder.adapter config.adapter
18
+ @connection =
19
+ Faraday.new(:url => config.hosts.first, :builder => builder)
20
+ else
21
+ builder.use Middleware::Rotor, *config.hosts
22
+ builder.adapter config.adapter
23
+ @connection = Faraday.new(:builder => builder)
24
+ end
25
+ end
26
+
27
+ def create(index, type, id, doc, params = {})
28
+ if id
29
+ @connection.put(
30
+ path_with_query("/#{index}/#{type}/#{id}/_create", params), doc)
31
+ else
32
+ @connection.post(path_with_query("/#{index}/#{type}", params), doc)
33
+ end.body
34
+ end
35
+
36
+ def update(index, type, id, doc, params = {})
37
+ @connection.put(path_with_query("/#{index}/#{type}/#{id}", params), doc)
38
+ end
39
+
40
+ def bulk(commands, params = {})
41
+ @connection.post(path_with_query('/_bulk', params), commands).body
42
+ end
43
+
44
+ def get(index, type, id, params = {})
45
+ @connection.get(path_with_query("/#{index}/#{type}/#{id}", params)).body
46
+ end
47
+
48
+ def mget(docspec, index = nil, type = nil)
49
+ path =
50
+ if index.present?
51
+ if type.present?
52
+ "/#{index}/#{type}/_mget"
53
+ else index.present?
54
+ "#{index}/_mget"
55
+ end
56
+ else
57
+ "/_mget"
58
+ end
59
+ @connection.post(path, 'docs' => docspec).body
60
+ end
61
+
62
+ def search(index, type, search, options = {})
63
+ path = "/#{index}/#{type}/_search"
64
+ @connection.post(
65
+ "#{path}?#{options.to_query}",
66
+ search
67
+ ).body
68
+ end
69
+
70
+ def scroll(id, options = {})
71
+ @connection.post(
72
+ "/_search/scroll?#{options.to_query}",
73
+ id
74
+ ).body
75
+ end
76
+
77
+ def put_mapping(index, type, mapping)
78
+ @connection.put("/#{index}/#{type}/_mapping", mapping).body
79
+ end
80
+
81
+ def delete(index = nil, type = nil, id = nil, params = {})
82
+ path =
83
+ if id then "/#{index}/#{type}/#{id}"
84
+ elsif type then "/#{index}/#{type}"
85
+ elsif index then "/#{index}"
86
+ else "/"
87
+ end
88
+ @connection.delete(path_with_query(path, params)).body
89
+ end
90
+
91
+ private
92
+
93
+ def path_with_query(path, query)
94
+ if query.present?
95
+ "#{path}?#{query.to_query}"
96
+ else
97
+ path
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,35 @@
1
+ module Elastictastic
2
+ class Configuration
3
+
4
+ attr_writer :hosts, :adapter, :default_index, :auto_refresh, :default_batch_size
5
+ attr_accessor :logger
6
+
7
+ def host=(host)
8
+ @hosts = [host]
9
+ end
10
+
11
+ def hosts
12
+ @hosts ||= ['http://localhost:9200']
13
+ end
14
+
15
+ def adapter
16
+ @adapter ||= :net_http
17
+ end
18
+
19
+ def default_index
20
+ @default_index ||= 'default'
21
+ end
22
+
23
+ def auto_refresh
24
+ !!@auto_refresh
25
+ end
26
+
27
+ def default_batch_size
28
+ @default_batch_size ||= 100
29
+ end
30
+
31
+ ActiveModel::Observing::ClassMethods.public_instance_methods(false).each do |method|
32
+ delegate method, :to => :"::Elastictastic::Observing"
33
+ end
34
+ end
35
+ end