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
data/lib/elastictastic.rb CHANGED
@@ -1,80 +1,201 @@
1
1
  require 'active_support/core_ext'
2
2
  require 'active_model'
3
+
4
+ require 'elastictastic/adapter'
5
+ require 'elastictastic/basic_document'
3
6
  require 'elastictastic/errors'
7
+ require 'elastictastic/client'
8
+ require 'elastictastic/configuration'
9
+ require 'elastictastic/discrete_persistence_strategy'
10
+ require 'elastictastic/embedded_document'
11
+ require 'elastictastic/field'
12
+ require 'elastictastic/index'
13
+ require 'elastictastic/middleware'
14
+ require 'elastictastic/multi_get'
15
+ require 'elastictastic/multi_search'
16
+ require 'elastictastic/optimistic_locking'
17
+ require 'elastictastic/persistence'
18
+ require 'elastictastic/properties'
19
+ require 'elastictastic/scope'
20
+ require 'elastictastic/scope_builder'
21
+ require 'elastictastic/scoped'
22
+ require 'elastictastic/search'
23
+ require 'elastictastic/server_error'
24
+ require 'elastictastic/util'
4
25
 
5
26
  module Elastictastic
6
27
  autoload :Association, 'elastictastic/association'
7
28
  autoload :BulkPersistenceStrategy, 'elastictastic/bulk_persistence_strategy'
8
29
  autoload :Callbacks, 'elastictastic/callbacks'
9
30
  autoload :ChildCollectionProxy, 'elastictastic/child_collection_proxy'
10
- autoload :Client, 'elastictastic/client'
11
- autoload :Configuration, 'elastictastic/configuration'
12
31
  autoload :Dirty, 'elastictastic/dirty'
13
- autoload :DiscretePersistenceStrategy, 'elastictastic/discrete_persistence_strategy'
14
32
  autoload :Document, 'elastictastic/document'
15
- autoload :Field, 'elastictastic/field'
16
- autoload :Index, 'elastictastic/index'
17
33
  autoload :MassAssignmentSecurity, 'elastictastic/mass_assignment_security'
18
- autoload :Middleware, 'elastictastic/middleware'
19
34
  autoload :NestedCollectionProxy, 'elastictastic/nested_collection_proxy'
20
- autoload :NestedDocument, 'elastictastic/nested_document'
21
35
  autoload :Observer, 'elastictastic/observer'
22
36
  autoload :Observing, 'elastictastic/observing'
23
37
  autoload :ParentChild, 'elastictastic/parent_child'
24
- autoload :Persistence, 'elastictastic/persistence'
25
- autoload :Properties, 'elastictastic/properties'
26
- autoload :Resource, 'elastictastic/resource'
27
- autoload :Scope, 'elastictastic/scope'
28
- autoload :ScopeBuilder, 'elastictastic/scope_builder'
29
- autoload :Scoped, 'elastictastic/scoped'
30
- autoload :Search, 'elastictastic/search'
31
- autoload :ServerError, 'elastictastic/server_error'
38
+ autoload :Rotor, 'elastictastic/rotor'
32
39
  autoload :TestHelpers, 'elastictastic/test_helpers'
33
- autoload :Util, 'elastictastic/util'
40
+ autoload :ThriftAdapter, 'elastictastic/thrift_adapter'
34
41
  autoload :Validations, 'elastictastic/validations'
35
42
 
43
+ autoload :NestedDocument, 'elastictastic/nested_document' # Deprecated
44
+
36
45
  class <<self
37
46
  attr_writer :config
38
47
 
48
+ #
49
+ # Elastictastic global configuration. In a Rails environment, you can
50
+ # configure Elastictastic by creating a `config/elastictastic.yml` file,
51
+ # whose keys will be passed into the Configuration object when your
52
+ # application boots. In non-Rails environment, you can configure
53
+ # Elastictastic directly using the object returned by this method.
54
+ #
55
+ # @return [Configuration] global configuration object
56
+ #
39
57
  def config
40
58
  @config ||= Configuration.new
41
59
  end
42
60
 
