elasticsearch-persistence 0.1.3 → 0.1.4

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 (53) hide show
  1. checksums.yaml +8 -8
  2. data/CHANGELOG.md +4 -0
  3. data/README.md +238 -7
  4. data/elasticsearch-persistence.gemspec +4 -1
  5. data/examples/music/album.rb +34 -0
  6. data/examples/music/artist.rb +50 -0
  7. data/examples/music/artists/_form.html.erb +8 -0
  8. data/examples/music/artists/artists_controller.rb +67 -0
  9. data/examples/music/artists/artists_controller_test.rb +53 -0
  10. data/examples/music/artists/index.html.erb +57 -0
  11. data/examples/music/artists/show.html.erb +51 -0
  12. data/examples/music/assets/application.css +226 -0
  13. data/examples/music/assets/autocomplete.css +48 -0
  14. data/examples/music/assets/blank_cover.png +0 -0
  15. data/examples/music/assets/form.css +113 -0
  16. data/examples/music/index_manager.rb +60 -0
  17. data/examples/music/search/index.html.erb +93 -0
  18. data/examples/music/search/search_controller.rb +41 -0
  19. data/examples/music/search/search_controller_test.rb +9 -0
  20. data/examples/music/search/search_helper.rb +15 -0
  21. data/examples/music/suggester.rb +45 -0
  22. data/examples/music/template.rb +392 -0
  23. data/examples/music/vendor/assets/jquery-ui-1.10.4.custom.min.css +7 -0
  24. data/examples/music/vendor/assets/jquery-ui-1.10.4.custom.min.js +6 -0
  25. data/examples/{sinatra → notes}/.gitignore +0 -0
  26. data/examples/{sinatra → notes}/Gemfile +0 -0
  27. data/examples/{sinatra → notes}/README.markdown +0 -0
  28. data/examples/{sinatra → notes}/application.rb +0 -0
  29. data/examples/{sinatra → notes}/config.ru +0 -0
  30. data/examples/{sinatra → notes}/test.rb +0 -0
  31. data/lib/elasticsearch/persistence.rb +19 -0
  32. data/lib/elasticsearch/persistence/model.rb +129 -0
  33. data/lib/elasticsearch/persistence/model/base.rb +75 -0
  34. data/lib/elasticsearch/persistence/model/errors.rb +8 -0
  35. data/lib/elasticsearch/persistence/model/find.rb +171 -0
  36. data/lib/elasticsearch/persistence/model/rails.rb +39 -0
  37. data/lib/elasticsearch/persistence/model/store.rb +239 -0
  38. data/lib/elasticsearch/persistence/model/utils.rb +0 -0
  39. data/lib/elasticsearch/persistence/repository.rb +3 -1
  40. data/lib/elasticsearch/persistence/repository/search.rb +25 -0
  41. data/lib/elasticsearch/persistence/version.rb +1 -1
  42. data/lib/rails/generators/elasticsearch/model/model_generator.rb +21 -0
  43. data/lib/rails/generators/elasticsearch/model/templates/model.rb.tt +9 -0
  44. data/lib/rails/generators/elasticsearch_generator.rb +2 -0
  45. data/test/integration/model/model_basic_test.rb +157 -0
  46. data/test/integration/repository/default_class_test.rb +6 -0
  47. data/test/unit/model_base_test.rb +40 -0
  48. data/test/unit/model_find_test.rb +147 -0
  49. data/test/unit/model_gateway_test.rb +99 -0
  50. data/test/unit/model_rails_test.rb +88 -0
  51. data/test/unit/model_store_test.rb +493 -0
  52. data/test/unit/repository_search_test.rb +17 -0
  53. metadata +79 -9
@@ -96,6 +96,12 @@ module Elasticsearch
96
96
  assert_equal 2, results.size
97
97
  end
98
98
 
99
+ should "count notes" do
100
+ @repository.save Note.new(title: 'Test')
101
+ @repository.client.indices.refresh index: @repository.index_name
102
+ assert_equal 1, @repository.count
103
+ end
104
+
99
105
  should "save and find a plain hash" do
100
106
  @repository.save id: 1, title: 'Hash'
101
107
  result = @repository.find(1)
