elasticsearch-persistence 5.1.0 → 6.0.0.pre

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +2 -0
  3. data/Gemfile +9 -0
  4. data/README.md +164 -323
  5. data/Rakefile +8 -8
  6. data/elasticsearch-persistence.gemspec +4 -5
  7. data/lib/elasticsearch/persistence.rb +2 -110
  8. data/lib/elasticsearch/persistence/repository.rb +212 -53
  9. data/lib/elasticsearch/persistence/repository/dsl.rb +94 -0
  10. data/lib/elasticsearch/persistence/repository/find.rb +27 -10
  11. data/lib/elasticsearch/persistence/repository/response/results.rb +17 -5
  12. data/lib/elasticsearch/persistence/repository/search.rb +15 -4
  13. data/lib/elasticsearch/persistence/repository/serialize.rb +65 -7
  14. data/lib/elasticsearch/persistence/repository/store.rb +38 -44
  15. data/lib/elasticsearch/persistence/version.rb +1 -1
  16. data/spec/repository/find_spec.rb +179 -0
  17. data/spec/repository/response/results_spec.rb +105 -0
  18. data/spec/repository/search_spec.rb +181 -0
  19. data/spec/repository/serialize_spec.rb +53 -0
  20. data/spec/repository/store_spec.rb +327 -0
  21. data/spec/repository_spec.rb +716 -0
  22. data/spec/spec_helper.rb +28 -0
  23. metadata +25 -80
  24. data/lib/elasticsearch/persistence/client.rb +0 -51
  25. data/lib/elasticsearch/persistence/model.rb +0 -153
  26. data/lib/elasticsearch/persistence/model/base.rb +0 -87
  27. data/lib/elasticsearch/persistence/model/errors.rb +0 -8
  28. data/lib/elasticsearch/persistence/model/find.rb +0 -180
  29. data/lib/elasticsearch/persistence/model/rails.rb +0 -47
  30. data/lib/elasticsearch/persistence/model/store.rb +0 -254
  31. data/lib/elasticsearch/persistence/model/utils.rb +0 -0
  32. data/lib/elasticsearch/persistence/repository/class.rb +0 -71
  33. data/lib/elasticsearch/persistence/repository/naming.rb +0 -115
  34. data/lib/rails/generators/elasticsearch/model/model_generator.rb +0 -21
  35. data/lib/rails/generators/elasticsearch/model/templates/model.rb.tt +0 -9
  36. data/lib/rails/generators/elasticsearch_generator.rb +0 -2
  37. data/test/integration/model/model_basic_test.rb +0 -238
  38. data/test/integration/repository/custom_class_test.rb +0 -85
  39. data/test/integration/repository/customized_class_test.rb +0 -82
  40. data/test/integration/repository/default_class_test.rb +0 -116
  41. data/test/integration/repository/virtus_model_test.rb +0 -118
  42. data/test/test_helper.rb +0 -55
  43. data/test/unit/model_base_test.rb +0 -72
  44. data/test/unit/model_find_test.rb +0 -153
  45. data/test/unit/model_gateway_test.rb +0 -101
  46. data/test/unit/model_rails_test.rb +0 -112
  47. data/test/unit/model_store_test.rb +0 -576
  48. data/test/unit/persistence_test.rb +0 -32
  49. data/test/unit/repository_class_test.rb +0 -51
  50. data/test/unit/repository_client_test.rb +0 -32
  51. data/test/unit/repository_find_test.rb +0 -388
  52. data/test/unit/repository_indexing_test.rb +0 -37
  53. data/test/unit/repository_module_test.rb +0 -146
  54. data/test/unit/repository_naming_test.rb +0 -146
  55. data/test/unit/repository_response_results_test.rb +0 -98
  56. data/test/unit/repository_search_test.rb +0 -117
  57. data/test/unit/repository_serialize_test.rb +0 -57
  58. data/test/unit/repository_store_test.rb +0 -303
