elastic_record 0.11.1 → 0.12.0
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.
- checksums.yaml +7 -0
- data/Gemfile +2 -1
- data/README.rdoc +37 -9
- data/elastic_record.gemspec +2 -2
- data/lib/elastic_record/callbacks.rb +1 -1
- data/lib/elastic_record/connection.rb +36 -13
- data/lib/elastic_record/index.rb +33 -4
- data/lib/elastic_record/index/configurator.rb +14 -0
- data/lib/elastic_record/index/documents.rb +21 -13
- data/lib/elastic_record/index/manage.rb +4 -9
- data/lib/elastic_record/index/mapping.rb +12 -0
- data/lib/elastic_record/index/percolator.rb +1 -0
- data/lib/elastic_record/index/settings.rb +4 -0
- data/lib/elastic_record/lucene.rb +40 -22
- data/lib/elastic_record/model.rb +8 -5
- data/lib/elastic_record/relation/batches.rb +8 -1
- data/lib/elastic_record/relation/finder_methods.rb +2 -2
- data/lib/elastic_record/relation/none.rb +3 -3
- data/lib/elastic_record/relation/search_methods.rb +33 -3
- data/lib/elastic_record/relation/value_methods.rb +1 -1
- data/lib/elastic_record/searches_many.rb +4 -0
- data/lib/elastic_record/searches_many/association.rb +25 -14
- data/lib/elastic_record/searches_many/reflection.rb +1 -1
- data/lib/elastic_record/tasks/index.rake +5 -21
- data/test/elastic_record/callbacks_test.rb +9 -0
- data/test/elastic_record/connection_test.rb +19 -1
- data/test/elastic_record/index/configurator_test.rb +18 -0
- data/test/elastic_record/index/documents_test.rb +22 -3
- data/test/elastic_record/index/manage_test.rb +7 -0
- data/test/elastic_record/index/mapping_test.rb +11 -0
- data/test/elastic_record/index/percolator_test.rb +11 -9
- data/test/elastic_record/index_test.rb +23 -6
- data/test/elastic_record/lucene_test.rb +21 -13
- data/test/elastic_record/model_test.rb +9 -9
- data/test/elastic_record/relation/batches_test.rb +8 -0
- data/test/elastic_record/relation/finder_methods_test.rb +6 -7
- data/test/elastic_record/relation/none_test.rb +3 -0
- data/test/elastic_record/relation/search_methods_test.rb +17 -41
- data/test/elastic_record/relation_test.rb +2 -0
- data/test/elastic_record/searches_many/reflection_test.rb +7 -0
- metadata +15 -19
data/lib/elastic_record/model.rb
CHANGED
@@ -9,13 +9,16 @@ module ElasticRecord
|
|
9
9
|
end
|
10
10
|
|
11
11
|
module ClassMethods
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
12
|
+
def inherited(child)
|
13
|
+
super
|
14
|
+
|
15
|
+
if child < child.base_class
|
16
|
+
child.elastic_index = elastic_index.dup
|
17
|
+
end
|
18
|
+
end
|
16
19
|
|
17
20
|
def elastic_connection
|
18
|
-
@elastic_connection ||= ElasticRecord::Connection.new(ElasticRecord::Config.servers)
|
21
|
+
@elastic_connection ||= ElasticRecord::Connection.new(ElasticRecord::Config.servers, ElasticRecord::Config.connection_options)
|
19
22
|
end
|
20
23
|
|
21
24
|
def elastic_connection=(connection)
|
@@ -8,6 +8,12 @@ module ElasticRecord
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def find_in_batches(options = {})
|
11
|
+
find_ids_in_batches(options) do |ids|
|
12
|
+
yield klass.find(ids)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def find_ids_in_batches(options = {})
|
11
17
|
scroll_keep_alive = '10m'
|
12
18
|
|
13
19
|
options = {
|
@@ -19,7 +25,7 @@ module ElasticRecord
|
|
19
25
|
scroll_id = klass.elastic_index.search(as_elastic, options)['_scroll_id']
|
20
26
|
|
21
27
|
while (hit_ids = get_scroll_hit_ids(scroll_id, scroll_keep_alive)).any?
|
22
|
-
yield
|
28
|
+
yield hit_ids
|
23
29
|
end
|
24
30
|
end
|
25
31
|
|
@@ -30,6 +36,7 @@ module ElasticRecord
|
|
30
36
|
end
|
31
37
|
|
32
38
|
private
|
39
|
+
|
33
40
|
def get_scroll_hit_ids(scroll_id, scroll_keep_alive)
|
34
41
|
json = klass.elastic_index.scroll(scroll_id, scroll_keep_alive)
|
35
42
|
json['hits']['hits'].map { |hit| hit['_id'] }
|
@@ -85,10 +85,10 @@ module ElasticRecord
|
|
85
85
|
end
|
86
86
|
|
87
87
|
def facet(facet_or_name, options = {})
|
88
|
-
clone.facet! facet_or_name, options
|
88
|
+
clone.facet! facet_or_name, options
|
89
89
|
end
|
90
90
|
|
91
|
-
def order!(*args)
|
91
|
+
def order!(*args) # :nodoc:
|
92
92
|
self.order_values += args.flatten
|
93
93
|
self
|
94
94
|
end
|
@@ -97,6 +97,18 @@ module ElasticRecord
|
|
97
97
|
clone.order! *args
|
98
98
|
end
|
99
99
|
|
100
|
+
# Reverse the existing order clause on the relation.
|
101
|
+
#
|
102
|
+
# User.order('name').reverse_order # generated search has 'sort: {'name' => :desc}
|
103
|
+
def reverse_order
|
104
|
+
clone.reverse_order!
|
105
|
+
end
|
106
|
+
|
107
|
+
def reverse_order! # :nodoc:
|
108
|
+
self.reverse_order_value = !reverse_order_value
|
109
|
+
self
|
110
|
+
end
|
111
|
+
|
100
112
|
def extending!(*modules, &block)
|
101
113
|
modules << Module.new(&block) if block_given?
|
102
114
|
|
@@ -214,7 +226,25 @@ module ElasticRecord
|
|
214
226
|
end
|
215
227
|
|
216
228
|
def build_orders(orders)
|
217
|
-
|
229
|
+
unless orders.empty?
|
230
|
+
orders = reverse_query_order(orders) if reverse_order_value
|
231
|
+
Arelastic::Searches::Sort.new(orders) unless orders.empty?
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
def reverse_query_order(orders)
|
236
|
+
orders.reverse.map do |o|
|
237
|
+
case o
|
238
|
+
when String, Symbol
|
239
|
+
{o => :desc}
|
240
|
+
when Hash
|
241
|
+
o.each_with_object({}) do |(field, dir), memo|
|
242
|
+
memo[field] = (dir.to_sym == :asc ? :desc : :asc )
|
243
|
+
end
|
244
|
+
else
|
245
|
+
o
|
246
|
+
end
|
247
|
+
end
|
218
248
|
end
|
219
249
|
end
|
220
250
|
end
|
@@ -43,6 +43,10 @@ module ElasticRecord
|
|
43
43
|
# is used on the associate class (such as a Post class). You can also specify a custom counter
|
44
44
|
# cache column by providing a column name instead of a +true+/+false+ value to this
|
45
45
|
# option (e.g., <tt>:counter_cache => :my_custom_counter</tt>.)
|
46
|
+
# [:class_name]
|
47
|
+
# Specify the class name of the association. Use it only if that name can't be inferred
|
48
|
+
# from the association name. So <tt>has_one :manager</tt> will by default be linked to the Manager class, but
|
49
|
+
# if the real class name is Person, you'll have to specify it with this option.
|
46
50
|
#
|
47
51
|
# === Example
|
48
52
|
#
|
@@ -19,6 +19,10 @@ module ElasticRecord
|
|
19
19
|
other_record.is_a?(Hash) ? klass.new(other_record) : other_record
|
20
20
|
end
|
21
21
|
|
22
|
+
delete(load_collection - other_records)
|
23
|
+
merge_collections(load_collection, other_records)
|
24
|
+
concat(other_records - load_collection)
|
25
|
+
|
22
26
|
if reflection.counter_cache_column
|
23
27
|
owner.send("#{reflection.counter_cache_column}=", other_records.size)
|
24
28
|
end
|
@@ -26,10 +30,6 @@ module ElasticRecord
|
|
26
30
|
if reflection.touch_column
|
27
31
|
owner.send("#{reflection.touch_column}=", Time.current)
|
28
32
|
end
|
29
|
-
|
30
|
-
delete(load_collection - other_records)
|
31
|
-
merge_collections(load_collection, other_records)
|
32
|
-
concat(other_records - load_collection)
|
33
33
|
end
|
34
34
|
|
35
35
|
def reader
|
@@ -55,15 +55,21 @@ module ElasticRecord
|
|
55
55
|
end
|
56
56
|
|
57
57
|
def delete(records)
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
58
|
+
records.each do |record|
|
59
|
+
callback(:before_remove, record)
|
60
|
+
|
61
|
+
if options[:autosave] || owner.new_record?
|
62
|
+
record.mark_for_destruction
|
63
|
+
else
|
64
|
+
record.destroy
|
65
|
+
end
|
66
|
+
|
67
|
+
callback(:after_remove, record)
|
62
68
|
end
|
63
69
|
end
|
64
70
|
|
65
71
|
def scope
|
66
|
-
search = klass.elastic_search.filter
|
72
|
+
search = klass.elastic_search.filter("#{reflection.belongs_to}_id" => owner.id).limit(1000)
|
67
73
|
if options[:as]
|
68
74
|
search.filter! "#{reflection.belongs_to}_type" => owner.class.name
|
69
75
|
end
|
@@ -76,7 +82,6 @@ module ElasticRecord
|
|
76
82
|
@loaded = true
|
77
83
|
end
|
78
84
|
|
79
|
-
loaded = true
|
80
85
|
collection
|
81
86
|
end
|
82
87
|
|
@@ -86,7 +91,13 @@ module ElasticRecord
|
|
86
91
|
end
|
87
92
|
|
88
93
|
def persisted_collection
|
89
|
-
|
94
|
+
@persisted_collection ||= begin
|
95
|
+
if reflection.counter_cache_column && (owner.send(reflection.counter_cache_column).nil? || owner.send(reflection.counter_cache_column) == 0)
|
96
|
+
[]
|
97
|
+
else
|
98
|
+
scope.to_a
|
99
|
+
end
|
100
|
+
end
|
90
101
|
end
|
91
102
|
|
92
103
|
def merge_collections(existing_records, new_records)
|
@@ -122,10 +133,10 @@ module ElasticRecord
|
|
122
133
|
|
123
134
|
def callback(method, record)
|
124
135
|
reflection.callbacks[method].each do |callback|
|
125
|
-
if callback.
|
126
|
-
owner.send(callback, record)
|
127
|
-
else
|
136
|
+
if callback.respond_to?(:call)
|
128
137
|
callback.call(owner, record)
|
138
|
+
else
|
139
|
+
owner.send(callback, record)
|
129
140
|
end
|
130
141
|
end
|
131
142
|
end
|
@@ -14,32 +14,16 @@ namespace :index do
|
|
14
14
|
desc "Create index for CLASS or all models."
|
15
15
|
task create: :environment do
|
16
16
|
ElasticRecord::Task.get_models.each do |model|
|
17
|
-
|
18
|
-
|
19
|
-
logger.info "Created #{model.name} index (#{index_name})"
|
20
|
-
# rescue => e
|
21
|
-
# if e.message =~ /IndexAlreadyExistsException/
|
22
|
-
# logger.info "#{model.name} index already exists"
|
23
|
-
# else
|
24
|
-
# raise e
|
25
|
-
# end
|
26
|
-
# end
|
17
|
+
index_name = model.elastic_index.create_and_deploy
|
18
|
+
logger.info "Created #{model.name} index (#{index_name})"
|
27
19
|
end
|
28
20
|
end
|
29
21
|
|
30
22
|
desc "Drop index for CLASS or all models."
|
31
23
|
task drop: :environment do
|
32
24
|
ElasticRecord::Task.get_models.each do |model|
|
33
|
-
|
34
|
-
|
35
|
-
logger.info "Dropped #{model.name} index"
|
36
|
-
# rescue => e
|
37
|
-
# if e.message =~ /IndexMissingException/
|
38
|
-
# logger.info "#{model.name} index does not exist"
|
39
|
-
# else
|
40
|
-
# raise e
|
41
|
-
# end
|
42
|
-
# end
|
25
|
+
model.elastic_index.delete_all
|
26
|
+
logger.info "Dropped #{model.name} index"
|
43
27
|
end
|
44
28
|
end
|
45
29
|
|
@@ -76,4 +60,4 @@ namespace :index do
|
|
76
60
|
logger.info " Done."
|
77
61
|
end
|
78
62
|
end
|
79
|
-
end
|
63
|
+
end
|
@@ -10,6 +10,15 @@ class ElasticRecord::CallbacksTest < MiniTest::Spec
|
|
10
10
|
assert Widget.elastic_index.record_exists?(widget.id)
|
11
11
|
end
|
12
12
|
|
13
|
+
def test_not_added_to_index_if_not_dirty
|
14
|
+
widget = Widget.new id: '10', color: 'green'
|
15
|
+
widget.changed_attributes.clear
|
16
|
+
|
17
|
+
widget.save
|
18
|
+
|
19
|
+
refute Widget.elastic_index.record_exists?(widget.id)
|
20
|
+
end
|
21
|
+
|
13
22
|
def test_deleted_from_index
|
14
23
|
widget = Widget.new id: '10', color: 'green'
|
15
24
|
Widget.elastic_index.index_document(widget.id, widget.as_search)
|
@@ -24,7 +24,7 @@ class ElasticRecord::ConnectionTest < MiniTest::Spec
|
|
24
24
|
assert_equal expected, connection.json_put("/test")
|
25
25
|
end
|
26
26
|
|
27
|
-
def
|
27
|
+
def test_json_request_with_error_status
|
28
28
|
response_json = {'error' => 'Doing it wrong'}
|
29
29
|
FakeWeb.register_uri(:get, %r[/error], status: ["404", "Not Found"], body: ActiveSupport::JSON.encode(response_json))
|
30
30
|
|
@@ -35,6 +35,24 @@ class ElasticRecord::ConnectionTest < MiniTest::Spec
|
|
35
35
|
assert_equal 'Doing it wrong', error.message
|
36
36
|
end
|
37
37
|
|
38
|
+
def test_execute_retries
|
39
|
+
responses = [
|
40
|
+
{exception: Errno::ECONNREFUSED},
|
41
|
+
{status: ["200", "OK"], body: ActiveSupport::JSON.encode('hello' => 'world')}
|
42
|
+
]
|
43
|
+
|
44
|
+
ElasticRecord::Connection.new(ElasticRecord::Config.servers, retries: 0).tap do |connection|
|
45
|
+
FakeWeb.register_uri :get, %r[/error], responses
|
46
|
+
assert_raises(Errno::ECONNREFUSED) { connection.json_get("/error") }
|
47
|
+
end
|
48
|
+
|
49
|
+
ElasticRecord::Connection.new(ElasticRecord::Config.servers, retries: 1).tap do |connection|
|
50
|
+
FakeWeb.register_uri :get, %r[/error], responses
|
51
|
+
json = connection.json_get("/error")
|
52
|
+
assert_equal({'hello' => 'world'}, json)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
38
56
|
private
|
39
57
|
def connection
|
40
58
|
ElasticRecord::Connection.new(ElasticRecord::Config.servers)
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class ElasticRecord::Index::ConfiguratorTest < MiniTest::Spec
|
4
|
+
def test_property
|
5
|
+
configurator.property :am_i_cool, type: "boolean"
|
6
|
+
|
7
|
+
expected = {type: "boolean"}
|
8
|
+
assert_equal expected, configurator.index.mapping[:properties][:am_i_cool]
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
def configurator
|
13
|
+
@configurator ||= begin
|
14
|
+
index = ElasticRecord::Index.new(Widget)
|
15
|
+
ElasticRecord::Index::Configurator.new(index)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -1,6 +1,12 @@
|
|
1
1
|
require 'helper'
|
2
2
|
|
3
3
|
class ElasticRecord::Index::DocumentsTest < MiniTest::Spec
|
4
|
+
class InheritedWidget < Widget
|
5
|
+
def self.base_class
|
6
|
+
Widget
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
4
10
|
def setup
|
5
11
|
super
|
6
12
|
index.disable_deferring!
|
@@ -32,7 +38,7 @@ class ElasticRecord::Index::DocumentsTest < MiniTest::Spec
|
|
32
38
|
end
|
33
39
|
|
34
40
|
def test_bulk
|
35
|
-
assert_nil index.instance_variable_get(:@
|
41
|
+
assert_nil index.instance_variable_get(:@_batch)
|
36
42
|
|
37
43
|
index.bulk do
|
38
44
|
index.index_document '5', color: 'green'
|
@@ -43,13 +49,26 @@ class ElasticRecord::Index::DocumentsTest < MiniTest::Spec
|
|
43
49
|
{color: "green"},
|
44
50
|
{delete: {_index: "widgets", _type: "widget", _id: "3"}}
|
45
51
|
]
|
46
|
-
assert_equal expected, index.instance_variable_get(:@
|
52
|
+
assert_equal expected, index.instance_variable_get(:@_batch)
|
47
53
|
end
|
48
54
|
|
49
|
-
assert_nil index.instance_variable_get(:@
|
55
|
+
assert_nil index.instance_variable_get(:@_batch)
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_bulk_inheritence
|
59
|
+
index.bulk do
|
60
|
+
InheritedWidget.elastic_index.index_document '5', color: 'green'
|
61
|
+
|
62
|
+
expected = [
|
63
|
+
{index: {_index: "widgets", _type: "widget", _id: "5"}},
|
64
|
+
{color: "green"}
|
65
|
+
]
|
66
|
+
assert_equal expected, index.instance_variable_get(:@_batch)
|
67
|
+
end
|
50
68
|
end
|
51
69
|
|
52
70
|
private
|
71
|
+
|
53
72
|
def index
|
54
73
|
@index ||= Widget.elastic_index
|
55
74
|
end
|
@@ -26,6 +26,13 @@ class ElasticRecord::Index::ManageTest < MiniTest::Spec
|
|
26
26
|
assert !index.exists?('widgets_bar')
|
27
27
|
end
|
28
28
|
|
29
|
+
def test_type_exists
|
30
|
+
index.create 'widgets_foo'
|
31
|
+
|
32
|
+
assert index.type_exists?('widgets_foo')
|
33
|
+
assert !index.type_exists?('widgets_bar')
|
34
|
+
end
|
35
|
+
|
29
36
|
def test_deploy
|
30
37
|
index.create 'widgets_foo'
|
31
38
|
|
@@ -1,6 +1,17 @@
|
|
1
1
|
require 'helper'
|
2
2
|
|
3
3
|
class ElasticRecord::Index::MappingTest < MiniTest::Spec
|
4
|
+
def test_delete_mapping
|
5
|
+
index_name = index.create
|
6
|
+
index.get_mapping(index_name)
|
7
|
+
|
8
|
+
index.delete_mapping(index_name)
|
9
|
+
|
10
|
+
assert_raises ElasticRecord::ConnectionError do
|
11
|
+
index.get_mapping(index_name)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
4
15
|
def test_default_mapping
|
5
16
|
mapping = index.mapping
|
6
17
|
|