elasticsearch-persistence 5.1.0 → 6.0.0.pre
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rspec +2 -0
- data/Gemfile +9 -0
- data/README.md +164 -323
- data/Rakefile +8 -8
- data/elasticsearch-persistence.gemspec +4 -5
- data/lib/elasticsearch/persistence.rb +2 -110
- data/lib/elasticsearch/persistence/repository.rb +212 -53
- data/lib/elasticsearch/persistence/repository/dsl.rb +94 -0
- data/lib/elasticsearch/persistence/repository/find.rb +27 -10
- data/lib/elasticsearch/persistence/repository/response/results.rb +17 -5
- data/lib/elasticsearch/persistence/repository/search.rb +15 -4
- data/lib/elasticsearch/persistence/repository/serialize.rb +65 -7
- data/lib/elasticsearch/persistence/repository/store.rb +38 -44
- data/lib/elasticsearch/persistence/version.rb +1 -1
- data/spec/repository/find_spec.rb +179 -0
- data/spec/repository/response/results_spec.rb +105 -0
- data/spec/repository/search_spec.rb +181 -0
- data/spec/repository/serialize_spec.rb +53 -0
- data/spec/repository/store_spec.rb +327 -0
- data/spec/repository_spec.rb +716 -0
- data/spec/spec_helper.rb +28 -0
- metadata +25 -80
- data/lib/elasticsearch/persistence/client.rb +0 -51
- data/lib/elasticsearch/persistence/model.rb +0 -153
- data/lib/elasticsearch/persistence/model/base.rb +0 -87
- data/lib/elasticsearch/persistence/model/errors.rb +0 -8
- data/lib/elasticsearch/persistence/model/find.rb +0 -180
- data/lib/elasticsearch/persistence/model/rails.rb +0 -47
- data/lib/elasticsearch/persistence/model/store.rb +0 -254
- data/lib/elasticsearch/persistence/model/utils.rb +0 -0
- data/lib/elasticsearch/persistence/repository/class.rb +0 -71
- data/lib/elasticsearch/persistence/repository/naming.rb +0 -115
- data/lib/rails/generators/elasticsearch/model/model_generator.rb +0 -21
- data/lib/rails/generators/elasticsearch/model/templates/model.rb.tt +0 -9
- data/lib/rails/generators/elasticsearch_generator.rb +0 -2
- data/test/integration/model/model_basic_test.rb +0 -238
- data/test/integration/repository/custom_class_test.rb +0 -85
- data/test/integration/repository/customized_class_test.rb +0 -82
- data/test/integration/repository/default_class_test.rb +0 -116
- data/test/integration/repository/virtus_model_test.rb +0 -118
- data/test/test_helper.rb +0 -55
- data/test/unit/model_base_test.rb +0 -72
- data/test/unit/model_find_test.rb +0 -153
- data/test/unit/model_gateway_test.rb +0 -101
- data/test/unit/model_rails_test.rb +0 -112
- data/test/unit/model_store_test.rb +0 -576
- data/test/unit/persistence_test.rb +0 -32
- data/test/unit/repository_class_test.rb +0 -51
- data/test/unit/repository_client_test.rb +0 -32
- data/test/unit/repository_find_test.rb +0 -388
- data/test/unit/repository_indexing_test.rb +0 -37
- data/test/unit/repository_module_test.rb +0 -146
- data/test/unit/repository_naming_test.rb +0 -146
- data/test/unit/repository_response_results_test.rb +0 -98
- data/test/unit/repository_search_test.rb +0 -117
- data/test/unit/repository_serialize_test.rb +0 -57
- data/test/unit/repository_store_test.rb +0 -303
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dd3b68c6704df1a51940545080b631c127701074727d9879c5eaf9bf29513d63
|
4
|
+
data.tar.gz: 966be9deff09a99f149b8c7206b383737be278dd96f368fbda45d30b59fc0439
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0ffc1d1c058660ee3060ffaac44449e4a19b0ee34b2f12f1818aa22753c4a670c7c3dfe9975f44bc41cff8913cb6604bcc6e8e61633e8263ef1888c4a024382b
|
7
|
+
data.tar.gz: 2e589e4a386389e4c41a95c92738d79def0ff49ec18ee2add9ba07e813ae4d2704156dbbe0a9dafeff3cc9767fda38fbfd570ad3c6a7d0be7973f92ab33e35b4
|
data/.rspec
ADDED
data/Gemfile
CHANGED
@@ -2,3 +2,12 @@ source 'https://rubygems.org'
|
|
2
2
|
|
3
3
|
# Specify your gem's dependencies in elasticsearch-persistence.gemspec
|
4
4
|
gemspec
|
5
|
+
|
6
|
+
gem 'elasticsearch-model', :path => File.expand_path("../../elasticsearch-model", __FILE__), :require => false
|
7
|
+
|
8
|
+
gem 'virtus'
|
9
|
+
|
10
|
+
group :development, :testing do
|
11
|
+
gem 'rspec'
|
12
|
+
gem 'pry-nav'
|
13
|
+
end
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Elasticsearch::Persistence
|
2
2
|
|
3
|
-
Persistence layer for Ruby domain objects in Elasticsearch, using the Repository
|
3
|
+
Persistence layer for Ruby domain objects in Elasticsearch, using the Repository pattern.
|
4
4
|
|
5
5
|
## Compatibility
|
6
6
|
|
@@ -14,6 +14,7 @@ is compatible with the Elasticsearch `master` branch, therefore, with the next m
|
|
14
14
|
| 0.1 | → | 1.x |
|
15
15
|
| 2.x | → | 2.x |
|
16
16
|
| 5.x | → | 5.x |
|
17
|
+
| 6.x | → | 6.x |
|
17
18
|
| master | → | master |
|
18
19
|
|
19
20
|
## Installation
|
@@ -24,7 +25,7 @@ Install the package from [Rubygems](https://rubygems.org):
|
|
24
25
|
|
25
26
|
To use an unreleased version, either add it to your `Gemfile` for [Bundler](http://bundler.io):
|
26
27
|
|
27
|
-
gem 'elasticsearch-persistence', git: 'git://github.com/elastic/elasticsearch-rails.git', branch: '
|
28
|
+
gem 'elasticsearch-persistence', git: 'git://github.com/elastic/elasticsearch-rails.git', branch: '6.x'
|
28
29
|
|
29
30
|
or install it from a source code checkout:
|
30
31
|
|
@@ -35,10 +36,7 @@ or install it from a source code checkout:
|
|
35
36
|
|
36
37
|
## Usage
|
37
38
|
|
38
|
-
The library provides
|
39
|
-
|
40
|
-
* [Repository Pattern](#the-repository-pattern)
|
41
|
-
* [ActiveRecord Pattern](#the-activerecord-pattern)
|
39
|
+
The library provides the Repository pattern for adding persistence to your Ruby objects.
|
42
40
|
|
43
41
|
### The Repository Pattern
|
44
42
|
|
@@ -68,7 +66,8 @@ Let's create a default, "dumb" repository, as a first step:
|
|
68
66
|
|
69
67
|
```ruby
|
70
68
|
require 'elasticsearch/persistence'
|
71
|
-
|
69
|
+
class MyRepository; include Elasticsearch::Persistence::Repository; end
|
70
|
+
repository = MyRepository.new
|
72
71
|
```
|
73
72
|
|
74
73
|
We can save a `Note` instance into the repository...
|
@@ -121,32 +120,17 @@ The repository module provides a number of features and facilities to configure
|
|
121
120
|
* Providing access to the Elasticsearch response for search results (aggregations, total, ...)
|
122
121
|
* Defining the methods for serialization and deserialization
|
123
122
|
|
124
|
-
|
123
|
+
There are two mixins you can include in your Repository class. The first `Elasticsearch::Persistence::Repository`,
|
124
|
+
provides the basic methods and settings you'll need. The second, `Elasticsearch::Persistence::Repository::DSL` adds
|
125
|
+
some additional class methods that allow you to set options that instances of the class will share.
|
125
126
|
|
126
|
-
####
|
127
|
+
#### Basic Repository mixin
|
127
128
|
|
128
|
-
For simple cases, you can
|
129
|
+
For simple cases, you can just include the Elasticsearch::Persistence::Repository mixin to your class:
|
129
130
|
|
130
131
|
```ruby
|
131
|
-
|
132
|
-
|
133
|
-
client Elasticsearch::Client.new url: ENV['ELASTICSEARCH_URL'], log: true
|
134
|
-
|
135
|
-
# Set a custom index name
|
136
|
-
index :my_notes
|
137
|
-
|
138
|
-
# Set a custom document type
|
139
|
-
type :my_note
|
140
|
-
|
141
|
-
# Specify the class to initialize when deserializing documents
|
142
|
-
klass Note
|
143
|
-
|
144
|
-
# Configure the settings and mappings for the Elasticsearch index
|
145
|
-
settings number_of_shards: 1 do
|
146
|
-
mapping do
|
147
|
-
indexes :text, analyzer: 'snowball'
|
148
|
-
end
|
149
|
-
end
|
132
|
+
class MyRepository
|
133
|
+
include Elasticsearch::Persistence::Repository
|
150
134
|
|
151
135
|
# Customize the serialization logic
|
152
136
|
def serialize(document)
|
@@ -155,10 +139,18 @@ repository = Elasticsearch::Persistence::Repository.new do
|
|
155
139
|
|
156
140
|
# Customize the de-serialization logic
|
157
141
|
def deserialize(document)
|
158
|
-
puts "# ***** CUSTOM DESERIALIZE LOGIC
|
142
|
+
puts "# ***** CUSTOM DESERIALIZE LOGIC... *****"
|
159
143
|
super
|
160
144
|
end
|
161
145
|
end
|
146
|
+
|
147
|
+
client = Elasticsearch::Client.new(url: ENV['ELASTICSEARCH_URL'], log: true)
|
148
|
+
repository = MyRepository.new(client: client, index_name: :my_notes, type: :my_note, klass: Note)
|
149
|
+
repository.settings number_of_shards: 1 do
|
150
|
+
mapping do
|
151
|
+
indexes :text, analyzer: 'snowball'
|
152
|
+
end
|
153
|
+
end
|
162
154
|
```
|
163
155
|
|
164
156
|
The custom Elasticsearch client will be used now, with a custom index and type names,
|
@@ -189,28 +181,27 @@ repository.find(1)
|
|
189
181
|
<Note:0x007f9bd782b7a0 @attributes={... "my_special_key"=>"my_special_stuff"}>
|
190
182
|
```
|
191
183
|
|
192
|
-
####
|
184
|
+
#### The DSL mixin
|
193
185
|
|
194
|
-
In
|
186
|
+
In some cases, you'll want to set some of the repository configurations at the class level. This makes
|
187
|
+
most sense when the instances of the repository will use that same configuration:
|
195
188
|
|
196
189
|
```ruby
|
197
190
|
require 'base64'
|
198
191
|
|
199
192
|
class NoteRepository
|
200
193
|
include Elasticsearch::Persistence::Repository
|
194
|
+
include Elasticsearch::Persistence::Repository::DSL
|
201
195
|
|
202
|
-
|
203
|
-
|
204
|
-
client Elasticsearch::Client.new url: options[:url], log: options[:log]
|
205
|
-
end
|
206
|
-
|
196
|
+
index_name 'notes'
|
197
|
+
document_type 'note'
|
207
198
|
klass Note
|
208
199
|
|
209
200
|
settings number_of_shards: 1 do
|
210
201
|
mapping do
|
211
202
|
indexes :text, analyzer: 'snowball'
|
212
203
|
# Do not index images
|
213
|
-
indexes :image, index:
|
204
|
+
indexes :image, index: false
|
214
205
|
end
|
215
206
|
end
|
216
207
|
|
@@ -232,23 +223,26 @@ class NoteRepository
|
|
232
223
|
end
|
233
224
|
```
|
234
225
|
|
235
|
-
|
226
|
+
You can create an instance of this custom class and get each of the configurations.
|
236
227
|
|
237
|
-
|
228
|
+
```ruby
|
229
|
+
client = Elasticsearch::Client.new(url: 'http://localhost:9200', log: true)
|
230
|
+
repository = NoteRepository.new(client: client)
|
231
|
+
repository.index_name
|
232
|
+
# => 'notes'
|
238
233
|
|
239
|
-
|
240
|
-
|
234
|
+
```
|
235
|
+
|
236
|
+
You can also override the default configuration with options passed to the initialize method:
|
241
237
|
|
242
238
|
```ruby
|
243
|
-
|
239
|
+
client = Elasticsearch::Client.new(url: 'http://localhost:9250', log: true)
|
240
|
+
client.transport.logger.formatter = proc { |s, d, p, m| "\e[2m# #{m}\n\e[0m" }
|
241
|
+
repository = NoteRepository.new(client: client, index_name: 'notes_development')
|
244
242
|
|
245
|
-
|
246
|
-
repository.index = 'notes_development'
|
247
|
-
repository.client.transport.logger.formatter = proc { |s, d, p, m| "\e[2m# #{m}\n\e[0m" }
|
243
|
+
repository.create_index!(force: true)
|
248
244
|
|
249
|
-
|
250
|
-
|
251
|
-
note = Note.new 'id' => 1, 'text' => 'Document with image', 'image' => '... BINARY DATA ...'
|
245
|
+
note = Note.new('id' => 1, 'text' => 'Document with image', 'image' => '... BINARY DATA ...')
|
252
246
|
|
253
247
|
repository.save(note)
|
254
248
|
# PUT http://localhost:9200/notes_development/note/1
|
@@ -259,47 +253,110 @@ puts repository.find(1).attributes['image']
|
|
259
253
|
# => ... BINARY DATA ...
|
260
254
|
```
|
261
255
|
|
262
|
-
####
|
256
|
+
#### Functionality Provided by the Repository mixin
|
257
|
+
|
258
|
+
Each of the following configurations can be set for a repository instance.
|
259
|
+
If you have included the `Elasticsearch::Persistence::Repository::DSL` mixin, then you can use the class-level DSL
|
260
|
+
methods to set each configuration. You can override the configuration for any instance by passing options to the
|
261
|
+
`#initialize` method.
|
262
|
+
If you don't use the DSL mixin, you can set also the instance configuration with options passed the `#initialize` method.
|
263
263
|
|
264
264
|
##### Client
|
265
265
|
|
266
|
-
The repository uses the standard Elasticsearch [client](https://github.com/elastic/elasticsearch-ruby#usage)
|
267
|
-
which is accessible with the `client` getter and setter methods:
|
266
|
+
The repository uses the standard Elasticsearch [client](https://github.com/elastic/elasticsearch-ruby#usage).
|
268
267
|
|
269
268
|
```ruby
|
270
|
-
|
269
|
+
client = Elasticsearch::Client.new(url: 'http://search.server.org')
|
270
|
+
repository = NoteRepository.new(client: client)
|
271
271
|
repository.client.transport.logger = Logger.new(STDERR)
|
272
272
|
```
|
273
273
|
|
274
|
+
or with the DSL mixin:
|
275
|
+
|
276
|
+
```ruby
|
277
|
+
class NoteRepository
|
278
|
+
include Elasticsearch::Persistence::Repository
|
279
|
+
include Elasticsearch::Persistence::Repository::DSL
|
280
|
+
|
281
|
+
client Elasticsearch::Client.new url: 'http://search.server.org'
|
282
|
+
end
|
283
|
+
|
284
|
+
repository = NoteRepository.new
|
285
|
+
|
286
|
+
```
|
287
|
+
|
274
288
|
##### Naming
|
275
289
|
|
276
|
-
The `
|
277
|
-
|
290
|
+
The `index_name` method specifies the Elasticsearch index to use for storage, lookup and search. The default index name
|
291
|
+
is 'repository'.
|
292
|
+
|
293
|
+
```ruby
|
294
|
+
repository = NoteRepository.new(index_name: 'notes_development')
|
295
|
+
```
|
296
|
+
|
297
|
+
or with the DSL mixin:
|
298
|
+
|
299
|
+
```ruby
|
300
|
+
class NoteRepository
|
301
|
+
include Elasticsearch::Persistence::Repository
|
302
|
+
include Elasticsearch::Persistence::Repository::DSL
|
303
|
+
|
304
|
+
index_name 'notes_development'
|
305
|
+
end
|
306
|
+
|
307
|
+
repository = NoteRepository.new
|
308
|
+
|
309
|
+
```
|
310
|
+
|
311
|
+
The `type` method specifies the Elasticsearch document type to use for storage, lookup and search. The default value is
|
312
|
+
'_doc'. Keep in mind that future versions of Elasticsearch will not allow you to set this yourself and will use the type,
|
313
|
+
'_doc'.
|
278
314
|
|
279
315
|
```ruby
|
280
|
-
repository
|
316
|
+
repository = NoteRepository.new(document_type: 'note')
|
281
317
|
```
|
282
318
|
|
283
|
-
|
284
|
-
(when not set, the value is inferred from the document class name, or `_all` is used):
|
319
|
+
or with the DSL mixin:
|
285
320
|
|
286
321
|
```ruby
|
287
|
-
|
322
|
+
class NoteRepository
|
323
|
+
include Elasticsearch::Persistence::Repository
|
324
|
+
include Elasticsearch::Persistence::Repository::DSL
|
325
|
+
|
326
|
+
document_type 'note'
|
327
|
+
end
|
328
|
+
|
329
|
+
repository = NoteRepository.new
|
330
|
+
|
288
331
|
```
|
289
332
|
|
290
333
|
The `klass` method specifies the Ruby class name to use when initializing objects from
|
291
|
-
documents retrieved from the repository
|
292
|
-
|
334
|
+
documents retrieved from the repository. If this value is not set, a Hash representation of the document will be
|
335
|
+
returned instead.
|
293
336
|
|
294
337
|
```ruby
|
295
|
-
repository.klass
|
338
|
+
repository = NoteRepository.new(klass: Note)
|
339
|
+
```
|
340
|
+
|
341
|
+
or with the DSL mixin:
|
342
|
+
|
343
|
+
```ruby
|
344
|
+
class NoteRepository
|
345
|
+
include Elasticsearch::Persistence::Repository
|
346
|
+
include Elasticsearch::Persistence::Repository::DSL
|
347
|
+
|
348
|
+
klass Note
|
349
|
+
end
|
350
|
+
|
351
|
+
repository = NoteRepository.new
|
352
|
+
|
296
353
|
```
|
297
354
|
|
298
355
|
##### Index Configuration
|
299
356
|
|
300
357
|
The `settings` and `mappings` methods, provided by the
|
301
358
|
[`elasticsearch-model`](http://rubydoc.info/gems/elasticsearch-model/Elasticsearch/Model/Indexing/ClassMethods)
|
302
|
-
gem, allow to configure the index properties:
|
359
|
+
gem, allow you to configure the index properties:
|
303
360
|
|
304
361
|
```ruby
|
305
362
|
repository.settings number_of_shards: 1
|
@@ -311,7 +368,39 @@ repository.mappings.to_hash
|
|
311
368
|
# => { :note => {:properties=> ... }}
|
312
369
|
```
|
313
370
|
|
371
|
+
or with the DSL mixin:
|
372
|
+
|
373
|
+
```ruby
|
374
|
+
class NoteRepository
|
375
|
+
include Elasticsearch::Persistence::Repository
|
376
|
+
include Elasticsearch::Persistence::Repository::DSL
|
377
|
+
|
378
|
+
mappings { indexes :title, analyzer: 'snowball' }
|
379
|
+
settings number_of_shards: 1
|
380
|
+
end
|
381
|
+
|
382
|
+
repository = NoteRepository.new
|
383
|
+
|
384
|
+
```
|
385
|
+
|
386
|
+
You can also use the `#create` method defined on the repository class to create and set the mappings and settings
|
387
|
+
on an instance with a block in one call:
|
388
|
+
|
389
|
+
```ruby
|
390
|
+
repository = NoteRepository.create(index_name: 'notes_development') do
|
391
|
+
settings number_of_shards: 1, number_of_replicas: 0 do
|
392
|
+
mapping dynamic: 'strict' do
|
393
|
+
indexes :foo do
|
394
|
+
indexes :bar
|
395
|
+
end
|
396
|
+
indexes :baz
|
397
|
+
end
|
398
|
+
end
|
399
|
+
end
|
400
|
+
```
|
401
|
+
|
314
402
|
The convenience methods `create_index!`, `delete_index!` and `refresh_index!` allow you to manage the index lifecycle.
|
403
|
+
These methods can only be called on repository instances and are not implemented at the class level.
|
315
404
|
|
316
405
|
##### Serialization
|
317
406
|
|
@@ -320,9 +409,12 @@ to the storage, and the initialization procedure when loading it from the storag
|
|
320
409
|
|
321
410
|
```ruby
|
322
411
|
class NoteRepository
|
412
|
+
include Elasticsearch::Persistence::Repository
|
413
|
+
|
323
414
|
def serialize(document)
|
324
415
|
Hash[document.to_hash.map() { |k,v| v.upcase! if k == :title; [k,v] }]
|
325
416
|
end
|
417
|
+
|
326
418
|
def deserialize(document)
|
327
419
|
MyNote.new ActiveSupport::HashWithIndifferentAccess.new(document['_source']).deep_symbolize_keys
|
328
420
|
end
|
@@ -427,9 +519,15 @@ end
|
|
427
519
|
results.total
|
428
520
|
# => 2
|
429
521
|
|
430
|
-
# Access the raw response as a Hashie::Mash instance
|
522
|
+
# Access the raw response as a Hashie::Mash instance.
|
523
|
+
# Note that a Hashie::Mash will only be created if the 'response' method is called on the results.
|
431
524
|
results.response._shards.failed
|
432
525
|
# => 0
|
526
|
+
|
527
|
+
# Access the raw response
|
528
|
+
results.raw_response
|
529
|
+
# => {...}
|
530
|
+
|
433
531
|
```
|
434
532
|
|
435
533
|
#### Example Application
|
@@ -445,265 +543,8 @@ and demonstrates a rich set of features:
|
|
445
543
|
|
446
544
|
### The ActiveRecord Pattern
|
447
545
|
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
The `Elasticsearch::Persistence::Model` module provides an implementation of the
|
452
|
-
active record [pattern](http://www.martinfowler.com/eaaCatalog/activeRecord.html),
|
453
|
-
with a familiar interface for using Elasticsearch as a persistence layer in
|
454
|
-
Ruby on Rails applications.
|
455
|
-
|
456
|
-
All the methods are documented with comprehensive examples in the source code,
|
457
|
-
available also online at <http://rubydoc.info/gems/elasticsearch-persistence/Elasticsearch/Persistence/Model>.
|
458
|
-
|
459
|
-
#### Installation/Usage
|
460
|
-
|
461
|
-
To use the library in a Rails application, add it to your `Gemfile` with a `require` statement:
|
462
|
-
|
463
|
-
```ruby
|
464
|
-
gem "elasticsearch-persistence", require: 'elasticsearch/persistence/model'
|
465
|
-
```
|
466
|
-
|
467
|
-
To use the library without Bundler, install it, and require the file:
|
468
|
-
|
469
|
-
```bash
|
470
|
-
gem install elasticsearch-persistence
|
471
|
-
```
|
472
|
-
|
473
|
-
```ruby
|
474
|
-
# In your code
|
475
|
-
require 'elasticsearch/persistence/model'
|
476
|
-
```
|
477
|
-
|
478
|
-
#### Model Definition
|
479
|
-
|
480
|
-
The integration is implemented by including the module in a Ruby class.
|
481
|
-
The model attribute definition support is implemented with the
|
482
|
-
[_Virtus_](https://github.com/solnic/virtus) Rubygem, and the
|
483
|
-
naming, validation, etc. features with the
|
484
|
-
[_ActiveModel_](https://github.com/rails/rails/tree/master/activemodel) Rubygem.
|
485
|
-
|
486
|
-
```ruby
|
487
|
-
class Article
|
488
|
-
include Elasticsearch::Persistence::Model
|
489
|
-
|
490
|
-
# Define a plain `title` attribute
|
491
|
-
#
|
492
|
-
attribute :title, String
|
493
|
-
|
494
|
-
# Define an `author` attribute, with multiple analyzers for this field
|
495
|
-
#
|
496
|
-
attribute :author, String, mapping: { fields: {
|
497
|
-
author: { type: 'text'},
|
498
|
-
raw: { type: 'keyword' }
|
499
|
-
} }
|
500
|
-
|
501
|
-
|
502
|
-
# Define a `views` attribute, with default value
|
503
|
-
#
|
504
|
-
attribute :views, Integer, default: 0, mapping: { type: 'integer' }
|
505
|
-
|
506
|
-
# Validate the presence of the `title` attribute
|
507
|
-
#
|
508
|
-
validates :title, presence: true
|
509
|
-
|
510
|
-
# Execute code after saving the model.
|
511
|
-
#
|
512
|
-
after_save { puts "Successfully saved: #{self}" }
|
513
|
-
end
|
514
|
-
```
|
515
|
-
|
516
|
-
Attribute validations work like for any other _ActiveModel_-compatible implementation:
|
517
|
-
|
518
|
-
```ruby
|
519
|
-
article = Article.new # => #<Article { ... }>
|
520
|
-
|
521
|
-
article.valid?
|
522
|
-
# => false
|
523
|
-
|
524
|
-
article.errors.to_a
|
525
|
-
# => ["Title can't be blank"]
|
526
|
-
```
|
527
|
-
|
528
|
-
#### Persistence
|
529
|
-
|
530
|
-
We can create a new article in the database...
|
531
|
-
|
532
|
-
```ruby
|
533
|
-
Article.create id: 1, title: 'Test', author: 'John'
|
534
|
-
# PUT http://localhost:9200/articles/article/1 [status:201, request:0.015s, query:n/a]
|
535
|
-
```
|
536
|
-
|
537
|
-
... and find it:
|
538
|
-
|
539
|
-
```ruby
|
540
|
-
article = Article.find(1)
|
541
|
-
# => #<Article { ... }>
|
542
|
-
|
543
|
-
article._index
|
544
|
-
# => "articles"
|
545
|
-
|
546
|
-
article.id
|
547
|
-
# => "1"
|
548
|
-
|
549
|
-
article.title
|
550
|
-
# => "Test"
|
551
|
-
```
|
552
|
-
|
553
|
-
To update the model, either update the attribute and save the model:
|
554
|
-
|
555
|
-
```ruby
|
556
|
-
article.title = 'Updated'
|
557
|
-
|
558
|
-
article.save
|
559
|
-
# => {"_index"=>"articles", "_type"=>"article", "_id"=>"1", "_version"=>2, "created"=>false}
|
560
|
-
```
|
561
|
-
|
562
|
-
... or use the `update_attributes` method:
|
563
|
-
|
564
|
-
```ruby
|
565
|
-
article.update_attributes title: 'Test', author: 'Mary'
|
566
|
-
# => {"_index"=>"articles", "_type"=>"article", "_id"=>"1", "_version"=>3}
|
567
|
-
```
|
568
|
-
|
569
|
-
The implementation supports the familiar interface for updating model timestamps:
|
570
|
-
|
571
|
-
```ruby
|
572
|
-
article.touch
|
573
|
-
# => => { ... "_version"=>4}
|
574
|
-
```
|
575
|
-
|
576
|
-
... and numeric attributes:
|
577
|
-
|
578
|
-
```ruby
|
579
|
-
article.views
|
580
|
-
# => 0
|
581
|
-
|
582
|
-
article.increment :views
|
583
|
-
article.views
|
584
|
-
# => 1
|
585
|
-
```
|
586
|
-
|
587
|
-
Any callbacks defined in the model will be triggered during the persistence operations:
|
588
|
-
|
589
|
-
```ruby
|
590
|
-
article.save
|
591
|
-
# Successfully saved: #<Article {...}>
|
592
|
-
```
|
593
|
-
|
594
|
-
The model also supports familiar `find_in_batches` and `find_each` methods to efficiently
|
595
|
-
retrieve big collections of model instances, using the Elasticsearch's _Scan API_:
|
596
|
-
|
597
|
-
```ruby
|
598
|
-
Article.find_each(_source_include: 'title') { |a| puts "===> #{a.title.upcase}" }
|
599
|
-
# GET http://localhost:9200/articles/article/_search?scroll=5m&size=20
|
600
|
-
# GET http://localhost:9200/_search/scroll?scroll=5m&scroll_id=c2Nhb...
|
601
|
-
# ===> TEST
|
602
|
-
# GET http://localhost:9200/_search/scroll?scroll=5m&scroll_id=c2Nhb...
|
603
|
-
# => "c2Nhb..."
|
604
|
-
```
|
605
|
-
|
606
|
-
#### Search
|
607
|
-
|
608
|
-
The model class provides a `search` method to retrieve model instances with a regular
|
609
|
-
search definition, including highlighting, aggregations, etc:
|
610
|
-
|
611
|
-
```ruby
|
612
|
-
results = Article.search query: { match: { title: 'test' } },
|
613
|
-
aggregations: { authors: { terms: { field: 'author.raw' } } },
|
614
|
-
highlight: { fields: { title: {} } }
|
615
|
-
|
616
|
-
puts results.first.title
|
617
|
-
# Test
|
618
|
-
|
619
|
-
puts results.first.hit.highlight['title']
|
620
|
-
# <em>Test</em>
|
621
|
-
|
622
|
-
puts results.response.aggregations.authors.buckets.each { |b| puts "#{b['key']} : #{b['doc_count']}" }
|
623
|
-
# John : 1
|
624
|
-
```
|
625
|
-
|
626
|
-
#### The Elasticsearch Client
|
627
|
-
|
628
|
-
The module will set up a [client](https://github.com/elastic/elasticsearch-ruby/tree/master/elasticsearch),
|
629
|
-
connected to `localhost:9200`, by default.
|
630
|
-
|
631
|
-
To use a client with different configuration:
|
632
|
-
|
633
|
-
```ruby
|
634
|
-
Elasticsearch::Persistence.client = Elasticsearch::Client.new log: true
|
635
|
-
```
|
636
|
-
|
637
|
-
To set up a specific client for a specific model:
|
638
|
-
|
639
|
-
```ruby
|
640
|
-
Article.gateway.client = Elasticsearch::Client.new host: 'api.server.org'
|
641
|
-
```
|
642
|
-
|
643
|
-
You might want to do this during you application bootstrap process, e.g. in a Rails initializer.
|
644
|
-
|
645
|
-
Please refer to the
|
646
|
-
[`elasticsearch-transport`](https://github.com/elasticsearch/elasticsearch-ruby/tree/master/elasticsearch-transport)
|
647
|
-
library documentation for all the configuration options, and to the
|
648
|
-
[`elasticsearch-api`](http://rubydoc.info/gems/elasticsearch-api) library documentation
|
649
|
-
for information about the Ruby client API.
|
650
|
-
|
651
|
-
#### Accessing the Repository Gateway and the Client
|
652
|
-
|
653
|
-
The integration with Elasticsearch is implemented by embedding the repository object in the model.
|
654
|
-
You can access it through the `gateway` method:
|
655
|
-
|
656
|
-
```ruby
|
657
|
-
Artist.gateway.client.info
|
658
|
-
# GET http://localhost:9200/ [status:200, request:0.011s, query:n/a]
|
659
|
-
# => {"status"=>200, "name"=>"Lightspeed", ...}
|
660
|
-
```
|
661
|
-
|
662
|
-
#### Rails Compatibility
|
663
|
-
|
664
|
-
The model instances are fully compatible with Rails' conventions and helpers:
|
665
|
-
|
666
|
-
```ruby
|
667
|
-
url_for article
|
668
|
-
# => "http://localhost:3000/articles/1"
|
669
|
-
|
670
|
-
div_for article
|
671
|
-
# => '<div class="article" id="article_1"></div>'
|
672
|
-
```
|
673
|
-
|
674
|
-
... as well as form values for dates and times:
|
675
|
-
|
676
|
-
```ruby
|
677
|
-
article = Article.new "title" => "Date", "published(1i)"=>"2014", "published(2i)"=>"1", "published(3i)"=>"1"
|
678
|
-
|
679
|
-
article.published.iso8601
|
680
|
-
# => "2014-01-01"
|
681
|
-
```
|
682
|
-
|
683
|
-
The library provides a Rails ORM generator to facilitate building the application scaffolding:
|
684
|
-
|
685
|
-
```bash
|
686
|
-
rails generate scaffold Person name:String email:String birthday:Date --orm=elasticsearch
|
687
|
-
```
|
688
|
-
|
689
|
-
#### Example application
|
690
|
-
|
691
|
-
A fully working Ruby on Rails application can be generated with the following command:
|
692
|
-
|
693
|
-
```bash
|
694
|
-
rails new music --force --skip --skip-bundle --skip-active-record --template https://raw.githubusercontent.com/elastic/elasticsearch-rails/master/elasticsearch-persistence/examples/music/template.rb
|
695
|
-
```
|
696
|
-
|
697
|
-
The application demonstrates:
|
698
|
-
|
699
|
-
* How to set up model attributes with custom mappings
|
700
|
-
* How to define model relationships with Elasticsearch's parent/child
|
701
|
-
* How to configure models to use a common index, and create the index with proper mappings
|
702
|
-
* How to use Elasticsearch's completion suggester to drive auto-complete functionality
|
703
|
-
* How to use Elasticsearch-persisted models in Rails' views and forms
|
704
|
-
* How to write controller tests
|
705
|
-
|
706
|
-
The source files for the application are available in the [`examples/music`](examples/music) folder.
|
546
|
+
The ActiveRecord pattern has been deprecated as of version 6.0.0 of this gem. Please use the
|
547
|
+
[Repository Pattern](#the-repository-pattern) instead.
|
707
548
|
|
708
549
|
## License
|
709
550
|
|