elastic_record 4.1.8 → 5.1.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.
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