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.
Files changed (81) hide show
  1. checksums.yaml +5 -5
  2. data/.rspec +2 -0
  3. data/Gemfile +9 -0
  4. data/README.md +206 -338
  5. data/Rakefile +15 -12
  6. data/elasticsearch-persistence.gemspec +6 -7
  7. data/examples/notes/application.rb +3 -4
  8. data/lib/elasticsearch/persistence.rb +2 -110
  9. data/lib/elasticsearch/persistence/repository.rb +212 -53
  10. data/lib/elasticsearch/persistence/repository/dsl.rb +94 -0
  11. data/lib/elasticsearch/persistence/repository/find.rb +27 -10
  12. data/lib/elasticsearch/persistence/repository/response/results.rb +21 -8
  13. data/lib/elasticsearch/persistence/repository/search.rb +30 -18
  14. data/lib/elasticsearch/persistence/repository/serialize.rb +65 -7
  15. data/lib/elasticsearch/persistence/repository/store.rb +38 -44
  16. data/lib/elasticsearch/persistence/version.rb +1 -1
  17. data/spec/repository/find_spec.rb +179 -0
  18. data/spec/repository/response/results_spec.rb +128 -0
  19. data/spec/repository/search_spec.rb +181 -0
  20. data/spec/repository/serialize_spec.rb +53 -0
  21. data/spec/repository/store_spec.rb +327 -0
  22. data/spec/repository_spec.rb +723 -0
  23. data/spec/spec_helper.rb +32 -0
  24. metadata +26 -104
  25. data/examples/music/album.rb +0 -54
  26. data/examples/music/artist.rb +0 -70
  27. data/examples/music/artists/_form.html.erb +0 -8
  28. data/examples/music/artists/artists_controller.rb +0 -67
  29. data/examples/music/artists/artists_controller_test.rb +0 -53
  30. data/examples/music/artists/index.html.erb +0 -60
  31. data/examples/music/artists/show.html.erb +0 -54
  32. data/examples/music/assets/application.css +0 -257
  33. data/examples/music/assets/autocomplete.css +0 -48
  34. data/examples/music/assets/blank_artist.png +0 -0
  35. data/examples/music/assets/blank_cover.png +0 -0
  36. data/examples/music/assets/form.css +0 -113
  37. data/examples/music/index_manager.rb +0 -73
  38. data/examples/music/search/index.html.erb +0 -95
  39. data/examples/music/search/search_controller.rb +0 -41
  40. data/examples/music/search/search_controller_test.rb +0 -12
  41. data/examples/music/search/search_helper.rb +0 -15
  42. data/examples/music/suggester.rb +0 -69
  43. data/examples/music/template.rb +0 -430
  44. data/examples/music/vendor/assets/jquery-ui-1.10.4.custom.min.css +0 -7
  45. data/examples/music/vendor/assets/jquery-ui-1.10.4.custom.min.js +0 -6
  46. data/examples/music/vendor/assets/stylesheets/ui-lightness/images/ui-bg_highlight-soft_100_eeeeee_1x100.png +0 -0
  47. data/lib/elasticsearch/persistence/client.rb +0 -51
  48. data/lib/elasticsearch/persistence/model.rb +0 -135
  49. data/lib/elasticsearch/persistence/model/base.rb +0 -87
  50. data/lib/elasticsearch/persistence/model/errors.rb +0 -8
  51. data/lib/elasticsearch/persistence/model/find.rb +0 -180
  52. data/lib/elasticsearch/persistence/model/rails.rb +0 -47
  53. data/lib/elasticsearch/persistence/model/store.rb +0 -254
  54. data/lib/elasticsearch/persistence/model/utils.rb +0 -0
  55. data/lib/elasticsearch/persistence/repository/class.rb +0 -71
  56. data/lib/elasticsearch/persistence/repository/naming.rb +0 -115
  57. data/lib/rails/generators/elasticsearch/model/model_generator.rb +0 -21
  58. data/lib/rails/generators/elasticsearch/model/templates/model.rb.tt +0 -9
  59. data/lib/rails/generators/elasticsearch_generator.rb +0 -2
  60. data/test/integration/model/model_basic_test.rb +0 -233
  61. data/test/integration/repository/custom_class_test.rb +0 -85
  62. data/test/integration/repository/customized_class_test.rb +0 -82
  63. data/test/integration/repository/default_class_test.rb +0 -116
  64. data/test/integration/repository/virtus_model_test.rb +0 -118
  65. data/test/test_helper.rb +0 -55
  66. data/test/unit/model_base_test.rb +0 -72
  67. data/test/unit/model_find_test.rb +0 -153
  68. data/test/unit/model_gateway_test.rb +0 -101
  69. data/test/unit/model_rails_test.rb +0 -112
  70. data/test/unit/model_store_test.rb +0 -576
  71. data/test/unit/persistence_test.rb +0 -32
  72. data/test/unit/repository_class_test.rb +0 -51
  73. data/test/unit/repository_client_test.rb +0 -32
  74. data/test/unit/repository_find_test.rb +0 -388
  75. data/test/unit/repository_indexing_test.rb +0 -37
  76. data/test/unit/repository_module_test.rb +0 -146
  77. data/test/unit/repository_naming_test.rb +0 -146
  78. data/test/unit/repository_response_results_test.rb +0 -98
  79. data/test/unit/repository_search_test.rb +0 -117
  80. data/test/unit/repository_serialize_test.rb +0 -57
  81. data/test/unit/repository_store_test.rb +0 -303
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 16991935a6a92bf65079ff6dcfe91e1893846421
4
- data.tar.gz: 6d831835f08f046e32f4255e0f2eb91ee25ce100
2
+ SHA256:
3
+ metadata.gz: 1d37ced5f5d7a21a94d37e5186a242b5c89f7dffa4503b5a286790061dfdf412
4
+ data.tar.gz: 412720f0597d5bd725e57cccffc17be2cef3242e33bd5720166b60af246c83e5
5
5
  SHA512:
