elasticsearch-model 5.1.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 (125) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +5 -0
  3. data/README.md +18 -11
  4. data/Rakefile +27 -36
  5. data/elasticsearch-model.gemspec +5 -5
  6. data/examples/activerecord_custom_analyzer.rb +135 -0
  7. data/examples/activerecord_mapping_completion.rb +3 -18
  8. data/examples/datamapper_article.rb +11 -1
  9. data/gemfiles/3.0.gemfile +6 -1
  10. data/gemfiles/4.0.gemfile +7 -1
  11. data/gemfiles/5.0.gemfile +7 -1
  12. data/lib/elasticsearch/model/adapters/active_record.rb +7 -23
  13. data/lib/elasticsearch/model/adapters/mongoid.rb +10 -3
  14. data/lib/elasticsearch/model/importing.rb +1 -1
  15. data/lib/elasticsearch/model/indexing.rb +6 -4
  16. data/lib/elasticsearch/model/naming.rb +9 -2
  17. data/lib/elasticsearch/model/response/aggregations.rb +1 -1
  18. data/lib/elasticsearch/model/response/base.rb +3 -2
  19. data/lib/elasticsearch/model/response/pagination/kaminari.rb +109 -0
  20. data/lib/elasticsearch/model/response/pagination/will_paginate.rb +95 -0
  21. data/lib/elasticsearch/model/response/pagination.rb +2 -192
  22. data/lib/elasticsearch/model/response/result.rb +1 -1
  23. data/lib/elasticsearch/model/response/suggestions.rb +1 -1
  24. data/lib/elasticsearch/model/response.rb +11 -10
  25. data/lib/elasticsearch/model/version.rb +1 -1
  26. data/lib/elasticsearch/model.rb +15 -8
  27. data/spec/elasticsearch/model/adapter_spec.rb +119 -0
  28. data/spec/elasticsearch/model/adapters/active_record/associations_spec.rb +334 -0
  29. data/spec/elasticsearch/model/adapters/active_record/basic_spec.rb +340 -0
  30. data/spec/elasticsearch/model/adapters/active_record/dynamic_index_name_spec.rb +18 -0
  31. data/spec/elasticsearch/model/adapters/active_record/import_spec.rb +187 -0
  32. data/spec/elasticsearch/model/adapters/active_record/multi_model_spec.rb +110 -0
  33. data/spec/elasticsearch/model/adapters/active_record/namespaced_model_spec.rb +38 -0
  34. data/spec/elasticsearch/model/adapters/active_record/pagination_spec.rb +315 -0
  35. data/spec/elasticsearch/model/adapters/active_record/parent_child_spec.rb +75 -0
  36. data/spec/elasticsearch/model/adapters/active_record/serialization_spec.rb +61 -0
  37. data/spec/elasticsearch/model/adapters/active_record_spec.rb +207 -0
  38. data/spec/elasticsearch/model/adapters/default_spec.rb +41 -0
  39. data/spec/elasticsearch/model/adapters/mongoid/basic_spec.rb +267 -0
  40. data/spec/elasticsearch/model/adapters/mongoid/multi_model_spec.rb +66 -0
  41. data/spec/elasticsearch/model/adapters/mongoid_spec.rb +235 -0
  42. data/spec/elasticsearch/model/adapters/multiple_spec.rb +125 -0
  43. data/spec/elasticsearch/model/callbacks_spec.rb +33 -0
  44. data/spec/elasticsearch/model/client_spec.rb +66 -0
  45. data/spec/elasticsearch/model/hash_wrapper_spec.rb +12 -0
  46. data/spec/elasticsearch/model/importing_spec.rb +214 -0
  47. data/spec/elasticsearch/model/indexing_spec.rb +918 -0
  48. data/spec/elasticsearch/model/module_spec.rb +101 -0
  49. data/spec/elasticsearch/model/multimodel_spec.rb +55 -0
  50. data/spec/elasticsearch/model/naming_inheritance_spec.rb +184 -0
  51. data/spec/elasticsearch/model/naming_spec.rb +186 -0
  52. data/spec/elasticsearch/model/proxy_spec.rb +107 -0
  53. data/spec/elasticsearch/model/response/aggregations_spec.rb +66 -0
  54. data/spec/elasticsearch/model/response/base_spec.rb +90 -0
  55. data/spec/elasticsearch/model/response/pagination/kaminari_spec.rb +410 -0
  56. data/spec/elasticsearch/model/response/pagination/will_paginate_spec.rb +262 -0
  57. data/spec/elasticsearch/model/response/records_spec.rb +118 -0
  58. data/spec/elasticsearch/model/response/response_spec.rb +131 -0
  59. data/spec/elasticsearch/model/response/result_spec.rb +122 -0
  60. data/spec/elasticsearch/model/response/results_spec.rb +56 -0
  61. data/spec/elasticsearch/model/searching_search_request_spec.rb +112 -0
  62. data/spec/elasticsearch/model/searching_spec.rb +49 -0
  63. data/spec/elasticsearch/model/serializing_spec.rb +22 -0
  64. data/spec/spec_helper.rb +161 -0
  65. data/spec/support/app/answer.rb +33 -0
  66. data/spec/support/app/article.rb +22 -0
  67. data/spec/support/app/article_for_pagination.rb +12 -0
  68. data/spec/support/app/article_with_custom_serialization.rb +13 -0
  69. data/spec/support/app/article_with_dynamic_index_name.rb +15 -0
  70. data/spec/support/app/author.rb +9 -0
  71. data/spec/support/app/authorship.rb +4 -0
  72. data/spec/support/app/category.rb +3 -0
  73. data/spec/support/app/comment.rb +3 -0
  74. data/spec/support/app/episode.rb +11 -0
  75. data/spec/support/app/image.rb +19 -0
  76. data/spec/support/app/import_article.rb +12 -0
  77. data/spec/support/app/mongoid_article.rb +21 -0
  78. data/spec/support/app/namespaced_book.rb +10 -0
  79. data/spec/support/app/parent_and_child_searchable.rb +24 -0
  80. data/spec/support/app/post.rb +14 -0
  81. data/spec/support/app/question.rb +27 -0
  82. data/spec/support/app/searchable.rb +48 -0
  83. data/spec/support/app/series.rb +11 -0
  84. data/spec/support/app.rb +21 -0
  85. data/spec/support/model.json +1 -0
  86. metadata +134 -90
  87. data/test/integration/active_record_associations_parent_child_test.rb +0 -147
  88. data/test/integration/active_record_associations_test.rb +0 -339
  89. data/test/integration/active_record_basic_test.rb +0 -251
  90. data/test/integration/active_record_custom_serialization_test.rb +0 -67
  91. data/test/integration/active_record_import_test.rb +0 -115
  92. data/test/integration/active_record_namespaced_model_test.rb +0 -54
  93. data/test/integration/active_record_pagination_test.rb +0 -149
  94. data/test/integration/dynamic_index_name_test.rb +0 -52
  95. data/test/integration/mongoid_basic_test.rb +0 -176
  96. data/test/integration/multiple_models_test.rb +0 -176
  97. data/test/support/model.json +0 -1
  98. data/test/test_helper.rb +0 -92
  99. data/test/unit/adapter_active_record_test.rb +0 -157
  100. data/test/unit/adapter_default_test.rb +0 -41
  101. data/test/unit/adapter_mongoid_test.rb +0 -104
  102. data/test/unit/adapter_multiple_test.rb +0 -106
  103. data/test/unit/adapter_test.rb +0 -69
  104. data/test/unit/callbacks_test.rb +0 -31
  105. data/test/unit/client_test.rb +0 -27
  106. data/test/unit/hash_wrapper_test.rb +0 -13
  107. data/test/unit/importing_test.rb +0 -203
  108. data/test/unit/indexing_test.rb +0 -687
  109. data/test/unit/module_test.rb +0 -68
  110. data/test/unit/multimodel_test.rb +0 -38
  111. data/test/unit/naming_inheritance_test.rb +0 -94
  112. data/test/unit/naming_test.rb +0 -103
  113. data/test/unit/proxy_test.rb +0 -98
  114. data/test/unit/response_aggregations_test.rb +0 -46
  115. data/test/unit/response_base_test.rb +0 -40
  116. data/test/unit/response_pagination_kaminari_test.rb +0 -433
  117. data/test/unit/response_pagination_will_paginate_test.rb +0 -398
  118. data/test/unit/response_records_test.rb +0 -91
  119. data/test/unit/response_result_test.rb +0 -90
  120. data/test/unit/response_results_test.rb +0 -31
  121. data/test/unit/response_test.rb +0 -104
  122. data/test/unit/searching_search_request_test.rb +0 -78
  123. data/test/unit/searching_test.rb +0 -41
  124. data/test/unit/serializing_test.rb +0 -17
  125. /data/{test → spec}/support/model.yml +0 -0
