elasticsearch-model 6.0.0.pre → 6.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 42c06d477404fd7b991c3af477e595f2e9ec662167cf264745ab43dff91d5e19
4
- data.tar.gz: 58c05971c219d2cd14a0c91acd1675d20699e55f4944afc5a71d10acb60d3aaa
3
+ metadata.gz: 726d16096fd6ca31266bab9b42c4f878cba6942eada1649de7fbf946af2575f0
4
+ data.tar.gz: 8ce1faffcdda9dae38ff2beddab31f0b161853c4fd9919b699cdb5e5a27f2baa
5
5
  SHA512:
6
- metadata.gz: 5d2b3ffc337dd8083cd4dcb6ea74555add7f427fbd7bf16734d76e46d0e4ad110f1c4f581ec1a23bb6dbb7540c131a9d08b659704e4c796ffa06d0ccc6474a0c
7
- data.tar.gz: 3162fbfac4daa561507929333235124f99828c4c845f1bfbe19e18c853b06a8d2210559dc44de93184524e2fe2c09b5e96e7c6186691c2121339a7e8923ff94d
6
+ metadata.gz: 63904bd61ec508918ee8a0d6cbe7cd089dc0b6df7c6f4d8ea1fef7538b2aea3a88962724e6a5fc486f5ec10146350e4176655b3fbeec868a66b6b3e00e767307
7
+ data.tar.gz: 36fccab94cf1f3cdfa599ad0a6d74cecc1b503c80da296e73381f3b3f2130244d57a9b51fabaff5e624e7f0955442e3b0e67d7006b6b94f705fdc4249252ac12
@@ -56,7 +56,10 @@ module Elasticsearch
56
56
  # Redefine the `to_a` method to the original one
57
57
  #
58
58
  sql_records.instance_exec do
59
- define_singleton_method(:to_a) do
59
+ ar_records_method_name = :to_a
60
+ ar_records_method_name = :records if defined?(::ActiveRecord) && ::ActiveRecord::VERSION::MAJOR >= 5
61
+
62
+ define_singleton_method(ar_records_method_name) do
60
63
  if defined?(::ActiveRecord) && ::ActiveRecord::VERSION::MAJOR >= 4
61
64
  self.load
62
65
  else
@@ -63,10 +63,17 @@ module Elasticsearch
63
63
  # @see https://github.com/karmi/retire/pull/724
64
64
  #
65
65
  def __find_in_batches(options={}, &block)
66
- options[:batch_size] ||= 1_000
66
+ batch_size = options[:batch_size] || 1_000
67
+ query = options[:query]
68
+ named_scope = options[:scope]
69
+ preprocess = options[:preprocess]
70
+
71
+ scope = all
72
+ scope = scope.send(named_scope) if named_scope
73
+ scope = query.is_a?(Proc) ? scope.class_exec(&query) : scope.where(query) if query
67
74
 
68
- all.no_timeout.each_slice(options[:batch_size]) do |items|
69
- yield items
75
+ scope.no_timeout.each_slice(batch_size) do |items|
76
+ yield (preprocess ? self.__send__(preprocess, items) : items)
70
77
  end
71
78
  end
72
79
 
@@ -130,7 +130,7 @@ module Elasticsearch
130
130
  errors += response['items'].select { |k, v| k.values.first['error'] }
131
131
  end
132
132
 
133
- self.refresh_index! if refresh
133
+ self.refresh_index! index: target_index if refresh
134
134
 
135
135
  case return_value
136
136
  when 'errors'
@@ -10,8 +10,7 @@ module Elasticsearch
10
10
  # Implements Enumerable and forwards its methods to the {#results} object.
11
11
  #
12
12
  class Response
13
- attr_reader :klass, :search, :response,
14
- :took, :timed_out, :shards
13
+ attr_reader :klass, :search
15
14
 
16
15
  include Enumerable
17
16
 