6
- metadata.gz: fc4df80b6eda6510e42c7a4d5f9111328efd1ca6aede2053d342061b3b2bb5b675fd326168b9252528cc86bcf62fe6e369433467b8b98410d940fdeba335466e
7
- data.tar.gz: dbce37f333f0b0becb3e62b0907305a325e4f36518e466891ff81e66832ed84a0fad6fb1a87e1c7caec9521634c06d2092818aa42a2ae994c15471a6b7109d17
6
+ metadata.gz: 941fc4d03706ebe777ae7b3d55490962a3880f9d840588b58f803b45fda482574b94639db8c7fe2e639909c68c0206da6bdf12f85a66cd1ef63c64ef3c197ae7
7
+ data.tar.gz: adcdc8229548e59497afb2c0af4c09d50a3a75ed42f1a3406655ad4e577bf7c15a1c4177b9e7dc6d06db1918f87f76eaa589d7ce2637317710840780bc879780
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --tty
2
+ --colour
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 and ActiveRecord patterns.
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: '5.x'
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 two different patterns for adding persistence to your Ruby objects:
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
- repository = Elasticsearch::Persistence::Repository.new
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/note/1 [status:201, request:0.210s, query:n/a]
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/_all/1 [status:200, request:0.003s, query:n/a]
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/note/1 [status:200, request:0.014s, query:n/a]
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
- You can use the default repository class, or include the module in your own. Let's review it in detail.
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
- #### The Default Class
127
+ #### Basic Repository mixin
127
128
 
128
- For simple cases, you can use the default, bundled repository class, and configure/customize it:
129
+ For simple cases, you can just include the Elasticsearch::Persistence::Repository mixin to your class:
129
130
 
130
131
  ```ruby
131
- repository = Elasticsearch::Persistence::Repository.new do
132
- # Configure the Elasticsearch client
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 KICKING IN... *****"
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/my_note/1
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 KICKING IN... *****
180
+ # ***** CUSTOM DESERIALIZE LOGIC... *****
189
181
  <Note:0x007f9bd782b7a0 @attributes={... "my_special_key"=>"my_special_stuff"}>
190
182
  ```
191
183
 
192
- #### A Custom Class
184
+ #### The DSL mixin
193
185
 
194
- In most cases, though, you'll want to use a custom class for the repository, so let's do that:
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
- def initialize(options={})
203
- index options[:index] || 'notes'
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: 'no'
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
- Include the `Elasticsearch::Persistence::Repository` module to add the repository methods into the class.
226
+ You can create an instance of this custom class and get each of the configurations.
236
227
 
237
- You can customize the repository in the familiar way, by calling the DSL-like methods.
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
- You can implement a custom initializer for your repository, add complex logic in its
240
- class and instance methods -- in general, have all the freedom of a standard Ruby class.
234
+ ```
241
235
 
242
- ```ruby
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
- # Configure the repository instance
246
- repository.index = 'notes_development'
247
- repository.client.transport.logger.formatter = proc { |s, d, p, m| "\e[2m# #{m}\n\e[0m" }
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! force: true
243
+ repository.create_index!(force: true)
250
244
 
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
- # PUT http://localhost:9200/notes_development/note/1
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/note/1
251
+ # GET http://localhost:9200/notes_development/_doc/1
258
252
  # < {... "_source" : { ... "image":"Li4uIEJJTkFSWSBEQVRBIC4uLg==\n"}}
259
253
  # => ... BINARY DATA ...
260
254
  ```
261
255
 
262
- #### Methods Provided by the Repository
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
- repository.client = Elasticsearch::Client.new url: 'http://search.server.org'
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 `index` method specifies the Elasticsearch index to use for storage, lookup and search
277
- (when not set, the value is inferred from the repository class name):
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.index = 'notes_development'
326
+ repository = NoteRepository.new(document_type: 'note')
327
+ repository.document_type
328
+ # => 'note'
329
+
281
330
  ```
282
331
 
283
- The `type` method specifies the Elasticsearch document type to use for storage, lookup and search
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
- repository.type = 'my_note'
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 (when not set, the value is inferred from the
292
- document `_type` as fetched from Elasticsearch):
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
- repository.klass = MyNote
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 passing it
319
- to the storage, and the initialization procedure when loading it from the storage:
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"=>"my_note", "_id"=>"1", "_version"=>1, "created"=>true}
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"=>"note", "_id"=>"1", "_version"=>2}
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"=>"note", "_id"=>"1", "_version"=>3}
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/my_note/_search?q=fox
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/my_note/_search
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 `Elasticsearch::Persistence::Model` module provides an implementation of the
449
- active record [pattern](http://www.martinfowler.com/eaaCatalog/activeRecord.html),
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