elastictastic 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +19 -0
- data/README.md +326 -0
- data/lib/elastictastic/association.rb +21 -0
- data/lib/elastictastic/bulk_persistence_strategy.rb +70 -0
- data/lib/elastictastic/callbacks.rb +30 -0
- data/lib/elastictastic/child_collection_proxy.rb +56 -0
- data/lib/elastictastic/client.rb +101 -0
- data/lib/elastictastic/configuration.rb +35 -0
- data/lib/elastictastic/dirty.rb +130 -0
- data/lib/elastictastic/discrete_persistence_strategy.rb +52 -0
- data/lib/elastictastic/document.rb +98 -0
- data/lib/elastictastic/errors.rb +7 -0
- data/lib/elastictastic/field.rb +38 -0
- data/lib/elastictastic/index.rb +19 -0
- data/lib/elastictastic/mass_assignment_security.rb +15 -0
- data/lib/elastictastic/middleware.rb +119 -0
- data/lib/elastictastic/nested_document.rb +29 -0
- data/lib/elastictastic/new_relic_instrumentation.rb +26 -0
- data/lib/elastictastic/observer.rb +3 -0
- data/lib/elastictastic/observing.rb +21 -0
- data/lib/elastictastic/parent_child.rb +115 -0
- data/lib/elastictastic/persistence.rb +67 -0
- data/lib/elastictastic/properties.rb +236 -0
- data/lib/elastictastic/railtie.rb +35 -0
- data/lib/elastictastic/resource.rb +4 -0
- data/lib/elastictastic/scope.rb +283 -0
- data/lib/elastictastic/scope_builder.rb +32 -0
- data/lib/elastictastic/scoped.rb +20 -0
- data/lib/elastictastic/search.rb +180 -0
- data/lib/elastictastic/server_error.rb +15 -0
- data/lib/elastictastic/test_helpers.rb +172 -0
- data/lib/elastictastic/util.rb +63 -0
- data/lib/elastictastic/validations.rb +45 -0
- data/lib/elastictastic/version.rb +3 -0
- data/lib/elastictastic.rb +82 -0
- data/spec/environment.rb +6 -0
- data/spec/examples/active_model_lint_spec.rb +20 -0
- data/spec/examples/bulk_persistence_strategy_spec.rb +233 -0
- data/spec/examples/callbacks_spec.rb +96 -0
- data/spec/examples/dirty_spec.rb +238 -0
- data/spec/examples/document_spec.rb +600 -0
- data/spec/examples/mass_assignment_security_spec.rb +13 -0
- data/spec/examples/middleware_spec.rb +92 -0
- data/spec/examples/observing_spec.rb +141 -0
- data/spec/examples/parent_child_spec.rb +308 -0
- data/spec/examples/properties_spec.rb +92 -0
- data/spec/examples/scope_spec.rb +491 -0
- data/spec/examples/search_spec.rb +382 -0
- data/spec/examples/spec_helper.rb +15 -0
- data/spec/examples/validation_spec.rb +65 -0
- data/spec/models/author.rb +9 -0
- data/spec/models/blog.rb +5 -0
- data/spec/models/comment.rb +5 -0
- data/spec/models/post.rb +41 -0
- data/spec/models/post_observer.rb +11 -0
- data/spec/support/fakeweb_request_history.rb +13 -0
- 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
|