elastic_record 4.1.8 → 5.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. checksums.yaml +5 -5
  2. data/.travis.yml +15 -8
  3. data/Gemfile +1 -1
  4. data/README.md +29 -21
  5. data/elastic_record.gemspec +1 -1
  6. data/lib/elastic_record.rb +25 -13
  7. data/lib/elastic_record/aggregation_response/aggregation.rb +19 -0
  8. data/lib/elastic_record/aggregation_response/bucket.rb +24 -0
  9. data/lib/elastic_record/aggregation_response/builder.rb +55 -0
  10. data/lib/elastic_record/aggregation_response/has_aggregations.rb +9 -0
  11. data/lib/elastic_record/aggregation_response/multi_bucket_aggregation.rb +9 -0
  12. data/lib/elastic_record/aggregation_response/multi_value_aggregation.rb +6 -0
  13. data/lib/elastic_record/aggregation_response/single_bucket_aggregation.rb +8 -0
  14. data/lib/elastic_record/aggregation_response/single_value_aggregation.rb +7 -0
  15. data/lib/elastic_record/as_document.rb +33 -16
  16. data/lib/elastic_record/callbacks.rb +1 -1
  17. data/lib/elastic_record/config.rb +3 -0
  18. data/lib/elastic_record/connection.rb +4 -4
  19. data/lib/elastic_record/errors.rb +7 -1
  20. data/lib/elastic_record/index.rb +6 -8
  21. data/lib/elastic_record/index/analyze.rb +10 -0
  22. data/lib/elastic_record/index/deferred.rb +2 -2
  23. data/lib/elastic_record/index/documents.rb +42 -29
  24. data/lib/elastic_record/index/manage.rb +6 -8
  25. data/lib/elastic_record/index/mapping.rb +16 -8
  26. data/lib/elastic_record/index/mapping_type.rb +16 -0
  27. data/lib/elastic_record/index/mapping_type_test.rb +18 -0
  28. data/lib/elastic_record/index/settings.rb +6 -17
  29. data/lib/elastic_record/model.rb +2 -14
  30. data/lib/elastic_record/percolator_model.rb +11 -19
  31. data/lib/elastic_record/relation.rb +9 -30
  32. data/lib/elastic_record/relation/batches.rb +5 -3
  33. data/lib/elastic_record/relation/delegation.rb +8 -4
  34. data/lib/elastic_record/relation/finder_methods.rb +8 -0
  35. data/lib/elastic_record/relation/hits.rb +34 -0
  36. data/lib/elastic_record/relation/search_methods.rb +17 -22
  37. data/lib/elastic_record/relation/value_methods.rb +1 -1
  38. data/test/dummy/app/models/project.rb +16 -4
  39. data/test/dummy/app/models/warehouse.rb +5 -16
  40. data/test/dummy/app/models/widget.rb +23 -12
  41. data/test/dummy/app/models/widget_query.rb +1 -0
  42. data/test/dummy/db/migrate/20151211225259_create_warehouses.rb +7 -0
  43. data/test/dummy/db/migrate/20180215140125_create_widgets.rb +11 -0
  44. data/test/dummy/db/schema.rb +10 -3
  45. data/test/elastic_record/aggregation_response/bucket_test.rb +8 -0
  46. data/test/elastic_record/aggregation_response/multi_bucket_aggregation_test.rb +33 -0
  47. data/test/elastic_record/aggregation_response/single_bucket_aggregation_test.rb +15 -0
  48. data/test/elastic_record/as_document_test.rb +55 -29
  49. data/test/elastic_record/callbacks_test.rb +7 -11
  50. data/test/elastic_record/connection_test.rb +3 -16
  51. data/test/elastic_record/index/documents_test.rb +56 -11
  52. data/test/elastic_record/index/manage_test.rb +0 -7
  53. data/test/elastic_record/index/mapping_test.rb +18 -5
  54. data/test/elastic_record/index/settings_test.rb +1 -7
  55. data/test/elastic_record/index_test.rb +0 -4
  56. data/test/elastic_record/integration/active_record_test.rb +6 -9
  57. data/test/elastic_record/model_test.rb +5 -2
  58. data/test/elastic_record/percolator_model_test.rb +25 -11
  59. data/test/elastic_record/relation/batches_test.rb +29 -47
  60. data/test/elastic_record/relation/delegation_test.rb +1 -1
  61. data/test/elastic_record/relation/finder_methods_test.rb +17 -31
  62. data/test/elastic_record/relation/hits_test.rb +49 -0
  63. data/test/elastic_record/relation/search_methods_test.rb +20 -16
  64. data/test/elastic_record/relation_test.rb +19 -50
  65. data/test/helper.rb +7 -3
  66. metadata +20 -9
  67. data/lib/elastic_record/doctype.rb +0 -43
  68. data/lib/elastic_record/json.rb +0 -29
  69. data/lib/elastic_record/name_cache.rb +0 -23
  70. data/test/dummy/db/migrate/20151211225259_create_projects.rb +0 -7
  71. data/test/elastic_record/doctype_test.rb +0 -45
  72. data/test/elastic_record/name_cache_test.rb +0 -16