@@ -27,9 +26,7 @@ module Elasticsearch
27
26
  # @return [Hash]
28
27
  #
29
28
  def response
30
- @response ||= begin
31
- HashWrapper.new(search.execute!)
32
- end
29
+ @response ||= HashWrapper.new(search.execute!)
33
30
  end
34
31
 
35
32
  # Returns the collection of "hits" from Elasticsearch
@@ -51,31 +48,35 @@ module Elasticsearch
51
48
  # Returns the "took" time
52
49
  #
53
50
  def took
54
- response['took']
51
+ raw_response['took']
55
52
  end
56
53
 
57
54
  # Returns whether the response timed out
58
55
  #
59
56
  def timed_out
60
- response['timed_out']
57
+ raw_response['timed_out']
61
58
  end
62
59
 
63
60
  # Returns the statistics on shards
64
61
  #
65
62
  def shards
66
- HashWrapper.new(response['_shards'])
63
+ @shards ||= HashWrapper.new(raw_response['_shards'])
67
64
  end
68
65
 
69
66
  # Returns a Hashie::Mash of the aggregations
70
67
  #
71
68
  def aggregations
72
- Aggregations.new(response['aggregations'])
69
+ @aggregations ||= Aggregations.new(raw_response['aggregations'])
73
70
  end
74
71
 
75
72
  # Returns a Hashie::Mash of the suggestions
76
73
  #
77
74
  def suggestions
78
- Suggestions.new(response['suggest'])
75
+ @suggestions ||= Suggestions.new(raw_response['suggest'])
76
+ end
77
+
78
+ def raw_response
79
+ @raw_response ||= search.execute!
79
80
  end
80
81
  end
81
82
  end
@@ -2,7 +2,7 @@ module Elasticsearch
2
2
  module Model
3
3
  module Response
4
4
 
5
- class Aggregations < Hashie::Mash
5
+ class Aggregations < HashWrapper
6
6
  disable_warnings if respond_to?(:disable_warnings)
7
7
 
8
8
  def initialize(attributes={})
@@ -4,7 +4,7 @@ module Elasticsearch
4
4
  # Common funtionality for classes in the {Elasticsearch::Model::Response} module
5
5
  #
6
6
  module Base
7
- attr_reader :klass, :response
7
+ attr_reader :klass, :response, :raw_response
8
8
 
9
9
  # @param klass [Class] The name of the model class
10
10
  # @param response [Hash] The full response returned from Elasticsearch client
@@ -12,7 +12,8 @@ module Elasticsearch
12
12
  #
13
13
  def initialize(klass, response, options={})
14
14
  @klass = klass
15
- @response = response
15
+ @raw_response = response
16
+ @response = response
16
17
  end
17
18
 
18
19
  # @abstract Implement this method in specific class
@@ -2,7 +2,7 @@ module Elasticsearch
2
2
  module Model
3
3
  module Response
4
4
 
5
- class Suggestions < Hashie::Mash
5
+ class Suggestions < HashWrapper
6
6
  disable_warnings if respond_to?(:disable_warnings)
7
7
 
8
8
  def terms
@@ -1,5 +1,5 @@
1
1
  module Elasticsearch
2
2
  module Model
3
- VERSION = '6.0.0.pre'
3
+ VERSION = '6.0.0'
4
4
  end
5
5
  end
@@ -223,8 +223,10 @@ module Elasticsearch
223
223
 
224
224
  if defined?(::ActiveRecord) && ::ActiveRecord::VERSION::MAJOR >= 4
225
225
  assert_equal 'Testing Coding', response.records.order(title: :desc).first.title
226
+ assert_equal 'Testing Coding', response.records.order(title: :desc)[0].title
226
227
  else
227
228
  assert_equal 'Testing Coding', response.records.order('title DESC').first.title
229
+ assert_equal 'Testing Coding', response.records.order('title DESC')[0].title
228
230
  end
229
231
  end
230
232
 