@@ -1,8 +0,0 @@
1
- module Elasticsearch
2
- module Persistence
3
- module Model
4
- class DocumentNotSaved < StandardError; end
5
- class DocumentNotPersisted < StandardError; end
6
- end
7
- end
8
- end
@@ -1,180 +0,0 @@
1
- module Elasticsearch
2
- module Persistence
3
- module Model
4
-
5
- module Find
6
- class SearchRequest < Elasticsearch::Model::Searching::SearchRequest
7
- def execute!
8
- klass.gateway.search(definition[:body] || definition[:q], options)
9
- end
10
- end
11
-
12
- module ClassMethods
13
-
14
- def search(query_or_definition, options={})
15
- SearchRequest.new(self, query_or_definition, options).execute!
16
- end
17
-
18
- # Returns all models (up to 10,000)
19
- #
20
- # @example Retrieve all people
21
- #
22
- # Person.all
23
- # # => [#<Person:0x007ff1d8fb04b0 ... ]
24
- #
25
- # @example Retrieve all people matching a query
26
- #
27
- # Person.all query: { match: { last_name: 'Smith' } }
28
- # # => [#<Person:0x007ff1d8fb04b0 ... ]
29
- #
30
- def all(query={ query: { match_all: {} } }, options={})
31
- query[:size] ||= 10_000
32
- search(query, options)
33
- end
34
-
35
- # Returns the number of models
36
- #
37
- # @example Return the count of all models
38
- #
39
- # Person.count
40
- # # => 2
41
- #
42
- # @example Return the count of models matching a simple query
43
- #
44
- # Person.count('fox or dog')
45
- # # => 1
46
- #
47
- # @example Return the count of models matching a query in the Elasticsearch DSL
48
- #
49
- # Person.search(query: { match: { title: 'fox dog' } })
50
- # # => 1
51
- #
52
- # @return [Integer]
53
- #
54
- def count(query_or_definition=nil, options={})
55
- gateway.count( query_or_definition, options )
56
- end
57
-
58
- # Returns all models efficiently via the Elasticsearch's scroll API
59
- #
60
- # You can restrict the models being returned with a query.
61
- #
62
- # The {http://rubydoc.info/gems/elasticsearch-api/Elasticsearch/API/Actions#search-instance_method Search API}
63
- # options are passed to the search method as parameters, all remaining options are passed
64
- # as the `:body` parameter.
65
- #
66
- # The full {Persistence::Repository::Response::Results} instance is yielded to the passed
67
- # block in each batch, so you can access any of its properties; calling `to_a` will
68
- # convert the object to an Array of model instances.
69
- #
70
- # @example Return all models in batches of 20 x number of primary shards
71
- #
72
- # Person.find_in_batches { |batch| puts batch.map(&:name) }
73
- #
74
- # @example Return all models in batches of 100 x number of primary shards
75
- #
76
- # Person.find_in_batches(size: 100) { |batch| puts batch.map(&:name) }
77
- #
78
- # @example Return all models matching a specific query
79
- #
80
- # Person.find_in_batches(query: { match: { name: 'test' } }) { |batch| puts batch.map(&:name) }
81
- #
82
- # @example Return all models, fetching only the `name` attribute from Elasticsearch
83
- #
84
- # Person.find_in_batches( _source_include: 'name') { |_| puts _.response.hits.hits.map(&:to_hash) }
85
- #
86
- # @example Leave out the block to return an Enumerator instance
87
- #
88
- # Person.find_in_batches(size: 100).map { |batch| batch.size }
89
- # # => [100, 100, 100, ... ]
90
- #
91
- # @return [String,Enumerator] The `scroll_id` for the request or Enumerator when the block is not passed
92
- #
93
- def find_in_batches(options={}, &block)
94
- return to_enum(:find_in_batches, options) unless block_given?
95
-
96
- search_params = options.extract!(
97
- :index,
98
- :type,
99
- :scroll,
100
- :size,
101
- :explain,
102
- :ignore_indices,
103
- :ignore_unavailable,
104
- :allow_no_indices,
105
- :expand_wildcards,
106
- :preference,
107
- :q,
108
- :routing,
109
- :source,
110
- :_source,
111
- :_source_include,
112
- :_source_exclude,
113
- :stats,
114
- :timeout)
115
-
116
- scroll = search_params.delete(:scroll) || '5m'
117
-
118
- body = options
119
-
120
- # Get the initial batch of documents and the scroll_id
121
- #
122
- response = gateway.client.search( {
123
- index: gateway.index_name,
124
- type: gateway.document_type,
125
- scroll: scroll,
126
- sort: ['_doc'],
127
- size: 20,
128
- body: body }.merge(search_params) )
129
-
130
-
131
- # Scroll the search object and break when receiving an empty array of hits
132
- #
133
- while response['hits']['hits'].any? do
134
- yield Repository::Response::Results.new(gateway, response)
135
-
136
- response = gateway.client.scroll( { scroll_id: response['_scroll_id'], scroll: scroll } )
137
- end
138
-
139
- return response['_scroll_id']
140
- end
141
-
142
- # Iterate effectively over models using the `find_in_batches` method.
143
- #
144
- # All the options are passed to `find_in_batches` and each result is yielded to the passed block.
145
- #
146
- # @example Print out the people's names by scrolling through the index
147
- #
148
- # Person.find_each { |person| puts person.name }
149
- #
150
- # # # GET http://localhost:9200/people/person/_search?scroll=5m&size=20
151
- # # # GET http://localhost:9200/_search/scroll?scroll=5m&scroll_id=c2Nhbj...
152
- # # Test 0
153
- # # Test 1
154
- # # Test 2
155
- # # ...
156
- # # # GET http://localhost:9200/_search/scroll?scroll=5m&scroll_id=c2Nhbj...
157
- # # Test 20
158
- # # Test 21
159
- # # Test 22
160
- #
161
- # @example Leave out the block to return an Enumerator instance
162
- #
163
- # Person.find_each.select { |person| person.name =~ /John/ }
164
- # # => => [#<Person {id: "NkltJP5vRxqk9_RMP7SU8Q", name: "John Smith", ...}>]
165
- #
166
- # @return [String,Enumerator] The `scroll_id` for the request or Enumerator when the block is not passed
167
- #
168
- def find_each(options = {})
169
- return to_enum(:find_each, options) unless block_given?
170
-
171
- find_in_batches(options) do |batch|
172
- batch.each { |result| yield result }
173
- end
174
- end
175
- end
176
- end
177
-
178
- end
179
- end
180
- end
@@ -1,47 +0,0 @@
1
- module Elasticsearch
2
- module Persistence
3
- module Model
4
-
5
- # Make the `Persistence::Model` models compatible with Ruby On Rails applications
6
- #
7
- module Rails
8
- def self.included(base)
9
- base.class_eval do
10
-
11
- def initialize(attributes={})
12
- super(__convert_rails_dates(attributes))
13
- end
14
-
15
- def update(attributes={}, options={})
16
- super(__convert_rails_dates(attributes), options)
17
- end
18
- end
19
- end
20
-
21
- # Decorates the passed in `attributes` so they extract the date & time values from Rails forms
22
- #
23
- # @example Correctly combine the date and time to a datetime string
24
- #
25
- # params = { "published_on(1i)"=>"2014",
26
- # "published_on(2i)"=>"1",
27
- # "published_on(3i)"=>"1",
28
- # "published_on(4i)"=>"12",
29
- # "published_on(5i)"=>"00"
30
- # }
31
- # MyRailsModel.new(params).published_on.iso8601
32
- # # => "2014-01-01T12:00:00+00:00"
33
- #
34
- def __convert_rails_dates(attributes={})
35
- day = attributes.select { |p| p =~ /\([1-3]/ }.reduce({}) { |sum, item| (sum[item.first.gsub(/\(.+\)/, '')] ||= '' )<< item.last+'-'; sum }
36
- time = attributes.select { |p| p =~ /\([4-6]/ }.reduce({}) { |sum, item| (sum[item.first.gsub(/\(.+\)/, '')] ||= '' )<< item.last+':'; sum }
37
- unless day.empty?
38
- attributes.update day.reduce({}) { |sum, item| sum[item.first] = item.last; sum[item.first] += ' ' + time[item.first] unless time.empty?; sum }
39
- end
40
-
41
- return attributes
42
- end; module_function :__convert_rails_dates
43
- end
44
-
45
- end
46
- end
47
- end
@@ -1,254 +0,0 @@
1
- module Elasticsearch
2
- module Persistence
3
- module Model
4
-
5
- # This module contains the storage related features of {Elasticsearch::Persistence::Model}
6
- #
7
- module Store
8
- module ClassMethods #:nodoc:
9
-
10
- # Creates a class instance, saves it, if validations pass, and returns it
11
- #
12
- # @example Create a new person
13
- #
14
- # Person.create name: 'John Smith'
15
- # # => #<Person:0x007f889e302b30 ... @id="bG7yQDAXRhCi3ZfVcx6oAA", @name="John Smith" ...>
16
- #
17
- # @return [Object] The model instance
18
- #
19
- def create(attributes, options={})
20
- object = self.new(attributes)
21
- object.run_callbacks :create do
22
- object.save(options)
23
- object
24
- end
25
- end
26
- end
27
-
28
- module InstanceMethods
29
-
30
- # Saves the model (if validations pass) and returns the response (or `false`)
31
- #
32
- # @example Save a valid model instance
33
- #
34
- # p = Person.new(name: 'John')
35
- # p.save
36
- # => {"_index"=>"people", ... "_id"=>"RzFSXFR0R8u1CZIWNs2Gvg", "_version"=>1, "created"=>true}
37
- #
38
- # @example Save an invalid model instance
39
- #
40
- # p = Person.new(name: nil)
41
- # p.save
42
- # # => false
43
- #
44
- # @return [Hash,FalseClass] The Elasticsearch response as a Hash or `false`
45
- #
46
- def save(options={})
47
- unless options.delete(:validate) == false
48
- return false unless valid?
49
- end
50
-
51
- run_callbacks :save do
52
- options.update id: self.id
53
- options.update index: self._index if self._index
54
- options.update type: self._type if self._type
55
-
56
- self[:updated_at] = Time.now.utc
57
-
58
- response = self.class.gateway.save(self, options)
59
-
60
- @_id = response['_id']
61
- @_index = response['_index']
62
- @_type = response['_type']
63
- @_version = response['_version']
64
- @persisted = true
65
-
66
- response
67
- end
68
- end
69
-
70
- # Deletes the model from Elasticsearch (if it's persisted), freezes it, and returns the response
71
- #
72
- # @example Delete a model instance
73
- #
74
- # p.destroy
75
- # => {"_index"=>"people", ... "_id"=>"RzFSXFR0R8u1CZIWNs2Gvg", "_version"=>2 ...}
76
- #
77
- # @return [Hash] The Elasticsearch response as a Hash
78
- #
79
- def destroy(options={})
80
- raise DocumentNotPersisted, "Object not persisted: #{self.inspect}" unless persisted?
81
-
82
- run_callbacks :destroy do
83
- options.update index: self._index if self._index
84
- options.update type: self._type if self._type
85
-
86
- response = self.class.gateway.delete(self.id, options)
87
-
88
- @destroyed = true
89
- @persisted = false
90
- self.freeze
91
- response
92
- end
93
- end; alias :delete :destroy
94
-
95
- # Updates the model (via Elasticsearch's "Update" API) and returns the response
96
- #
97
- # @example Update a model with partial attributes
98
- #
99
- # p.update name: 'UPDATED'
100
- # => {"_index"=>"people", ... "_version"=>2}
101
- #
102
- # @example Pass a version for concurrency control
103
- #
104
- # p.update( { name: 'UPDATED' }, { version: 2 } )
105
- # => {"_index"=>"people", ... "_version"=>3}
106
- #
107
- # @example An exception is raised when the version doesn't match
108
- #
109
- # p.update( { name: 'UPDATED' }, { version: 2 } )
110
- # => Elasticsearch::Transport::Transport::Errors::Conflict: [409] {"error" ... }
111
- #
112
- # @return [Hash] The Elasticsearch response as a Hash
113
- #
114
- def update(attributes={}, options={})
115
- unless options.delete(:validate) == false
116
- return false unless valid?
117
- end
118
- raise DocumentNotPersisted, "Object not persisted: #{self.inspect}" unless persisted?
119
-
120
- run_callbacks :update do
121
- options.update index: self._index if self._index
122
- options.update type: self._type if self._type
123
-
124
- attributes.update( { updated_at: Time.now.utc } )
125
- response = self.class.gateway.update(self.id, { doc: attributes}.merge(options))
126
-
127
- self.attributes = self.attributes.merge(attributes)
128
- @_index = response['_index']
129
- @_type = response['_type']
130
- @_version = response['_version']
131
-
132
- response
133
- end
134
- end; alias :update_attributes :update
135
-
136
- # Increments a numeric attribute (via Elasticsearch's "Update" API) and returns the response
137
- #
138
- # @example Increment the `salary` attribute by 1
139
- #
140
- # p.increment :salary
141
- #
142
- # @example Increment the `salary` attribute by 100
143
- #
144
- # p.increment :salary, 100
145
- #
146
- # @return [Hash] The Elasticsearch response as a Hash
147
- #
148
- def increment(attribute, value=1, options={})
149
- raise DocumentNotPersisted, "Object not persisted: #{self.inspect}" unless persisted?
150
-
151
- options.update index: self._index if self._index
152
- options.update type: self._type if self._type
153
-
154
- response = self.class.gateway.update(self.id, { script: "ctx._source.#{attribute} += #{value}"}.merge(options))
155
-
156
- self[attribute] += value
157
-
158
- @_index = response['_index']
159
- @_type = response['_type']
160
- @_version = response['_version']
161
-
162
- response
163
- end
164
-
165
- # Decrements a numeric attribute (via Elasticsearch's "Update" API) and returns the response
166
- #
167
- # @example Decrement the `salary` attribute by 1
168
- #
169
- # p.decrement :salary
170
- #
171
- # @example Decrement the `salary` attribute by 100
172
- #
173
- # p.decrement :salary, 100
174
- #
175
- # @return [Hash] The Elasticsearch response as a Hash
176
- #
177
- def decrement(attribute, value=1, options={})
178
- raise DocumentNotPersisted, "Object not persisted: #{self.inspect}" unless persisted?
179
-
180
- options.update index: self._index if self._index
181
- options.update type: self._type if self._type
182
-
183
- response = self.class.gateway.update(self.id, { script: "ctx._source.#{attribute} = ctx._source.#{attribute} - #{value}"}.merge(options))
184
- self[attribute] -= value
185
-
186
- @_index = response['_index']
187
- @_type = response['_type']
188
- @_version = response['_version']
189
-
190
- response
191
- end
192
-
193
- # Updates the `updated_at` attribute, saves the model and returns the response
194
- #
195
- # @example Update the `updated_at` attribute (default)
196
- #
197
- # p.touch
198
- #
199
- # @example Update a custom attribute: `saved_on`
200
- #
201
- # p.touch :saved_on
202
- #
203
- # @return [Hash] The Elasticsearch response as a Hash
204
- #
205
- def touch(attribute=:updated_at, options={})
206
- raise DocumentNotPersisted, "Object not persisted: #{self.inspect}" unless persisted?
207
- raise ArgumentError, "Object does not have '#{attribute}' attribute" unless respond_to?(attribute)
208
-
209
- run_callbacks :touch do
210
- options.update index: self._index if self._index
211
- options.update type: self._type if self._type
212
-
213
- value = Time.now.utc
214
- response = self.class.gateway.update(self.id, { doc: { attribute => value.iso8601 }}.merge(options))
215
-
216
- self[attribute] = value
217
-
218
- @_index = response['_index']
219
- @_type = response['_type']
220
- @_version = response['_version']
221
-
222
- response
223
- end
224
- end
225
-
226
- # Returns true when the model has been destroyed, false otherwise
227
- #
228
- # @return [TrueClass,FalseClass]
229
- #
230
- def destroyed?
231
- !!@destroyed
232
- end
233
-
234
- # Returns true when the model has been already saved to the database, false otherwise
235
- #
236
- # @return [TrueClass,FalseClass]
237
- #
238
- def persisted?
239
- !!@persisted && !destroyed?
240
- end
241
-
242
- # Returns true when the model has not been saved yet, false otherwise
243
- #
244
- # @return [TrueClass,FalseClass]
245
- #
246
- def new_record?
247
- !persisted? && !destroyed?
248
- end
249
- end
250
- end
251
-
252
- end
253
- end
254
- end