@@ -0,0 +1,8 @@
1
+ require 'helper'
2
+
3
+ class ElasticRecord::AggregationResponse::BucketTest < MiniTest::Test
4
+ def test_inspect
5
+ bucket = ElasticRecord::AggregationResponse::Bucket.new('key' => 'Seattle', 'count' => 12)
6
+ assert_equal '#<ElasticRecord::AggregationResponse::Bucket {"key"=>"Seattle", "count"=>12}>', bucket.inspect
7
+ end
8
+ end
@@ -0,0 +1,33 @@
1
+ require 'helper'
2
+
3
+ class ElasticRecord::AggregationResponse::MultiBucketAggregationTest < MiniTest::Test
4
+ def test_single
5
+ agg = ElasticRecord::AggregationResponse::MultiBucketAggregation.new 'states', {
6
+ "buckets" => [
7
+ {
8
+ "key" => "WA",
9
+ "doc_count" => 4,
10
+ "lterms#sales_per_month" => {
11
+ "buckets" => [
12
+ {
13
+ "key_as_string" => "2015-02-01",
14
+ "key" => 1422748800000,
15
+ "doc_count" => 3
16
+ },
17
+ {
18
+ "key_as_string" => "2015-03-01",
19
+ "key" => 1425168000000,
20
+ "doc_count" => 1
21
+ }
22
+ ]
23
+ }
24
+ }
25
+ ]
26
+ }
27
+
28
+ bucket = agg.buckets.first
29
+ assert_equal 4, bucket.doc_count
30
+ assert_equal ['sales_per_month'], bucket.aggregations.keys
31
+ assert_equal 2, bucket.aggregations['sales_per_month'].buckets.size
32
+ end
33
+ end
@@ -0,0 +1,15 @@
1
+ require 'helper'
2
+
3
+ class ElasticRecord::AggregationResponse::SingleBucketAggregationTest < MiniTest::Test
4
+ def test_single
5
+ agg = ElasticRecord::AggregationResponse::SingleBucketAggregation.new 'resellers', {
6
+ 'doc_count' => 0,
7
+ 'min#min_price' => {
8
+ 'value' => 350
9
+ }
10
+ }
11
+
12
+ assert_equal 0, agg.doc_count
13
+ assert_equal 350, agg.aggregations['min_price'].value
14
+ end
15
+ end
@@ -2,64 +2,90 @@ require 'helper'
2
2
 
3
3
  class ElasticRecord::AsDocumentTest < MiniTest::Test
4
4
  def test_as_search_document
5
- Widget.new(id: '10', color: 'green').tap do |widget|
5
+ Widget.new(color: 'green').tap do |widget|
6
6
  assert_equal({"color" => "green"}, widget.as_search_document)
7
7
  end
8
8
 
9
- Widget.new(id: '10', color: '').tap do |widget|
9
+ Widget.new(color: '').tap do |widget|
10
10
  assert_equal({}, widget.as_search_document)
11
11
  end