61
+ def multi_get(&block)
62
+ MultiGet.new.tap(&block).to_a
63
+ end
64
+
65
+ #
66
+ # Perform multiple searches in a single request to ElasticSearch. Each
67
+ # scope will be eagerly populated with results.
68
+ #
69
+ # @param [Scope, Array] collection of scopes to execute multisearch on
70
+ #
71
+ def multi_search(*scopes)
72
+ end
73
+
74
+ #
75
+ # Return a lower-level ElasticSearch client. This is likely to be extracted
76
+ # into a separate gem in the future.
77
+ #
78
+ # @return [Client] client
79
+ # @api private
80
+ #
43
81
  def client
44
82
  Thread.current['Elastictastic::client'] ||= Client.new(config)
45
83
  end
46
84
 
85
+ #
86
+ # Set the current persistence strategy
87
+ #
88
+ # @param [DiscretePersistenceStrategy,BulkPersistenceStrategy] persistence strategy
89
+ # @api private
90
+ # @see ::persister
91
+ #
47
92
  def persister=(persister)
48
93
  Thread.current['Elastictastic::persister'] = persister
49
94
  end
50
95
 
96
+ #
97
+ # The current persistence strategy for ElasticSearch. Usually this will
98
+ # be the DiscretePersistenceStrategy singleton; inside a ::bulk block, it
99
+ # will be an instance of BulkPersistenceStrategy
100
+ #
101
+ # @return [DiscretePersistenceStrategy,BulkPersistenceStrategy] current persistence strategy
102
+ # @api private
103
+ # @see ::bulk
104
+ #
51
105
  def persister
52
106
  Thread.current['Elastictastic::persister'] ||=
53
107
  Elastictastic::DiscretePersistenceStrategy.instance
54
108
  end
55
109
 
56
- def bulk
110
+ #
111
+ # Perform write operations in a single request to ElasticSearch. Highly
112
+ # recommended for any operation which writes a large quantity of data to
113
+ # ElasticSearch. Write operations (e.g. save/destroy documents) are buffered
114
+ # in the client and sent to ElasticSearch when the bulk operation exits
115
+ # (or when an auto-flush threshold is reached; see below).
116
+ #
117
+ # @example Create posts in bulk
118
+ # Elastictastic.bulk do
119
+ # params[:posts].each do |post_params|
120
+ # Post.new(post_params).save!
121
+ # end
122
+ # end # posts are actually persisted here
123
+ #
124
+ # Since write operations inside a bulk block are not performed
125
+ # synchronously, server-side errors will only be raised once the bulk block
126
+ # completes; you may pass a block into Document#save and Document#destroy
127
+ # that will be called once the operation completes. The block is passed an
128
+ # error param if the operation was not successful.
129
+ #
130
+ # @example Custom handling for conflicting IDs in a bulk block
131
+ # errors = []
132
+ # Elastictastic.bulk do
133
+ # params[:posts].each do |post_params|
134
+ # Post.new(post_params).save! do |e|
135
+ # case e
136
+ # when nil # success!
137
+ # when Elastictastic::ServerError::DocumentAlreadyExistsEngineException
138
+ # conflicting_ids << post_params[:id]
139
+ # else
140
+ # raise e
141
+ # end
142
+ # end
143
+ # end
144
+ # end
145
+ #
146
+ # @option options [Fixnum] :auto_flush Flush to ElasticSearch after this many operations performed.
147
+ # @yield Block during which all write operations are buffered for bulk
148
+ #
149
+ def bulk(options = {})
57
150
  original_persister = self.persister
151
+ bulk_persister = self.persister =
152
+ Elastictastic::BulkPersistenceStrategy.new(options)
58
153
  begin
59
- self.persister = Elastictastic::BulkPersistenceStrategy.new
60
154
  yield
61
- self.persister.flush
62
- rescue Elastictastic::CancelBulkOperation
63
- # Nothing to see here...
64
155
  ensure
65
156
  self.persister = original_persister
66
157
  end
158
+ bulk_persister.flush
67
159
  end
68
160
 
69
- def Index(name_or_index)
70
- Index === name_or_index ? name_or_index : Index.new(name_or_index)
161
+ #
162
+ # Use Elastictastic's configured JSON encoder to encode a JSON message.
163
+ #
164
+ # @param [Object] Object to encode to JSON
165
+ # @return JSON representation of object
166
+ # @api private
167
+ #
168
+ def json_encode(object)
169
+ if config.json_engine.respond_to?(:encode)
170
+ config.json_engine.encode(object)
171
+ else
172
+ config.json_engine.dump(object)
173
+ end
71
174
  end
72
175
 
