eden_cloud_search 0.1.10

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 (37) hide show
  1. data/.gitignore +19 -0
  2. data/.rspec +1 -0
  3. data/.travis.yml +3 -0
  4. data/Gemfile +13 -0
  5. data/Guardfile +10 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +142 -0
  8. data/Rakefile +9 -0
  9. data/eden_cloud_search.gemspec +30 -0
  10. data/lib/eden_cloud_search/config.rb +62 -0
  11. data/lib/eden_cloud_search/document.rb +95 -0
  12. data/lib/eden_cloud_search/exceptions.rb +3 -0
  13. data/lib/eden_cloud_search/indexer.rb +44 -0
  14. data/lib/eden_cloud_search/invalid_document.rb +11 -0
  15. data/lib/eden_cloud_search/search_response.rb +73 -0
  16. data/lib/eden_cloud_search/searcher.rb +118 -0
  17. data/lib/eden_cloud_search/version.rb +3 -0
  18. data/lib/eden_cloud_search.rb +24 -0
  19. data/spec/cloud_search/config_spec.rb +23 -0
  20. data/spec/cloud_search/document_spec.rb +335 -0
  21. data/spec/cloud_search/indexer_spec.rb +146 -0
  22. data/spec/cloud_search/invalid_document_spec.rb +9 -0
  23. data/spec/cloud_search/search_response_spec.rb +220 -0
  24. data/spec/cloud_search/searcher_spec.rb +254 -0
  25. data/spec/fixtures/full.json +1 -0
  26. data/spec/fixtures/vcr_cassettes/index/request/add.yml +38 -0
  27. data/spec/fixtures/vcr_cassettes/index/request/add_in_batch.yml +40 -0
  28. data/spec/fixtures/vcr_cassettes/index/request/delete.yml +37 -0
  29. data/spec/fixtures/vcr_cassettes/search/request/facets.yml +121 -0
  30. data/spec/fixtures/vcr_cassettes/search/request/facets_with_constraints.yml +123 -0
  31. data/spec/fixtures/vcr_cassettes/search/request/full.yml +114 -0
  32. data/spec/fixtures/vcr_cassettes/search/request/paginated.yml +36 -0
  33. data/spec/fixtures/vcr_cassettes/search/request/paginated_first_page.yml +56 -0
  34. data/spec/fixtures/vcr_cassettes/search/request/paginated_second_page.yml +81 -0
  35. data/spec/spec_helper.rb +17 -0
  36. data/spec/support/vcr.rb +5 -0
  37. metadata +212 -0
