elastic_record 2.0.2 → 3.0.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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.travis.yml +15 -9
  4. data/Gemfile +5 -4
  5. data/README.md +214 -0
  6. data/elastic_record.gemspec +7 -7
  7. data/lib/elastic_record.rb +1 -0
  8. data/lib/elastic_record/callbacks.rb +46 -14
  9. data/lib/elastic_record/config.rb +1 -21
  10. data/lib/elastic_record/connection.rb +24 -14
  11. data/lib/elastic_record/errors.rb +5 -0
  12. data/lib/elastic_record/index.rb +11 -1
  13. data/lib/elastic_record/index/deferred.rb +1 -0
  14. data/lib/elastic_record/index/documents.rb +95 -18
  15. data/lib/elastic_record/index/manage.rb +0 -8
  16. data/lib/elastic_record/index/mapping.rb +1 -10
  17. data/lib/elastic_record/json.rb +29 -0
  18. data/lib/elastic_record/relation.rb +9 -5
  19. data/lib/elastic_record/relation/batches.rb +4 -40
  20. data/lib/elastic_record/relation/none.rb +0 -4
  21. data/lib/elastic_record/relation/search_methods.rb +48 -38
  22. data/lib/elastic_record/relation/value_methods.rb +2 -2
  23. data/lib/elastic_record/tasks/index.rake +2 -2
  24. data/test/dummy/.env.example +1 -0
  25. data/test/dummy/.env.test +1 -0
  26. data/test/dummy/app/models/project.rb +1 -1
  27. data/test/dummy/app/models/test_model.rb +3 -2
  28. data/test/dummy/app/models/widget.rb +3 -3
  29. data/test/dummy/config/initializers/elastic_record.rb +1 -1
  30. data/test/dummy/db/migrate/20151211225259_create_projects.rb +7 -0
  31. data/test/dummy/db/schema.rb +8 -1
  32. data/test/elastic_record/callbacks_test.rb +16 -2
  33. data/test/elastic_record/config_test.rb +1 -2
  34. data/test/elastic_record/connection_test.rb +52 -9
  35. data/test/elastic_record/index/documents_test.rb +55 -21
  36. data/test/elastic_record/index/mapping_test.rb +0 -10
  37. data/test/elastic_record/integration/active_record_test.rb +3 -3
  38. data/test/elastic_record/log_subscriber_test.rb +4 -4
  39. data/test/elastic_record/relation/batches_test.rb +5 -24
  40. data/test/elastic_record/relation/delegation_test.rb +4 -3
  41. data/test/elastic_record/relation/finder_methods_test.rb +1 -0
  42. data/test/elastic_record/relation/search_methods_test.rb +47 -45
  43. data/test/elastic_record/relation_test.rb +18 -10
  44. data/test/helper.rb +4 -3
  45. metadata +21 -12
  46. data/README.rdoc +0 -146
  47. data/test/dummy/config/database.yml +0 -15
@@ -1,6 +1,6 @@
1
1
  module ElasticRecord
2
2
  class Relation
3
- MULTI_VALUE_METHODS = [:extending, :facet, :filter, :order, :select, :aggregation]
4
- SINGLE_VALUE_METHODS = [:query, :limit, :offset, :reverse_order]
3
+ MULTI_VALUE_METHODS = [:extending, :filter, :order, :select, :aggregation]
4
+ SINGLE_VALUE_METHODS = [:query, :limit, :offset, :search_type, :reverse_order]
5
5
  end
6
6
  end
@@ -33,7 +33,7 @@ namespace :index do
33
33
  puts "Dropped #{model.name} index"
34
34
 
35
35
  if index.has_percolator
36
- index_name = index.delete_percolator_index
36
+ index.delete_percolator_index
37
37
  puts "Dropped #{model.name} percolator index (#{index.percolator_name})"
38
38
  end
39
39
  end
@@ -63,7 +63,7 @@ namespace :index do
63
63
 
64
64
  puts " Reindexing into #{index_name}"
65
65
  model.find_in_batches(batch_size: 100) do |records|
66
- model.elastic_index.bulk_add(records, index_name)
66
+ model.elastic_index.bulk_add(records, index_name: index_name)
67
67
  end
68
68
 