@@ -0,0 +1,262 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Elasticsearch::Model::Response::Response WillPaginate' do
4
+
5
+ before(:all) do
6
+ class ModelClass
7
+ def self.index_name; 'foo'; end
8
+ def self.document_type; 'bar'; end
9
+
10
+ def self.per_page
11
+ 33
12
+ end
13
+ end
14
+
15
+ # Subclass Response so we can include WillPaginate module without conflicts with Kaminari.
16
+ class WillPaginateResponse < Elasticsearch::Model::Response::Response
17
+ include Elasticsearch::Model::Response::Pagination::WillPaginate
18
+ end
19
+ end
20
+
21
+ after(:all) do
22
+ remove_classes(ModelClass, WillPaginateResponse)
23
+ end
24
+
25
+ let(:response_document) do
26
+ { 'took' => '5', 'timed_out' => false, '_shards' => {'one' => 'OK'},
27
+ 'hits' => { 'total' => 100, 'hits' => (1..100).to_a.map { |i| { _id: i } } } }
28
+ end
29
+
30
+ let(:search) do
31
+ Elasticsearch::Model::Searching::SearchRequest.new(model, '*')
32
+ end
33
+
34
+ let(:response) do
35
+ allow(model).to receive(:client).and_return(client)
36
+ WillPaginateResponse.new(model, search, response_document).tap do |resp|
37
+ allow(resp).to receive(:client).and_return(client)
38
+ end
39
+ end
40
+
41
+ let(:client) do
42
+ double('client')
43
+ end
44
+
45
+ shared_examples_for 'a search request that can be paginated' do
46
+
47
+ describe '#offset' do
48
+
49
+ context 'when per_page and page are set' do
50
+
51
+ before do
52
+ response.per_page(3).page(3)
53
+ end
54
+
55
+ it 'sets the correct offset' do
56
+ expect(response.offset).to eq(6)
57
+ end
58
+ end
59
+ end
60
+
61
+ describe '#length' do
62
+
63
+ context 'when per_page and page are set' do
64
+
65
+ before do
66
+ response.per_page(3).page(3)
67
+ end
68
+
69
+ it 'sets the correct offset' do
70
+ expect(response.length).to eq(3)
71
+ end
72
+ end
73
+ end
74
+
75
+ describe '#paginate' do
76
+
77
+ context 'when there are no settings' do
78
+
79
+ context 'when page is set to nil' do
80
+
81
+ before do
82
+ response.paginate(page: nil)
83
+ end
84
+
85
+ it 'uses the defaults' do
86
+ expect(response.search.definition[:size]).to eq(default_per_page)
87
+ expect(response.search.definition[:from]).to eq(0)
88
+ end
89
+ end
90
+
91
+ context 'when page is set to a value' do
92
+
93
+ before do
94
+ response.paginate(page: 2)
95
+ end
96
+
97
+ it 'uses the defaults' do
98
+ expect(response.search.definition[:size]).to eq(default_per_page)
99
+ expect(response.search.definition[:from]).to eq(default_per_page)
100
+ end
101
+ end
102
+
103
+ context 'when a custom page and per_page is set' do
104
+
105
+ before do
106
+ response.paginate(page: 3, per_page: 9)
107
+ end
108
+
109
+ it 'uses the custom values' do
110
+ expect(response.search.definition[:size]).to eq(9)
111
+ expect(response.search.definition[:from]).to eq(18)
112
+ end
113
+ end
114
+
115
+ context 'fall back to first page if invalid value is provided' do
116
+
117
+ before do
118
+ response.paginate(page: -1)
119
+ end
120
+
121
+ it 'uses the custom values' do
122
+ expect(response.search.definition[:size]).to eq(default_per_page)
123
+ expect(response.search.definition[:from]).to eq(0)
124
+ end
125
+ end
126
+ end
127
+ end
128
+
129
+ describe '#page' do
130
+
131
+ context 'when a value is provided for page' do
132
+
133
+ before do
134
+ response.page(5)
135
+ end
136
+
137
+ it 'calculates the correct :size and :from' do
138
+ expect(response.search.definition[:size]).to eq(default_per_page)
139
+ expect(response.search.definition[:from]).to eq(default_per_page * 4)
140
+ end
141
+ end
142
+
143
+ context 'when a value is provided for page and per_page' do
144
+
145
+ before do
146
+ response.page(5).per_page(3)
147
+ end
148
+
149
+ it 'calculates the correct :size and :from' do
150
+ expect(response.search.definition[:size]).to eq(3)
151
+ expect(response.search.definition[:from]).to eq(12)
152
+ end
153
+ end
154
+
155
+ context 'when a value is provided for per_page and page' do
156
+
157
+ before do
158
+ response.per_page(3).page(5)
159
+ end
160
+
161
+ it 'calculates the correct :size and :from' do
162
+ expect(response.search.definition[:size]).to eq(3)
163
+ expect(response.search.definition[:from]).to eq(12)
164
+ end
165
+ end
166
+ end
167
+
168
+ describe '#current_page' do
169
+
170
+ context 'when no values are set' do
171
+
172
+ before do
173
+ response.paginate({})
174
+ end
175
+
176
+ it 'returns the first page' do
177
+ expect(response.current_page).to eq(1)
178
+ end
179
+ end
180
+
181
+ context 'when values are provided for per_page and page' do
182
+
183
+ before do
184
+ response.paginate(page: 3, per_page: 9)
185
+ end
186
+
187
+ it 'calculates the correct current page' do
188
+ expect(response.current_page).to eq(3)
189
+ end
190
+ end
191
+
192
+ context 'when #paginate has not been called on the response' do
193
+
194
+ it 'returns nil' do
195
+ expect(response.current_page).to be_nil
196
+ end
197
+ end
198
+ end
199
+
200
+ describe '#per_page' do
201
+
202
+ context 'when a value is set via the #paginate method' do
203
+
204
+ before do
205
+ response.paginate(per_page: 8)
206
+ end
207
+
208
+ it 'returns the per_page value' do
209
+ expect(response.per_page).to eq(8)
210
+ end
211
+ end
212
+
213
+ context 'when a value is set via the #per_page method' do
214
+
215
+ before do
216
+ response.per_page(8)
217
+ end
218
+
219
+ it 'returns the per_page value' do
220
+ expect(response.per_page).to eq(8)
221
+ end
222
+ end
223
+ end
224
+
225
+ describe '#total_entries' do
226
+
227
+ before do
228
+ allow(response).to receive(:results).and_return(double('results', total: 100))
229
+ end
230
+
231
+ it 'returns the total results' do
232
+ expect(response.total_entries).to eq(100)
233
+ end
234
+ end
235
+ end
236
+
237
+ context 'when the model is a single one' do
238
+
239
+ let(:model) do
240
+ ModelClass
241
+ end
242
+
243
+ let(:default_per_page) do
244
+ 33
245
+ end
246
+
247
+ it_behaves_like 'a search request that can be paginated'
248
+ end
249
+
250
+ context 'when the model is a multimodel' do
251
+
252
+ let(:model) do
253
+ Elasticsearch::Model::Multimodel.new(ModelClass)
254
+ end
255
+
256
+ let(:default_per_page) do
257
+ ::WillPaginate.per_page
258
+ end
259
+
260
+ it_behaves_like 'a search request that can be paginated'
261
+ end
262
+ end
@@ -0,0 +1,118 @@
1
+ require 'spec_helper'
2
+
3
+ describe Elasticsearch::Model::Response::Records do
4
+
5
+ before(:all) do
6
+ class DummyCollection
7
+ include Enumerable
8
+
9
+ def each(&block); ['FOO'].each(&block); end
10
+ def size; ['FOO'].size; end
11
+ def empty?; ['FOO'].empty?; end
12
+ def foo; 'BAR'; end
13
+ end
14
+
15
+ class DummyModel
16
+ def self.index_name; 'foo'; end
17
+ def self.document_type; 'bar'; end
18
+
19
+ def self.find(*args)
20
+ DummyCollection.new
21
+ end
22
+ end
23
+ end
24
+
25
+ after(:all) do
26
+ remove_classes(DummyCollection, DummyModel)
27
+ end
28
+
29
+ let(:response_document) do
30
+ { 'hits' => { 'total' => 123, 'max_score' => 456, 'hits' => [{'_id' => '1', 'foo' => 'bar'}] } }
31
+ end
32
+
33
+ let(:results) do
34
+ Elasticsearch::Model::Response::Results.new(DummyModel, response_document)
35
+ end
36
+
37
+ let(:search) do
38
+ Elasticsearch::Model::Searching::SearchRequest.new(DummyModel, '*').tap do |request|
39
+ allow(request).to receive(:execute!).and_return(response_document)
40
+ end
41
+ end
42
+
43
+ let(:response) do
44
+ Elasticsearch::Model::Response::Response.new(DummyModel, search)
45
+ end
46
+
47
+ let(:records) do
48
+ described_class.new(DummyModel, response)
49
+ end
50
+
51
+ context 'when the records are accessed' do
52
+
53
+ it 'returns the records' do
54
+ expect(records.records.size).to eq(1)
55
+ expect(records.records.first).to eq('FOO')
56
+ end
57
+
58
+ it 'delegates methods to records' do
59
+ expect(records.foo).to eq('BAR')
60
+ end
61
+ end
62
+
63
+ describe '#each_with_hit' do
64
+
65
+ it 'returns each record with its Elasticsearch hit' do
66
+ records.each_with_hit do |record, hit|
67
+ expect(record).to eq('FOO')
68
+ expect(hit.foo).to eq('bar')
69
+ end
70
+ end
71
+ end
72
+
73
+ describe '#map_with_hit' do
74
+
75
+ let(:value) do
76
+ records.map_with_hit { |record, hit| "#{record}---#{hit.foo}" }
77
+ end
78
+
79
+ it 'returns each record with its Elasticsearch hit' do
80
+ expect(value).to eq(['FOO---bar'])
81
+ end
82
+ end
83
+
84
+ describe '#ids' do
85
+
86
+ it 'returns the ids' do
87
+ expect(records.ids).to eq(['1'])
88
+ end
89
+ end
90
+
91
+ context 'when an adapter is used' do
92
+
93
+ before do
94
+ module DummyAdapter
95
+ module RecordsMixin
96
+ def records
97
+ ['FOOBAR']
98
+ end
99
+ end
100
+
101
+ def records_mixin
102
+ RecordsMixin
103
+ end; module_function :records_mixin
104
+ end
105
+
106
+ allow(Elasticsearch::Model::Adapter).to receive(:from_class).and_return(DummyAdapter)
107
+ end
108
+
109
+ after do
110
+ Elasticsearch::Model::Adapter::Adapter.adapters.delete(DummyAdapter)
111
+ Object.send(:remove_const, :DummyAdapter) if defined?(DummyAdapter)
112
+ end
113
+
114
+ it 'delegates the records method to the adapter' do
115
+ expect(records.records).to eq(['FOOBAR'])
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,131 @@
1
+ require 'spec_helper'
2
+
3
+ describe Elasticsearch::Model::Response::Response do
4
+
5
+ before(:all) do
6
+ class OriginClass
7
+ def self.index_name; 'foo'; end
8
+ def self.document_type; 'bar'; end
9
+ end
10
+ end
11
+
12
+ after(:all) do
13
+ remove_classes(OriginClass)
14
+ end
15
+
16
+ let(:response_document) do
17
+ { 'took' => '5', 'timed_out' => false, '_shards' => {'one' => 'OK'}, 'hits' => { 'hits' => [] },
18
+ 'aggregations' => {'foo' => {'bar' => 10}},
19
+ 'suggest' => {'my_suggest' => [ { 'text' => 'foo', 'options' => [ { 'text' => 'Foo', 'score' => 2.0 },
20
+ { 'text' => 'Bar', 'score' => 1.0 } ] } ]}}
21
+
22
+ end
23
+
24
+ let(:search) do
25
+ Elasticsearch::Model::Searching::SearchRequest.new(OriginClass, '*').tap do |request|
26
+ allow(request).to receive(:execute!).and_return(response_document)
27
+ end
28
+ end
29
+
30
+ let(:response) do
31
+ Elasticsearch::Model::Response::Response.new(OriginClass, search)
32
+ end
33
+
34
+ it 'performs the Elasticsearch request lazily' do
35
+ expect(search).not_to receive(:execute!)
36
+ response
37
+ end
38
+
39
+ describe '#klass' do
40
+
41
+ it 'returns the class' do
42
+ expect(response.klass).to be(OriginClass)
43
+ end
44
+ end
45
+
46
+ describe '#search' do
47
+
48
+ it 'returns the search object' do
49
+ expect(response.search).to eq(search)
50
+ end
51
+ end
52
+
53
+ describe '#took' do
54
+
55
+ it 'returns the took field' do
56
+ expect(response.took).to eq('5')
57
+ end
58
+ end
59
+
60
+ describe '#timed_out' do
61
+
62
+ it 'returns the timed_out field' do
63
+ expect(response.timed_out).to eq(false)
64
+ end
65
+ end
66
+
67
+ describe '#shards' do
68
+
69
+ it 'returns a Hashie::Mash' do
70
+ expect(response.shards.one).to eq('OK')
71
+ end
72
+ end
73
+
74
+ describe '#response' do
75
+
76
+ it 'returns the response document' do
77
+ expect(response.response).to eq(response_document)
78
+ end
79
+ end
80
+
81
+ describe '#results' do
82
+
83
+ it 'provides access to the results' do
84
+ expect(response.results).to be_a(Elasticsearch::Model::Response::Results)
85
+ expect(response.size).to be(0)
86
+ end
87
+ end
88
+
89
+ describe '#records' do
90
+
91
+ it 'provides access to the records' do
92
+ expect(response.records).to be_a(Elasticsearch::Model::Response::Records)
93
+ expect(response.size).to be(0)
94
+ end
95
+ end
96
+
97
+ describe 'enumerable methods' do
98
+
99
+ it 'delegates the methods to the results' do
100
+ expect(response.empty?).to be(true)
101
+ end
102
+ end
103
+
104
+ describe 'aggregations' do
105
+
106
+ it 'provides access to the aggregations' do
107
+ expect(response.aggregations).to be_a(Hashie::Mash)
108
+ expect(response.aggregations.foo.bar).to eq(10)
109
+ end
110
+ end
111
+
112
+ describe 'suggestions' do
113
+
114
+ it 'provides access to the suggestions' do
115
+ expect(response.suggestions).to be_a(Hashie::Mash)
116
+ expect(response.suggestions.my_suggest.first.options.first.text).to eq('Foo')
117
+ expect(response.suggestions.terms).to eq([ 'Foo', 'Bar' ])
118
+ end
119
+
120
+ context 'when there are no suggestions' do
121
+
122
+ let(:response_document) do
123
+ { }
124
+ end
125
+
126
+ it 'returns an empty list' do
127
+ expect(response.suggestions.terms).to eq([ ])
128
+ end
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,122 @@
1
+ require 'spec_helper'
2
+ require 'active_support/json/encoding'
3
+
4
+ describe Elasticsearch::Model::Response::Result do
5
+
6
+ let(:result) do
7
+ described_class.new(foo: 'bar', bar: { bam: 'baz' })
8
+ end
9
+
10
+ it 'provides access to the properties' do
11
+ expect(result.foo).to eq('bar')
12
+ expect(result.bar.bam).to eq('baz')
13
+ expect { result.xoxo }.to raise_exception(NoMethodError)
14
+ end
15
+
16
+ describe '#id' do
17
+
18
+ let(:result) do
19
+ described_class.new(foo: 'bar', _id: 42, _source: { id: 12 })
20
+ end
21
+
22
+ it 'returns the _id field' do
23
+ expect(result.id).to eq(42)
24
+ end
25
+
26
+ it 'provides access to the source id field' do
27
+ expect(result._source.id).to eq(12)
28
+ end
29
+ end
30
+
31
+ describe '#type' do
32
+
33
+ let(:result) do
34
+ described_class.new(foo: 'bar', _type: 'baz', _source: { type: 'BAM' })
35
+ end
36
+
37
+ it 'returns the _type field' do
38
+ expect(result.type).to eq('baz')
39
+ end
40
+
41
+ it 'provides access to the source type field' do
42
+ expect(result._source.type).to eq('BAM')
43
+ end
44
+ end
45
+
46
+ describe 'method delegation' do
47
+
48
+ let(:result) do
49
+ described_class.new(foo: 'bar', _source: { bar: { bam: 'baz' } })
50
+ end
51
+
52
+ it 'provides access to the _source field via a method' do
53
+ expect(result._source).to eq('bar' => { 'bam' => 'baz' })
54
+ end
55
+
56
+ it 'is recognized by #method' do
57
+ expect(result.method :bar).to be_a Method
58
+ end
59
+
60
+ it 'respond_to? still works' do
61
+ expect(result.respond_to? :bar).to be true
62
+ end
63
+
64
+ context 'when methods map to keys in subdocuments of the response from Elasticsearch' do
65
+
66
+ it 'provides access to top level fields via a method' do
67
+ expect(result.foo).to eq('bar')
68
+ expect(result.fetch(:foo)).to eq('bar')
69
+ expect(result.fetch(:does_not_exist, 'moo')).to eq('moo')
70
+ end
71
+
72
+ it 'responds to hash methods' do
73
+ expect(result.keys).to eq(['foo', '_source'])
74
+ expect(result.to_hash).to eq('foo' => 'bar', '_source' => { 'bar' => { 'bam' => 'baz' } })
75
+ end
76
+
77
+ it 'provides access to fields in the _source subdocument via a method' do
78
+ expect(result.bar).to eq('bam' => 'baz')
79
+ expect(result.bar.bam).to eq('baz')
80
+ expect(result._source.bar).to eq('bam' => 'baz')
81
+ expect(result._source.bar.bam).to eq('baz')
82
+ end
83
+
84
+ context 'when boolean methods are called' do
85
+
86
+ it 'provides access to top level fields via a method' do
87
+ expect(result.foo?).to eq(true)
88
+ expect(result.boo?).to eq(false)
89
+ end
90
+
91
+ it 'delegates to fields in the _source subdocument via a method' do
92
+ expect(result.bar?).to eq(true)
93
+ expect(result.bar.bam?).to eq(true)
94
+ expect(result.boo?).to eq(false)
95
+ expect(result.bar.boo?).to eq(false)
96
+ expect(result._source.bar?).to eq(true)
97
+ expect(result._source.bar.bam?).to eq(true)
98
+ expect(result._source.boo?).to eq(false)
99
+ expect(result._source.bar.boo?).to eq(false)
100
+ end
101
+ end
102
+ end
103
+
104
+ context 'when methods do not map to keys in subdocuments of the response from Elasticsearch' do
105
+
106
+ it 'raises a NoMethodError' do
107
+ expect { result.does_not_exist }.to raise_exception(NoMethodError)
108
+ end
109
+ end
110
+ end
111
+
112
+ describe '#as_json' do
113
+
114
+ let(:result) do
115
+ described_class.new(foo: 'bar', _source: { bar: { bam: 'baz' } })
116
+ end
117
+
118
+ it 'returns a json string' do
119
+ expect(result.as_json(except: 'foo')).to eq({'_source'=>{'bar'=>{'bam'=>'baz'}}})
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,56 @@
1
+ require 'spec_helper'
2
+
3
+ describe Elasticsearch::Model::Response::Results do
4
+
5
+ before(:all) do
6
+ class OriginClass
7
+ def self.index_name; 'foo'; end
8
+ def self.document_type; 'bar'; end
9
+ end
10
+ end
11
+
12
+ after(:all) do
13
+ remove_classes(OriginClass)
14
+ end
15
+
16
+ let(:response_document) do
17
+ { 'hits' => { 'total' => 123, 'max_score' => 456, 'hits' => [{'foo' => 'bar'}] } }
18
+ end
19
+
20
+ let(:search) do
21
+ Elasticsearch::Model::Searching::SearchRequest.new(OriginClass, '*').tap do |request|
22
+ allow(request).to receive(:execute!).and_return(response_document)
23
+ end
24
+ end
25
+
26
+ let(:response) do
27
+ Elasticsearch::Model::Response::Response.new(OriginClass, search)
28
+ end
29
+
30
+ let(:results) do
31
+ response.results
32
+ end
33
+
34
+ describe '#results' do
35
+
36
+ it 'provides access to the results' do
37
+ expect(results.results.size).to be(1)
38
+ expect(results.results.first.foo).to eq('bar')
39
+ end
40
+ end
41
+
42
+ describe 'Enumerable' do
43
+
44
+ it 'deletebates enumerable methods to the results' do
45
+ expect(results.empty?).to be(false)
46
+ expect(results.first.foo).to eq('bar')
47
+ end
48
+ end
49
+
50
+ describe '#raw_response' do
51
+
52
+ it 'returns the raw response document' do
53
+ expect(response.raw_response).to eq(response_document)
54
+ end
55
+ end
56
+ end