elasticsearch-model 6.0.0 → 6.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (118) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +5 -0
  3. data/README.md +14 -7
  4. data/Rakefile +27 -36
  5. data/elasticsearch-model.gemspec +1 -1
  6. data/examples/activerecord_mapping_completion.rb +2 -15
  7. data/gemfiles/3.0.gemfile +6 -1
  8. data/gemfiles/4.0.gemfile +7 -1
  9. data/gemfiles/5.0.gemfile +6 -0
  10. data/lib/elasticsearch/model.rb +15 -8
  11. data/lib/elasticsearch/model/adapters/active_record.rb +7 -26
  12. data/lib/elasticsearch/model/indexing.rb +5 -3
  13. data/lib/elasticsearch/model/naming.rb +6 -1
  14. data/lib/elasticsearch/model/response.rb +2 -2
  15. data/lib/elasticsearch/model/response/pagination.rb +2 -192
  16. data/lib/elasticsearch/model/response/pagination/kaminari.rb +109 -0
  17. data/lib/elasticsearch/model/response/pagination/will_paginate.rb +95 -0
  18. data/lib/elasticsearch/model/response/result.rb +1 -1
  19. data/lib/elasticsearch/model/version.rb +1 -1
  20. data/spec/elasticsearch/model/adapter_spec.rb +119 -0
  21. data/spec/elasticsearch/model/adapters/active_record/associations_spec.rb +334 -0
  22. data/spec/elasticsearch/model/adapters/active_record/basic_spec.rb +340 -0
  23. data/spec/elasticsearch/model/adapters/active_record/dynamic_index_name_spec.rb +18 -0
  24. data/spec/elasticsearch/model/adapters/active_record/import_spec.rb +187 -0
  25. data/spec/elasticsearch/model/adapters/active_record/multi_model_spec.rb +110 -0
  26. data/spec/elasticsearch/model/adapters/active_record/namespaced_model_spec.rb +38 -0
  27. data/spec/elasticsearch/model/adapters/active_record/pagination_spec.rb +315 -0
  28. data/spec/elasticsearch/model/adapters/active_record/parent_child_spec.rb +75 -0
  29. data/spec/elasticsearch/model/adapters/active_record/serialization_spec.rb +61 -0
  30. data/spec/elasticsearch/model/adapters/active_record_spec.rb +207 -0
  31. data/spec/elasticsearch/model/adapters/default_spec.rb +41 -0
  32. data/spec/elasticsearch/model/adapters/mongoid/basic_spec.rb +267 -0
  33. data/spec/elasticsearch/model/adapters/mongoid/multi_model_spec.rb +66 -0
  34. data/spec/elasticsearch/model/adapters/mongoid_spec.rb +235 -0
  35. data/spec/elasticsearch/model/adapters/multiple_spec.rb +125 -0
  36. data/spec/elasticsearch/model/callbacks_spec.rb +33 -0
  37. data/spec/elasticsearch/model/client_spec.rb +66 -0
  38. data/spec/elasticsearch/model/hash_wrapper_spec.rb +12 -0
  39. data/spec/elasticsearch/model/importing_spec.rb +214 -0
  40. data/spec/elasticsearch/model/indexing_spec.rb +918 -0
  41. data/spec/elasticsearch/model/module_spec.rb +101 -0
  42. data/spec/elasticsearch/model/multimodel_spec.rb +55 -0
  43. data/spec/elasticsearch/model/naming_inheritance_spec.rb +184 -0
  44. data/spec/elasticsearch/model/naming_spec.rb +186 -0
  45. data/spec/elasticsearch/model/proxy_spec.rb +107 -0
  46. data/spec/elasticsearch/model/response/aggregations_spec.rb +66 -0
  47. data/spec/elasticsearch/model/response/base_spec.rb +90 -0
  48. data/spec/elasticsearch/model/response/pagination/kaminari_spec.rb +410 -0
  49. data/spec/elasticsearch/model/response/pagination/will_paginate_spec.rb +262 -0
  50. data/spec/elasticsearch/model/response/records_spec.rb +118 -0
  51. data/spec/elasticsearch/model/response/response_spec.rb +131 -0
  52. data/spec/elasticsearch/model/response/result_spec.rb +122 -0
  53. data/spec/elasticsearch/model/response/results_spec.rb +56 -0
  54. data/spec/elasticsearch/model/searching_search_request_spec.rb +112 -0
  55. data/spec/elasticsearch/model/searching_spec.rb +49 -0
  56. data/spec/elasticsearch/model/serializing_spec.rb +22 -0
  57. data/spec/spec_helper.rb +161 -0
  58. data/spec/support/app.rb +21 -0
  59. data/spec/support/app/answer.rb +33 -0
  60. data/spec/support/app/article.rb +22 -0
  61. data/spec/support/app/article_for_pagination.rb +12 -0
  62. data/spec/support/app/article_with_custom_serialization.rb +13 -0
  63. data/spec/support/app/article_with_dynamic_index_name.rb +15 -0
  64. data/spec/support/app/author.rb +9 -0
  65. data/spec/support/app/authorship.rb +4 -0
  66. data/spec/support/app/category.rb +3 -0
  67. data/spec/support/app/comment.rb +3 -0
  68. data/spec/support/app/episode.rb +11 -0
  69. data/spec/support/app/image.rb +19 -0
  70. data/spec/support/app/import_article.rb +12 -0
  71. data/spec/support/app/mongoid_article.rb +21 -0
  72. data/spec/support/app/namespaced_book.rb +10 -0
  73. data/spec/support/app/parent_and_child_searchable.rb +24 -0
  74. data/spec/support/app/post.rb +14 -0
  75. data/spec/support/app/question.rb +27 -0
  76. data/spec/support/app/searchable.rb +48 -0
  77. data/spec/support/app/series.rb +11 -0
  78. data/spec/support/model.json +1 -0
  79. data/{test → spec}/support/model.yml +0 -0
  80. metadata +129 -86
  81. data/test/integration/active_record_associations_parent_child_test.rb +0 -188
  82. data/test/integration/active_record_associations_test.rb +0 -339
  83. data/test/integration/active_record_basic_test.rb +0 -255
  84. data/test/integration/active_record_custom_serialization_test.rb +0 -67
  85. data/test/integration/active_record_import_test.rb +0 -168
  86. data/test/integration/active_record_namespaced_model_test.rb +0 -56
  87. data/test/integration/active_record_pagination_test.rb +0 -149
  88. data/test/integration/dynamic_index_name_test.rb +0 -52
  89. data/test/integration/mongoid_basic_test.rb +0 -240
  90. data/test/integration/multiple_models_test.rb +0 -176
  91. data/test/support/model.json +0 -1
  92. data/test/test_helper.rb +0 -92
  93. data/test/unit/adapter_active_record_test.rb +0 -157
  94. data/test/unit/adapter_default_test.rb +0 -41
  95. data/test/unit/adapter_mongoid_test.rb +0 -161
  96. data/test/unit/adapter_multiple_test.rb +0 -106
  97. data/test/unit/adapter_test.rb +0 -69
  98. data/test/unit/callbacks_test.rb +0 -31
  99. data/test/unit/client_test.rb +0 -27
  100. data/test/unit/hash_wrapper_test.rb +0 -13
  101. data/test/unit/importing_test.rb +0 -224
  102. data/test/unit/indexing_test.rb +0 -720
  103. data/test/unit/module_test.rb +0 -68
  104. data/test/unit/multimodel_test.rb +0 -38
  105. data/test/unit/naming_inheritance_test.rb +0 -94
  106. data/test/unit/naming_test.rb +0 -103
  107. data/test/unit/proxy_test.rb +0 -98
  108. data/test/unit/response_aggregations_test.rb +0 -46
  109. data/test/unit/response_base_test.rb +0 -40
  110. data/test/unit/response_pagination_kaminari_test.rb +0 -433
  111. data/test/unit/response_pagination_will_paginate_test.rb +0 -398
  112. data/test/unit/response_records_test.rb +0 -91
  113. data/test/unit/response_result_test.rb +0 -90
  114. data/test/unit/response_results_test.rb +0 -34
  115. data/test/unit/response_test.rb +0 -104
  116. data/test/unit/searching_search_request_test.rb +0 -78
  117. data/test/unit/searching_test.rb +0 -41
  118. data/test/unit/serializing_test.rb +0 -17