69
69
  puts " Deploying index..."
@@ -0,0 +1 @@
1
+ DATABASE_URL=postgres://localhost/elastic_record_development
@@ -0,0 +1 @@
1
+ DATABASE_URL=postgres://postgres:@localhost/elastic_record_test
@@ -2,6 +2,6 @@ class Project < ActiveRecord::Base
2
2
  include ElasticRecord::Model
3
3
 
4
4
  self.elastic_index.mapping[:properties].update(
5
- name: { type: 'string', index: 'not_analyzed' }
5
+ 'name' => { type: 'string', index: 'not_analyzed' }
6
6
  )
7
7
  end
@@ -4,7 +4,7 @@ module TestModel
4
4
  included do
5
5
  extend ActiveModel::Naming
6
6
  extend ActiveModel::Callbacks
7
- define_model_callbacks :save, :destroy
7
+ define_model_callbacks :save, :update, :create, :destroy
8
8
 
9
9
  include ActiveModel::Dirty
10
10
  include ActiveModel::Validations
@@ -71,7 +71,8 @@ module TestModel
71
71
 
72
72
  def save
73
73
  @persisted = true
74
- run_callbacks :save
74
+ callback_method = new_record? ? :create : :update
75
+ run_callbacks callback_method
75
76
  end
76
77
 
77
78
  def destroy
@@ -8,17 +8,17 @@ class Widget
8
8
  self.elastic_index.has_percolator = true
9
9
 