@@ -10,21 +10,22 @@ module Elasticsearch
10
10
  module Model
11
11
  class ActiveRecordImportIntegrationTest < Elasticsearch::Test::IntegrationTestCase
12
12
 
13
- class ::ImportArticle < ActiveRecord::Base
14
- include Elasticsearch::Model
13
+ context "ActiveRecord importing" do
14
+ setup do
15
+ Object.send(:remove_const, :ImportArticle) if defined?(ImportArticle)
16
+ class ::ImportArticle < ActiveRecord::Base
17
+ include Elasticsearch::Model
15
18
 
16
- scope :popular, -> { where('views >= 50') }
19
+ scope :popular, -> { where('views >= 50') }
17
20
 
18
- mapping do
19
- indexes :title, type: 'text'
20
- indexes :views, type: 'integer'
21
- indexes :numeric, type: 'integer'
22
- indexes :created_at, type: 'date'
23
- end
24
- end
21
+ mapping do
22
+ indexes :title, type: 'text'
23
+ indexes :views, type: 'integer'
24
+ indexes :numeric, type: 'integer'
25
+ indexes :created_at, type: 'date'
26
+ end
27
+ end
25
28
 
26
- context "ActiveRecord importing" do
27
- setup do
28
29
  ActiveRecord::Schema.define(:version => 1) do
29
30
  create_table :import_articles do |t|
30
31
  t.string :title
@@ -110,6 +111,58 @@ module Elasticsearch
110
111
  end
111
112
  end
112
113
 
114
+ context "ActiveRecord importing when the model has a default scope" do
115
+
116
+ setup do
117
+ Object.send(:remove_const, :ImportArticle) if defined?(ImportArticle)
118
+ class ::ImportArticle < ActiveRecord::Base
119
+ include Elasticsearch::Model
120
+
121
+ default_scope { where('views >= 8') }
122
+
123
+ mapping do
124
+ indexes :title, type: 'text'
125
+ indexes :views, type: 'integer'
126
+ indexes :numeric, type: 'integer'
127
+ indexes :created_at, type: 'date'
128
+ end
129
+ end
130
+
131
+ ActiveRecord::Schema.define(:version => 1) do
132
+ create_table :import_articles do |t|
133
+ t.string :title
134
+ t.integer :views
135
+ t.string :numeric # For the sake of invalid data sent to Elasticsearch
136
+ t.datetime :created_at, :default => 'NOW()'
137
+ end
138
+ end
139
+
140
+ ImportArticle.delete_all
141
+ ImportArticle.__elasticsearch__.delete_index! force: true
142
+ ImportArticle.__elasticsearch__.create_index! force: true
143
+ ImportArticle.__elasticsearch__.client.cluster.health wait_for_status: 'yellow'
144
+
145
+ 10.times { |i| ImportArticle.create! title: "Test #{i}", views: i }
146
+ end
147
+
148
+ should "import only documents from the default scope" do
149
+ assert_equal 2, ImportArticle.count
150
+
151
+ assert_equal 0, ImportArticle.import
152
+
153
+ ImportArticle.__elasticsearch__.refresh_index!
154
+ assert_equal 2, ImportArticle.search('*').results.total
155
+ end
156
+
157
+ should "import only documents from a specific query combined with the default scope" do
158
+ assert_equal 2, ImportArticle.count
159
+
160
+ assert_equal 0, ImportArticle.import(query: -> { where("title = 'Test 9'") })
161
+
162
+ ImportArticle.__elasticsearch__.refresh_index!
163
+ assert_equal 1, ImportArticle.search('*').results.total
164
+ end
165
+ end
113
166
  end
114
167
  end
115
168
  end
@@ -167,6 +167,70 @@ if MongoDB.available?
167
167
  assert response.results.any?, "Search has not returned results: #{response.to_a}"
168
168
  end
169
169
  end
