elasticsearch-persistence 5.1.0 → 6.0.0.pre
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.
- 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
|
|