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 +4 -4
- data/lib/elasticsearch/model/adapters/active_record.rb +4 -1
- data/lib/elasticsearch/model/adapters/mongoid.rb +10 -3
- data/lib/elasticsearch/model/importing.rb +1 -1
- data/lib/elasticsearch/model/response.rb +11 -10
- data/lib/elasticsearch/model/response/aggregations.rb +1 -1
- data/lib/elasticsearch/model/response/base.rb +3 -2
- data/lib/elasticsearch/model/response/suggestions.rb +1 -1
- data/lib/elasticsearch/model/version.rb +1 -1
- data/test/integration/active_record_basic_test.rb +2 -0
- data/test/integration/active_record_import_test.rb +65 -12
- data/test/integration/mongoid_basic_test.rb +64 -0
- data/test/unit/adapter_mongoid_test.rb +58 -1
- data/test/unit/importing_test.rb +21 -0
- data/test/unit/response_results_test.rb +3 -0
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 726d16096fd6ca31266bab9b42c4f878cba6942eada1649de7fbf946af2575f0
|
4
|
+
data.tar.gz: 8ce1faffcdda9dae38ff2beddab31f0b161853c4fd9919b699cdb5e5a27f2baa
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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]
|
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
|
-
|
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
|
|
@@ -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
|
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 ||=
|
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
|
-
|
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
|
-
|
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(
|
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(
|
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(
|
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
|
@@ -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
|
-
@
|
15
|
+
@raw_response = response
|
16
|
+
@response = response
|
16
17
|
end
|
17
18
|
|
18
19
|
# @abstract Implement this method in specific class
|
@@ -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
|
-
|
14
|
-
|
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
|
-
|
19
|
+
scope :popular, -> { where('views >= 50') }
|
17
20
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
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
|
data/test/unit/importing_test.rb
CHANGED
@@ -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)
|
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
|
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-
|
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:
|
433
|
+
version: '0'
|
434
434
|
requirements: []
|
435
435
|
rubyforge_project:
|
436
436
|
rubygems_version: 2.7.7
|