@@ -0,0 +1,40 @@
1
+ require 'test_helper'
2
+
3
+ require 'elasticsearch/persistence/model'
4
+ require 'elasticsearch/persistence/model/rails'
5
+
6
+ class Elasticsearch::Persistence::ModelBaseTest < Test::Unit::TestCase
7
+ context "The model" do
8
+ setup do
9
+ class DummyBaseModel
10
+ include Elasticsearch::Persistence::Model
11
+
12
+ attribute :name, String
13
+ end
14
+ end
15
+
16
+ should "respond to id, _id, _index, _type and _version" do
17
+ model = DummyBaseModel.new
18
+
19
+ [:id, :_id, :_index, :_type, :_version].each { |method| assert_respond_to model, method }
20
+ end
21
+
22
+ should "set the ID from attributes during initialization" do
23
+ m = DummyBaseModel.new id: 1
24
+ assert_equal 1, m.id
25
+
26
+ m = DummyBaseModel.new 'id' => 2
27
+ assert_equal 2, m.id
28
+ end
29
+
30
+ should "have ID in attributes" do
31
+ m = DummyBaseModel.new id: 1, name: 'Test'
32
+ assert_equal 1, m.attributes[:id]
33
+ end
34
+
35
+ should "have the customized inspect method" do
36
+ m = DummyBaseModel.new name: 'Test'
37
+ assert_match /name\: "Test"/, m.inspect
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,147 @@
1
+ require 'test_helper'
2
+
3
+ require 'active_model'
4
+ require 'virtus'
5
+
6
+ require 'elasticsearch/persistence/model/errors'
7
+ require 'elasticsearch/persistence/model/find'
8
+
9
+ class Elasticsearch::Persistence::ModelFindTest < Test::Unit::TestCase
10
+ context "The model find module," do
11
+
12
+ class DummyFindModel
13
+ include ActiveModel::Naming
14
+ include ActiveModel::Conversion
15
+ include ActiveModel::Serialization
16
+ include ActiveModel::Serializers::JSON
17
+ include ActiveModel::Validations
18
+
19
+ include Virtus.model
20
+
21
+ extend Elasticsearch::Persistence::Model::Find::ClassMethods
22
+
23
+ extend ActiveModel::Callbacks
24
+ define_model_callbacks :create, :save, :update, :destroy
25
+ define_model_callbacks :find, :touch, only: :after
26
+
27
+ attribute :title, String
28
+ attribute :count, Integer, default: 0
29
+ attribute :created_at, DateTime, default: lambda { |o,a| Time.now.utc }
30
+ attribute :updated_at, DateTime, default: lambda { |o,a| Time.now.utc }
31
+ end
32
+
33
+ setup do
34
+ @gateway = stub(client: stub(), index_name: 'foo', document_type: 'bar')
35
+ DummyFindModel.stubs(:gateway).returns(@gateway)
36
+
37
+ @response = MultiJson.load <<-JSON
38
+ {
39
+ "took": 14,
40
+ "timed_out": false,
41
+ "_shards": {
42
+ "total": 5,
43
+ "successful": 5,
44
+ "failed": 0
45
+ },
46
+ "hits": {
47
+ "total": 1,
48
+ "max_score": 1.0,
49
+ "hits": [
50
+ {
51
+ "_index": "dummy",
52
+ "_type": "dummy",
53
+ "_id": "abc123",
54
+ "_score": 1.0,
55
+ "_source": {
56
+ "name": "TEST"
57
+ }
58
+ }
59
+ ]
60
+ }
61
+ }
62
+ JSON
63
+ end
64
+
65
+ should "find all records" do
66
+ @gateway
67
+ .expects(:search)
68
+ .with({ query: { match_all: {} }, size: 10_000 })
69
+ .returns(@response)
70
+
71
+ DummyFindModel.all
72
+ end
73
+
74
+ should "pass options when finding all records" do
75
+ @gateway
76
+ .expects(:search)
77
+ .with({ query: { match: { title: 'test' } }, size: 10_000, routing: 'abc123' })
78
+ .returns(@response)
79
+
80
+ DummyFindModel.all( { query: { match: { title: 'test' } }, routing: 'abc123' } )
81
+ end
82
+
83
+ context "finding via scan/scroll" do
84
+ setup do
85
+ @gateway
86
+ .expects(:deserialize)
87
+ .with('_source' => {'foo' => 'bar'})
88
+ .returns('_source' => {'foo' => 'bar'})
89
+
90
+ @gateway.client
91
+ .expects(:search)
92
+ .with do |arguments|
93
+ assert_equal 'scan', arguments[:search_type]
94
+ assert_equal 'foo', arguments[:index]
95
+ assert_equal 'bar', arguments[:type]
96
+ end
97
+ .returns(MultiJson.load('{"_scroll_id":"abc123==", "hits":{"hits":[]}}'))
98
+
99
+ @gateway.client
100
+ .expects(:scroll)
101
+ .twice
102
+ .returns(MultiJson.load('{"_scroll_id":"abc456==", "hits":{"hits":[{"_source":{"foo":"bar"}}]}}'))
103
+ .then
104
+ .returns(MultiJson.load('{"_scroll_id":"abc789==", "hits":{"hits":[]}}'))
105
+ end
106
+
107
+ should "find all records in batches" do
108
+ @doc = nil
109
+ result = DummyFindModel.find_in_batches { |batch| @doc = batch.first['_source']['foo'] }
110
+
111
+ assert_equal 'abc789==', result
112
+ assert_equal 'bar', @doc
113
+ end
114
+
115
+ should "return an Enumerator for find in batches" do
116
+ @doc = nil
117
+ assert_nothing_raised do
118
+ e = DummyFindModel.find_in_batches
119
+ assert_instance_of Enumerator, e
120
+
121
+ e.each { |batch| @doc = batch.first['_source']['foo'] }
122
+ assert_equal 'bar', @doc
123
+ end
124
+ end
125
+
126
+ should "find each" do
127
+ @doc = nil
128
+ result = DummyFindModel.find_each { |doc| @doc = doc['_source']['foo'] }
129
+
130
+ assert_equal 'abc789==', result
131
+ assert_equal 'bar', @doc
132
+ end
133
+
134
+ should "return an Enumerator for find each" do
135
+ @doc = nil
136
+ assert_nothing_raised do
137
+ e = DummyFindModel.find_each
138
+ assert_instance_of Enumerator, e
139
+
140
+ e.each { |doc| @doc = doc['_source']['foo'] }
141
+ assert_equal 'bar', @doc
142
+ end
143
+ end
144
+ end
145
+
146
+ end
147
+ end
@@ -0,0 +1,99 @@
1
+ require 'test_helper'
2
+
3
+ require 'elasticsearch/persistence/model'
4
+ require 'elasticsearch/persistence/model/rails'
5
+
6
+ class Elasticsearch::Persistence::ModelGatewayTest < Test::Unit::TestCase
7
+ context "The model gateway" do
8
+ setup do
9
+ class DummyGatewayModel
10
+ include Elasticsearch::Persistence::Model
11
+ end
12
+ end
13
+
14
+ teardown do
15
+ Elasticsearch::Persistence::ModelGatewayTest.__send__ :remove_const, :DummyGatewayModel \
16
+ rescue NameError; nil
17
+ end
18
+
19
+ should "be accessible" do
20
+ assert_instance_of Elasticsearch::Persistence::Repository::Class, DummyGatewayModel.gateway
21
+
22
+ $a = 0
23
+ DummyGatewayModel.gateway { $a += 1 }
24
+ assert_equal 1, $a
25
+
26
+ @b = 0
27
+ def run!; DummyGatewayModel.gateway { |g| @b += 1 }; end
28
+ run!
29
+ assert_equal 1, @b
30
+
31
+ assert_equal DummyGatewayModel, DummyGatewayModel.gateway.klass
32
+ end
33
+
34
+ should "define common attributes" do
35
+ d = DummyGatewayModel.new
36
+
37
+ assert_respond_to d, :updated_at
38
+ assert_respond_to d, :created_at
39
+ end
40
+
41
+ should "allow to configure settings" do
42
+ DummyGatewayModel.settings(number_of_shards: 1)
43
+
44
+ assert_equal 1, DummyGatewayModel.settings.to_hash[:number_of_shards]
45
+ end
46
+
47
+ should "allow to configure mappings" do
48
+ DummyGatewayModel.mapping { indexes :name, analyzer: 'snowball' }
49
+
50
+ assert_equal 'snowball',
51
+ DummyGatewayModel.mapping.to_hash[:dummy_gateway_model][:properties][:name][:analyzer]
52
+ end
53
+
54
+ should "configure the mapping via attribute" do
55
+ DummyGatewayModel.attribute :name, String, mapping: { analyzer: 'snowball' }
56
+
57
+ assert_respond_to DummyGatewayModel, :name
58
+ assert_equal 'snowball',
59
+ DummyGatewayModel.mapping.to_hash[:dummy_gateway_model][:properties][:name][:analyzer]
60
+ end
61
+
62
+ should "configure the mapping via an attribute block" do
63
+ DummyGatewayModel.attribute :name, String do
64
+ indexes :name, analyzer: 'custom'
65
+ end
66
+
67
+ assert_respond_to DummyGatewayModel, :name
68
+ assert_equal 'custom',
69
+ DummyGatewayModel.mapping.to_hash[:dummy_gateway_model][:properties][:name][:analyzer]
70
+ end
71
+
72
+ should "properly look up types for classes" do
73
+ assert_equal 'string', Elasticsearch::Persistence::Model::Utils::lookup_type(String)
74
+ assert_equal 'integer', Elasticsearch::Persistence::Model::Utils::lookup_type(Integer)
75
+ assert_equal 'float', Elasticsearch::Persistence::Model::Utils::lookup_type(Float)
76
+ assert_equal 'date', Elasticsearch::Persistence::Model::Utils::lookup_type(Date)
77
+ assert_equal 'boolean', Elasticsearch::Persistence::Model::Utils::lookup_type(Virtus::Attribute::Boolean)
78
+ end
79
+
80
+ should "remove IDs from hash when serializing" do
81
+ assert_equal( {foo: 'bar'}, DummyGatewayModel.gateway.serialize(id: '123', foo: 'bar') )
82
+ end
83
+
84
+ should "set IDs from hash when deserializing" do
85
+ assert_equal 'abc123', DummyGatewayModel.gateway.deserialize('_id' => 'abc123', '_source' => {}).id
86
+ end
87
+
88
+ should "set @persisted variable from hash when deserializing" do
89
+ assert DummyGatewayModel.gateway.deserialize('_id' => 'abc123', '_source' => {}).instance_variable_get(:@persisted)
90
+ end
91
+
92
+ should "allow to access the raw hit from results as Hashie::Mash" do
93
+ assert_equal 0.42, DummyGatewayModel.gateway.deserialize('_score' => 0.42, '_source' => {}).hit._score
94
+ end
95
+
96
+
97
+
98
+ end
99
+ end
@@ -0,0 +1,88 @@
1
+ require 'test_helper'
2
+
3
+ require 'elasticsearch/persistence/model'
4
+ require 'elasticsearch/persistence/model/rails'
5
+
6
+ require 'rails'
7
+ require 'action_controller/railtie'
8
+ require 'action_view/railtie'
9
+
10
+ class ::MyRailsModel
11
+ include Elasticsearch::Persistence::Model
12
+ include Elasticsearch::Persistence::Model::Rails
13
+
14
+ attribute :name, String, mapping: { analyzer: 'string' }
15
+ attribute :published_at, DateTime
16
+ attribute :published_on, Date
17
+ end
18
+
19
+ class Application < Rails::Application
20
+ config.eager_load = false
21
+ config.root = File.dirname(File.expand_path('../../../tmp', __FILE__))
22
+ config.logger = Logger.new($stderr)
23
+
24
+ routes.append do
25
+ resources :my_rails_models
26
+ end
27
+ end
28
+
29
+ class ApplicationController < ActionController::Base
30
+ include Application.routes.url_helpers
31
+ include ActionController::UrlFor
32
+ end
33
+ ApplicationController.default_url_options = { host: 'localhost' }
34
+ ApplicationController._routes.append { resources :my_rails_models }
35
+
36
+ class MyRailsModelController < ApplicationController; end
37
+
38
+ Application.initialize!
39
+
40
+ class Elasticsearch::Persistence::ModelRailsTest < Test::Unit::TestCase
41
+ context "The model in a Rails application" do
42
+
43
+ should "generate proper URLs and paths" do
44
+ model = MyRailsModel.new name: 'Test'
45
+ model.stubs(:id).returns(1)
46
+ model.stubs(:persisted?).returns(true)
47
+
48
+ controller = MyRailsModelController.new
49
+ controller.request = ActionDispatch::Request.new({})
50
+
51
+ assert_equal 'http://localhost/my_rails_models/1', controller.url_for(model)
52
+ assert_equal '/my_rails_models/1/edit', controller.edit_my_rails_model_path(model)
53
+ end
54
+
55
+ should "generate a link" do
56
+ class MyView; include ActionView::Helpers::UrlHelper; end
57
+
58
+ model = MyRailsModel.new name: 'Test'
59
+ view = MyView.new
60
+ view.expects(:url_for).with(model).returns('foo/bar')
61
+
62
+ assert_equal '<a href="foo/bar">Show</a>', view.link_to('Show', model)
63
+ end
64
+
65
+ should "parse DateTime from Rails forms" do
66
+ params = { "published_at(1i)"=>"2014",
67
+ "published_at(2i)"=>"1",
68
+ "published_at(3i)"=>"1",
69
+ "published_at(4i)"=>"12",
70
+ "published_at(5i)"=>"00"
71
+ }
72
+
73
+ m = MyRailsModel.new params
74
+ assert_equal "2014-01-01T12:00:00+00:00", m.published_at.iso8601
75
+ end
76
+
77
+ should "parse Date from Rails forms" do
78
+ params = { "published_on(1i)"=>"2014",
79
+ "published_on(2i)"=>"1",
80
+ "published_on(3i)"=>"1"
81
+ }
82
+
83
+ m = MyRailsModel.new params
84
+ assert_equal "2014-01-01", m.published_on.iso8601
85
+ end
86
+
87
+ end
88
+ end
@@ -0,0 +1,493 @@
1
+ require 'test_helper'
2
+
3
+ require 'active_model'
4
+ require 'virtus'
5
+
6
+ require 'elasticsearch/persistence/model/base'
7
+ require 'elasticsearch/persistence/model/errors'
8
+ require 'elasticsearch/persistence/model/store'
9
+
10
+ class Elasticsearch::Persistence::ModelStoreTest < Test::Unit::TestCase
11
+ context "The model store module," do
12
+
13
+ class DummyStoreModel
14
+ include ActiveModel::Naming
15
+ include ActiveModel::Conversion
16
+ include ActiveModel::Serialization
17
+ include ActiveModel::Serializers::JSON
18
+ include ActiveModel::Validations
19
+
20
+ include Virtus.model
21
+
22
+ include Elasticsearch::Persistence::Model::Base::InstanceMethods
23
+ extend Elasticsearch::Persistence::Model::Store::ClassMethods
24
+ include Elasticsearch::Persistence::Model::Store::InstanceMethods
25
+
26
+ extend ActiveModel::Callbacks
27
+ define_model_callbacks :create, :save, :update, :destroy
28
+ define_model_callbacks :find, :touch, only: :after
29
+
30
+ attribute :title, String
31
+ attribute :count, Integer, default: 0
32
+ attribute :created_at, DateTime, default: lambda { |o,a| Time.now.utc }
33
+ attribute :updated_at, DateTime, default: lambda { |o,a| Time.now.utc }
34
+ end
35
+
36
+ setup do
37
+ @shoulda_subject = DummyStoreModel.new title: 'Test'
38
+ @gateway = stub
39
+ DummyStoreModel.stubs(:gateway).returns(@gateway)
40
+ end
41
+
42
+ teardown do
43
+ Elasticsearch::Persistence::ModelStoreTest.__send__ :remove_const, :DummyStoreModelWithCallback \
44
+ rescue NameError; nil
45
+ end
46
+
47
+ should "be new_record" do
48
+ assert subject.new_record?
49
+ end
50
+
51
+ context "when creating," do
52
+ should "save the object and return it" do
53
+ DummyStoreModel.any_instance.expects(:save).returns({'_id' => 'X'})
54
+
55
+ assert_instance_of DummyStoreModel, DummyStoreModel.create(title: 'Test')
56
+ end
57
+
58
+ should "execute the callbacks" do
59
+ DummyStoreModelWithCallback = Class.new(DummyStoreModel)
60
+ @gateway.expects(:save).returns({'_id' => 'X'})
61
+
62
+ DummyStoreModelWithCallback.after_create { $stderr.puts "CREATED" }
63
+ DummyStoreModelWithCallback.after_save { $stderr.puts "SAVED" }
64
+
65
+ $stderr.expects(:puts).with('CREATED')
66
+ $stderr.expects(:puts).with('SAVED')
67
+
68
+ DummyStoreModelWithCallback.create name: 'test'
69
+ end
70
+ end
71
+
72
+ context "when saving," do
73
+ should "save the model" do
74
+ @gateway
75
+ .expects(:save)
76
+ .with do |object, options|
77
+ assert_equal subject, object
78
+ assert_equal nil, options[:id]
79
+ end
80
+ .returns({'_id' => 'abc123'})
81
+
82
+ assert ! subject.persisted?
83
+
84
+ assert subject.save
85
+ assert subject.persisted?
86
+ end
87
+
88
+ should "save the model and set the ID" do
89
+ @gateway
90
+ .expects(:save)
91
+ .returns({'_id' => 'abc123'})
92
+
93
+ assert_nil subject.id
94
+
95
+ subject.save
96
+ assert_equal 'abc123', subject.id
97
+ end
98
+
99
+ should "save the model and update the timestamp" do
100
+ Time.expects(:now).returns(Time.parse('2014-01-01T00:00:00Z')).at_least_once
101
+ @gateway
102
+ .expects(:save)
103
+ .returns({'_id' => 'abc123'})
104
+
105
+ subject.save
106
+ assert_equal Time.parse('2014-01-01T00:00:00Z'), subject.updated_at
107
+ end
108
+
109
+ should "pass the options to gateway" do
110
+ @gateway
111
+ .expects(:save)
112
+ .with do |object, options|
113
+ assert_equal 'ABC', options[:routing]
114
+ end
115
+ .returns({'_id' => 'abc123'})
116
+
117
+ assert subject.save routing: 'ABC'
118
+ end
119
+
120
+ should "return the response" do
121
+ @gateway
122
+ .expects(:save)
123
+ .returns('FOOBAR')
124
+
125
+ assert_equal 'FOOBAR', subject.save
126
+ end
127
+
128
+ should "execute the callbacks" do
129
+ @gateway.expects(:save).returns({'_id' => 'abc'})
130
+ DummyStoreModelWithCallback = Class.new(DummyStoreModel)
131
+
132
+ DummyStoreModelWithCallback.after_save { $stderr.puts "SAVED" }
133
+
134
+ $stderr.expects(:puts).with('SAVED')
135
+ d = DummyStoreModelWithCallback.new name: 'Test'
136
+ d.save
137
+ end
138
+
139
+ should "save the model to its own index" do
140
+ @gateway.expects(:save)
141
+ .with do |model, options|
142
+ assert_equal 'my_custom_index', options[:index]
143
+ assert_equal 'my_custom_type', options[:type]
144
+ end
145
+ .returns({'_id' => 'abc'})
146
+
147
+ d = DummyStoreModel.new name: 'Test'
148
+ d.instance_variable_set(:@_index, 'my_custom_index')
149
+ d.instance_variable_set(:@_type, 'my_custom_type')
150
+ d.save
151
+ end
152
+
153
+ should "set the meta attributes from response" do
154
+ @gateway.expects(:save)
155
+ .with do |model, options|
156
+ assert_equal 'my_custom_index', options[:index]
157
+ assert_equal 'my_custom_type', options[:type]
158
+ end
159
+ .returns({'_id' => 'abc', '_index' => 'foo', '_type' => 'bar', '_version' => '100'})
160
+
161
+ d = DummyStoreModel.new name: 'Test'
162
+ d.instance_variable_set(:@_index, 'my_custom_index')
163
+ d.instance_variable_set(:@_type, 'my_custom_type')
164
+ d.save
165
+
166
+ assert_equal 'foo', d._index
167
+ assert_equal 'bar', d._type
168
+ assert_equal '100', d._version
169
+ end
170
+ end
171
+
172
+ context "when destroying," do
173
+ should "remove the model from Elasticsearch" do
174
+ subject.expects(:persisted?).returns(true)
175
+ subject.expects(:id).returns('abc123')
176
+ subject.expects(:freeze).returns(subject)
177
+
178
+ @gateway
179
+ .expects(:delete)
180
+ .with('abc123', {})
181
+ .returns({'_id' => 'abc123', 'version' => 2})
182
+
183
+ assert subject.destroy
184
+ assert subject.destroyed?
185
+ end
186
+
187
+ should "pass the options to gateway" do
188
+ subject.expects(:persisted?).returns(true)
189
+ subject.expects(:freeze).returns(subject)
190
+
191
+ @gateway
192
+ .expects(:delete)
193
+ .with do |object, options|
194
+ assert_equal 'ABC', options[:routing]
195
+ end
196
+ .returns({'_id' => 'abc123'})
197
+
198
+ assert subject.destroy routing: 'ABC'
199
+ end
200
+
201
+ should "return the response" do
202
+ subject.expects(:persisted?).returns(true)
203
+ subject.expects(:freeze).returns(subject)
204
+
205
+ @gateway
206
+ .expects(:delete)
207
+ .returns('FOOBAR')
208
+
209
+ assert_equal 'FOOBAR', subject.destroy
210
+ end
211
+
212
+ should "execute the callbacks" do
213
+ @gateway.expects(:delete).returns({'_id' => 'abc'})
214
+ DummyStoreModelWithCallback = Class.new(DummyStoreModel)
215
+
216
+ DummyStoreModelWithCallback.after_destroy { $stderr.puts "DELETED" }
217
+
218
+ $stderr.expects(:puts).with('DELETED')
219
+ d = DummyStoreModelWithCallback.new name: 'Test'
220
+ d.expects(:persisted?).returns(true)
221
+ d.expects(:freeze).returns(d)
222
+
223
+ d.destroy
224
+ end
225
+
226
+ should "remove the model from its own index" do
227
+ @gateway.expects(:delete)
228
+ .with do |model, options|
229
+ assert_equal 'my_custom_index', options[:index]
230
+ assert_equal 'my_custom_type', options[:type]
231
+ end
232
+ .returns({'_id' => 'abc'})
233
+
234
+ d = DummyStoreModel.new name: 'Test'
235
+ d.instance_variable_set(:@_index, 'my_custom_index')
236
+ d.instance_variable_set(:@_type, 'my_custom_type')
237
+ d.expects(:persisted?).returns(true)
238
+ d.expects(:freeze).returns(d)
239
+
240
+ d.destroy
241
+ end
242
+ end
243
+
244
+ context "when updating," do
245
+ should "update the document with partial attributes" do
246
+ subject.expects(:persisted?).returns(true)
247
+ subject.expects(:id).returns('abc123').at_least_once
248
+
249
+ @gateway
250
+ .expects(:update)
251
+ .with do |id, options|
252
+ assert_equal 'abc123', id
253
+ assert_equal 'UPDATED', options[:doc][:title]
254
+ end
255
+ .returns({'_id' => 'abc123', 'version' => 2})
256
+
257
+ assert subject.update title: 'UPDATED'
258
+
259
+ assert_equal 'UPDATED', subject.title
260
+ end
261
+
262
+ should "allow to update the document with a custom script" do
263
+ subject.expects(:persisted?).returns(true)
264
+ subject.expects(:id).returns('abc123').at_least_once
265
+
266
+ @gateway
267
+ .expects(:update)
268
+ .with do |id, options|
269
+ assert_equal 'abc123', id
270
+ assert_equal 'EXEC', options[:script]
271
+ end
272
+ .returns({'_id' => 'abc123', 'version' => 2})
273
+
274
+ assert subject.update( {}, { script: 'EXEC' } )
275
+ end
276
+
277
+ should "pass the options to gateway" do
278
+ subject.expects(:persisted?).returns(true)
279
+
280
+ @gateway
281
+ .expects(:update)
282
+ .with do |object, options|
283
+ assert_equal 'ABC', options[:routing]
284
+ end
285
+ .returns({'_id' => 'abc123'})
286
+
287
+ assert subject.update( { title: 'UPDATED' }, { routing: 'ABC' } )
288
+ end
289
+
290
+ should "return the response" do
291
+ subject.expects(:persisted?).returns(true)
292
+
293
+ @gateway
294
+ .expects(:update)
295
+ .returns('FOOBAR')
296
+
297
+ assert_equal 'FOOBAR', subject.update
298
+ end
299
+
300
+ should "execute the callbacks" do
301
+ @gateway.expects(:update).returns({'_id' => 'abc'})
302
+ DummyStoreModelWithCallback = Class.new(DummyStoreModel)
303
+
304
+ DummyStoreModelWithCallback.after_update { $stderr.puts "UPDATED" }
305
+
306
+ $stderr.expects(:puts).with('UPDATED')
307
+ d = DummyStoreModelWithCallback.new name: 'Test'
308
+ d.expects(:persisted?).returns(true)
309
+ d.update name: 'Update'
310
+ end
311
+
312
+ should "update the model in its own index" do
313
+ @gateway.expects(:update)
314
+ .with do |model, options|
315
+ assert_equal 'my_custom_index', options[:index]
316
+ assert_equal 'my_custom_type', options[:type]
317
+ end
318
+ .returns({'_id' => 'abc'})
319
+
320
+ d = DummyStoreModel.new name: 'Test'
321
+ d.instance_variable_set(:@_index, 'my_custom_index')
322
+ d.instance_variable_set(:@_type, 'my_custom_type')
323
+ d.expects(:persisted?).returns(true)
324
+
325
+ d.update name: 'Update'
326
+ end
327
+
328
+ should "set the meta attributes from response" do
329
+ @gateway.expects(:update)
330
+ .with do |model, options|
331
+ assert_equal 'my_custom_index', options[:index]
332
+ assert_equal 'my_custom_type', options[:type]
333
+ end
334
+ .returns({'_id' => 'abc', '_index' => 'foo', '_type' => 'bar', '_version' => '100'})
335
+
336
+ d = DummyStoreModel.new name: 'Test'
337
+ d.instance_variable_set(:@_index, 'my_custom_index')
338
+ d.instance_variable_set(:@_type, 'my_custom_type')
339
+ d.expects(:persisted?).returns(true)
340
+
341
+ d.update name: 'Update'
342
+
343
+ assert_equal 'foo', d._index
344
+ assert_equal 'bar', d._type
345
+ assert_equal '100', d._version
346
+ end
347
+ end
348
+
349
+ context "when incrementing," do
350
+ should "increment the attribute" do
351
+ subject.expects(:persisted?).returns(true)
352
+
353
+ @gateway
354
+ .expects(:update)
355
+ .with do |id, options|
356
+ assert_equal 'ctx._source.count += 1', options[:script]
357
+ end
358
+ .returns({'_id' => 'abc123', 'version' => 2})
359
+
360
+ assert subject.increment :count
361
+
362
+ assert_equal 1, subject.count
363
+ end
364
+
365
+ should "set the meta attributes from response" do
366
+ subject.expects(:persisted?).returns(true)
367
+
368
+ @gateway.expects(:update)
369
+ .with do |model, options|
370
+ assert_equal 'my_custom_index', options[:index]
371
+ assert_equal 'my_custom_type', options[:type]
372
+ end
373
+ .returns({'_id' => 'abc', '_index' => 'foo', '_type' => 'bar', '_version' => '100'})
374
+
375
+ subject.instance_variable_set(:@_index, 'my_custom_index')
376
+ subject.instance_variable_set(:@_type, 'my_custom_type')
377
+
378
+ subject.increment :count
379
+
380
+ assert_equal 'foo', subject._index
381
+ assert_equal 'bar', subject._type
382
+ assert_equal '100', subject._version
383
+ end
384
+ end
385
+
386
+ context "when decrement," do
387
+ should "decrement the attribute" do
388
+ subject.expects(:persisted?).returns(true)
389
+
390
+ @gateway
391
+ .expects(:update)
392
+ .with do |id, options|
393
+ assert_equal 'ctx._source.count = ctx._source.count - 1', options[:script]
394
+ end
395
+ .returns({'_id' => 'abc123', 'version' => 2})
396
+
397
+ assert subject.decrement :count
398
+
399
+ assert_equal -1, subject.count
400
+ end
401
+
402
+ should "set the meta attributes from response" do
403
+ subject.expects(:persisted?).returns(true)
404
+
405
+ @gateway.expects(:update)
406
+ .with do |model, options|
407
+ assert_equal 'my_custom_index', options[:index]
408
+ assert_equal 'my_custom_type', options[:type]
409
+ end
410
+ .returns({'_id' => 'abc', '_index' => 'foo', '_type' => 'bar', '_version' => '100'})
411
+
412
+ subject.instance_variable_set(:@_index, 'my_custom_index')
413
+ subject.instance_variable_set(:@_type, 'my_custom_type')
414
+
415
+ subject.decrement :count
416
+
417
+ assert_equal 'foo', subject._index
418
+ assert_equal 'bar', subject._type
419
+ assert_equal '100', subject._version
420
+ end
421
+ end
422
+
423
+ context "when touching," do
424
+ should "raise exception when touching not existing attribute" do
425
+ subject.expects(:persisted?).returns(true)
426
+ assert_raise(ArgumentError) { subject.touch :foobar }
427
+ end
428
+
429
+ should "update updated_at by default" do
430
+ subject.expects(:persisted?).returns(true)
431
+ Time.expects(:now).returns(Time.parse('2014-01-01T00:00:00Z')).at_least_once
432
+
433
+ @gateway
434
+ .expects(:update)
435
+ .with do |id, options|
436
+ assert_equal '2014-01-01T00:00:00Z', options[:doc][:updated_at]
437
+ end
438
+ .returns({'_id' => 'abc123', 'version' => 2})
439
+
440
+ subject.touch
441
+ assert_equal Time.parse('2014-01-01T00:00:00Z'), subject.updated_at
442
+ end
443
+
444
+ should "update a custom attribute by default" do
445
+ subject.expects(:persisted?).returns(true)
446
+ Time.expects(:now).returns(Time.parse('2014-01-01T00:00:00Z')).at_least_once
447
+
448
+ @gateway
449
+ .expects(:update)
450
+ .with do |id, options|
451
+ assert_equal '2014-01-01T00:00:00Z', options[:doc][:created_at]
452
+ end
453
+ .returns({'_id' => 'abc123', 'version' => 2})
454
+
455
+ subject.touch :created_at
456
+ assert_equal Time.parse('2014-01-01T00:00:00Z'), subject.created_at
457
+ end
458
+
459
+ should "execute the callbacks" do
460
+ @gateway.expects(:update).returns({'_id' => 'abc'})
461
+ DummyStoreModelWithCallback = Class.new(DummyStoreModel)
462
+
463
+ DummyStoreModelWithCallback.after_touch { $stderr.puts "TOUCHED" }
464
+
465
+ $stderr.expects(:puts).with('TOUCHED')
466
+ d = DummyStoreModelWithCallback.new name: 'Test'
467
+ d.expects(:persisted?).returns(true)
468
+ d.touch
469
+ end
470
+
471
+ should "set the meta attributes from response" do
472
+ subject.expects(:persisted?).returns(true)
473
+
474
+ @gateway.expects(:update)
475
+ .with do |model, options|
476
+ assert_equal 'my_custom_index', options[:index]
477
+ assert_equal 'my_custom_type', options[:type]
478
+ end
479
+ .returns({'_id' => 'abc', '_index' => 'foo', '_type' => 'bar', '_version' => '100'})
480
+
481
+ subject.instance_variable_set(:@_index, 'my_custom_index')
482
+ subject.instance_variable_set(:@_type, 'my_custom_type')
483
+
484
+ subject.touch
485
+
486
+ assert_equal 'foo', subject._index
487
+ assert_equal 'bar', subject._type
488
+ assert_equal '100', subject._version
489
+ end
490
+ end
491
+
492
+ end
493
+ end