elasticsearch-model 6.0.0 → 6.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 (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