elastictastic 0.5.0 → 0.10.2

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 (64) hide show
  1. data/LICENSE +1 -1
  2. data/README.md +161 -10
  3. data/lib/elastictastic/adapter.rb +84 -0
  4. data/lib/elastictastic/association.rb +6 -0
  5. data/lib/elastictastic/basic_document.rb +213 -0
  6. data/lib/elastictastic/bulk_persistence_strategy.rb +64 -19
  7. data/lib/elastictastic/callbacks.rb +18 -12
  8. data/lib/elastictastic/child_collection_proxy.rb +15 -11
  9. data/lib/elastictastic/client.rb +47 -24
  10. data/lib/elastictastic/configuration.rb +59 -4
  11. data/lib/elastictastic/dirty.rb +43 -28
  12. data/lib/elastictastic/discrete_persistence_strategy.rb +48 -23
  13. data/lib/elastictastic/document.rb +1 -85
  14. data/lib/elastictastic/embedded_document.rb +34 -0
  15. data/lib/elastictastic/errors.rb +17 -5
  16. data/lib/elastictastic/field.rb +3 -0
  17. data/lib/elastictastic/mass_assignment_security.rb +2 -4
  18. data/lib/elastictastic/middleware.rb +66 -84
  19. data/lib/elastictastic/multi_get.rb +30 -0
  20. data/lib/elastictastic/multi_search.rb +70 -0
  21. data/lib/elastictastic/nested_document.rb +3 -27
  22. data/lib/elastictastic/new_relic_instrumentation.rb +8 -8
  23. data/lib/elastictastic/observing.rb +8 -6
  24. data/lib/elastictastic/optimistic_locking.rb +57 -0
  25. data/lib/elastictastic/parent_child.rb +56 -54
  26. data/lib/elastictastic/persistence.rb +16 -16
  27. data/lib/elastictastic/properties.rb +136 -96
  28. data/lib/elastictastic/railtie.rb +1 -1
  29. data/lib/elastictastic/rotor.rb +105 -0
  30. data/lib/elastictastic/scope.rb +186 -56
  31. data/lib/elastictastic/server_error.rb +20 -1
  32. data/lib/elastictastic/test_helpers.rb +152 -97
  33. data/lib/elastictastic/thrift/constants.rb +12 -0
  34. data/lib/elastictastic/thrift/rest.rb +83 -0
  35. data/lib/elastictastic/thrift/types.rb +124 -0
  36. data/lib/elastictastic/thrift_adapter.rb +61 -0
  37. data/lib/elastictastic/transport_methods.rb +27 -0
  38. data/lib/elastictastic/validations.rb +11 -13
  39. data/lib/elastictastic/version.rb +1 -1
  40. data/lib/elastictastic.rb +148 -27
  41. data/spec/environment.rb +1 -1
  42. data/spec/examples/bulk_persistence_strategy_spec.rb +151 -23
  43. data/spec/examples/callbacks_spec.rb +65 -34
  44. data/spec/examples/dirty_spec.rb +160 -1
  45. data/spec/examples/document_spec.rb +168 -106
  46. data/spec/examples/middleware_spec.rb +1 -61
  47. data/spec/examples/multi_get_spec.rb +127 -0
  48. data/spec/examples/multi_search_spec.rb +113 -0
  49. data/spec/examples/observing_spec.rb +24 -3
  50. data/spec/examples/optimistic_locking_spec.rb +417 -0
  51. data/spec/examples/parent_child_spec.rb +73 -33
  52. data/spec/examples/properties_spec.rb +53 -0
  53. data/spec/examples/rotor_spec.rb +132 -0
  54. data/spec/examples/scope_spec.rb +78 -18
  55. data/spec/examples/search_spec.rb +26 -0
  56. data/spec/examples/validation_spec.rb +7 -1
  57. data/spec/models/author.rb +1 -1
  58. data/spec/models/blog.rb +2 -0
  59. data/spec/models/comment.rb +1 -1
  60. data/spec/models/photo.rb +9 -0
  61. data/spec/models/post.rb +3 -0
  62. metadata +97 -78
  63. data/lib/elastictastic/resource.rb +0 -4
  64. data/spec/examples/active_model_lint_spec.rb +0 -20
