elasticsearch-persistence 5.0.2 → 6.1.1
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 +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
|
|