elasticsearch-persistence 5.1.0 → 6.0.0.pre

Sign up to get free protection for your applications and to get access to all the features.
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