@@ -15,7 +15,7 @@ describe Elastictastic::Dirty do
15
15
  end
16
16
 
17
17
  before do
18
- stub_elasticsearch_update('default', 'post', '1')
18
+ stub_es_update('default', 'post', '1')
19
19
  end
20
20
 
21
21
  context 'top-level attribute' do
@@ -71,6 +71,58 @@ describe Elastictastic::Dirty do
71
71
  post.should_not be_title_changed
72
72
  end
73
73
  end
74
+
75
+ context 'after change and reload' do
76
+ before do
77
+ post.title = 'hey guy'
78
+ stub_es_get('default', 'post', '1', 'title' => 'second title')
79
+ post.reload
80
+ end
81
+
82
+ it 'should not be changed' do
83
+ post.should_not be_changed
84
+ end
85
+ end
86
+
87
+ context 'changing it to the same thing' do
88
+ before do
89
+ post.title = 'first title'
90
+ end
91
+
92
+ it 'should not be changed' do
93
+ post.should_not be_changed
94
+ end
95
+
96
+ it 'should not have any changes' do
97
+ post.changes.should be_empty
98
+ end
99
+
100
+ it 'should not have the title attribute changed' do
101
+ post.should_not be_title_changed
102
+ end
103
+ end
104
+
105
+ context 'changing it twice' do
106
+ before do
107
+ post.title = 'second title'
108
+ post.title = 'third title'
109
+ end
110
+
111
+ it 'should keep track of original value' do
112
+ post.title_change.should == ['first title', 'third title']
113
+ end
114
+ end
115
+
116
+ context 'changing it to something else and then back' do
117
+ before do
118
+ post.title = 'second title'
119
+ post.title = 'first title'
120
+ end
121
+
122
+ it 'should not be changed' do
123
+ post.should_not be_changed
124
+ end
125
+ end
74
126
  end
75
127
 
76
128
  context 'single nested document' do
@@ -113,6 +165,12 @@ describe Elastictastic::Dirty do
113
165
  post.save
114
166
  post.author.should_not be_changed
115
167
  end
168
+
169
+ it 'should not be changed when setting it to the same thing' do
170
+ post.save
171
+ post.author.name = post.author.name
172
+ post.author.should_not be_changed
173
+ end
116
174
  end
117
175
 
118
176
  context 'with nested document replaced' do
@@ -129,6 +187,37 @@ describe Elastictastic::Dirty do
129
187
  ['Mat Brown', 'Barack Obama']
130
188
  end
131
189
  end
190
+
191
+ context 'with nested document replaced by nil' do
192
+ before do
193
+ post.author = Author.new(:name => 'Barack Obama')
194
+ post.save
195
+ post.author = nil
196
+ end
197
+
198
+ it 'should be changed' do
199
+ post.should be_changed
200
+ end
201
+
202
+ it 'should expose changes' do
203
+ post.changes['author'][0].name.should == 'Barack Obama'
204
+ post.changes['author'][1].should == nil
205
+ end
206
+
207
+ it 'should save properly' do
208
+ post.save!
209
+ end
210
+ end
211
+
212
+ context 'with nested document replaced by itself' do
213
+ before do
214
+ post.author = Author.new(:name => 'Mat Brown')
215
+ end
216
+
217
+ it 'should not be changed' do
218
+ post.should_not be_changed
219
+ end
220
+ end
132
221
  end
133
222
 
134
223
  context 'nested document collection' do
@@ -168,6 +257,33 @@ describe Elastictastic::Dirty do
168
257
  post.save
169
258
  post.comments.first.should_not be_changed
170
259
  end