@@ -0,0 +1,75 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Elasticsearch::Model::Adapter::ActiveRecord Parent-Child' do
4
+
5
+ before(:all) do
6
+ ActiveRecord::Schema.define(version: 1) do
7
+ create_table :questions do |t|
8
+ t.string :title
9
+ t.text :text
10
+ t.string :author
11
+ t.timestamps null: false
12
+ end
13
+
14
+ create_table :answers do |t|
15
+ t.text :text
16
+ t.string :author
17
+ t.references :question
18
+ t.timestamps null: false
19
+ end
20
+
21
+ add_index(:answers, :question_id) unless index_exists?(:answers, :question_id)
22
+
23
+ clear_tables(Question)
24
+ ParentChildSearchable.create_index!(force: true)
25
+
26
+ q_1 = Question.create!(title: 'First Question', author: 'John')
27
+ q_2 = Question.create!(title: 'Second Question', author: 'Jody')
28
+
29
+ q_1.answers.create!(text: 'Lorem Ipsum', author: 'Adam')
30
+ q_1.answers.create!(text: 'Dolor Sit', author: 'Ryan')
31
+
32
+ q_2.answers.create!(text: 'Amet Et', author: 'John')
33
+
34
+ Question.__elasticsearch__.refresh_index!
35
+ end
36
+ end
37
+
38
+ describe 'has_child search' do
39
+
40
+ let(:search_result) do
41
+ Question.search(query: { has_child: { type: 'answer', query: { match: { author: 'john' } } } })
42
+ end
43
+
44
+ it 'finds parents by matching on child search criteria' do
45
+ expect(search_result.records.first.title).to eq('Second Question')
46
+ end
47
+ end
48
+
49
+ describe 'hash_parent search' do
50
+
51
+ let(:search_result) do
52
+ Answer.search(query: { has_parent: { parent_type: 'question', query: { match: { author: 'john' } } } })
53
+ end
54
+
55
+ it 'finds children by matching in parent criteria' do
56
+ expect(search_result.records.map(&:author)).to match(['Adam', 'Ryan'])
57
+ end
58
+ end
59
+
60
+ context 'when a parent is deleted' do
61
+
62
+ before do
63
+ Question.where(title: 'First Question').each(&:destroy)
64
+ Question.__elasticsearch__.refresh_index!
65
+ end
66
+
67
+ let(:search_result) do
68
+ Answer.search(query: { has_parent: { parent_type: 'question', query: { match_all: {} } } })
69
+ end
70
+
71
+ it 'deletes the children' do
72
+ expect(search_result.results.total).to eq(1)
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,61 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Elasticsearch::Model::Adapter::ActiveRecord Serialization' do
4
+
5
+ before(:all) do
6
+ ActiveRecord::Schema.define(:version => 1) do
7
+ create_table ArticleWithCustomSerialization.table_name do |t|
8
+ t.string :title
9
+ t.string :status
10
+ end
11
+ end
12
+
13
+ ArticleWithCustomSerialization.delete_all
14
+ ArticleWithCustomSerialization.__elasticsearch__.create_index!(force: true)
15
+ end
16
+
17
+ context 'when the model has a custom serialization defined' do
18
+
19
+ before do
20
+ ArticleWithCustomSerialization.create!(title: 'Test', status: 'green')
21
+ ArticleWithCustomSerialization.__elasticsearch__.refresh_index!
22
+ end
23
+
24
+ context 'when a document is indexed' do
25
+
26
+ let(:search_result) do
27
+ ArticleWithCustomSerialization.__elasticsearch__.client.get(index: 'article_with_custom_serializations',
28
+ type: '_doc',
29
+ id: '1')
30
+ end
31
+
32
+ it 'applies the serialization when indexing' do
33
+ expect(search_result['_source']).to eq('title' => 'Test')
34
+ end
35
+ end
36
+
37
+ context 'when a document is updated' do
38
+
39
+ before do
40
+ article.update_attributes(title: 'UPDATED', status: 'yellow')
41
+ ArticleWithCustomSerialization.__elasticsearch__.refresh_index!
42
+ end
43
+
44
+ let!(:article) do
45
+ art = ArticleWithCustomSerialization.create!(title: 'Test', status: 'red')
46
+ ArticleWithCustomSerialization.__elasticsearch__.refresh_index!
47
+ art
48
+ end
49
+
50
+ let(:search_result) do
51
+ ArticleWithCustomSerialization.__elasticsearch__.client.get(index: 'article_with_custom_serializations',
52
+ type: '_doc',
53
+ id: article.id)
54
+ end
55
+
56
+ it 'applies the serialization when updating' do
57
+ expect(search_result['_source']).to eq('title' => 'UPDATED')
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,207 @@
1
+ require 'spec_helper'
2
+
3
+ describe Elasticsearch::Model::Adapter::ActiveRecord do
4
+
5
+ before(:all) do
6
+ class DummyClassForActiveRecord; end
7
+ end
8
+
9
+ after(:all) do
10
+ Elasticsearch::Model::Adapter::Adapter.adapters.delete(DummyClassForActiveRecord)
11
+ remove_classes(DummyClassForActiveRecord)
12
+ end
13
+
14
+ let(:model) do
15
+ DummyClassForActiveRecord.new.tap do |m|
16
+ allow(m).to receive(:response).and_return(double('response', response: response))
17
+ allow(m).to receive(:ids).and_return(ids)
18
+ end
19
+ end
20
+
21
+ let(:response) do
22
+ { 'hits' => {'hits' => [ {'_id' => 2 }, {'_id' => 1 } ]} }
23
+ end
24
+
25
+ let(:ids) do
26
+ [2, 1]
27
+ end
28
+
29
+ let(:record_1) do
30
+ double('record').tap do |rec|
31
+ allow(rec).to receive(:id).and_return(1)
32
+ end
33
+ end
34
+
35
+ let(:record_2) do
36
+ double('record').tap do |rec|
37
+ allow(rec).to receive(:id).and_return(2)
38
+ end
39
+ end
40
+
41
+ let(:records) do
42
+ [record_1, record_2].tap do |r|
43
+ allow(r).to receive(:load).and_return(true)
44
+ allow(r).to receive(:exec_queries).and_return(true)
45
+ end
46
+ end
47
+
48
+ describe 'adapter registration' do
49
+
50
+ before(:all) do
51
+ DummyClassForActiveRecord.__send__ :include, Elasticsearch::Model::Adapter::ActiveRecord::Records
52
+ end
53
+
54
+ it 'can register an adapater' do
55
+ expect(Elasticsearch::Model::Adapter.adapters[Elasticsearch::Model::Adapter::ActiveRecord]).not_to be_nil
56
+ expect(Elasticsearch::Model::Adapter.adapters[Elasticsearch::Model::Adapter::ActiveRecord].call(DummyClassForActiveRecord)).to be(false)
57
+ end
58
+ end
59
+
60
+ describe '#records' do
61
+
62
+ before(:all) do
63
+ DummyClassForActiveRecord.__send__ :include, Elasticsearch::Model::Adapter::ActiveRecord::Records
64
+ end
65
+
66
+ let(:instance) do
67
+ model.tap do |inst|
68
+ allow(inst).to receive(:klass).and_return(double('class', primary_key: :some_key, where: records)).at_least(:once)
69
+ allow(inst).to receive(:order).and_return(double('class', primary_key: :some_key, where: records)).at_least(:once)
70
+ end
71
+ end
72
+
73
+ it 'returns the list of records' do
74
+ expect(instance.records).to eq(records)
75
+ end
76
+
77
+ it 'loads the records' do
78
+ expect(instance.load).to eq(true)
79
+ end
80
+
81
+ context 'when :includes is specified' do
82
+
83
+ before do
84
+ expect(records).to receive(:includes).with([:submodel]).once.and_return(records)
85
+ instance.options[:includes] = [:submodel]
86
+ end
87
+
88
+ it 'incorporates the includes option in the query' do
89
+ expect(instance.records).to eq(records)
90
+ end
91
+ end
92
+ end
93
+
94
+ describe 'callbacks registration' do
95
+
96
+ before do
97
+ expect(DummyClassForActiveRecord).to receive(:after_commit).exactly(3).times
98
+ end
99
+
100
+ it 'should register the model class for callbacks' do
101
+ Elasticsearch::Model::Adapter::ActiveRecord::Callbacks.included(DummyClassForActiveRecord)
102
+ end
103
+ end
104
+
105
+ describe 'importing' do
106
+
107
+ before do
108
+ DummyClassForActiveRecord.__send__ :extend, Elasticsearch::Model::Adapter::ActiveRecord::Importing
109
+ end
110
+
111
+ context 'when an invalid scope is specified' do
112
+
113
+ it 'raises a NoMethodError' do
114
+ expect {
115
+ DummyClassForActiveRecord.__find_in_batches(scope: :not_found_method)
116
+ }.to raise_exception(NoMethodError)
117
+ end
118
+ end
119
+
120
+ context 'when a valid scope is specified' do
121
+
122
+ before do
123
+ expect(DummyClassForActiveRecord).to receive(:find_in_batches).once.and_return([])
124
+ expect(DummyClassForActiveRecord).to receive(:published).once.and_return(DummyClassForActiveRecord)
125
+ end
126
+
127
+ it 'uses the scope' do
128
+ expect(DummyClassForActiveRecord.__find_in_batches(scope: :published)).to eq([])
129
+ end
130
+ end
131
+
132
+ context 'allow query criteria to be specified' do
133
+
134
+ before do
135
+ expect(DummyClassForActiveRecord).to receive(:find_in_batches).once.and_return([])
136
+ expect(DummyClassForActiveRecord).to receive(:where).with(color: 'red').once.and_return(DummyClassForActiveRecord)
137
+ end
138
+
139
+ it 'uses the scope' do
140
+ expect(DummyClassForActiveRecord.__find_in_batches(query: -> { where(color: 'red') })).to eq([])
141
+ end
142
+ end
143
+
144
+ context 'when preprocessing batches' do
145
+
146
+ context 'if the query returns results' do
147
+
148
+ before do
149
+ class << DummyClassForActiveRecord
150
+ def find_in_batches(options = {}, &block)
151
+ yield [:a, :b]
152
+ end
153
+
154
+ def update_batch(batch)
155
+ batch.collect { |b| b.to_s + '!' }
156
+ end
157
+ end
158
+ end
159
+
160
+ it 'applies the preprocessing method' do
161
+ DummyClassForActiveRecord.__find_in_batches(preprocess: :update_batch) do |batch|
162
+ expect(batch).to match(['a!', 'b!'])
163
+ end
164
+ end
165
+ end
166
+
167
+ context 'if the query does not return results' do
168
+
169
+ before do
170
+ class << DummyClassForActiveRecord
171
+ def find_in_batches(options = {}, &block)
172
+ yield [:a, :b]
173
+ end
174
+
175
+ def update_batch(batch)
176
+ []
177
+ end
178
+ end
179
+ end
180
+
181
+ it 'applies the preprocessing method' do
182
+ DummyClassForActiveRecord.__find_in_batches(preprocess: :update_batch) do |batch|
183
+ expect(batch).to match([])
184
+ end
185
+ end
186
+ end
187
+ end
188
+
189
+ context 'when transforming models' do
190
+
191
+ let(:instance) do
192
+ model.tap do |inst|
193
+ allow(inst).to receive(:id).and_return(1)
194
+ allow(inst).to receive(:__elasticsearch__).and_return(double('object', id: 1, as_indexed_json: {}))
195
+ end
196
+ end
197
+
198
+ it 'returns an proc' do
199
+ expect(DummyClassForActiveRecord.__transform.respond_to?(:call)).to be(true)
200
+ end
201
+
202
+ it 'provides a default transformation' do
203
+ expect(DummyClassForActiveRecord.__transform.call(instance)).to eq(index: { _id: 1, data: {} })
204
+ end
205
+ end
206
+ end
207
+ end
@@ -0,0 +1,41 @@
1
+ require 'spec_helper'
2
+
3
+ describe Elasticsearch::Model::Adapter::Default do
4
+
5
+ before(:all) do
6
+ class DummyClassForDefaultAdapter; end
7
+ DummyClassForDefaultAdapter.__send__ :include, Elasticsearch::Model::Adapter::Default::Records
8
+ DummyClassForDefaultAdapter.__send__ :include, Elasticsearch::Model::Adapter::Default::Importing
9
+ end
10
+
11
+ after(:all) do
12
+ Elasticsearch::Model::Adapter::Adapter.adapters.delete(DummyClassForDefaultAdapter)
13
+ remove_classes(DummyClassForDefaultAdapter)
14
+ end
15
+
16
+ let(:instance) do
17
+ DummyClassForDefaultAdapter.new.tap do |m|
18
+ allow(m).to receive(:klass).and_return(double('class', primary_key: :some_key, find: [1])).at_least(:once)
19
+ end
20
+ end
21
+
22
+ it 'should have the default records implementation' do
23
+ expect(instance.records).to eq([1])
24
+ end
25
+
26
+ it 'should have the default Callback implementation' do
27
+ expect(Elasticsearch::Model::Adapter::Default::Callbacks).to be_a(Module)
28
+ end
29
+
30
+ it 'should have the default Importing implementation' do
31
+ expect {
32
+ DummyClassForDefaultAdapter.new.__find_in_batches
33
+ }.to raise_exception(Elasticsearch::Model::NotImplemented)
34
+ end
35
+
36
+ it 'should have the default transform implementation' do
37
+ expect {
38
+ DummyClassForDefaultAdapter.new.__transform
39
+ }.to raise_exception(Elasticsearch::Model::NotImplemented)
40
+ end
41
+ end
@@ -0,0 +1,267 @@
1
+ require 'spec_helper'
2
+
3
+ describe Elasticsearch::Model::Adapter::Mongoid, if: test_mongoid? do
4
+
5
+ before(:all) do
6
+ connect_mongoid('mongoid_test')
7
+ Elasticsearch::Model::Adapter.register \
8
+ Elasticsearch::Model::Adapter::Mongoid,
9
+ lambda { |klass| !!defined?(::Mongoid::Document) && klass.respond_to?(:ancestors) && klass.ancestors.include?(::Mongoid::Document) }
10
+
11
+ MongoidArticle.__elasticsearch__.create_index! force: true
12
+
13
+ MongoidArticle.delete_all
14
+
15
+ MongoidArticle.__elasticsearch__.refresh_index!
16
+ MongoidArticle.__elasticsearch__.client.cluster.health wait_for_status: 'yellow'
17
+ end
18
+
19
+ after do
20
+ clear_indices(MongoidArticle)
21
+ clear_tables(MongoidArticle)
22
+ end
23
+
24
+ describe 'searching' do
25
+
26
+ before do
27
+ MongoidArticle.create! title: 'Test'
28
+ MongoidArticle.create! title: 'Testing Coding'
29
+ MongoidArticle.create! title: 'Coding'
30
+ MongoidArticle.__elasticsearch__.refresh_index!
31
+ end
32
+
33
+ let(:search_result) do
34
+ MongoidArticle.search('title:test')
35
+ end
36
+
37
+ it 'find the documents successfully' do
38
+ expect(search_result.results.size).to eq(2)
39
+ expect(search_result.records.size).to eq(2)
40
+ end
41
+
42
+ describe '#results' do
43
+
44
+ it 'returns a Elasticsearch::Model::Response::Result' do
45
+ expect(search_result.results.first).to be_a(Elasticsearch::Model::Response::Result)
46
+ end
47
+
48
+ it 'retrieves the document from Elasticsearch' do
49
+ expect(search_result.results.first.title).to eq('Test')
50
+ end
51
+
52
+ it 'retrieves all results' do
53
+ expect(search_result.results.collect(&:title)).to match(['Test', 'Testing Coding'])
54
+ end
55
+ end
56
+
57
+ describe '#records' do
58
+
59
+ it 'returns an instance of the model' do
60
+ expect(search_result.records.first).to be_a(MongoidArticle)
61
+ end
62
+
63
+ it 'retrieves the document from Elasticsearch' do
64
+ expect(search_result.records.first.title).to eq('Test')
65
+ end
66
+
67
+ it 'iterates over the records' do
68
+ expect(search_result.records.first.title).to eq('Test')
69
+ end
70
+
71
+ it 'retrieves all records' do
72
+ expect(search_result.records.collect(&:title)).to match(['Test', 'Testing Coding'])
73
+ end
74
+
75
+ describe '#each_with_hit' do
76
+
77
+ it 'yields each hit with the model object' do
78
+ search_result.records.each_with_hit do |r, h|
79
+ expect(h._source).not_to be_nil
80
+ expect(h._source.title).not_to be_nil
81
+ end
82
+ end
83
+
84
+ it 'preserves the search order' do
85
+ search_result.records.each_with_hit do |r, h|
86
+ expect(r.id.to_s).to eq(h._id)
87
+ end
88
+ end
89
+ end
90
+
91
+ describe '#map_with_hit' do
92
+
93
+ it 'yields each hit with the model object' do
94
+ search_result.records.map_with_hit do |r, h|
95
+ expect(h._source).not_to be_nil
96
+ expect(h._source.title).not_to be_nil
97
+ end
98
+ end
99
+
100
+ it 'preserves the search order' do
101
+ search_result.records.map_with_hit do |r, h|
102
+ expect(r.id.to_s).to eq(h._id)
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
108
+
109
+ describe '#destroy' do
110
+
111
+ let(:article) do
112
+ MongoidArticle.create!(title: 'Test')
113
+ end
114
+
115
+ before do
116
+ article
117
+ MongoidArticle.create!(title: 'Coding')
118
+ article.destroy
119
+ MongoidArticle.__elasticsearch__.refresh_index!
120
+ end
121
+
122
+ it 'removes documents from the index' do
123
+ expect(MongoidArticle.search('title:test').results.total).to eq(0)
124
+ expect(MongoidArticle.search('title:code').results.total).to eq(1)
125
+ end
126
+ end
127
+
128
+ describe 'updates to the document' do
129
+
130
+ let(:article) do
131
+ MongoidArticle.create!(title: 'Test')
132
+ end
133
+
134
+ before do
135
+ article.title = 'Writing'
136
+ article.save
137
+ MongoidArticle.__elasticsearch__.refresh_index!
138
+ end
139
+
140
+ it 'indexes updates' do
141
+ expect(MongoidArticle.search('title:write').results.total).to eq(1)
142
+ expect(MongoidArticle.search('title:test').results.total).to eq(0)
143
+ end
144
+ end
145
+
146
+ describe 'DSL search' do
147
+
148
+ before do
149
+ MongoidArticle.create! title: 'Test'
150
+ MongoidArticle.create! title: 'Testing Coding'
151
+ MongoidArticle.create! title: 'Coding'
152
+ MongoidArticle.__elasticsearch__.refresh_index!
153
+ end
154
+
155
+ let(:search_result) do
156
+ MongoidArticle.search(query: { match: { title: { query: 'test' } } })
157
+ end
158
+
159
+ it 'finds the matching documents' do
160
+ expect(search_result.results.size).to eq(2)
161
+ expect(search_result.records.size).to eq(2)
162
+ end
163
+ end
164
+
165
+ describe 'paging a collection' do
166
+
167
+ before do
168
+ MongoidArticle.create! title: 'Test'
169
+ MongoidArticle.create! title: 'Testing Coding'
170
+ MongoidArticle.create! title: 'Coding'
171
+ MongoidArticle.__elasticsearch__.refresh_index!
172
+ end
173
+
174
+ let(:search_result) do
175
+ MongoidArticle.search(query: { match: { title: { query: 'test' } } },
176
+ size: 2,
177
+ from: 1)
178
+ end
179
+
180
+ it 'applies the size and from parameters' do
181
+ expect(search_result.results.size).to eq(1)
182
+ expect(search_result.results.first.title).to eq('Testing Coding')
183
+ expect(search_result.records.size).to eq(1)
184
+ expect(search_result.records.first.title).to eq('Testing Coding')
185
+ end
186
+ end
187
+
188
+ describe 'importing' do
189
+
190
+ before do
191
+ 97.times { |i| MongoidArticle.create! title: "Test #{i}" }
192
+ MongoidArticle.__elasticsearch__.create_index! force: true
193
+ MongoidArticle.__elasticsearch__.client.cluster.health wait_for_status: 'yellow'
194
+ end
195
+
196
+ context 'when there is no default scope' do
197
+
198
+ let!(:batch_count) do
199
+ batches = 0
200
+ errors = MongoidArticle.import(batch_size: 10) do |response|
201
+ batches += 1
202
+ end
203
+ MongoidArticle.__elasticsearch__.refresh_index!
204
+ batches
205
+ end
206
+
207
+ it 'imports all the documents' do
208
+ expect(MongoidArticle.search('*').results.total).to eq(97)
209
+ end
210
+
211
+ it 'uses the specified batch size' do
212
+ expect(batch_count).to eq(10)
213
+ end
214
+ end
215
+
216
+ context 'when there is a default scope' do
217
+
218
+ around(:all) do |example|
219
+ 10.times { |i| MongoidArticle.create! title: 'Test', views: "#{i}" }
220
+ MongoidArticle.default_scope -> { MongoidArticle.gt(views: 3) }
221
+ example.run
222
+ MongoidArticle.default_scoping = nil
223
+ end
224
+
225
+ before do
226
+ MongoidArticle.__elasticsearch__.import
227
+ MongoidArticle.__elasticsearch__.refresh_index!
228
+ end
229
+
230
+ it 'uses the default scope' do
231
+ expect(MongoidArticle.search('*').results.total).to eq(6)
232
+ end
233
+ end
234
+
235
+ context 'when there is a default scope and a query specified' do
236
+
237
+ around(:all) do |example|
238
+ 10.times { |i| MongoidArticle.create! title: 'Test', views: "#{i}" }
239
+ MongoidArticle.default_scope -> { MongoidArticle.gt(views: 3) }
240
+ example.run
241
+ MongoidArticle.default_scoping = nil
242
+ end
243
+
244
+ before do
245
+ MongoidArticle.import(query: -> { lte(views: 4) })
246
+ MongoidArticle.__elasticsearch__.refresh_index!
247
+ end
248
+
249
+ it 'combines the query and the default scope' do
250
+ expect(MongoidArticle.search('*').results.total).to eq(1)
251
+ end
252
+ end
253
+
254
+ context 'when the batch is empty' do
255
+
256
+ before do
257
+ MongoidArticle.delete_all
258
+ MongoidArticle.import
259
+ MongoidArticle.__elasticsearch__.refresh_index!
260
+ end
261
+
262
+ it 'does not make any requests to create documents' do
263
+ expect(MongoidArticle.search('*').results.total).to eq(0)
264
+ end
265
+ end
266
+ end
267
+ end