170
+
171
+ context "importing when the model has a default scope" do
172
+ class ::MongoidArticleWithDefaultScope
173
+ include Mongoid::Document
174
+ include Elasticsearch::Model
175
+
176
+ default_scope -> { where(status: 'active') }
177
+
178
+ field :id, type: String
179
+ field :title, type: String
180
+ field :status, type: String, default: 'active'
181
+
182
+ attr_accessible :title if respond_to? :attr_accessible
183
+ attr_accessible :status if respond_to? :attr_accessible
184
+
185
+ settings index: { number_of_shards: 1, number_of_replicas: 0 } do
186
+ mapping do
187
+ indexes :title, type: 'text', analyzer: 'snowball'
188
+ indexes :status, type: 'text'
189
+ indexes :created_at, type: 'date'
190
+ end
191
+ end
192
+
193
+ def as_indexed_json(options={})
194
+ as_json(except: [:id, :_id])
195
+ end
196
+ end
197
+
198
+ setup do
199
+ Elasticsearch::Model::Adapter.register \
200
+ Elasticsearch::Model::Adapter::Mongoid,
201
+ lambda { |klass| !!defined?(::Mongoid::Document) && klass.respond_to?(:ancestors) && klass.ancestors.include?(::Mongoid::Document) }
202
+
203
+ MongoidArticleWithDefaultScope.__elasticsearch__.create_index! force: true
204
+
205
+ MongoidArticleWithDefaultScope.delete_all
206
+
207
+ MongoidArticleWithDefaultScope.create! title: 'Test'
208
+ MongoidArticleWithDefaultScope.create! title: 'Testing Coding'
209
+ MongoidArticleWithDefaultScope.create! title: 'Coding'
210
+ MongoidArticleWithDefaultScope.create! title: 'Test legacy code', status: 'removed'
211
+
212
+ MongoidArticleWithDefaultScope.__elasticsearch__.refresh_index!
213
+ MongoidArticleWithDefaultScope.__elasticsearch__.client.cluster.health wait_for_status: 'yellow'
214
+ end
215
+
216
+ should "import only documents from the default scope" do
217
+ assert_equal 3, MongoidArticleWithDefaultScope.count
218
+
219
+ assert_equal 0, MongoidArticleWithDefaultScope.import
220
+
221
+ MongoidArticleWithDefaultScope.__elasticsearch__.refresh_index!
222
+ assert_equal 3, MongoidArticleWithDefaultScope.search('*').results.total
223
+ end
224
+
225
+ should "import only documents from a specific query combined with the default scope" do
226
+ assert_equal 3, MongoidArticleWithDefaultScope.count
227
+
228
+ assert_equal 0, MongoidArticleWithDefaultScope.import(query: -> { where(title: /^Test/) })
229
+
230
+ MongoidArticleWithDefaultScope.__elasticsearch__.refresh_index!
231
+ assert_equal 2, MongoidArticleWithDefaultScope.search('*').results.total
232
+ end
233
+ end
170
234
  end
171
235
 
172
236
  end
@@ -98,7 +98,64 @@ class Elasticsearch::Model::AdapterMongoidTest < Test::Unit::TestCase
98
98
  assert_equal @transform.call(model), { index: { _id: "1", data: {} } }
99
99
  end
100
100
  end
101
- end
102
101
 