260
+
261
+ it 'should not be changed when setting it to the same thing' do
262
+ post.save
263
+ post.comments.first.body = post.comments.first.body
264
+ post.should_not be_changed
265
+ end
266
+ end
267
+
268
+ context 'when nested document value set to same value' do
269
+ before do
270
+ post.comments.first.body = 'I like pizza'
271
+ end
272
+
273
+ it 'should not be changed' do
274
+ post.should_not be_changed
275
+ end
276
+ end
277
+
278
+ context 'when nested document value set to a different value and then back' do
279
+ before do
280
+ post.comments.first.body = 'I like lasagne'
281
+ post.comments.first.body = 'I like pizza'
282
+ end
283
+
284
+ it 'should not be changed' do
285
+ post.should_not be_changed
286
+ end
171
287
  end
172
288
 
173
289
  context 'when document added' do
@@ -216,6 +332,17 @@ describe Elastictastic::Dirty do
216
332
  end
217
333
  end
218
334
 
335
+ context 'with document removed and then identical document added' do
336
+ before do
337
+ post.comments.delete(post.comments.first)
338
+ post.comments << Comment.new(:body => 'I like pizza')
339
+ end
340
+
341
+ it 'should not be changed' do
342
+ post.should_not be_changed
343
+ end
344
+ end
345
+
219
346
  context 'with collection replaced' do
220
347
  before do
221
348
  post.comments = [Comment.new(:body => 'I like lasagne')]
@@ -234,5 +361,37 @@ describe Elastictastic::Dirty do
234
361
  post.should_not be_changed
235
362
  end
236
363
  end
364
+
365
+ context 'with collection replaced with identical collection' do
366
+ before do
367
+ post.comments = [Comment.new(:body => 'I like pizza')]
368
+ end
369
+
370
+ it 'should not be changed' do
371
+ post.should_not be_changed
372
+ end
373
+ end
374
+
375
+ context 'with collection replaced by a different collection and then back to the original' do
376
+ before do
377
+ post.comments = [Comment.new(:body => 'I am the walrus')]
378
+ post.comments = [Comment.new(:body => 'I like pizza')]
379
+ end
380
+
381
+ it 'should not be changed' do
382
+ post.should_not be_changed
383
+ end
384
+ end
385
+
386
+ context 'with collection replaced and then populated with identical members' do
387
+ before do
388
+ post.comments = []
389
+ post.comments << Comment.new(:body => 'I like pizza')
390
+ end
391
+
392
+ it 'should not be changed' do
393
+ post.should_not be_changed
394
+ end
395
+ end
237
396
  end
238
397
  end
@@ -4,11 +4,11 @@ describe Elastictastic::Document do
4
4
  include Elastictastic::TestHelpers
5
5
 
6
6
  let(:last_request) { FakeWeb.last_request }
7
- let(:last_request_body) { JSON.parse(last_request.body) }
7
+ let(:last_request_body) { Elastictastic.json_decode(last_request.body) }
8
8
 
9
9
  describe '#save' do
10
10
  context 'new object' do
11
- let!(:id) { stub_elasticsearch_create('default', 'post') }
11
+ let!(:id) { stub_es_create('default', 'post') }
12
12
  let(:post) { Post.new }
13
13
 
14
14
  before do
@@ -25,27 +25,48 @@ describe Elastictastic::Document do
25
25
  end
26
26
 
27
27
  it 'should send document in the body' do
28
- last_request.body.should == post.elasticsearch_doc.to_json
28
+ last_request.body.should == Elastictastic.json_encode(post.elasticsearch_doc)
29
29
  end
30
30
 
31
31
  it 'should populate ID of model object' do
32
32
  post.id.should == id
33
33
  end
34
34
 
35
+ it 'should populate version' do
36
+ post.version.should == 1
37
+ end
38
+
35
39
  it 'should mark object as persisted' do
36
40
  post.should be_persisted
37
41
  end
38
42
  end # context 'new object'
39
43
 