73
- private
176
+ #
177
+ # Use Elastictastic's configured JSON decoder to decode a JSON message
178
+ #
179
+ # @param [String] JSON message to decode
180
+ # @return Ruby object represented by json param
181
+ # @api private
182
+ #
183
+ def json_decode(json)
184
+ if config.json_engine.respond_to?(:decode)
185
+ config.json_engine.decode(json)
186
+ else
187
+ config.json_engine.load(json)
188
+ end
189
+ end
74
190
 
75
- def new_transport
76
- transport_class = const_get("#{config.transport.camelize}Transport")
77
- transport_class.new(config)
191
+ #
192
+ # Coerce the argument to an Elastictastic index.
193
+ #
194
+ # @param [String,Elastictastic::Index] name_or_index Index name or object
195
+ # @api private
196
+ #
197
+ def Index(name_or_index)
198
+ Index === name_or_index ? name_or_index : Index.new(name_or_index)
78
199
  end
79
200
  end
80
201
  end
data/spec/environment.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  require 'bundler'
2
2
  Bundler.require(:default, :test, :development)
3
3
 
4
- %w(author blog comment post post_observer).each do |model|
4
+ %w(author blog comment photo post post_observer).each do |model|
5
5
  require File.expand_path("../models/#{model}", __FILE__)
6
6
  end
@@ -6,7 +6,7 @@ describe Elastictastic::BulkPersistenceStrategy do
6
6
  let(:last_request) { FakeWeb.last_request }
7
7
  let(:bulk_requests) do
8
8
  last_request.body.split("\n").map do |request|
9
- JSON.parse(request)
9
+ Elastictastic.json_decode(request)
10
10
  end
11
11
  end
12
12
 
@@ -14,7 +14,7 @@ describe Elastictastic::BulkPersistenceStrategy do
14
14
  let(:post) { Post.new }
15
15
 
16
16
  before do