12
-
13
- Widget.new(id: '10', color: false).tap do |widget|
14
- assert_equal({"color" => false}, widget.as_search_document)
15
- end
16
12
  end
17
13
 
18
- def test_as_dirty_search
19
- Widget.new(id: '10', color: 'green').tap do |widget|
20
- assert_equal({'color' => 'green'}, widget.as_partial_update_document)
21
- end
14
+ def test_as_partial_update_document
15
+ widget = Widget.create(name: 'elmo', color: 'green')
22
16
 
23
- Widget.new(id: '10').tap do |widget|
24
- assert_equal({}, widget.as_partial_update_document)
25
- end
17
+ Widget.elastic_index.update_document widget.id, name: 'wilbur'
26
18
 
27
- Widget.new(id: '10', color: '').tap do |widget|
28
- assert_equal({'color' => nil}, widget.as_partial_update_document)
29
- end
19
+ widget.update! color: 'grey', widget_part: { name: 'Doohicky' }
20
+
21
+ assert_equal 1, Widget.elastic_search.filter(color: 'grey').count
22
+ assert_equal 1, Widget.elastic_search.filter('widget_part.name' => 'Doohicky').count
23
+ assert_equal 0, Widget.elastic_search.filter(name: 'elmo').count
30
24
  end
31
25
 
32
26
  class SpecialFieldsModel
33
27
  include TestModel
28
+ attr_accessor :meta, :book_length
34
29
 
35
30
  class Author
36
- def as_search_document
37
- {name: 'Jonny'}
38
- end
31
+ include TestModel
32
+ attr_accessor :name, :salary_estimate
39
33
  end
40
34
 