44
+ context 'new object with routing' do
45
+ let!(:id) { stub_es_create('default', 'photo') }
46
+ let(:photo) { Photo.new(:post_id => '123') }
47
+
48
+ before do
49
+ photo.save
50
+ end
51
+
52
+ it 'should send routing param' do
53
+ last_request_uri.query.split('&').should include('routing=123')
54
+ end
55
+ end
56
+
40
57
  context 'new object with ID' do
41
58
  let(:post) { Post.new.tap { |post| post.id = '123' }}
42
59
 
43
60
  context 'with unique id' do
44
61
  before do
45
- stub_elasticsearch_create('default', 'post', post.id)
62
+ stub_es_create('default', 'post', post.id)
46
63
  post.save
47
64
  end
48
65
 
66
+ it 'should populate version' do
67
+ post.version.should == 1
68
+ end
69
+
49
70
  it 'should send PUT request' do
50
71
  last_request.method.should == 'PUT'
51
72
  end
@@ -55,18 +76,17 @@ describe Elastictastic::Document do
55
76
  end
56
77
 
57
78
  it 'should send document in body' do
58
- last_request.body.should == post.elasticsearch_doc.to_json
79
+ last_request.body.should == Elastictastic.json_encode(post.elasticsearch_doc)
59
80
  end
60
81
  end # context 'with unique ID'
61
82
 
62
83
  context 'with duplicate ID' do
63
84
  before do