17
- stub_elasticsearch_bulk(
17
+ stub_es_bulk(
18
18
  'create' => { '_index' => 'default', '_type' => 'post', '_id' => '123', '_version' => 1, 'ok' => true }
19
19
  )
20
20
  Elastictastic.bulk do
@@ -38,6 +38,10 @@ describe Elastictastic::BulkPersistenceStrategy do
38
38
  post.id.should == '123'
39
39
  end
40
40
 
41
+ it 'should set version' do
42
+ post.version.should == 1
43
+ end
44
+
41
45
  it 'should set persisted' do
42
46
  post.should be_persisted
43
47
  end
@@ -55,7 +59,7 @@ describe Elastictastic::BulkPersistenceStrategy do
55
59
  example.run
56
60
  # have to do this here because the before/after hooks run inside the
57
61
  # around hook
58
- stub_elasticsearch_bulk(
62
+ stub_es_bulk(
59
63
  'create' => { '_index' => 'default', '_type' => 'post', '_id' => '123', '_version' => 1, 'ok' => true }
60
64
  )
61
65
  end
@@ -80,7 +84,7 @@ describe Elastictastic::BulkPersistenceStrategy do
80
84
  let(:posts) { Array.new(2) { Post.new }}
81
85
 
82
86
  before do
83
- stub_elasticsearch_bulk(
87
+ stub_es_bulk(
84
88
  { 'create' => { '_index' => 'default', '_type' => 'post', '_id' => '123', '_version' => 1, 'ok' => true }},
85
89
  { 'create' => { '_index' => 'default', '_type' => 'post', '_id' => '124', '_version' => 1, 'ok' => true }}
86
90
  )
@@ -99,6 +103,10 @@ describe Elastictastic::BulkPersistenceStrategy do
99
103
  it 'should set IDs' do
100
104
  posts.map { |post| post.id }.should == %w(123 124)
101
105
  end
106
+
107
+ it 'should set versions' do
108
+ posts.each { |post| post.version.should == 1 }
109
+ end
102
110
  end
103
111
 
104
112
  describe 'create with ID set' do
@@ -110,7 +118,7 @@ describe Elastictastic::BulkPersistenceStrategy do
110
118
  end
111
119
 
112
120
  before do
113
- stub_elasticsearch_bulk(
121
+ stub_es_bulk(
114
122
  'create' => { '_index' => 'default', '_type' => 'post', '_id' => '123', '_version' => 1, 'ok' => true }
115
123
  )
116
124
  Elastictastic.bulk { post.save }
@@ -130,30 +138,38 @@ describe Elastictastic::BulkPersistenceStrategy do
130
138
  it 'should retain ID' do
131
139
  post.id.should == '123'
132
140
  end
141
+
142
+ it 'should set version' do
143
+ post.version.should == 1
144
+ end
133
145
  end
134
146
 
135
147
  describe '#update' do
136
148
  let(:post) do
137
149
  Post.new.tap do |post|
138
150
  post.id = '123'
151
+ post.version = 1
139
152
  post.persisted!
140
153
  end
141
154
  end
142
155
 
143
156
  before do
144
- stub_elasticsearch_bulk(
145
- 'index' => { '_index' => 'default', '_type' => 'post', '_id' => '123', '_version' => 1, 'ok' => true }
157
+ stub_es_bulk(
158
+ 'index' => { '_index' => 'default', '_type' => 'post', '_id' => '123', '_version' => 2, 'ok' => true }
146
159
  )
160
+ Elastictastic.bulk { post.save }
147
161
  end
148
162
 
149
163
  it 'should send update' do
150
- Elastictastic.bulk { post.save }
151
-
152
164
  bulk_requests.should == [
153
- { 'index' => { '_index' => 'default', '_type' => 'post', '_id' => '123' }},
165
+ { 'index' => { '_index' => 'default', '_type' => 'post', '_id' => '123', '_version' => 1 }},
154
166
  post.elasticsearch_doc
155
167
  ]
156
168
  end
169
+
170
+ it 'should set version' do
171
+ post.version.should == 2
172
+ end
157
173
  end
158
174
 
159
175
  describe 'destroy' do
@@ -161,20 +177,21 @@ describe Elastictastic::BulkPersistenceStrategy do
161
177
  Post.new.tap do |post|
162
178
  post.id = '123'
163
179
  post.title = 'bulky'
180
+ post.version = 1
164
181
  post.persisted!
165
182
  end
166
183
  end
167
184
 
168
185
  before do
169
- stub_elasticsearch_bulk(
170
- 'destroy' => { '_index' => 'default', '_type' => 'post', '_id' => '123', '_version' => 1, 'ok' => true }
186
+ stub_es_bulk(
187
+ 'delete' => { '_index' => 'default', '_type' => 'post', '_id' => '123', '_version' => 2, 'ok' => true }
171
188
  )
172
189
  Elastictastic.bulk { post.destroy }
173
190
  end
174
191
 
175
192
  it 'should send destroy' do
176
193
  bulk_requests.should == [
177
- { 'delete' => { '_index' => 'default', '_type' => 'post', '_id' => '123' }}
194
+ { 'delete' => { '_index' => 'default', '_type' => 'post', '_id' => '123', '_version' => 1 }}
178
195
  ]
179
196
  end
180
197
 
@@ -191,7 +208,7 @@ describe Elastictastic::BulkPersistenceStrategy do
191
208
 
192
209
  it 'should return to individual persistence strategy' do
193
210
  error_proc.call rescue nil
194
- stub_elasticsearch_create('default', 'post')
211
+ stub_es_create('default', 'post')
195
212
  Post.new.save
196
213
  last_request.path.should == '/default/post'
197
214
  end
@@ -214,20 +231,131 @@ describe Elastictastic::BulkPersistenceStrategy do
214
231
  it_should_behave_like 'block with error'
215
232
  end
216
233
 
217
- describe 'raising CancelBulkOperation' do
218
- let :error_proc do
219
- lambda do
220
- Elastictastic.bulk do
221
- Post.new.save
222
- raise Elastictastic::CancelBulkOperation
234
+ describe 'with :auto_flush specified' do
235
+ before do
236
+ responses = Array.new(3) do
237
+ { 'create' => generate_es_hit('post').except('_source').merge('ok' => true) }
238
+ end.each_slice(2).map { |slice| { 'items' => slice } }
239
+ stub_request_json(
240
+ :post,
241
+ match_es_path('/_bulk'),
242
+ *responses
243
+ )
244
+ Elastictastic.bulk(:auto_flush => 2) { 3.times { Post.new.save }}
245
+ end
246
+
247
+ it 'should perform multiple requests when auto-flush triggered' do
248
+ FakeWeb.should have(2).requests
249
+ end
250
+
251
+ it 'should flush after specified number of operations' do
252
+ FakeWeb.requests.first.body.split("\n").should have(4).items
253
+ FakeWeb.requests.last.body.split("\n").should have(2).items
254
+ end
255
+ end
256
+
257
+ describe 'multiple operations on the same document' do
258
+ let(:post) do
259
+ Post.new.tap do |post|
260
+ post.id = '1'
261
+ post.version = 1
262
+ post.persisted!
263
+ end
264
+ end
265
+
266
+ before do
267
+ stub_es_bulk(
268
+ 'delete' => generate_es_hit('post', :version => 2, :id => '1').merge('ok' => true)
269
+ )
270
+ Elastictastic.bulk do
271
+ post.save
272
+ post.destroy
273
+ end
274
+ end
275
+
276
+ it 'should only send one operation per document' do
277
+ bulk_requests.length.should == 1
278
+ end
279
+
280
+ it 'should send last operation for each document' do
281
+ bulk_requests.last.should == { 'delete' => generate_es_hit('post', :id => '1').except('_source') }
282
+ end
283
+ end
284
+
285
+ describe 'multiple creates' do
286
+ before do
287
+ stub_es_bulk(
288
+ { 'create' => generate_es_hit('post', :id => '1').merge('ok' => true) },
289
+ { 'create' => generate_es_hit('post', :id => '2').merge('ok' => true) }
290
+ )
291
+ Elastictastic.bulk { 2.times { |i| Post.new(:title => "post #{i}").save }}
292
+ end
293
+
294
+ it 'should create all documents' do
295
+ bulk_requests.length.should == 4
296
+ end
297
+
298
+ it 'should send correct info for each document' do
299
+ bulk_requests.each_slice(2).map do |commands|
300
+ commands.last['title']
301
+ end.should == ['post 0', 'post 1']
302
+ end
303
+ end
304
+
305
+ describe 'updating documents with same ID but different index' do
306
+ let(:posts) do
307
+ %w(default my_index).map do |index|
308
+ Post.in_index(index).new.tap do |post|
309
+ post.id = '1'
310
+ post.persisted!
223
311
  end
224
312
  end
225
313
  end
226
314
 
227
- it 'should not propagate error' do
228
- expect(&error_proc).not_to raise_error
315
+ before do
316
+ stub_es_bulk(
317
+ { 'index' => generate_es_hit('post', :id => '1').merge('ok' => true) },
318
+ { 'index' => generate_es_hit('post', :index => 'my_index', :id => '1').merge('ok' => true) }
319
+ )
320
+ Elastictastic.bulk { posts.map { |post| post.save }}
229
321
  end
230
322
 
231
- it_should_behave_like 'block with error'
323
+ it 'should send updates for both documents' do
324
+ bulk_requests.length.should == 4
325
+ end
326
+ end
327
+
328
+ describe 'with routing' do
329
+ let(:photo) { Photo.new(:post_id => 1) }
330
+
331
+ it 'should include routing on create' do
332
+ stub_es_bulk(
333
+ 'create' => { '_index' => 'default', '_type' => 'photo', '_id' => '123', '_version' => 1, 'ok' => true }
334
+ )
335
+ Elastictastic.bulk { photo.save }
336
+ bulk_requests.first['create']['_routing'].should == '1'
337
+ end
338
+
339
+ it 'should include routing on update' do
340
+ photo.id = '123'
341
+ photo.version = 1
342
+ photo.persisted!
343
+ stub_es_bulk(
344
+ 'index' => { '_index' => 'default', '_type' => 'photo', '_id' => '123', '_version' => 2, 'ok' => true }
345
+ )
346
+ Elastictastic.bulk { photo.save }
347
+ bulk_requests.first['index']['_routing'].should == '1'
348
+ end
349
+
350
+ it 'should include routing on destroy' do
351
+ photo.id = '123'
352
+ photo.version = 1
353
+ photo.persisted!
354
+ stub_es_bulk(
355
+ 'delete' => { '_index' => 'default', '_type' => 'post', '_id' => '123', '_version' => 2, 'ok' => true }
356
+ )
357
+ Elastictastic.bulk { photo.destroy }
358
+ bulk_requests.first['delete']['_routing'].should == '1'
359
+ end
232
360
  end
233
361
  end
@@ -15,6 +15,10 @@ describe Elastictastic::Callbacks do
15
15
  before_create :before_create_ran!
16
16
  before_update :before_update_ran!
17
17
  before_destroy :before_destroy_ran!
18
+ after_save :after_save_ran!
19
+ after_create :after_create_ran!
20
+ after_update :after_update_ran!
21
+ after_destroy :after_destroy_ran!
18
22
 
19
23
  def hooks_that_ran
20
24
  @hooks_that_ran ||= Set[]
@@ -45,52 +49,79 @@ describe Elastictastic::Callbacks do
45
49
  end
46
50
 
47
51
  before do
48
- stub_elasticsearch_create('default', 'my_model')
49
- stub_elasticsearch_update('default', 'my_model', id)
50
- stub_elasticsearch_destroy('default', 'my_model', id)
52
+ stub_es_create('default', 'my_model')
53
+ stub_es_update('default', 'my_model', id)
54
+ stub_es_destroy('default', 'my_model', id)
51
55
  end
52
56
 
53
- describe '#before_save' do
57
+ %w(before after).each do |position|
58
+ describe "##{position}_save" do
54
59
 
55
- it 'should run before create' do
56
- instance.save
57
- instance.should have_run_hook(:before_save)
58
- end
60
+ it "should run #{position} create" do
61
+ instance.save
62
+ instance.should have_run_hook(:"#{position}_save")
63
+ end
59
64
 
60
- it 'should run before update' do
61
- persisted_instance.save
62
- persisted_instance.should have_run_hook(:before_save)
63
- end
64
- end
65
+ it 'should run before update' do
66
+ persisted_instance.save
67
+ persisted_instance.should have_run_hook(:"#{position}_save")
68
+ end
65
69
 
66
- describe '#before_create' do
67
- it 'should run before create' do
68
- instance.save
69
- instance.should have_run_hook(:before_create)
70
- end
70
+ it 'should not run before create when callbacks disabled' do
71
+ instance.save(:callbacks => false)
72
+ instance.should_not have_run_hook(:"#{position}_save")
73
+ end
71
74
 
72
- it 'should run before update' do
73
- persisted_instance.save
74
- persisted_instance.should_not have_run_hook(:before_create)
75
+ it 'should not run before update when hooks disabled' do
76
+ persisted_instance.save(:callbacks => false)
77
+ instance.should_not have_run_hook(:"#{position}_save")
78
+ end
75
79
  end
76
- end
77
80
 
78
- describe '#before_update' do
79
- it 'should not run before create' do
80
- instance.save
81
- instance.should_not have_run_hook :before_update
81
+ describe "##{position}_create" do
82
+ it "should run #{position} create" do
83
+ instance.save
84
+ instance.should have_run_hook(:"#{position}_create")
85
+ end
86
+
87
+ it "should not run #{position} update" do
88
+ persisted_instance.save
89
+ persisted_instance.should_not have_run_hook(:"#{position}_create")
90
+ end
91
+
92
+ it "should not run #{position} create when callbacks disabled" do
93
+ instance.save(:callbacks => false)
94
+ instance.should_not have_run_hook(:"#{position}_create")
95
+ end
82
96
  end
83
97
 
84
- it 'should run before update' do
85
- persisted_instance.save
86
- persisted_instance.should have_run_hook(:before_update)
98
+ describe "##{position}_update" do
99
+ it "should not run #{position} create" do
100
+ instance.save
101
+ instance.should_not have_run_hook :"#{position}_update"
102
+ end
103
+
104
+ it "should run #{position} update" do
105
+ persisted_instance.save
106
+ persisted_instance.should have_run_hook(:"#{position}_update")
107
+ end
108
+
109
+ it "should not run #{position} update if callbacks disabled" do
110
+ persisted_instance.save(:callbacks => false)
111
+ persisted_instance.should_not have_run_hook(:"#{position}_update")
112
+ end
87
113
  end
88
- end
89
114
 
90
- describe '#before_destroy' do
91
- it 'should run before destroy' do
92
- persisted_instance.destroy
93
- persisted_instance.should have_run_hook(:before_destroy)
115
+ describe "##{position}_destroy" do
116
+ it "should run #{position} destroy" do
117
+ persisted_instance.destroy
118
+ persisted_instance.should have_run_hook(:"#{position}_destroy")
119
+ end
120
+
121
+ it "should not run #{position} destroy if callbacks disabled" do
122
+ persisted_instance.destroy(:callbacks => false)
123
+ persisted_instance.should_not have_run_hook(:"#{position}_destroy")
124
+ end
94
125
  end
95
126
  end
96
127
  end