41
- self.doctype.mapping[:properties].update(
42
- author: {
43
- type: :object
35
+ self.elastic_index.mapping[:properties].update(
36
+ author: {
37
+ type: :object,
38
+ properties: {
39
+ name: { type: :string },
40
+ salary_estimate: { type: :integer_range }
41
+ }
42
+ },
43
+ commenters: {
44
+ type: :nested,
45
+ properties: {
46
+ name: { type: :string }
47
+ }
44
48
  },
45
- commenters: {
46
- type: :nested
47
- }
49
+ meta: { type: "object" },
50
+ book_length: { type: :integer_range }
48
51
  )
49
52
 
50
53
  def author
51
- Author.new
54
+ Author.new(name: 'Jonny', salary_estimate: 250..Float::INFINITY)
52
55
  end
53
56
 
54
57
  def commenters
55
- [Author.new, Author.new]
58
+ [Author.new(name: 'Jonny'), Author.new(name: 'Jonny')]
56
59
  end
57
60
  end
58
61
 
59
62
  def test_as_search_document_with_special_fields
60
- doc = SpecialFieldsModel.new.as_search_document
63
+ record = SpecialFieldsModel.new(meta: { some: "value" })
61
64
 
62
- assert_equal({name: 'Jonny'}, doc[:author])
65
+ doc = record.as_search_document
66
+
67
+ assert_equal({name: 'Jonny', salary_estimate: { 'gte' => 250, 'lte' => nil }}, doc[:author])
63
68
  assert_equal([{name: 'Jonny'}, {name: 'Jonny'}], doc[:commenters])
69
+ assert_equal({some: 'value'}, doc[:meta])
70
+ end
71
+
72
+ def test_as_search_document_with_range_fields
73
+ record = SpecialFieldsModel.new(book_length: 250..500)
74
+ doc = record.as_search_document
75
+ assert_equal({ "gte" => 250, "lte" => 500 }, doc[:book_length])
76
+
77
+ record = SpecialFieldsModel.new(book_length: -Float::INFINITY..500)
78
+ doc = record.as_search_document
79
+ assert_equal({ "gte" => nil, "lte" => 500 }, doc[:book_length])
80
+
81
+ record = SpecialFieldsModel.new(book_length: 250..Float::INFINITY)
82
+ doc = record.as_search_document
83
+ assert_equal({ "gte" => 250, "lte" => nil }, doc[:book_length])
84
+ end
85
+
86
+ def test_as_search_document_with_invalid_range_fields
87
+ record = SpecialFieldsModel.new(book_length: 500..250)
88
+ invalid_elasticsearch_doc = record.as_search_document
89
+ assert_equal({ "gte" => 500, "lte" => 250 }, invalid_elasticsearch_doc[:book_length])
64
90
  end
65
91
  end
@@ -2,8 +2,7 @@ require 'helper'
2
2
 
3
3
  class ElasticRecord::CallbacksTest < MiniTest::Test
4
4
  def test_added_to_index
5
- widget = Widget.new id: '10', color: 'green'
6
- refute Widget.elastic_index.record_exists?(widget.id)
5
+ widget = Widget.new color: 'green'
7
6
 
8
7
  widget.save
9
8
 
@@ -11,22 +10,19 @@ class ElasticRecord::CallbacksTest < MiniTest::Test
11
10
  end
12
11
 
13
12
  def test_not_added_to_index_if_not_dirty
14
- widget = Widget.new id: '10', color: 'green'
15
- widget.changed_attributes.clear
13
+ widget = Widget.create color: 'green'
16
14
 
17
- widget.save
15
+ widget.elastic_index.delete_document(widget.id)
18
16
 
19
- refute Widget.elastic_index.record_exists?(widget.id)
17
+ widget.save
18
+ assert_equal 0, Widget.elastic_search.count
20
19
  end
21
20
 
22
21
  def test_deleted_from_index
23
- widget = Widget.new id: '10', color: 'green'
24
- Widget.elastic_index.index_document(widget.id, widget.as_search_document)
25
-
22
+ widget = Widget.create color: 'green'
26
23
  assert Widget.elastic_index.record_exists?(widget.id)
27
24
 
28
25
  widget.destroy
29
-
30
26
  refute Widget.elastic_index.record_exists?(widget.id)
31
27
  end
32
28
 
@@ -35,7 +31,7 @@ class ElasticRecord::CallbacksTest < MiniTest::Test
35
31
 
36
32
  define_attributes [:height]
37
33
 
38
- self.doctype.mapping[:properties].update(
34
+ self.elastic_index.mapping[:properties].update(
39
35
  height: {
40
36
  type: 'keyword'
41
37
  }
@@ -1,14 +1,6 @@
1
1
  require 'helper'
2
2
 
3
3
  class ElasticRecord::ConnectionTest < MiniTest::Test
4
- def setup
5
- ElasticRecord::JSON.parser = :active_support
6
- end
7
-
8
- def teardown
9
- ElasticRecord::JSON.parser = :active_support
10
- end
11
-
12
4
  def test_servers
13
5
  assert_equal ['foo'], ElasticRecord::Connection.new('foo').servers
14
6
  assert_equal ['foo', 'bar'], ElasticRecord::Connection.new(['foo', 'bar']).servers
@@ -30,7 +22,7 @@ class ElasticRecord::ConnectionTest < MiniTest::Test
30
22
 
31
23
  def test_json_requests
32
24
  expected = {'foo' => 'bar'}
33
- stub_es_request(:any, "/test").to_return(status: 200, body: ElasticRecord::JSON.encode(expected))
25
+ stub_es_request(:any, "/test").to_return(status: 200, body: JSON.generate(expected))
34
26
 
35
27
  assert_equal expected, connection.json_delete("/test")
36
28
  assert_equal expected, connection.json_get("/test")
@@ -38,14 +30,9 @@ class ElasticRecord::ConnectionTest < MiniTest::Test
38
30
  assert_equal expected, connection.json_put("/test")
39
31
  end
40
32
 
41
- def test_json_requests_with_oj
42
- ElasticRecord::JSON.parser = :oj
43
- test_json_requests
44
- end
45
-
46
33
  def test_json_request_with_valid_error_status
47
34
  response_json = {'error' => 'Doing it wrong'}
48
- stub_es_request(:get, "/error").to_return(status: 404, body: ElasticRecord::JSON.encode(response_json))
35
+ stub_es_request(:get, "/error").to_return(status: 404, body: JSON.generate(response_json))
49
36
 
50
37
  error = assert_raises ElasticRecord::ConnectionError do
51
38
  connection.json_get("/error")
@@ -57,7 +44,7 @@ class ElasticRecord::ConnectionTest < MiniTest::Test
57
44
  def test_retry_server_exceptions
58
45
  responses = [
59
46
  {exception: Errno::ECONNREFUSED},
60
- {status: ["200", "OK"], body: ElasticRecord::JSON.encode('hello' => 'world')}
47
+ {status: ["200", "OK"], body: JSON.generate('hello' => 'world')}
61
48
  ]
62
49
 
63
50
  ElasticRecord::Connection.new(ElasticRecord::Config.servers, retries: 0).tap do |connection|
@@ -49,7 +49,7 @@ class ElasticRecord::Index::DocumentsTest < MiniTest::Test
49
49
  index.update_document('abc', color: 'blue')
50
50
 
51
51
  expected = {'warehouse_id' => '5', 'color' => 'blue'}
52
- assert_equal expected, index.get('abc', Widget.doctype)['_source']
52
+ assert_equal expected, index.get('abc', index.mapping_type)['_source']
53
53
 
54
54
  assert_raises RuntimeError do
55
55
  index.update_document(nil, color: 'blue')
@@ -88,17 +88,31 @@ class ElasticRecord::Index::DocumentsTest < MiniTest::Test
88
88
  assert_equal 1, scroll_enumerator.request_more_ids.size
89
89
  end
90
90
 
91
- def test_bulk_add
92
- record = Widget.new(id: 'abc', color: 'red')
93
-
94
- index.bulk_add [record]
91
+ def test_expired_scroll_error
92
+ index.index_document('bob', name: 'bob')
93
+ index.index_document('bobs', name: 'bob')
94
+
95
+ scroll_enumerator = index.build_scroll_enumerator(
96
+ search: { 'query' => { query_string: { query: 'name:bob' } } },
97
+ batch_size: 1,
98
+ keep_alive: '1ms'
99
+ )
100
+
101
+ scroll_enumerator.request_more_hits
102
+ index.connection.json_delete '/_search/scroll', scroll_id: scroll_enumerator.scroll_id
103
+ assert_raises ElasticRecord::ExpiredScrollError do
104
+ scroll_enumerator.request_more_hits
105
+ end
106
+ end
95
107
 
96
- assert index.record_exists?('abc')
97
- refute index.record_exists?('xyz')
108
+ def test_invalid_scroll_error
109
+ assert_raises ElasticRecord::InvalidScrollError do
110
+ index.scroll('invalid', '1m')
111
+ end
98
112
  end
99
113
 
100
114
  def test_bulk
101
- assert_nil index.instance_variable_get(:@_batch)
115
+ assert_nil index.current_bulk_batch
102
116
 
103
117
  index.bulk do
104
118
  index.index_document '5', color: 'green'
@@ -108,9 +122,9 @@ class ElasticRecord::Index::DocumentsTest < MiniTest::Test
108
122
  expected = [
109
123
  {index: {_index: index.alias_name, _type: "widget", _id: "5"}},
110
124
  {color: "green"},
111
- {update: {_index: "widgets", _type: "widget", _id: "5", _retry_on_conflict: 3}},
125
+ {update: {_index: "widgets", _type: "widget", _id: "5", retry_on_conflict: 3}},
112
126
  {doc: {color: "blue"}, doc_as_upsert: true},
113
- {delete: {_index: index.alias_name, _type: "widget", _id: "3", _retry_on_conflict: 3}}
127
+ {delete: {_index: index.alias_name, _type: "widget", _id: "3", retry_on_conflict: 3}}
114
128
  ]
115
129
  assert_equal expected, index.current_bulk_batch
116
130
  end
@@ -118,6 +132,37 @@ class ElasticRecord::Index::DocumentsTest < MiniTest::Test
118
132
  assert_nil index.current_bulk_batch
119
133
  end
120
134
 
135
+ def test_bulk_nested
136
+ expected_warehouse_count = Warehouse.count + 2
137
+
138
+ Warehouse.elastic_index.bulk do
139
+ Warehouse.elastic_index.bulk do
140
+ Warehouse.create(name: 'Warehouse 13')
141
+ end
142
+ Warehouse.create(name: 'Warehouse 12')
143
+ end
144
+
145
+ assert_equal 2, Warehouse.elastic_relation.count
146
+ assert_equal expected_warehouse_count, Warehouse.count
147
+ end
148
+
149
+ def test_bulk_nested_with_error
150
+ expected_warehouse_count = Warehouse.count
151
+
152
+ begin
153
+ Warehouse.elastic_index.bulk do
154
+ Warehouse.elastic_index.bulk do
155
+ Warehouse.create(name: 'Warehouse 13')
156
+ end
157
+
158
+ Warehouse.create(name: nil)
159
+ end
160
+ rescue ActiveRecord::NotNullViolation
161
+ end
162
+
163
+ assert_equal 0, Warehouse.elastic_relation.count
164
+ end
165
+
121
166
  def test_bulk_error
122
167
  without_deferring(index) do
123
168
  begin
@@ -133,7 +178,7 @@ class ElasticRecord::Index::DocumentsTest < MiniTest::Test
133
178
  end
134
179
  end
135
180
 
136
- def test_bulk_inheritence
181
+ def test_bulk_inheritance
137
182
  without_deferring(index) do
138
183
  index.bulk do
139
184
  InheritedWidget.elastic_index.index_document '5', color: 'green'
@@ -25,13 +25,6 @@ class ElasticRecord::Index::ManageTest < MiniTest::Test
25
25
  assert !index.exists?('felons_bar')
26
26
  end
27
27
 
28
- def test_type_exists
29
- index.create 'felons_foo'
30
-
31
- assert index.type_exists?('felons_foo')
32
- assert !index.type_exists?('felons_bar')
33
- end
34
-
35
28
  def test_deploy
36
29
  index.create 'felons_foo'
37
30
 
@@ -4,14 +4,23 @@ class ElasticRecord::Index::MappingTest < MiniTest::Test
4
4
  def test_get_mapping
5
5
  expected = {
6
6
  "widget" => {
7
- "_all" => { "enabled" => false },
8
7
  "properties" => {
9
8
  "color" => { "type" => "keyword" },
10
9
  "name" => {
11
- "type" => "keyword",
12
- "fields" => { "analyzed" => { "type" => "text" } }
10
+ "type" => "text",
11
+ "fields" => {
12
+ "raw" => { "type" => "keyword" }
13
+ }
13
14
  },
14
- "warehouse_id" => { "type" => "keyword" }
15
+ "price" => {
16
+ "type" => "long"
17
+ },
18
+ "warehouse_id" => { "type" => "keyword" },
19
+ "widget_part" => {
20
+ "properties" => {
21
+ "name" => { "type" => "keyword" }
22
+ }
23
+ }
15
24
  }
16
25
  }
17
26
  }
@@ -19,9 +28,13 @@ class ElasticRecord::Index::MappingTest < MiniTest::Test
19
28
  assert_equal expected, index.get_mapping
20
29
  end
21
30
 
31
+ def test_update_mapping
32
+ index.update_mapping
33
+ end
34
+
22
35
  private
23
36
 
24
37
  def index
25
- @index ||= ElasticRecord::Index.new(Widget)
38
+ @index ||= Widget.elastic_index
26
39
  end
27
40
  end
@@ -9,7 +9,7 @@ class ElasticRecord::Index::SettingsTest < MiniTest::Test
9
9
  class ModelWithAnalyzers
10
10
  include TestModel
11
11
 
12
- doctype.analysis = {
12
+ elastic_index.analysis = {
13
13
  "analyzer": {
14
14
  "my_custom_analyzer": {
15
15
  "type": "custom",
@@ -38,10 +38,4 @@ class ElasticRecord::Index::SettingsTest < MiniTest::Test
38
38
 
39
39
  assert_equal expected, ModelWithAnalyzers.elastic_index.settings
40
40
  end
41
-
42
- private
43
-
44
- def index
45
- @index ||= ElasticRecord::Index.new(Widget)
46
- end
47
41
  end