elastictastic 0.5.0
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/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
|