64
- stub_elasticsearch_create(
65
- 'default', 'post', '123',
66
- :body => {
67
- 'error' => 'DocumentAlreadyExistsEngineException[[post][2] [post][1]]: document already exists',
68
- 'status' => 409
69
- }.to_json
85
+ stub_request_json(
86
+ :put,
87
+ match_es_resource('default', 'post', '123', '_create'),
88
+ 'error' => 'DocumentAlreadyExistsEngineException[[post][2] [post][1]]: document already exists',
89
+ 'status' => 409
70
90
  )
71
91
  end
72
92
 
@@ -90,6 +110,16 @@ describe Elastictastic::Document do
90
110
  end # context 'with duplicate ID'
91
111
  end # context 'new object with ID'
92
112
 
113
+ context 'new object with ID with routing' do
114
+ let(:photo) { Photo.new(:id => 'abc', :post_id => '123') }
115
+
116
+ it 'should include routing param when saving' do
117
+ stub_es_create('default', 'photo', 'abc')
118
+ photo.save
119
+ last_request_uri.query.split('&').should include('routing=123')
120
+ end
121
+ end
122
+
93
123
  shared_examples_for 'persisted object' do
94
124
  describe 'identity attributes' do
95
125
  it 'should not allow setting of ID' do
@@ -99,7 +129,7 @@ describe Elastictastic::Document do
99
129
 
100
130
  describe '#save' do
101
131
  before do
102
- stub_elasticsearch_update('default', 'post', post.id)
132
+ stub_es_update('default', 'post', post.id)
103
133
  post.title = 'Fun Factories for Fickle Ferrets'
104
134
  post.save
105
135
  end
@@ -108,19 +138,23 @@ describe Elastictastic::Document do
108
138
  last_request.method.should == 'PUT'
109
139
  end
110
140
 
111
- it "should send to document's resource path" do
112
- last_request.path.should == "/default/post/#{post.id}"
141
+ it "should send to document's resource path with version" do
142
+ last_request.path.should == "/default/post/#{post.id}?version=1"
113
143
  end
114
144
 
115
145
  it "should send document's body in request" do
116
- last_request.body.should == post.elasticsearch_doc.to_json
146
+ last_request.body.should == Elastictastic.json_encode(post.elasticsearch_doc)
147
+ end
148
+
149
+ it 'should populate new version' do
150
+ post.version.should == 2
117
151
  end
118
152
  end # describe '#save'
119
153
  end # shared_examples_for 'persisted object'
120
154
 
121
155
  context 'object after save' do
122
156
  let(:post) do
123
- stub_elasticsearch_create('default', 'post')
157
+ stub_es_create('default', 'post')
124
158
  Post.new.tap { |post| post.save }
125
159
  end
126
160
 
@@ -131,12 +165,33 @@ describe Elastictastic::Document do
131
165
  let(:post) do
132
166
  Post.new.tap do |post|
133
167
  post.id = '123'
168
+ post.version = 1
134
169
  post.persisted!
135
170
  end
136
171
  end
137
172
 
138
173
  it_should_behave_like 'persisted object'
139
174
  end # context 'existing persisted object'
175
+
176
+ context 'persisted object with routing' do
177
+ let(:photo) do
178
+ Photo.new.tap do |photo|
179
+ photo.id = 'abc'
180
+ photo.post_id = '123'
181
+ photo.version = 1
182
+ photo.persisted!
183
+ end
184
+ end
185
+
186
+ before do
187
+ stub_es_update('default', 'photo', 'abc')
188
+ photo.save!
189
+ end
190
+
191
+ it 'should include routing param' do
192
+ last_request_uri.query.split('&').should include('routing=123')
193
+ end
194
+ end
140
195
  end # describe '#save'
141
196
 
142
197
  describe '#destroy' do
@@ -149,7 +204,7 @@ describe Elastictastic::Document do
149
204
  end
150
205
 
151
206
  before do
152
- stub_elasticsearch_destroy('default', 'post', '123')
207
+ stub_es_destroy('default', 'post', '123')
153
208
  @result = post.destroy
154
209
  end
155
210
 
@@ -170,6 +225,26 @@ describe Elastictastic::Document do
170
225
  end
171
226
  end # context 'existing persisted object'
172
227
 
228
+ context 'persisted object with routing' do
229
+ let(:photo) do
230
+ Photo.new.tap do |photo|
231
+ photo.id = 'abc'
232
+ photo.post_id = '123'
233
+ photo.version = 1
234
+ photo.persisted!
235
+ end
236
+ end
237
+
238
+ before do
239
+ stub_es_destroy('default', 'photo', 'abc')
240
+ photo.destroy
241
+ end
242
+
243
+ it 'should include routing param' do
244
+ last_request_uri.query.split('&').should include('routing=123')
245
+ end
246
+ end
247
+
173
248
  context 'transient object' do
174
249
  let(:post) { Post.new }
175
250
 
@@ -187,16 +262,9 @@ describe Elastictastic::Document do
187
262
  end
188
263
 
189
264
  before do
190
- stub_elasticsearch_destroy(
265
+ stub_es_destroy(
191
266
  'default', 'post', '123',
192
- :body => {
193
- 'ok' => true,
194
- 'found' => false,
195
- '_index' => 'default',
196
- '_type' => 'post',
197
- '_id' => '123',
198
- '_version' => 0
199
- }.to_json
267
+ 'found' => false
200
268
  )
201
269
  @result = post.destroy
202
270
  end
@@ -207,10 +275,32 @@ describe Elastictastic::Document do
207
275
  end # describe 'non-existent persisted object'
208
276
  end # describe '#destroy'
209
277
 
278
+ describe '#reload' do
279
+ context 'with persisted object' do
280
+ let :post do
281
+ Post.new.tap do |post|
282
+ post.id = '1'
283
+ post.title = 'Title'
284
+ post.persisted!
285
+ end
286
+ end
287
+
288
+ before do
289
+ stub_es_get('default', 'post', '1', 'title' => 'Title')
290
+ post.title = 'Something'
291
+ post.reload
292
+ end
293
+
294
+ it 'should reset changed attributes' do
295
+ post.title.should == 'Title'
296
+ end
297
+ end
298
+ end
299
+
210
300
  describe '::destroy_all' do
211
301
  describe 'with default index' do
212
302
  before do
213
- stub_elasticsearch_destroy_all('default', 'post')
303
+ stub_es_destroy_all('default', 'post')
214
304
  Post.destroy_all
215
305
  end
216
306
 
@@ -225,7 +315,7 @@ describe Elastictastic::Document do
225
315
 
226
316
  describe 'with specified index' do
227
317
  before do
228
- stub_elasticsearch_destroy_all('my_index', 'post')
318
+ stub_es_destroy_all('my_index', 'post')
229
319
  Post.in_index('my_index').destroy_all
230
320
  end
231
321
 
@@ -242,13 +332,13 @@ describe Elastictastic::Document do
242
332
  end
243
333
 
244
334
  it 'should send mapping to ES' do
245
- last_request.body.should == Post.mapping.to_json
335
+ last_request.body.should == Elastictastic.json_encode(Post.mapping)
246
336
  end
247
337
  end # shared_examples_for 'put mapping'
248
338
 
249
339
  context 'with default index' do
250
340
  before do
251
- stub_elasticsearch_put_mapping('default', 'post')
341
+ stub_es_put_mapping('default', 'post')
252
342
  Post.sync_mapping
253
343
  end
254
344
 
@@ -261,7 +351,7 @@ describe Elastictastic::Document do
261
351
 
262
352
  context 'with specified index' do
263
353
  before do
264
- stub_elasticsearch_put_mapping('my_cool_index', 'post')
354
+ stub_es_put_mapping('my_cool_index', 'post')
265
355
  Post.in_index('my_cool_index').sync_mapping
266
356
  end
267
357
 
@@ -278,7 +368,7 @@ describe Elastictastic::Document do
278
368
  shared_examples_for 'single document lookup' do
279
369
  context 'when document is found' do
280
370
  before do
281
- stub_elasticsearch_get(
371
+ stub_es_get(
282
372
  index, 'post', '1',
283
373
  )
284
374
  end
@@ -287,6 +377,10 @@ describe Elastictastic::Document do
287
377
  post.should be_a(Post)
288
378
  end
289
379
 
380
+ it 'should populate version' do
381
+ post.version.should == 1
382
+ end
383
+
290
384
  it 'should request specified fields if specified' do
291
385
  scope.fields('name', 'author.name').find(1)
292
386
  last_request.path.should == "/#{index}/post/1?fields=name%2Cauthor.name"
@@ -301,7 +395,7 @@ describe Elastictastic::Document do
301
395
 
302
396
  context 'when document is not found' do
303
397
  before do
304
- stub_elasticsearch_get(index, 'post', '1', nil)
398
+ stub_es_get(index, 'post', '1', nil)
305
399
  end
306
400
 
307
401
  it 'should return nil' do
@@ -313,7 +407,7 @@ describe Elastictastic::Document do
313
407
  shared_examples_for 'multi document single index lookup' do
314
408
 
315
409
  before do
316
- stub_elasticsearch_mget(index, 'post', '1', '2')
410
+ stub_es_mget(index, 'post', '1', '2')
317
411
  posts
318
412
  end
319
413
 
@@ -370,82 +464,12 @@ describe Elastictastic::Document do
370
464
  it_should_behave_like 'single document lookup'
371
465
  it_should_behave_like 'multi document single index lookup'
372
466
 
373
- describe 'multi-index multi-get' do
374
- before do
375
- stub_elasticsearch_mget(
376
- nil,
377
- nil,
378
- ['1', 'default'], ['2', 'my_index'], ['3', 'my_index']
379
- )
380
- posts
381
- end
382
-
383
- describe 'with no options' do
384
- let(:posts) { Post.find('default' => '1', 'my_index' => %w(2 3)) }
385
-
386
- it 'should send request to base path' do
387
- last_request.path.should == '/_mget'
388
- end
389
-
390
- it 'should request ids with type and index' do
391
- last_request_body.should == {
392
- 'docs' => [{
393
- '_id' => '1',
394
- '_type' => 'post',
395
- '_index' => 'default'
396
- }, {
397
- '_id' => '2',
398
- '_type' => 'post',
399
- '_index' => 'my_index'
400
- }, {
401
- '_id' => '3',
402
- '_type' => 'post',
403
- '_index' => 'my_index'
404
- }]
405
- }
406
- end
407
-
408
- it 'should return docs with IDs' do
409
- posts.map(&:id).should == %w(1 2 3)
410
- end
411
-
412
- it 'should set proper indices' do
413
- posts.map { |post| post.index.name }.should ==
414
- %w(default my_index my_index)
415
- end
416
- end # context 'with no options'
417
-
418
- context 'with fields specified' do
419
- let(:posts) { Post.fields('title').find('default' => '1', 'my_index' => %w(2 3)) }
420
-
421
- it 'should inject fields into each identifier' do
422
- last_request_body.should == {
423
- 'docs' => [{
424
- '_id' => '1',
425
- '_type' => 'post',
426
- '_index' => 'default',
427
- 'fields' => %w(title)
428
- }, {
429
- '_id' => '2',
430
- '_type' => 'post',
431
- '_index' => 'my_index',
432
- 'fields' => %w(title)
433
- }, {
434
- '_id' => '3',
435
- '_type' => 'post',
436
- '_index' => 'my_index',
437
- 'fields' => %w(title)
438
- }]
439
- }
440
- end
441
- end
442
- end # describe 'multi-index multi-get'
443
467
 
444
468
  context 'when documents are missing' do
445
469
  let(:posts) { Post.find('1', '2') }
446
470
 
447
471
  before do
448
- stub_elasticsearch_mget('default', 'post', '1' => {}, '2' => nil)
472
+ stub_es_mget('default', 'post', '1' => {}, '2' => nil)
449
473
  end
450
474
 
451
475
  it 'should only return docs that exist' do
@@ -462,15 +486,49 @@ describe Elastictastic::Document do
462
486
  it_should_behave_like 'single document lookup'
463
487
  it_should_behave_like 'multi document single index lookup'
464
488
  end # context 'with specified index'
489
+
490
+ context 'with routing specified' do
491
+ context 'single document lookup' do
492
+ it 'should specify routing' do
493
+ stub_es_get('default', 'photo', 'abc')
494
+ Photo.routing('123').find('abc')
495
+ last_request_uri.query.split('&').should include('routing=123')
496
+ end
497
+
498
+ it 'should complain if routing required but not specified' do
499
+ expect { Photo.find('abc') }.
500
+ to raise_error(Elastictastic::MissingParameter)
501
+ end
502
+ end
503
+ end
465
504
  end # describe '::find'
466
505
 
467
- describe '::new_from_elasticsearch_hit' do
506
+ describe '::exists?' do
507
+ it 'should return true if document exists' do
508
+ stub_es_head('my_index', 'post', 1, true)
509
+ Post.in_index('my_index').exists?(1).should be_true
510
+ end
511
+
512
+ it 'should return false if document does not exist' do
513
+ stub_es_head('my_index', 'post', 1, false)
514
+ Post.in_index('my_index').exists?(1).should be_false
515
+ end
516
+
517
+ it 'should send routing when given' do
518
+ stub_es_head('my_index', 'post', 1, true)
519
+ Post.in_index('my_index').routing('my_route').exists?(1)
520
+ last_request_uri.query.split('&').should include('routing=my_route')
521
+ end
522
+ end
523
+
524
+ describe '#elasticsearch_hit=' do
468
525
  context 'with full _source' do
469
526
  let :post do
470
527
  Post.new.tap do |post|
471
528
  post.elasticsearch_hit = {
472
529
  '_id' => '1',
473
530
  '_index' => 'my_index',
531
+ '_version' => 2,
474
532
  '_source' => {
475
533
  'title' => 'Testy time',
476
534
  'tags' => %w(search lucene),
@@ -494,6 +552,10 @@ describe Elastictastic::Document do
494
552
  post.index.name.should == 'my_index'
495
553
  end
496
554
 
555
+ it 'should populate version' do
556
+ post.version.should == 2
557
+ end
558
+
497
559
  it 'should mark document perisistent' do
498
560
  post.should be_persisted
499
561
  end