102
+ should "limit the relation to a specific scope" do
103
+ relation = mock()
104
+ relation.stubs(:no_timeout).returns(relation)
105
+ relation.expects(:published).returns(relation)
106
+ relation.expects(:each_slice).returns([])
107
+ DummyClassForMongoid.expects(:all).returns(relation)
108
+
109
+ DummyClassForMongoid.__send__ :extend, Elasticsearch::Model::Adapter::Mongoid::Importing
110
+ DummyClassForMongoid.__find_in_batches(scope: :published) do; end
111
+ end
112
+
113
+ context "when limit the relation with proc" do
114
+ setup do
115
+ @query = Proc.new { where(color: "red") }
116
+ end
117
+ should "query with a specific criteria" do
118
+ relation = mock()
119
+ relation.stubs(:no_timeout).returns(relation)
120
+ relation.expects(:class_exec).returns(relation)
121
+ relation.expects(:each_slice).returns([])
122
+ DummyClassForMongoid.expects(:all).returns(relation)
123
+
124
+ DummyClassForMongoid.__find_in_batches(query: @query) do; end
125
+ end
126
+ end
127
+
128
+ context "when limit the relation with hash" do
129
+ setup do
130
+ @query = { color: "red" }
131
+ end
132
+ should "query with a specific criteria" do
133
+ relation = mock()
134
+ relation.stubs(:no_timeout).returns(relation)
135
+ relation.expects(:where).with(@query).returns(relation)
136
+ relation.expects(:each_slice).returns([])
137
+ DummyClassForMongoid.expects(:all).returns(relation)
138
+
139
+ DummyClassForMongoid.__find_in_batches(query: @query) do; end
140
+ end
141
+ end
142
+
143
+ should "preprocess the batch if option provided" do
144
+ class << DummyClassForMongoid
145
+ # Updates/transforms the batch while fetching it from the database
146
+ # (eg. with information from an external system)
147
+ #
148
+ def update_batch(batch)
149
+ batch.collect { |b| b.to_s + '!' }
150
+ end
151
+ end
152
+
153
+ DummyClassForMongoid.expects(:__find_in_batches).returns( [:a, :b] )
154
+
155
+ DummyClassForMongoid.__find_in_batches(preprocess: :update_batch) do |batch|
156
+ assert_same_elements ["a!", "b!"], batch
157
+ end
158
+ end
159
+ end
103
160
  end
104
161
  end
@@ -146,6 +146,27 @@ class Elasticsearch::Model::ImportingTest < Test::Unit::TestCase
146
146
  end
147
147
  end
148
148
 
149
+ context "with the refresh option" do
150
+ should "refresh the index" do
151
+ DummyImportingModel.expects(:__find_in_batches).with do |options|
152
+ assert_equal 'bar', options[:foo]
153
+ assert_nil options[:refresh]
154
+ true
155
+ end
156
+
157
+ DummyImportingModel.expects(:refresh_index!).with do |options|
158
+ assert_equal 'foo', options[:index]
159
+ true
160
+ end
161
+
162
+ DummyImportingModel.expects(:index_name).returns('foo')
163
+ DummyImportingModel.expects(:document_type).returns('foo')
164
+ DummyImportingModel.stubs(:index_exists?).returns(true)
165
+
166
+ DummyImportingModel.import refresh: true, foo: 'bar'
167
+ end
168
+ end
169
+
149
170
  should "allow passing a different index / type" do
150
171
  Elasticsearch::Model::Adapter.expects(:from_class)
151
172
  .with(DummyImportingModel)
@@ -27,5 +27,8 @@ class Elasticsearch::Model::ResultsTest < Test::Unit::TestCase
27
27
  assert_equal 'bar', @results.first.foo
28
28
  end
29
29
 
30
+ should "provide access to the raw response" do
31
+ assert_equal RESPONSE, @response.raw_response
32
+ end
30
33
  end
31
34
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: elasticsearch-model
3
3
  version: !ruby/object:Gem::Version
4
- version: 6.0.0.pre
4
+ version: 6.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Karel Minarik
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-08-13 00:00:00.000000000 Z
11
+ date: 2018-09-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: elasticsearch
@@ -428,9 +428,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
428
428
  version: 1.9.3
429
429
  required_rubygems_version: !ruby/object:Gem::Requirement
430
430
  requirements:
431
- - - ">"
431
+ - - ">="
432
432
  - !ruby/object:Gem::Version
433
- version: 1.3.1
433
+ version: '0'
434
434
  requirements: []
435
435
  rubyforge_project:
436
436
  rubygems_version: 2.7.7