10
10
  self.elastic_index.mapping[:properties].update(
11
- name: {
11
+ 'name' => {
12
12
  type: 'multi_field',
13
13
  fields: {
14
14
  name: {type: 'string', index: 'not_analyzed'},
15
15
  analyzed: {type: 'string', index: 'analyzed'}
16
16
  }
17
17
  },
18
- color: {
18
+ 'color' => {
19
19
  type: 'string', index: 'not_analyzed'
20
20
  },
21
- warehouse_id: {
21
+ 'warehouse_id' => {
22
22
  type: 'string', index: 'not_analyzed'
23
23
  }
24
24
  )
@@ -1,3 +1,3 @@
1
1
  ElasticRecord.configure do |config|
2
- config.model_names = %w(Warehouse Widget)
2
+ config.model_names = %w(Warehouse Widget Project)
3
3
  end
@@ -0,0 +1,7 @@
1
+ class CreateProjects < ActiveRecord::Migration
2
+ def change
3
+ create_table :projects do |t|
4
+ t.string :name, null: false
5
+ end
6
+ end
7
+ end
@@ -11,6 +11,13 @@
11
11
  #
12
12
  # It's strongly recommended that you check this file into your version control system.
13
13
 
14
- ActiveRecord::Schema.define(version: 0) do
14
+ ActiveRecord::Schema.define(version: 20151211225259) do
15
+
16
+ # These are extensions that must be enabled in order to support this database
17
+ enable_extension "plpgsql"
18
+
19
+ create_table "projects", force: :cascade do |t|
20
+ t.string "name", null: false
21
+ end
15
22
 
16
23
  end
@@ -32,7 +32,7 @@ class ElasticRecord::CallbacksTest < MiniTest::Test
32
32
 
33
33
  def test_as_search
34
34
  Widget.new(id: '10', color: 'green').tap do |widget|
35
- assert_equal({color: "green"}, widget.as_search)
35
+ assert_equal({"color" => "green"}, widget.as_search)
36
36
  end
37
37
 
38
38
  Widget.new(id: '10', color: '').tap do |widget|
@@ -40,7 +40,21 @@ class ElasticRecord::CallbacksTest < MiniTest::Test
40
40
  end
41
41
 
42
42
  Widget.new(id: '10', color: false).tap do |widget|
43
- assert_equal({color: false}, widget.as_search)
43
+ assert_equal({"color" => false}, widget.as_search)
44
+ end
45
+ end
46
+
47
+ def test_as_dirty_search
48
+ Widget.new(id: '10', color: 'green').tap do |widget|
49
+ assert_equal({'color' => 'green'}, widget.as_partial_update_document)
50
+ end
51
+
52
+ Widget.new(id: '10').tap do |widget|
53
+ assert_equal({}, widget.as_partial_update_document)
54
+ end
55
+
56
+ Widget.new(id: '10', color: '').tap do |widget|
57
+ assert_equal({'color' => nil}, widget.as_partial_update_document)
44
58
  end
45
59
  end
46
60
 
@@ -8,7 +8,7 @@ class ElasticRecord::ConfigTest < MiniTest::Test
8
8
  def test_models
9
9
  ElasticRecord::Config.model_names = %w(Widget)
10
10
 
11
- assert_equal [Warehouse, Widget], ElasticRecord::Config.models
11
+ assert_equal [Warehouse, Widget, Project], ElasticRecord::Config.models
12
12
  end
13
13
 
14
14
  def test_servers
@@ -21,7 +21,6 @@ class ElasticRecord::ConfigTest < MiniTest::Test
21
21
  end
22
22
  end
23
23
 
24
-
25
24
  private
26
25
 
27
26
  def with_servers(values)
@@ -1,6 +1,14 @@
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
+
4
12
  def test_servers
5
13
  assert_equal ['foo'], ElasticRecord::Connection.new('foo').servers
6
14
  assert_equal ['foo', 'bar'], ElasticRecord::Connection.new(['foo', 'bar']).servers
@@ -12,16 +20,17 @@ class ElasticRecord::ConnectionTest < MiniTest::Test
12
20
  end
13
21
 
14
22
  def test_head
15
- FakeWeb.register_uri(:head, %r[/success], status: ["200", "OK"])
23
+ stub_es_request(:head, "/success").to_return(status: 200)
24
+
16
25
  assert_equal "200", connection.head("/success")
17
26
 
18
- FakeWeb.register_uri(:head, %r[/failure], status: ["404", "Not Found"])
27
+ stub_es_request(:head, "/failure").to_return(status: 404)
19
28
  assert_equal "404", connection.head("/failure")
20
29
  end
21
30
 
22
31
  def test_json_requests
23
32
  expected = {'foo' => 'bar'}
24
- FakeWeb.register_uri(:any, %r[/test], status: ["200", "OK"], body: ActiveSupport::JSON.encode(expected))
33
+ stub_es_request(:any, "/test").to_return(status: 200, body: ElasticRecord::JSON.encode(expected))
25
34
 
26
35
  assert_equal expected, connection.json_delete("/test")
27
36
  assert_equal expected, connection.json_get("/test")
@@ -29,9 +38,14 @@ class ElasticRecord::ConnectionTest < MiniTest::Test
29
38
  assert_equal expected, connection.json_put("/test")
30
39
  end
31
40
 
32
- def test_json_request_with_error_status
41
+ def test_json_requests_with_oj
42
+ ElasticRecord::JSON.parser = :oj
43
+ test_json_requests
44
+ end
45
+
46
+ def test_json_request_with_valid_error_status
33
47
  response_json = {'error' => 'Doing it wrong'}
34
- FakeWeb.register_uri(:get, %r[/error], status: ["404", "Not Found"], body: ActiveSupport::JSON.encode(response_json))
48
+ stub_es_request(:get, "/error").to_return(status: 404, body: ElasticRecord::JSON.encode(response_json))
35
49
 
36
50
  error = assert_raises ElasticRecord::ConnectionError do
37
51
  connection.json_get("/error")
@@ -40,26 +54,55 @@ class ElasticRecord::ConnectionTest < MiniTest::Test
40
54
  assert_equal 'Doing it wrong', error.message
41
55
  end
42
56
 
43
- def test_execute_retries
57
+ def test_retry_server_exceptions
44
58
  responses = [
45
59
  {exception: Errno::ECONNREFUSED},
46
- {status: ["200", "OK"], body: ActiveSupport::JSON.encode('hello' => 'world')}
60
+ {status: ["200", "OK"], body: ElasticRecord::JSON.encode('hello' => 'world')}
47
61
  ]
48
62
 
49
63
  ElasticRecord::Connection.new(ElasticRecord::Config.servers, retries: 0).tap do |connection|
50
- FakeWeb.register_uri :get, %r[/error], responses
64
+ stub_es_request(:get, "/error").to_return(*responses)
51
65
  assert_raises(Errno::ECONNREFUSED) { connection.json_get("/error") }
52
66
  end
53
67
 
54
68
  ElasticRecord::Connection.new(ElasticRecord::Config.servers, retries: 1).tap do |connection|
55
- FakeWeb.register_uri :get, %r[/error], responses
69
+ stub_es_request(:get, "/error").to_return(*responses)
70
+ json = connection.json_get("/error")
71
+ assert_equal({'hello' => 'world'}, json)
72
+ end
73
+ end
74
+
75
+ def test_retry_server_500_errors
76
+ responses = [
77
+ {status: ["500", "OK"], body: {'error' => 'temporarily_unavailable'}.to_json},
78
+ {status: ["200", "OK"], body: {'hello' => 'world'}.to_json}
79
+ ]
80
+
81
+ ElasticRecord::Connection.new(ElasticRecord::Config.servers, retries: 0).tap do |connection|
82
+ stub_es_request(:get, "/error").to_return(*responses)
83
+
84
+ error = assert_raises ElasticRecord::ConnectionError do
85
+ connection.json_get("/error")
86
+ end
87
+
88
+ assert_equal '500', error.status_code
89
+ assert_equal '{"error":"temporarily_unavailable"}', error.message
90
+ end
91
+
92
+ ElasticRecord::Connection.new(ElasticRecord::Config.servers, retries: 1).tap do |connection|
93
+ stub_es_request(:get, "/error").to_return(*responses)
56
94
  json = connection.json_get("/error")
57
95
  assert_equal({'hello' => 'world'}, json)
58
96
  end
59
97
  end
60
98
 
61
99
  private
100
+
62
101
  def connection
63
102
  ElasticRecord::Connection.new(ElasticRecord::Config.servers)
64
103
  end
104
+
105
+ def stub_es_request(method, path)
106
+ stub_request(method, "#{connection.servers.first}#{path}")
107
+ end
65
108
  end
@@ -7,12 +7,6 @@ class ElasticRecord::Index::DocumentsTest < MiniTest::Test
7
7
  end
8
8
  end
9
9
 
10
- def setup
11
- super
12
- index.disable_deferring!
13
- index.reset
14
- end
15
-
16
10
  def test_index_record
17
11
  record = Widget.new(id: '5', color: 'red')
18
12
 
@@ -29,12 +23,24 @@ class ElasticRecord::Index::DocumentsTest < MiniTest::Test
29
23
  refute index.record_exists?('xyz')
30
24
  end
31
25
 
26
+ def test_update_document
27
+ index.index_document('abc', warehouse_id: '5', color: 'red')
28
+ index.update_document('abc', color: 'blue')
29
+
30
+ expected = {'warehouse_id' => '5', 'color' => 'blue'}
31
+ assert_equal expected, index.get('abc')['_source']
32
+ end
33
+
32
34
  def test_delete_document
33
35
  index.index_document('abc', color: 'red')
34
36
  assert index.record_exists?('abc')
35
37
 
36
38
  index.delete_document('abc')
37
39
  refute index.record_exists?('abc')
40
+
41
+ assert_raises RuntimeError do
42
+ index.delete_document('')
43
+ end
38
44
  end
39
45
 
40
46
  def test_delete_by_query
@@ -47,6 +53,17 @@ class ElasticRecord::Index::DocumentsTest < MiniTest::Test
47
53
  assert index.record_exists?('joe')
48
54
  end
49
55
 
56
+ def test_create_scan_search
57
+ index.index_document('bob', name: 'bob')
58
+ index.index_document('joe', name: 'joe')
59
+
60
+ scan_search = index.create_scan_search('query' => {query_string: {query: 'name.analyzed:bob'}})
61
+
62
+ assert_equal 1, scan_search.total_hits
63
+ refute_nil scan_search.scroll_id
64
+ assert_equal 1, scan_search.request_more_ids.size
65
+ end
66
+
50
67
  def test_bulk_add
51
68
  record = Widget.new(id: 'abc', color: 'red')
52
69
 
@@ -61,12 +78,15 @@ class ElasticRecord::Index::DocumentsTest < MiniTest::Test
61
78
 
62
79
  index.bulk do
63
80
  index.index_document '5', color: 'green'
81
+ index.update_document '5', color: 'blue'
64
82
  index.delete_document '3'
65
83
 
66
84
  expected = [
67
85
  {index: {_index: index.alias_name, _type: "widget", _id: "5"}},
68
86
  {color: "green"},
69
- {delete: {_index: index.alias_name, _type: "widget", _id: "3"}}
87
+ {update: {_index: "widgets", _type: "widget", _id: "5", _retry_on_conflict: 3}},
88
+ {doc: {color: "blue"}, doc_as_upsert: true},
89
+ {delete: {_index: index.alias_name, _type: "widget", _id: "3", _retry_on_conflict: 3}}
70
90
  ]
71
91
  assert_equal expected, index.current_bulk_batch
72
92
  end
@@ -75,29 +95,43 @@ class ElasticRecord::Index::DocumentsTest < MiniTest::Test
75
95
  end
76
96
 
77
97
  def test_bulk_error
78
- index.bulk do
79
- index.index_document '5', color: 'green'
80
- index.index_document '3', color: {'bad' => 'stuff'}
98
+ without_deferring do
99
+ begin
100
+ index.bulk do
101
+ index.index_document '5', color: 'green'
102
+ index.index_document '3', color: {'bad' => 'stuff'}
103
+ end
104
+ refute index.record_exists?('3')
105
+ assert false, 'Expected ElasticRecord::BulkError'
106
+ rescue => e
107
+ assert_match '[{"index"', e.message
108
+ end
81
109
  end
82
- assert false, 'Expected ElasticRecord::BulkError'
83
- rescue => e
84
- assert_match '[{"index"', e.message
85
110
  end
86
111
 
87
112
  def test_bulk_inheritence
88
- index.bulk do
89
- InheritedWidget.elastic_index.index_document '5', color: 'green'
90
-
91
- expected = [
92
- {index: {_index: index.alias_name, _type: "widget", _id: "5"}},
93
- {color: "green"}
94
- ]
95
- assert_equal expected, index.current_bulk_batch
113
+ without_deferring do
114
+ index.bulk do
115
+ InheritedWidget.elastic_index.index_document '5', color: 'green'
116
+
117
+ expected = [
118
+ {index: {_index: index.alias_name, _type: "widget", _id: "5"}},
119
+ {color: "green"}
120
+ ]
121
+ assert_equal expected, index.current_bulk_batch
122
+ end
96
123
  end
97
124
  end
98
125
 
99
126
  private
100
127
 
128
+ def without_deferring
129
+ index.disable_deferring!
130
+ yield
131
+ index.reset
132
+ index.enable_deferring!
133
+ end
134
+
101
135
  def index
102
136
  @index ||= Widget.elastic_index
103
137
  end
@@ -1,19 +1,9 @@
1
1
  require 'helper'
2
2
 
3
3
  class ElasticRecord::Index::MappingTest < MiniTest::Test
4
- def test_delete_mapping
5
- index_name = index.create
6
- refute_nil index.get_mapping(index_name)['widget']
7
-
8
- index.delete_mapping(index_name)
9
-
10
- assert_nil index.get_mapping(index_name)
11
- end
12
-
13
4
  def test_default_mapping
14
5
  mapping = index.mapping
15
6
 
16
- refute_nil mapping[:_source]
17
7
  refute_nil mapping[:properties]
18
8
  end
19
9
 
@@ -48,7 +48,7 @@ class ElasticRecord::Mysql2Test < MiniTest::Test
48
48
  setup_project_database(
49
49
  'adapter' => 'mysql2',
50
50
  'host' => "localhost",
51
- 'database' => 'elastic_record_test',
51
+ 'database' => 'elastic_record_subtest',
52
52
  'username' => 'root'
53
53
  )
54
54
  end
@@ -63,9 +63,9 @@ class ElasticRecord::PostgresqlTest < MiniTest::Test
63
63
  setup_project_database(
64
64
  'adapter' => 'postgresql',
65
65
  'encoding' => 'unicode',
66
- 'database' => 'content_system_development',
66
+ 'database' => 'elastic_record_subtest',
67
67
  'pool' => 5,
68
- 'username' => 'postgres',
68
+ 'host' => 'localhost',
69
69
  'password' => ''
70
70
  )
71
71
  end