@@ -0,0 +1,24 @@
1
+ # require "em-http"
2
+ require "rest_client"
3
+ require "json"
4
+ require "eden_cloud_search/version"
5
+
6
+ module CloudSearch
7
+ autoload :Config, "eden_cloud_search/config"
8
+ autoload :ConfigurationChecking, "eden_cloud_search/config"
9
+ autoload :Searcher, "eden_cloud_search/searcher"
10
+ autoload :SearchResponse, "eden_cloud_search/search_response"
11
+ autoload :Indexer, "eden_cloud_search/indexer"
12
+ autoload :Document, "eden_cloud_search/document"
13
+ autoload :InvalidDocument, "eden_cloud_search/invalid_document"
14
+ autoload :InsufficientParametersException, "eden_cloud_search/exceptions"
15
+
16
+ def self.config
17
+ Config.instance
18
+ end
19
+
20
+ def self.configure(&block)
21
+ block.call(self.config)
22
+ end
23
+ end
24
+
@@ -0,0 +1,23 @@
1
+ require "spec_helper"
2
+
3
+ describe CloudSearch::Config do
4
+ subject { CloudSearch::Config.instance }
5
+
6
+ before do
7
+ CloudSearch.configure do |config|
8
+ config.domain_id = "pl6u4t3elu7dhsbwaqbsy3y6be"
9
+ config.domain_name = "imdb-movies"
10
+ end
11
+ end
12
+
13
+ describe "after set some configurations" do
14
+ it { expect(subject.api_version).to eql("2011-02-01") }
15
+ it { expect(subject.configuration_url).to eql("https://cloudsearch.us-east-1.amazonaws.com") }
16
+ it { expect(subject.domain_id).to eql("pl6u4t3elu7dhsbwaqbsy3y6be") }
17
+ it { expect(subject.domain_name).to eql("imdb-movies") }
18
+ it { expect(subject.document_url).to eql("http://doc-imdb-movies-pl6u4t3elu7dhsbwaqbsy3y6be.us-east-1.cloudsearch.amazonaws.com/2011-02-01") }
19
+ it { expect(subject.region).to eql("us-east-1") }
20
+ it { expect(subject.search_url).to eql("http://search-imdb-movies-pl6u4t3elu7dhsbwaqbsy3y6be.us-east-1.cloudsearch.amazonaws.com/2011-02-01") }
21
+ end
22
+ end
23
+
@@ -0,0 +1,335 @@
1
+ # encoding: utf-8
2
+
3
+ require "spec_helper"
4
+
5
+ describe CloudSearch::Document do
6
+ it "has a 'id' attribute" do
7
+ expect(described_class.new(:id => 123).id).to eq("123")
8
+ end
9
+
10
+ it "has a 'type' attribute" do
11
+ expect(described_class.new(:type => "add").type).to eq("add")
12
+ end
13
+
14
+ it "has a 'version' attribute" do
15
+ expect(described_class.new(:version => 1234).version).to eq(1234)
16
+ end
17
+
18
+ it "has a 'lang' attribute" do
19
+ expect(described_class.new(:lang => "en").lang).to eq("en")
20
+ end
21
+
22
+ it "has a 'fields' attribute" do
23
+ expect(described_class.new(:fields => {:foo => "bar"}).fields).to eq(:foo => "bar")
24
+ end
25
+
26
+ it "clears errors between validations" do
27
+ document = described_class.new :id => nil
28
+ expect(document).to_not be_valid
29
+ document.id = "123"
30
+ document.valid?
31
+ expect(document.errors[:id]).to be_nil
32
+ end
33
+
34
+ context "id validation" do
35
+ it "is invalid without an id" do
36
+ document = described_class.new
37
+ document.valid?
38
+ expect(document.errors[:id]).to eq(["can't be blank"])
39
+ end
40
+
41
+ %w(- ? A & * ç à @ % $ ! = +).each do |char|
42
+ it "is invalid containing #{char}" do
43
+ document = described_class.new :id => "1#{char}2"
44
+ document.valid?
45
+ expect(document.errors[:id]).to eq(["is invalid"])
46
+ end
47
+ end
48
+
49
+ it "is invalid starting with an '_'" do
50
+ document = described_class.new :id => "_abc"
51
+ document.valid?
52
+ expect(document.errors[:id]).to eq(["is invalid"])
53
+ end
54
+
55
+ it "is invalid with a string containing only spaces" do
56
+ document = described_class.new :id => " "
57
+ document.valid?
58
+ expect(document.errors[:id]).to eq(["can't be blank"])
59
+ end
60
+
61
+ it "is valid with a valid id" do
62
+ document = described_class.new :id => "507c54a44a42c408f4000001"
63
+ document.valid?
64
+ expect(document.errors[:id]).to be_nil
65
+ end
66
+
67
+ it "is valid with integers" do
68
+ document = described_class.new :id => 123
69
+ document.valid?
70
+ expect(document.errors[:id]).to be_nil
71
+ end
72
+
73
+ it "converts integers to strings" do
74
+ expect(described_class.new(:id => 123).id).to eq("123")
75
+ end
76
+ end
77
+
78
+ context "version validation" do
79
+ it "is invalid with a non numeric value" do
80
+ document = described_class.new :version => "123a3545656"
81
+ document.valid?
82
+ expect(document.errors[:version]).to eq(["is invalid"])
83
+ end
84
+
85
+ it "is invalid with a nil value" do
86
+ document = described_class.new :version => nil
87
+ document.valid?
88
+ expect(document.errors[:version]).to eq(["can't be blank"])
89
+ end
90
+
91
+ it "converts strings to integers" do
92
+ expect(described_class.new(:version => "123").version).to eq(123)
93
+ end
94
+
95
+ it "does not convert strings to integers if they contain non numerical characters" do
96
+ expect(described_class.new(:version => "123abc567").version).to eq("123abc567")
97
+ end
98
+
99
+ it "is invalid if value is greater than CloudSearch::Document::MAX_VERSION" do
100
+ document = described_class.new :version => 4294967296
101
+ document.valid?
102
+ expect(document.errors[:version]).to eq(["must be less than 4294967296"])
103
+ end
104
+
105
+ it "is valid with integers greater than zero and less or equal to CloudSearch::Document::MAX_VERSION" do
106
+ document = described_class.new :version => 4294967295
107
+ document.valid?
108
+ expect(document.errors[:version]).to be_nil
109
+ end
110
+ end
111
+
112
+ context "type validation" do
113
+ it "is valid if type is 'add'" do
114
+ document = described_class.new :type => "add"
115
+ document.valid?
116
+ expect(document.errors[:type]).to be_nil
117
+ end
118
+
119
+ it "is valid if type is 'delete'" do
120
+ document = described_class.new :type => "delete"
121
+ document.valid?
122
+ expect(document.errors[:type]).to be_nil
123
+ end
124
+
125
+ it "is invalid if type is anything else" do
126
+ document = described_class.new :type => "wrong"
127
+ document.valid?
128
+ expect(document.errors[:type]).to eq(["is invalid"])
129
+ end
130
+
131
+ it "is invalid if type is nil" do
132
+ document = described_class.new :type => nil
133
+ document.valid?
134
+ expect(document.errors[:type]).to eq(["can't be blank"])
135
+ end
136
+
137
+ it "is invalid if type is a blank string" do
138
+ document = described_class.new :type => " "
139
+ document.valid?
140
+ expect(document.errors[:type]).to eq(["can't be blank"])
141
+ end
142
+ end
143
+
144
+ context "lang validation" do
145
+ context "when type is 'add'" do
146
+ it "is invalid if lang is nil" do
147
+ document = described_class.new :lang => nil, :type => "add"
148
+ document.valid?
149
+ expect(document.errors[:lang]).to eql(["can't be blank"])
150
+ end
151
+
152
+ it "is invalid if lang contains digits" do
153
+ document = described_class.new :lang => "a1", :type => "add"
154
+ document.valid?
155
+ expect(document.errors[:lang]).to eql(["is invalid"])
156
+ end
157
+
158
+ it "is invalid if lang contains more than 2 characters" do
159
+ document = described_class.new :lang => "abc", :type => "add"
160
+ document.valid?
161
+ expect(document.errors[:lang]).to eql(["is invalid"])
162
+ end
163
+
164
+ it "is invalid if lang contains upper case characters" do
165
+ document = described_class.new :lang => "Ab", :type => "add"
166
+ document.valid?
167
+ expect(document.errors[:lang]).to eql(["is invalid"])
168
+ end
169
+
170
+ it "is valid if lang contains 2 lower case characters" do
171
+ document = described_class.new :lang => "en", :type => "add"
172
+ document.valid?
173
+ expect(document.errors[:lang]).to be_nil
174
+ end
175
+ end
176
+
177
+ context "when type is 'delete'" do
178
+ it "is optional" do
179
+ document = described_class.new :type => "delete"
180
+ document.valid?
181
+ expect(document.errors[:lang]).to be_nil
182
+ end
183
+ end
184
+ end
185
+
186
+ context "fields validation" do
187
+ context "when type is 'add'" do
188
+ it "is invalid if fields is nil" do
189
+ document = described_class.new :fields => nil, :type => "add"
190
+ document.valid?
191
+ expect(document.errors[:fields]).to eql(["can't be empty"])
192
+ end
193
+
194
+ it "is invalid if fields is not a hash" do
195
+ document = described_class.new :fields => [], :type => "add"
196
+ document.valid?
197
+ expect(document.errors[:fields]).to eql(["must be an instance of Hash"])
198
+ end
199
+
200
+ it "is valid with a Hash" do
201
+ document = described_class.new :fields => {}, :type => "add"
202
+ document.valid?
203
+ expect(document.errors[:fields]).to be_nil
204
+ end
205
+ end
206
+
207
+ context "when type is 'delete'" do
208
+ it "is optional" do
209
+ document = described_class.new :type => "delete"
210
+ document.valid?
211
+ expect(document.errors[:fields]).to be_nil
212
+ end
213
+ end
214
+ end
215
+
216
+ context "#as_json" do
217
+ let(:attributes) { {
218
+ :type => type,
219
+ :id => "123abc",
220
+ :version => 123456,
221
+ :lang => "pt",
222
+ :fields => {:foo => "bar"}
223
+ } }
224
+ let(:document) { described_class.new attributes }
225
+ let(:as_json) { document.as_json }
226
+
227
+ context "when 'type' is 'add'" do
228
+ let(:type) { "add" }
229
+
230
+ it "includes the 'type' attribute" do
231
+ expect(as_json[:type]).to eq("add")
232
+ end
233
+
234
+ it "includes the 'id' attribute" do
235
+ expect(as_json[:id]).to eq("123abc")
236
+ end
237
+
238
+ it "includes the 'version' attribute" do
239
+ expect(as_json[:version]).to eq(123456)
240
+ end
241
+
242
+ it "includes the 'lang' attribute" do
243
+ expect(as_json[:lang]).to eq("pt")
244
+ end
245
+
246
+ it "includes the 'fields' attribute" do
247
+ expect(as_json[:fields]).to eq(:foo => "bar")
248
+ end
249
+ end
250
+
251
+ context "when 'type' is 'delete'" do
252
+ let(:type) { "delete" }
253
+
254
+ it "includes the 'type' attribute" do
255
+ expect(as_json[:type]).to eq("delete")
256
+ end
257
+
258
+ it "includes the 'id' attribute" do
259
+ expect(as_json[:id]).to eq("123abc")
260
+ end
261
+
262
+ it "includes the 'version' attribute" do
263
+ expect(as_json[:version]).to eq(123456)
264
+ end
265
+
266
+ it "does not include the 'lang' attribute" do
267
+ expect(as_json[:lang]).to be_nil
268
+ end
269
+
270
+ it "does not include the 'fields' attribute" do
271
+ expect(as_json[:fields]).to be_nil
272
+ end
273
+ end
274
+ end
275
+
276
+ context "#to_json" do
277
+ let(:attributes) { {
278
+ :type => type,
279
+ :id => "123abc",
280
+ :version => 123456,
281
+ :lang => "pt",
282
+ :fields => {:foo => "bar"}
283
+ } }
284
+ let(:parsed_json) { JSON.parse(described_class.new(attributes).to_json) }
285
+
286
+ context "when 'type' is 'add'" do
287
+ let(:type) { "add" }
288
+
289
+ it "includes the 'type' attribute" do
290
+ expect(parsed_json["type"]).to eq("add")
291
+ end
292
+
293
+ it "includes the 'id' attribute" do
294
+ expect(parsed_json["id"]).to eq("123abc")
295
+ end
296
+
297
+ it "includes the 'version' attribute" do
298
+ expect(parsed_json["version"]).to eq(123456)
299
+ end
300
+
301
+ it "includes the 'lang' attribute" do
302
+ expect(parsed_json["lang"]).to eq("pt")
303
+ end
304
+
305
+ it "includes the 'fields' attribute" do
306
+ expect(parsed_json["fields"]).to eq("foo" => "bar")
307
+ end
308
+ end
309
+
310
+ context "when 'type' is 'delete'" do
311
+ let(:type) { "delete" }
312
+
313
+ it "includes the 'type' attribute" do
314
+ expect(parsed_json["type"]).to eq("delete")
315
+ end
316
+
317
+ it "includes the 'id' attribute" do
318
+ expect(parsed_json["id"]).to eq("123abc")
319
+ end
320
+
321
+ it "includes the 'version' attribute" do
322
+ expect(parsed_json["version"]).to eq(123456)
323
+ end
324
+
325
+ it "does not include the 'lang' attribute" do
326
+ expect(parsed_json["lang"]).to be_nil
327
+ end
328
+
329
+ it "does not include the 'fields' attribute" do
330
+ expect(parsed_json["fields"]).to be_nil
331
+ end
332
+ end
333
+ end
334
+ end
335
+
@@ -0,0 +1,146 @@
1
+ require 'spec_helper'
2
+
3
+ describe CloudSearch::Indexer do
4
+ let(:indexer) { described_class.new }
5
+
6
+ describe "#documents" do
7
+ it "returns the list of documents to be indexed" do
8
+ doc1 = stub.as_null_object
9
+ doc2 = stub.as_null_object
10
+ indexer << doc1
11
+ indexer << doc2
12
+ expect(indexer.documents).to eq([doc1, doc2])
13
+ end
14
+
15
+ it "returns a frozen version of the documents list" do
16
+ expect {
17
+ indexer.documents << stub
18
+ }.to raise_error(RuntimeError)
19
+ end
20
+ end
21
+
22
+ describe "#<<" do
23
+ let(:document) { stub :valid? => true }
24
+
25
+ it "adds a new item to the list of documents" do
26
+ indexer << document
27
+ expect(indexer.documents).to have(1).item
28
+ end
29
+
30
+ it "is aliased to #add" do
31
+ indexer.add document
32
+ expect(indexer.documents).to have(1).item
33
+ end
34
+
35
+ it "raises an exception if the document is invalid" do
36
+ expect {
37
+ indexer << stub(:valid? => false, :errors => {})
38
+ }.to raise_error(CloudSearch::InvalidDocument)
39
+ end
40
+ end
41
+
42
+ describe "#index" do
43
+ let(:document) { CloudSearch::Document.new(
44
+ :type => type,
45
+ :id => 678,
46
+ :version => version,
47
+ :lang => :en,
48
+ :fields => {:actor => ["Cassio Marques", "Willian Fernandes"], :director => ["Lucas, George"], :title => "Troy Wars"}
49
+ ) }
50
+ let(:indexer) { described_class.new }
51
+
52
+ context "adding a new document" do
53
+ around do |example|
54
+ VCR.use_cassette "index/request/add", &example
55
+ end
56
+
57
+ let(:type) { "add" }
58
+ let(:version) { 1 }
59
+ let(:url) { "#{CloudSearch.config.document_url}/documents/batch" }
60
+ let(:json) { [document].to_json }
61
+
62
+ it "succeeds" do
63
+ indexer << document
64
+ resp, message = indexer.index
65
+ expect(resp["status"]).to eq("success")
66
+ expect(resp["adds"]).to eq(1)
67
+ expect(resp["deletes"]).to eq(0)
68
+ expect(message).to match(/^200/)
69
+ end
70
+
71
+ it "sends http headers with json format info" do
72
+ indexer << document
73
+ RestClient.should_receive(:post).with(url, kind_of(String), {"Content-Type" => "application/json", "Accept" => "application/json" }).and_return(stub(:code => 1, :length => 10, :body => '{}'))
74
+ indexer.index
75
+ end
76
+
77
+ context "when the domain id was not configured" do
78
+ around do |example|
79
+ domain_id = CloudSearch.config.domain_id
80
+ CloudSearch.config.domain_id = nil
81
+ example.call
82
+ CloudSearch.config.domain_id = domain_id
83
+ end
84
+
85
+ it "raises an error" do
86
+ expect {
87
+ indexer.index
88
+ }.to raise_error(CloudSearch::MissingConfigurationError, "Missing 'domain_id' configuration parameter")
89
+ end
90
+ end
91
+
92
+ context "when the domain name was not configured" do
93
+ around do |example|
94
+ domain_name = CloudSearch.config.domain_name
95
+ CloudSearch.config.domain_name = nil
96
+ example.call
97
+ CloudSearch.config.domain_name = domain_name
98
+ end
99
+
100
+ it "raises an error" do
101
+ expect {
102
+ indexer.index
103
+ }.to raise_error(CloudSearch::MissingConfigurationError, "Missing 'domain_name' configuration parameter")
104
+ end
105
+ end
106
+ end
107
+
108
+ context "adding a batch of documents" do
109
+ around do |example|
110
+ VCR.use_cassette "index/request/add_in_batch", &example
111
+ end
112
+
113
+ let(:type) { "add" }
114
+ let(:version) { 5 }
115
+
116
+ it "succeeds" do
117
+ indexer << document
118
+ document2 = CloudSearch::Document.new :type => type, :version => version, :id => 679, :lang => :en, :fields => {:title => "Fight Club"}
119
+ document3 = CloudSearch::Document.new :type => type, :version => version, :id => 680, :lang => :en, :fields => {:title => "Lord of the Rings"}
120
+ indexer << document2
121
+ indexer << document3
122
+ resp, message = indexer.index
123
+ expect(resp["adds"]).to eq(3)
124
+ expect(resp["status"]).to eq("success")
125
+ end
126
+ end
127
+
128
+ context "deleting a document" do
129
+ around do |example|
130
+ VCR.use_cassette "index/request/delete", &example
131
+ end
132
+
133
+ let(:type) { "delete" }
134
+ let(:version) { 2 }
135
+
136
+ it "succeeds" do
137
+ indexer << document
138
+ resp, message = indexer.index
139
+ expect(resp["status"]).to eq("success")
140
+ expect(resp["adds"]).to eq(0)
141
+ expect(resp["deletes"]).to eq(1)
142
+ expect(message).to match(/^200/)
143
+ end
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,9 @@
1
+ require 'spec_helper'
2
+
3
+ describe CloudSearch::InvalidDocument do
4
+ let(:document) { CloudSearch::Document.new }
5
+
6
+ it "has a message with the document errors" do
7
+ expect(described_class.new(document).message).to eq("id: can't be blank; version: can't be blank; type: can't be blank")
8
+ end
9
+ end