elasticsearch-persistence 5.0.2 → 6.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.rspec +2 -0
- data/Gemfile +9 -0
- data/README.md +206 -338
- data/Rakefile +15 -12
- data/elasticsearch-persistence.gemspec +6 -7
- data/examples/notes/application.rb +3 -4
- 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 +21 -8
- data/lib/elasticsearch/persistence/repository/search.rb +30 -18
- 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 +128 -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 +723 -0
- data/spec/spec_helper.rb +32 -0
- metadata +26 -104
- data/examples/music/album.rb +0 -54
- data/examples/music/artist.rb +0 -70
- data/examples/music/artists/_form.html.erb +0 -8
- data/examples/music/artists/artists_controller.rb +0 -67
- data/examples/music/artists/artists_controller_test.rb +0 -53
- data/examples/music/artists/index.html.erb +0 -60
- data/examples/music/artists/show.html.erb +0 -54
- data/examples/music/assets/application.css +0 -257
- data/examples/music/assets/autocomplete.css +0 -48
- data/examples/music/assets/blank_artist.png +0 -0
- data/examples/music/assets/blank_cover.png +0 -0
- data/examples/music/assets/form.css +0 -113
- data/examples/music/index_manager.rb +0 -73
- data/examples/music/search/index.html.erb +0 -95
- data/examples/music/search/search_controller.rb +0 -41
- data/examples/music/search/search_controller_test.rb +0 -12
- data/examples/music/search/search_helper.rb +0 -15
- data/examples/music/suggester.rb +0 -69
- data/examples/music/template.rb +0 -430
- data/examples/music/vendor/assets/jquery-ui-1.10.4.custom.min.css +0 -7
- data/examples/music/vendor/assets/jquery-ui-1.10.4.custom.min.js +0 -6
- data/examples/music/vendor/assets/stylesheets/ui-lightness/images/ui-bg_highlight-soft_100_eeeeee_1x100.png +0 -0
- data/lib/elasticsearch/persistence/client.rb +0 -51
- data/lib/elasticsearch/persistence/model.rb +0 -135
- 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 -233
- 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
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 1d37ced5f5d7a21a94d37e5186a242b5c89f7dffa4503b5a286790061dfdf412
|
4
|
+
data.tar.gz: 412720f0597d5bd725e57cccffc17be2cef3242e33bd5720166b60af246c83e5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 941fc4d03706ebe777ae7b3d55490962a3880f9d840588b58f803b45fda482574b94639db8c7fe2e639909c68c0206da6bdf12f85a66cd1ef63c64ef3c197ae7
|
7
|
+
data.tar.gz: adcdc8229548e59497afb2c0af4c09d50a3a75ed42f1a3406655ad4e577bf7c15a1c4177b9e7dc6d06db1918f87f76eaa589d7ce2637317710840780bc879780
|
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,16 +36,13 @@ 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
|
|
45
43
|
The `Elasticsearch::Persistence::Repository` module provides an implementation of the
|
46
44
|
[repository pattern](http://martinfowler.com/eaaCatalog/repository.html) and allows
|
47
|
-
to save, delete, find and search objects stored in Elasticsearch, as well as configure
|
45
|
+
you to save, delete, find and search objects stored in Elasticsearch, as well as configure
|
48
46
|
mappings and settings for the index. It's an unobtrusive and decoupled way of adding
|
49
47
|
persistence to your Ruby objects.
|
50
48
|
|
@@ -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...
|
@@ -77,7 +76,7 @@ We can save a `Note` instance into the repository...
|
|
77
76
|
note = Note.new id: 1, text: 'Test'
|
78
77
|
|
79
78
|
repository.save(note)
|
80
|
-
# PUT http://localhost:9200/repository/
|
79
|
+
# PUT http://localhost:9200/repository/_doc/1 [status:201, request:0.210s, query:n/a]
|
81
80
|
# > {"id":1,"text":"Test"}
|
82
81
|
# < {"_index":"repository","_type":"note","_id":"1","_version":1,"created":true}
|
83
82
|
```
|
@@ -86,7 +85,7 @@ repository.save(note)
|
|
86
85
|
|
87
86
|
```ruby
|
88
87
|
n = repository.find(1)
|
89
|
-
# GET http://localhost:9200/repository/
|
88
|
+
# GET http://localhost:9200/repository/_doc/1 [status:200, request:0.003s, query:n/a]
|
90
89
|
# < {"_index":"repository","_type":"note","_id":"1","_version":2,"found":true, "_source" : {"id":1,"text":"Test"}}
|
91
90
|
=> <Note:0x007fcbfc0c4980 @attributes={"id"=>1, "text"=>"Test"}>
|
92
91
|
```
|
@@ -105,7 +104,7 @@ repository.search(query: { match: { text: 'test' } }).first
|
|
105
104
|
|
106
105
|
```ruby
|
107
106
|
repository.delete(note)
|
108
|
-
# DELETE http://localhost:9200/repository/
|
107
|
+
# DELETE http://localhost:9200/repository/_doc/1 [status:200, request:0.014s, query:n/a]
|
109
108
|
# < {"found":true,"_index":"repository","_type":"note","_id":"1","_version":3}
|
110
109
|
=> {"found"=>true, "_index"=>"repository", "_type"=>"note", "_id"=>"1", "_version"=>2}
|
111
110
|
```
|
@@ -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: :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,
|
@@ -176,7 +168,7 @@ Save the document with extra properties added by the `serialize` method:
|
|
176
168
|
|
177
169
|
```ruby
|
178
170
|
repository.save(note)
|
179
|
-
# PUT http://localhost:9200/my_notes/
|
171
|
+
# PUT http://localhost:9200/my_notes/note/1
|
180
172
|
# > {"id":1,"text":"Test","my_special_key":"my_special_stuff"}
|
181
173
|
{"_index"=>"my_notes", "_type"=>"my_note", "_id"=>"1", "_version"=>4, ... }
|
182
174
|
```
|
@@ -185,32 +177,31 @@ And `deserialize` it:
|
|
185
177
|
|
186
178
|
```ruby
|
187
179
|
repository.find(1)
|
188
|
-
# ***** CUSTOM DESERIALIZE LOGIC
|
180
|
+
# ***** CUSTOM DESERIALIZE LOGIC... *****
|
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,74 +223,160 @@ 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
|
-
class and instance methods -- in general, have all the freedom of a standard Ruby class.
|
234
|
+
```
|
241
235
|
|
242
|
-
|
243
|
-
repository = NoteRepository.new url: 'http://localhost:9200', log: true
|
236
|
+
You can also override the default configuration with options passed to the initialize method:
|
244
237
|
|
245
|
-
|
246
|
-
|
247
|
-
|
238
|
+
```ruby
|
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')
|
248
242
|
|
249
|
-
repository.create_index!
|
243
|
+
repository.create_index!(force: true)
|
250
244
|
|
251
|
-
note = Note.new
|
245
|
+
note = Note.new('id' => 1, 'text' => 'Document with image', 'image' => '... BINARY DATA ...')
|
252
246
|
|
253
247
|
repository.save(note)
|
254
|
-
# PUT http://localhost:9200/notes_development/
|
248
|
+
# PUT http://localhost:9200/notes_development/_doc/1
|
255
249
|
# > {"id":1,"text":"Document with image","image":"Li4uIEJJTkFSWSBEQVRBIC4uLg==\n"}
|
256
250
|
puts repository.find(1).attributes['image']
|
257
|
-
# GET http://localhost:9200/notes_development/
|
251
|
+
# GET http://localhost:9200/notes_development/_doc/1
|
258
252
|
# < {... "_source" : { ... "image":"Li4uIEJJTkFSWSBEQVRBIC4uLg==\n"}}
|
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 value. You can still override the configuration for any instance by passing options to the
|
261
|
+
`#initialize` method.
|
262
|
+
Even if you don't use the DSL mixin, you can set 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
|
+
repository.client
|
273
|
+
# => Elasticsearch::Client
|
274
|
+
|
275
|
+
```
|
276
|
+
|
277
|
+
or with the DSL mixin:
|
278
|
+
|
279
|
+
```ruby
|
280
|
+
class NoteRepository
|
281
|
+
include Elasticsearch::Persistence::Repository
|
282
|
+
include Elasticsearch::Persistence::Repository::DSL
|
283
|
+
|
284
|
+
client Elasticsearch::Client.new url: 'http://search.server.org'
|
285
|
+
end
|
286
|
+
|
287
|
+
repository = NoteRepository.new
|
288
|
+
repository.client
|
289
|
+
# => Elasticsearch::Client
|
290
|
+
|
272
291
|
```
|
273
292
|
|
274
293
|
##### Naming
|
275
294
|
|
276
|
-
The `
|
277
|
-
|
295
|
+
The `index_name` method specifies the Elasticsearch index to use for storage, lookup and search. The default index name
|
296
|
+
is 'repository'.
|
297
|
+
|
298
|
+
```ruby
|
299
|
+
repository = NoteRepository.new(index_name: 'notes_development')
|
300
|
+
repository.index_name
|
301
|
+
# => 'notes_development'
|
302
|
+
|
303
|
+
```
|
304
|
+
|
305
|
+
or with the DSL mixin:
|
306
|
+
|
307
|
+
```ruby
|
308
|
+
class NoteRepository
|
309
|
+
include Elasticsearch::Persistence::Repository
|
310
|
+
include Elasticsearch::Persistence::Repository::DSL
|
311
|
+
|
312
|
+
index_name 'notes_development'
|
313
|
+
end
|
314
|
+
|
315
|
+
repository = NoteRepository.new
|
316
|
+
repository.index_name
|
317
|
+
# => 'notes_development'
|
318
|
+
|
319
|
+
```
|
320
|
+
|
321
|
+
The `document_type` method specifies the Elasticsearch document type to use for storage, lookup and search. The default value is
|
322
|
+
'_doc'. Keep in mind that future versions of Elasticsearch will not allow you to set this yourself and will use the type,
|
323
|
+
'_doc'.
|
278
324
|
|
279
325
|
```ruby
|
280
|
-
repository
|
326
|
+
repository = NoteRepository.new(document_type: 'note')
|
327
|
+
repository.document_type
|
328
|
+
# => 'note'
|
329
|
+
|
281
330
|
```
|
282
331
|
|
283
|
-
|
284
|
-
(when not set, the value is inferred from the document class name, or `_all` is used):
|
332
|
+
or with the DSL mixin:
|
285
333
|
|
286
334
|
```ruby
|
287
|
-
|
335
|
+
class NoteRepository
|
336
|
+
include Elasticsearch::Persistence::Repository
|
337
|
+
include Elasticsearch::Persistence::Repository::DSL
|
338
|
+
|
339
|
+
document_type 'note'
|
340
|
+
end
|
341
|
+
|
342
|
+
repository = NoteRepository.new
|
343
|
+
repository.document_type
|
344
|
+
# => 'note'
|
345
|
+
|
288
346
|
```
|
289
347
|
|
290
348
|
The `klass` method specifies the Ruby class name to use when initializing objects from
|
291
|
-
documents retrieved from the repository
|
292
|
-
|
349
|
+
documents retrieved from the repository. If this value is not set, a Hash representation of the document will be
|
350
|
+
returned instead.
|
351
|
+
|
352
|
+
```ruby
|
353
|
+
repository = NoteRepository.new(klass: Note)
|
354
|
+
repository.klass
|
355
|
+
# => Note
|
356
|
+
|
357
|
+
```
|
358
|
+
|
359
|
+
or with the DSL mixin:
|
293
360
|
|
294
361
|
```ruby
|
295
|
-
|
362
|
+
class NoteRepository
|
363
|
+
include Elasticsearch::Persistence::Repository
|
364
|
+
include Elasticsearch::Persistence::Repository::DSL
|
365
|
+
|
366
|
+
klass Note
|
367
|
+
end
|
368
|
+
|
369
|
+
repository = NoteRepository.new
|
370
|
+
repository.klass
|
371
|
+
# => Note
|
372
|
+
|
296
373
|
```
|
297
374
|
|
298
375
|
##### Index Configuration
|
299
376
|
|
300
377
|
The `settings` and `mappings` methods, provided by the
|
301
378
|
[`elasticsearch-model`](http://rubydoc.info/gems/elasticsearch-model/Elasticsearch/Model/Indexing/ClassMethods)
|
302
|
-
gem, allow to configure the index properties:
|
379
|
+
gem, allow you to configure the index properties:
|
303
380
|
|
304
381
|
```ruby
|
305
382
|
repository.settings number_of_shards: 1
|
@@ -311,18 +388,57 @@ repository.mappings.to_hash
|
|
311
388
|
# => { :note => {:properties=> ... }}
|
312
389
|
```
|
313
390
|
|
391
|
+
or with the DSL mixin:
|
392
|
+
|
393
|
+
```ruby
|
394
|
+
class NoteRepository
|
395
|
+
include Elasticsearch::Persistence::Repository
|
396
|
+
include Elasticsearch::Persistence::Repository::DSL
|
397
|
+
|
398
|
+
mappings { indexes :title, analyzer: 'snowball' }
|
399
|
+
settings number_of_shards: 1
|
400
|
+
end
|
401
|
+
|
402
|
+
repository = NoteRepository.new
|
403
|
+
|
404
|
+
```
|
405
|
+
|
406
|
+
##### Create a Repository and set its configuration with a block
|
407
|
+
|
408
|
+
You can also use the `#create` method to instantiate and set the mappings and settings on an instance
|
409
|
+
with a block in one call:
|
410
|
+
|
411
|
+
```ruby
|
412
|
+
repository = NoteRepository.create(index_name: 'notes_development') do
|
413
|
+
settings number_of_shards: 1, number_of_replicas: 0 do
|
414
|
+
mapping dynamic: 'strict' do
|
415
|
+
indexes :foo do
|
416
|
+
indexes :bar
|
417
|
+
end
|
418
|
+
indexes :baz
|
419
|
+
end
|
420
|
+
end
|
421
|
+
end
|
422
|
+
```
|
423
|
+
|
424
|
+
##### Index Management
|
425
|
+
|
314
426
|
The convenience methods `create_index!`, `delete_index!` and `refresh_index!` allow you to manage the index lifecycle.
|
427
|
+
These methods can only be called on repository instances and are not implemented at the class level.
|
315
428
|
|
316
429
|
##### Serialization
|
317
430
|
|
318
|
-
The `serialize` and `deserialize` methods allow you to customize the serialization of the document when
|
319
|
-
to
|
431
|
+
The `serialize` and `deserialize` methods allow you to customize the serialization of the document when it
|
432
|
+
is persisted to Elasticsearch, and define the initialization procedure when loading it from the storage:
|
320
433
|
|
321
434
|
```ruby
|
322
435
|
class NoteRepository
|
436
|
+
include Elasticsearch::Persistence::Repository
|
437
|
+
|
323
438
|
def serialize(document)
|
324
439
|
Hash[document.to_hash.map() { |k,v| v.upcase! if k == :title; [k,v] }]
|
325
440
|
end
|
441
|
+
|
326
442
|
def deserialize(document)
|
327
443
|
MyNote.new ActiveSupport::HashWithIndifferentAccess.new(document['_source']).deep_symbolize_keys
|
328
444
|
end
|
@@ -336,7 +452,7 @@ The `save` method allows you to store a domain object in the repository:
|
|
336
452
|
```ruby
|
337
453
|
note = Note.new id: 1, title: 'Quick Brown Fox'
|
338
454
|
repository.save(note)
|
339
|
-
# => {"_index"=>"notes_development", "_type"=>"
|
455
|
+
# => {"_index"=>"notes_development", "_type"=>"_doc", "_id"=>"1", "_version"=>1, "created"=>true}
|
340
456
|
```
|
341
457
|
|
342
458
|
The `update` method allows you to perform a partial update of a document in the repository.
|
@@ -344,18 +460,18 @@ Use either a partial document:
|
|
344
460
|
|
345
461
|
```ruby
|
346
462
|
repository.update id: 1, title: 'UPDATED', tags: []
|
347
|
-
# => {"_index"=>"notes_development", "_type"=>"
|
463
|
+
# => {"_index"=>"notes_development", "_type"=>"_doc", "_id"=>"1", "_version"=>2}
|
348
464
|
```
|
349
465
|
|
350
466
|
Or a script (optionally with parameters):
|
351
467
|
|
352
468
|
```ruby
|
353
469
|
repository.update 1, script: 'if (!ctx._source.tags.contains(t)) { ctx._source.tags += t }', params: { t: 'foo' }
|
354
|
-
# => {"_index"=>"notes_development", "_type"=>"
|
470
|
+
# => {"_index"=>"notes_development", "_type"=>"_doc", "_id"=>"1", "_version"=>3}
|
355
471
|
```
|
356
472
|
|
357
473
|
|
358
|
-
The `delete` method allows to remove objects from the repository (pass either the object itself or its ID):
|
474
|
+
The `delete` method allows you to remove objects from the repository (pass either the object itself or its ID):
|
359
475
|
|
360
476
|
```ruby
|
361
477
|
repository.delete(note)
|
@@ -364,7 +480,7 @@ repository.delete(1)
|
|
364
480
|
|
365
481
|
##### Finding
|
366
482
|
|
367
|
-
The `find` method allows to find one or many documents in the storage and returns them as deserialized Ruby objects:
|
483
|
+
The `find` method allows you to find one or many documents in the storage and returns them as deserialized Ruby objects:
|
368
484
|
|
369
485
|
```ruby
|
370
486
|
repository.save Note.new(id: 2, title: 'Fast White Dog')
|
@@ -387,15 +503,15 @@ Handle the missing objects in the application code, or call `compact` on the res
|
|
387
503
|
|
388
504
|
##### Search
|
389
505
|
|
390
|
-
The `search` method to retrieve objects from the repository by a query string or definition in the Elasticsearch DSL:
|
506
|
+
The `search` method is used to retrieve objects from the repository by a query string or definition in the Elasticsearch DSL:
|
391
507
|
|
392
508
|
```ruby
|
393
509
|
repository.search('fox or dog').to_a
|
394
|
-
# GET http://localhost:9200/notes_development/
|
510
|
+
# GET http://localhost:9200/notes_development/_doc/_search?q=fox
|
395
511
|
# => [<MyNote ... FOX ...>, <MyNote ... DOG ...>]
|
396
512
|
|
397
513
|
repository.search(query: { match: { title: 'fox dog' } }).to_a
|
398
|
-
# GET http://localhost:9200/notes_development/
|
514
|
+
# GET http://localhost:9200/notes_development/_doc/_search
|
399
515
|
# > {"query":{"match":{"title":"fox dog"}}}
|
400
516
|
# => [<MyNote ... FOX ...>, <MyNote ... DOG ...>]
|
401
517
|
```
|
@@ -427,9 +543,15 @@ end
|
|
427
543
|
results.total
|
428
544
|
# => 2
|
429
545
|
|
430
|
-
# Access the raw response as a Hashie::Mash instance
|
546
|
+
# Access the raw response as a Hashie::Mash instance.
|
547
|
+
# Note that a Hashie::Mash will only be created if the 'response' method is called on the results.
|
431
548
|
results.response._shards.failed
|
432
549
|
# => 0
|
550
|
+
|
551
|
+
# Access the raw response
|
552
|
+
results.raw_response
|
553
|
+
# => {...}
|
554
|
+
|
433
555
|
```
|
434
556
|
|
435
557
|
#### Example Application
|
@@ -445,262 +567,8 @@ and demonstrates a rich set of features:
|
|
445
567
|
|
446
568
|
### The ActiveRecord Pattern
|
447
569
|
|
448
|
-
The
|
449
|
-
|
450
|
-
with a familiar interface for using Elasticsearch as a persistence layer in
|
451
|
-
Ruby on Rails applications.
|
452
|
-
|
453
|
-
All the methods are documented with comprehensive examples in the source code,
|
454
|
-
available also online at <http://rubydoc.info/gems/elasticsearch-persistence/Elasticsearch/Persistence/Model>.
|
455
|
-
|
456
|
-
#### Installation/Usage
|
457
|
-
|
458
|
-
To use the library in a Rails application, add it to your `Gemfile` with a `require` statement:
|
459
|
-
|
460
|
-
```ruby
|
461
|
-
gem "elasticsearch-persistence", require: 'elasticsearch/persistence/model'
|
462
|
-
```
|
463
|
-
|
464
|
-
To use the library without Bundler, install it, and require the file:
|
465
|
-
|
466
|
-
```bash
|
467
|
-
gem install elasticsearch-persistence
|
468
|
-
```
|
469
|
-
|
470
|
-
```ruby
|
471
|
-
# In your code
|
472
|
-
require 'elasticsearch/persistence/model'
|
473
|
-
```
|
474
|
-
|
475
|
-
#### Model Definition
|
476
|
-
|
477
|
-
The integration is implemented by including the module in a Ruby class.
|
478
|
-
The model attribute definition support is implemented with the
|
479
|
-
[_Virtus_](https://github.com/solnic/virtus) Rubygem, and the
|
480
|
-
naming, validation, etc. features with the
|
481
|
-
[_ActiveModel_](https://github.com/rails/rails/tree/master/activemodel) Rubygem.
|
482
|
-
|
483
|
-
```ruby
|
484
|
-
class Article
|
485
|
-
include Elasticsearch::Persistence::Model
|
486
|
-
|
487
|
-
# Define a plain `title` attribute
|
488
|
-
#
|
489
|
-
attribute :title, String
|
490
|
-
|
491
|
-
# Define an `author` attribute, with multiple analyzers for this field
|
492
|
-
#
|
493
|
-
attribute :author, String, mapping: { fields: {
|
494
|
-
author: { type: 'text'},
|
495
|
-
raw: { type: 'keyword' }
|
496
|
-
} }
|
497
|
-
|
498
|
-
|
499
|
-
# Define a `views` attribute, with default value
|
500
|
-
#
|
501
|
-
attribute :views, Integer, default: 0, mapping: { type: 'integer' }
|
502
|
-
|
503
|
-
# Validate the presence of the `title` attribute
|
504
|
-
#
|
505
|
-
validates :title, presence: true
|
506
|
-
|
507
|
-
# Execute code after saving the model.
|
508
|
-
#
|
509
|
-
after_save { puts "Successfully saved: #{self}" }
|
510
|
-
end
|
511
|
-
```
|
512
|
-
|
513
|
-
Attribute validations work like for any other _ActiveModel_-compatible implementation:
|
514
|
-
|
515
|
-
```ruby
|
516
|
-
article = Article.new # => #<Article { ... }>
|
517
|
-
|
518
|
-
article.valid?
|
519
|
-
# => false
|
520
|
-
|
521
|
-
article.errors.to_a
|
522
|
-
# => ["Title can't be blank"]
|
523
|
-
```
|
524
|
-
|
525
|
-
#### Persistence
|
526
|
-
|
527
|
-
We can create a new article in the database...
|
528
|
-
|
529
|
-
```ruby
|
530
|
-
Article.create id: 1, title: 'Test', author: 'John'
|
531
|
-
# PUT http://localhost:9200/articles/article/1 [status:201, request:0.015s, query:n/a]
|
532
|
-
```
|
533
|
-
|
534
|
-
... and find it:
|
535
|
-
|
536
|
-
```ruby
|
537
|
-
article = Article.find(1)
|
538
|
-
# => #<Article { ... }>
|
539
|
-
|
540
|
-
article._index
|
541
|
-
# => "articles"
|
542
|
-
|
543
|
-
article.id
|
544
|
-
# => "1"
|
545
|
-
|
546
|
-
article.title
|
547
|
-
# => "Test"
|
548
|
-
```
|
549
|
-
|
550
|
-
To update the model, either update the attribute and save the model:
|
551
|
-
|
552
|
-
```ruby
|
553
|
-
article.title = 'Updated'
|
554
|
-
|
555
|
-
article.save
|
556
|
-
# => {"_index"=>"articles", "_type"=>"article", "_id"=>"1", "_version"=>2, "created"=>false}
|
557
|
-
```
|
558
|
-
|
559
|
-
... or use the `update_attributes` method:
|
560
|
-
|
561
|
-
```ruby
|
562
|
-
article.update_attributes title: 'Test', author: 'Mary'
|
563
|
-
# => {"_index"=>"articles", "_type"=>"article", "_id"=>"1", "_version"=>3}
|
564
|
-
```
|
565
|
-
|
566
|
-
The implementation supports the familiar interface for updating model timestamps:
|
567
|
-
|
568
|
-
```ruby
|
569
|
-
article.touch
|
570
|
-
# => => { ... "_version"=>4}
|
571
|
-
```
|
572
|
-
|
573
|
-
... and numeric attributes:
|
574
|
-
|
575
|
-
```ruby
|
576
|
-
article.views
|
577
|
-
# => 0
|
578
|
-
|
579
|
-
article.increment :views
|
580
|
-
article.views
|
581
|
-
# => 1
|
582
|
-
```
|
583
|
-
|
584
|
-
Any callbacks defined in the model will be triggered during the persistence operations:
|
585
|
-
|
586
|
-
```ruby
|
587
|
-
article.save
|
588
|
-
# Successfully saved: #<Article {...}>
|
589
|
-
```
|
590
|
-
|
591
|
-
The model also supports familiar `find_in_batches` and `find_each` methods to efficiently
|
592
|
-
retrieve big collections of model instances, using the Elasticsearch's _Scan API_:
|
593
|
-
|
594
|
-
```ruby
|
595
|
-
Article.find_each(_source_include: 'title') { |a| puts "===> #{a.title.upcase}" }
|
596
|
-
# GET http://localhost:9200/articles/article/_search?scroll=5m&size=20
|
597
|
-
# GET http://localhost:9200/_search/scroll?scroll=5m&scroll_id=c2Nhb...
|
598
|
-
# ===> TEST
|
599
|
-
# GET http://localhost:9200/_search/scroll?scroll=5m&scroll_id=c2Nhb...
|
600
|
-
# => "c2Nhb..."
|
601
|
-
```
|
602
|
-
|
603
|
-
#### Search
|
604
|
-
|
605
|
-
The model class provides a `search` method to retrieve model instances with a regular
|
606
|
-
search definition, including highlighting, aggregations, etc:
|
607
|
-
|
608
|
-
```ruby
|
609
|
-
results = Article.search query: { match: { title: 'test' } },
|
610
|
-
aggregations: { authors: { terms: { field: 'author.raw' } } },
|
611
|
-
highlight: { fields: { title: {} } }
|
612
|
-
|
613
|
-
puts results.first.title
|
614
|
-
# Test
|
615
|
-
|
616
|
-
puts results.first.hit.highlight['title']
|
617
|
-
# <em>Test</em>
|
618
|
-
|
619
|
-
puts results.response.aggregations.authors.buckets.each { |b| puts "#{b['key']} : #{b['doc_count']}" }
|
620
|
-
# John : 1
|
621
|
-
```
|
622
|
-
|
623
|
-
#### The Elasticsearch Client
|
624
|
-
|
625
|
-
The module will set up a [client](https://github.com/elastic/elasticsearch-ruby/tree/master/elasticsearch),
|
626
|
-
connected to `localhost:9200`, by default.
|
627
|
-
|
628
|
-
To use a client with different configuration:
|
629
|
-
|
630
|
-
```ruby
|
631
|
-
Elasticsearch::Persistence.client = Elasticsearch::Client.new log: true
|
632
|
-
```
|
633
|
-
|
634
|
-
To set up a specific client for a specific model:
|
635
|
-
|
636
|
-
```ruby
|
637
|
-
Article.gateway.client = Elasticsearch::Client.new host: 'api.server.org'
|
638
|
-
```
|
639
|
-
|
640
|
-
You might want to do this during you application bootstrap process, e.g. in a Rails initializer.
|
641
|
-
|
642
|
-
Please refer to the
|
643
|
-
[`elasticsearch-transport`](https://github.com/elasticsearch/elasticsearch-ruby/tree/master/elasticsearch-transport)
|
644
|
-
library documentation for all the configuration options, and to the
|
645
|
-
[`elasticsearch-api`](http://rubydoc.info/gems/elasticsearch-api) library documentation
|
646
|
-
for information about the Ruby client API.
|
647
|
-
|
648
|
-
#### Accessing the Repository Gateway and the Client
|
649
|
-
|
650
|
-
The integration with Elasticsearch is implemented by embedding the repository object in the model.
|
651
|
-
You can access it through the `gateway` method:
|
652
|
-
|
653
|
-
```ruby
|
654
|
-
Artist.gateway.client.info
|
655
|
-
# GET http://localhost:9200/ [status:200, request:0.011s, query:n/a]
|
656
|
-
# => {"status"=>200, "name"=>"Lightspeed", ...}
|
657
|
-
```
|
658
|
-
|
659
|
-
#### Rails Compatibility
|
660
|
-
|
661
|
-
The model instances are fully compatible with Rails' conventions and helpers:
|
662
|
-
|
663
|
-
```ruby
|
664
|
-
url_for article
|
665
|
-
# => "http://localhost:3000/articles/1"
|
666
|
-
|
667
|
-
div_for article
|
668
|
-
# => '<div class="article" id="article_1"></div>'
|
669
|
-
```
|
670
|
-
|
671
|
-
... as well as form values for dates and times:
|
672
|
-
|
673
|
-
```ruby
|
674
|
-
article = Article.new "title" => "Date", "published(1i)"=>"2014", "published(2i)"=>"1", "published(3i)"=>"1"
|
675
|
-
|
676
|
-
article.published.iso8601
|
677
|
-
# => "2014-01-01"
|
678
|
-
```
|
679
|
-
|
680
|
-
The library provides a Rails ORM generator to facilitate building the application scaffolding:
|
681
|
-
|
682
|
-
```bash
|
683
|
-
rails generate scaffold Person name:String email:String birthday:Date --orm=elasticsearch
|
684
|
-
```
|
685
|
-
|
686
|
-
#### Example application
|
687
|
-
|
688
|
-
A fully working Ruby on Rails application can be generated with the following command:
|
689
|
-
|
690
|
-
```bash
|
691
|
-
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
|
692
|
-
```
|
693
|
-
|
694
|
-
The application demonstrates:
|
695
|
-
|
696
|
-
* How to set up model attributes with custom mappings
|
697
|
-
* How to define model relationships with Elasticsearch's parent/child
|
698
|
-
* How to configure models to use a common index, and create the index with proper mappings
|
699
|
-
* How to use Elasticsearch's completion suggester to drive auto-complete functionality
|
700
|
-
* How to use Elasticsearch-persisted models in Rails' views and forms
|
701
|
-
* How to write controller tests
|
702
|
-
|
703
|
-
The source files for the application are available in the [`examples/music`](examples/music) folder.
|
570
|
+
The ActiveRecord pattern has been deprecated as of version 6.0.0 of this gem. Please use the
|
571
|
+
[Repository Pattern](#the-repository-pattern) instead.
|
704
572
|
|
705
573
|
## License
|
706
574
|
|