cloud_search 0.0.1 → 0.0.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.
- data/.gitignore +1 -0
- data/.travis.yml +3 -0
- data/Guardfile +10 -0
- data/README.md +27 -13
- data/cloud_search.gemspec +3 -0
- data/lib/cloud_search.rb +7 -2
- data/lib/cloud_search/config.rb +2 -2
- data/lib/cloud_search/document.rb +94 -0
- data/lib/cloud_search/indexer.rb +54 -0
- data/lib/cloud_search/invalid_document.rb +11 -0
- data/lib/cloud_search/search_response.rb +23 -0
- data/lib/cloud_search/searcher.rb +48 -0
- data/lib/cloud_search/version.rb +1 -1
- data/spec/cloud_search/config_spec.rb +2 -2
- data/spec/cloud_search/document_spec.rb +315 -0
- data/spec/cloud_search/indexer_spec.rb +108 -0
- data/spec/cloud_search/invalid_document_spec.rb +9 -0
- data/spec/cloud_search/search_response_spec.rb +60 -0
- data/spec/cloud_search/searcher_spec.rb +39 -0
- data/spec/fixtures/full.yml +218 -0
- data/spec/fixtures/vcr_cassettes/index/request/add.yml +38 -0
- data/spec/fixtures/vcr_cassettes/index/request/add_in_batch.yml +40 -0
- data/spec/fixtures/vcr_cassettes/index/request/delete.yml +37 -0
- data/spec/spec_helper.rb +5 -0
- metadata +76 -7
- data/lib/cloud_search/search.rb +0 -35
- data/spec/cloud_search/search_spec.rb +0 -39
data/.gitignore
CHANGED
data/.travis.yml
ADDED
data/Guardfile
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
|
4
|
+
guard 'rspec', :version => 2 do
|
5
|
+
watch(%r{^spec/.+_spec\.rb$})
|
6
|
+
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
|
7
|
+
watch('spec/spec_helper.rb') { "spec" }
|
8
|
+
watch(%r{^spec/support/(.+)\.rb$}) { "spec" }
|
9
|
+
end
|
10
|
+
|
data/README.md
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
[](http://travis-ci.org/willian/cloud_search)
|
2
|
+
|
1
3
|
# CloudSearch
|
2
4
|
|
3
5
|
TODO: Write a gem description
|
@@ -28,24 +30,22 @@ CloudSearch.configure do |config|
|
|
28
30
|
end
|
29
31
|
|
30
32
|
# Search for 'star wars' on 'imdb-movies'
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
:year,
|
36
|
-
:text_relevance)
|
33
|
+
search = CloudSearch::Search.new
|
34
|
+
resp = search.with_fields(:actor, :director, :title, :year, :text_relevance)
|
35
|
+
.query("star wars")
|
36
|
+
.request
|
37
37
|
|
38
38
|
# Or you can search using part of the name
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
:text_relevance)
|
39
|
+
search = CloudSearch::Search.new
|
40
|
+
resp = search.with_fields(:actor, :director, :title, :year, :text_relevance)
|
41
|
+
.query("matri*")
|
42
|
+
.request
|
44
43
|
|
45
44
|
# Number of results
|
46
|
-
resp
|
45
|
+
resp.hits
|
47
46
|
|
48
|
-
|
47
|
+
# Results
|
48
|
+
res.results.each do |result|
|
49
49
|
movie = result["data"]
|
50
50
|
|
51
51
|
# List of actors on the movie
|
@@ -60,6 +60,20 @@ resp["hit"].each do |result|
|
|
60
60
|
end
|
61
61
|
```
|
62
62
|
|
63
|
+
## Indexing documents
|
64
|
+
|
65
|
+
``` ruby
|
66
|
+
document = CloudSearch::Document.new :type => "add", # or "delete"
|
67
|
+
:version => 123,
|
68
|
+
:id => 680, :lang => :en,
|
69
|
+
:fields => {:title => "Lord of the Rings"}
|
70
|
+
|
71
|
+
indexer = CloudSearch::Indexer.new
|
72
|
+
indexer << document # add as many documents as you want
|
73
|
+
indexer.index
|
74
|
+
```
|
75
|
+
|
76
|
+
|
63
77
|
## Contributing
|
64
78
|
|
65
79
|
1. Fork it
|
data/cloud_search.gemspec
CHANGED
@@ -24,6 +24,9 @@ Gem::Specification.new do |gem|
|
|
24
24
|
gem.add_development_dependency "simplecov" , "~> 0.6"
|
25
25
|
gem.add_development_dependency "vcr" , "~> 2.2"
|
26
26
|
gem.add_development_dependency "webmock"
|
27
|
+
gem.add_development_dependency "guard"
|
28
|
+
gem.add_development_dependency "guard-rspec"
|
29
|
+
gem.add_development_dependency "rb-fsevent"
|
27
30
|
|
28
31
|
gem.add_dependency "em-http-request" , "~> 1.0"
|
29
32
|
end
|
data/lib/cloud_search.rb
CHANGED
@@ -1,9 +1,14 @@
|
|
1
1
|
require "em-http"
|
2
|
+
require "json"
|
2
3
|
require "cloud_search/version"
|
3
4
|
|
4
5
|
module CloudSearch
|
5
|
-
autoload :Config,
|
6
|
-
autoload :
|
6
|
+
autoload :Config, "cloud_search/config"
|
7
|
+
autoload :Searcher, "cloud_search/searcher"
|
8
|
+
autoload :SearchResponse, "cloud_search/search_response"
|
9
|
+
autoload :Indexer, "cloud_search/indexer"
|
10
|
+
autoload :Document, "cloud_search/document"
|
11
|
+
autoload :InvalidDocument, "cloud_search/invalid_document"
|
7
12
|
|
8
13
|
def self.config
|
9
14
|
Config.instance
|
data/lib/cloud_search/config.rb
CHANGED
@@ -21,7 +21,7 @@ module CloudSearch
|
|
21
21
|
end
|
22
22
|
|
23
23
|
def document_url
|
24
|
-
@document_url ||= "http://doc-#{self.domain_name}-#{self.domain_id}.#{self.region}.cloudsearch.amazonaws.com"
|
24
|
+
@document_url ||= "http://doc-#{self.domain_name}-#{self.domain_id}.#{self.region}.cloudsearch.amazonaws.com/#{self.api_version}"
|
25
25
|
end
|
26
26
|
|
27
27
|
def region
|
@@ -29,7 +29,7 @@ module CloudSearch
|
|
29
29
|
end
|
30
30
|
|
31
31
|
def search_url
|
32
|
-
@search_url ||= "http://search-#{self.domain_name}-#{self.domain_id}.#{self.region}.cloudsearch.amazonaws.com"
|
32
|
+
@search_url ||= "http://search-#{self.domain_name}-#{self.domain_id}.#{self.region}.cloudsearch.amazonaws.com/#{self.api_version}"
|
33
33
|
end
|
34
34
|
end
|
35
35
|
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
module CloudSearch
|
2
|
+
class Document
|
3
|
+
MAX_VERSION = 4294967295
|
4
|
+
|
5
|
+
attr_accessor :type, :lang, :fields
|
6
|
+
attr_reader :errors, :id, :version
|
7
|
+
|
8
|
+
def initialize(attributes = {})
|
9
|
+
attributes.each_pair { |key, value| self.__send__("#{key}=", value) }
|
10
|
+
@errors = {}
|
11
|
+
end
|
12
|
+
|
13
|
+
def id=(_id)
|
14
|
+
@id = _id.to_s
|
15
|
+
end
|
16
|
+
|
17
|
+
def version=(_version)
|
18
|
+
begin
|
19
|
+
@version = Integer(_version)
|
20
|
+
rescue ArgumentError, TypeError
|
21
|
+
@version = _version
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def valid?
|
26
|
+
@errors = {}
|
27
|
+
run_id_validations
|
28
|
+
run_version_validations
|
29
|
+
run_type_validations
|
30
|
+
run_lang_validations
|
31
|
+
run_fields_validations
|
32
|
+
errors.empty?
|
33
|
+
end
|
34
|
+
|
35
|
+
def as_json
|
36
|
+
{:type => type, :id => id, :version => version}.tap do |hash|
|
37
|
+
hash.merge!(:lang => lang, :fields => fields) if type == "add"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def to_json
|
42
|
+
JSON.unparse as_json
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def run_id_validations
|
48
|
+
validate :id do |messages|
|
49
|
+
messages << "can't be blank" if blank?(:id)
|
50
|
+
messages << "is invalid" unless blank?(:id) or id =~ /\A[^_][a-z0-9_]+\z/
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def run_version_validations
|
55
|
+
validate :version do |messages|
|
56
|
+
messages << "can't be blank" if blank?(:version)
|
57
|
+
messages << "is invalid" unless blank?(:version) or version.to_s =~ /\A[0-9]+\z/
|
58
|
+
messages << "must be less than #{MAX_VERSION + 1}" if messages.empty? and version > MAX_VERSION
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def run_type_validations
|
63
|
+
validate :type do |messages|
|
64
|
+
messages << "can't be blank" if blank?(:type)
|
65
|
+
messages << "is invalid" if !blank?(:type) and !%w(add delete).include?(type)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def run_lang_validations
|
70
|
+
validate :lang do |messages|
|
71
|
+
messages << "can't be blank" if blank?(:lang)
|
72
|
+
messages << "is invalid" unless blank?(:lang) or lang =~ /\A[a-z]{2}\z/
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def run_fields_validations
|
77
|
+
validate :fields do |messages|
|
78
|
+
messages << "can't be empty" if fields.nil?
|
79
|
+
messages << "must be an instance of Hash" if !fields.nil? and !fields.instance_of?(Hash)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def blank?(attr)
|
84
|
+
self.__send__(attr).to_s.strip.length.zero?
|
85
|
+
end
|
86
|
+
|
87
|
+
def validate(attr, &block)
|
88
|
+
messages = []
|
89
|
+
yield messages
|
90
|
+
errors[attr] = messages unless messages.empty?
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module CloudSearch
|
2
|
+
class Indexer
|
3
|
+
def initialize
|
4
|
+
@documents = []
|
5
|
+
end
|
6
|
+
|
7
|
+
def <<(document)
|
8
|
+
raise InvalidDocument.new(document) unless document.valid?
|
9
|
+
@documents << document
|
10
|
+
end
|
11
|
+
|
12
|
+
alias :add :<<
|
13
|
+
|
14
|
+
def documents
|
15
|
+
@documents.freeze
|
16
|
+
end
|
17
|
+
|
18
|
+
def index
|
19
|
+
response, message = nil
|
20
|
+
EM.run do
|
21
|
+
http = EM::HttpRequest.new(url).post :body => documents_json, :head => headers
|
22
|
+
|
23
|
+
http.callback {
|
24
|
+
message = "#{http.response_header.status} - #{http.response.length} bytes\n#{url}\n"
|
25
|
+
response = JSON.parse(http.response)
|
26
|
+
|
27
|
+
EM.stop
|
28
|
+
}
|
29
|
+
|
30
|
+
http.errback {
|
31
|
+
message = "#{url}\n#{http.error}"
|
32
|
+
|
33
|
+
EM.stop
|
34
|
+
}
|
35
|
+
end
|
36
|
+
|
37
|
+
[response, message]
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def headers
|
43
|
+
{"Content-Type" => "application/json"}
|
44
|
+
end
|
45
|
+
|
46
|
+
def documents_json
|
47
|
+
JSON.unparse(@documents.map(&:as_json))
|
48
|
+
end
|
49
|
+
|
50
|
+
def url
|
51
|
+
"#{CloudSearch.config.document_url}/documents/batch"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module CloudSearch
|
2
|
+
class InvalidDocument < StandardError
|
3
|
+
def initialize(document)
|
4
|
+
document.valid?
|
5
|
+
error_message = document.errors.map do
|
6
|
+
|attribute, errors| errors.empty? ? nil : "#{attribute}: #{errors.join(", ")}"
|
7
|
+
end.join("; ")
|
8
|
+
super error_message
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module CloudSearch
|
2
|
+
class SearchResponse
|
3
|
+
attr_accessor :body
|
4
|
+
attr_accessor :http_code
|
5
|
+
|
6
|
+
def results
|
7
|
+
(_hits and _hits['hit']) or []
|
8
|
+
end
|
9
|
+
|
10
|
+
def hits
|
11
|
+
(_hits and _hits['found']) or 0
|
12
|
+
end
|
13
|
+
|
14
|
+
def found?
|
15
|
+
hits >= 1
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
def _hits
|
20
|
+
body and body['hits']
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module CloudSearch
|
2
|
+
class Searcher
|
3
|
+
|
4
|
+
def search
|
5
|
+
response = SearchResponse.new
|
6
|
+
|
7
|
+
EM.run do
|
8
|
+
http = EM::HttpRequest.new(url).get
|
9
|
+
|
10
|
+
http.callback do
|
11
|
+
response.http_code = http.response_header.status
|
12
|
+
response.body = JSON.parse(http.response)
|
13
|
+
|
14
|
+
EM.stop
|
15
|
+
end
|
16
|
+
|
17
|
+
http.errback do
|
18
|
+
response.http_code = http.error
|
19
|
+
response.body = http.response
|
20
|
+
|
21
|
+
EM.stop
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
response
|
26
|
+
end
|
27
|
+
|
28
|
+
def query(q)
|
29
|
+
@query = q
|
30
|
+
self
|
31
|
+
end
|
32
|
+
|
33
|
+
def with_fields(*fields)
|
34
|
+
@fields = fields
|
35
|
+
self
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def url
|
41
|
+
url = CloudSearch.config.search_url
|
42
|
+
url+= "/search"
|
43
|
+
url+= "?q=#{CGI.escape(@query)}"
|
44
|
+
url+= "&return-fields=#{CGI.escape(@fields.join(","))}" if @fields.any?
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
data/lib/cloud_search/version.rb
CHANGED
@@ -15,9 +15,9 @@ describe CloudSearch::Config do
|
|
15
15
|
it { expect(subject.configuration_url).to eql("https://cloudsearch.us-east-1.amazonaws.com") }
|
16
16
|
it { expect(subject.domain_id).to eql("pl6u4t3elu7dhsbwaqbsy3y6be") }
|
17
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") }
|
18
|
+
it { expect(subject.document_url).to eql("http://doc-imdb-movies-pl6u4t3elu7dhsbwaqbsy3y6be.us-east-1.cloudsearch.amazonaws.com/2011-02-01") }
|
19
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") }
|
20
|
+
it { expect(subject.search_url).to eql("http://search-imdb-movies-pl6u4t3elu7dhsbwaqbsy3y6be.us-east-1.cloudsearch.amazonaws.com/2011-02-01") }
|
21
21
|
end
|
22
22
|
end
|
23
23
|
|
@@ -0,0 +1,315 @@
|
|
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
|
+
it "is invalid if lang is nil" do
|
146
|
+
document = described_class.new :lang => nil
|
147
|
+
document.valid?
|
148
|
+
expect(document.errors[:lang]).to eql(["can't be blank"])
|
149
|
+
end
|
150
|
+
|
151
|
+
it "is invalid if lang contains digits" do
|
152
|
+
document = described_class.new :lang => "a1"
|
153
|
+
document.valid?
|
154
|
+
expect(document.errors[:lang]).to eql(["is invalid"])
|
155
|
+
end
|
156
|
+
|
157
|
+
it "is invalid if lang contains more than 2 characters" do
|
158
|
+
document = described_class.new :lang => "abc"
|
159
|
+
document.valid?
|
160
|
+
expect(document.errors[:lang]).to eql(["is invalid"])
|
161
|
+
end
|
162
|
+
|
163
|
+
it "is invalid if lang contains upper case characters" do
|
164
|
+
document = described_class.new :lang => "Ab"
|
165
|
+
document.valid?
|
166
|
+
expect(document.errors[:lang]).to eql(["is invalid"])
|
167
|
+
end
|
168
|
+
|
169
|
+
it "is valid if lang contains 2 lower case characters" do
|
170
|
+
document = described_class.new :lang => "en"
|
171
|
+
document.valid?
|
172
|
+
expect(document.errors[:lang]).to be_nil
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
context "fields validation" do
|
177
|
+
it "is invalid if fields is nil" do
|
178
|
+
document = described_class.new :fields => nil
|
179
|
+
document.valid?
|
180
|
+
expect(document.errors[:fields]).to eql(["can't be empty"])
|
181
|
+
end
|
182
|
+
|
183
|
+
it "is invalid if fields is not a hash" do
|
184
|
+
document = described_class.new :fields => []
|
185
|
+
document.valid?
|
186
|
+
expect(document.errors[:fields]).to eql(["must be an instance of Hash"])
|
187
|
+
end
|
188
|
+
|
189
|
+
it "is valid with a Hash" do
|
190
|
+
document = described_class.new :fields => {}
|
191
|
+
document.valid?
|
192
|
+
expect(document.errors[:fields]).to be_nil
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
context "#as_json" do
|
197
|
+
let(:attributes) { {
|
198
|
+
:type => type,
|
199
|
+
:id => "123abc",
|
200
|
+
:version => 123456,
|
201
|
+
:lang => "pt",
|
202
|
+
:fields => {:foo => "bar"}
|
203
|
+
} }
|
204
|
+
let(:document) { described_class.new attributes }
|
205
|
+
let(:as_json) { document.as_json }
|
206
|
+
|
207
|
+
context "when 'type' is 'add'" do
|
208
|
+
let(:type) { "add" }
|
209
|
+
|
210
|
+
it "includes the 'type' attribute" do
|
211
|
+
expect(as_json[:type]).to eq("add")
|
212
|
+
end
|
213
|
+
|
214
|
+
it "includes the 'id' attribute" do
|
215
|
+
expect(as_json[:id]).to eq("123abc")
|
216
|
+
end
|
217
|
+
|
218
|
+
it "includes the 'version' attribute" do
|
219
|
+
expect(as_json[:version]).to eq(123456)
|
220
|
+
end
|
221
|
+
|
222
|
+
it "includes the 'lang' attribute" do
|
223
|
+
expect(as_json[:lang]).to eq("pt")
|
224
|
+
end
|
225
|
+
|
226
|
+
it "includes the 'fields' attribute" do
|
227
|
+
expect(as_json[:fields]).to eq(:foo => "bar")
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
context "when 'type' is 'delete'" do
|
232
|
+
let(:type) { "delete" }
|
233
|
+
|
234
|
+
it "includes the 'type' attribute" do
|
235
|
+
expect(as_json[:type]).to eq("delete")
|
236
|
+
end
|
237
|
+
|
238
|
+
it "includes the 'id' attribute" do
|
239
|
+
expect(as_json[:id]).to eq("123abc")
|
240
|
+
end
|
241
|
+
|
242
|
+
it "includes the 'version' attribute" do
|
243
|
+
expect(as_json[:version]).to eq(123456)
|
244
|
+
end
|
245
|
+
|
246
|
+
it "does not include the 'lang' attribute" do
|
247
|
+
expect(as_json[:lang]).to be_nil
|
248
|
+
end
|
249
|
+
|
250
|
+
it "does not include the 'fields' attribute" do
|
251
|
+
expect(as_json[:fields]).to be_nil
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
context "#to_json" do
|
257
|
+
let(:attributes) { {
|
258
|
+
:type => type,
|
259
|
+
:id => "123abc",
|
260
|
+
:version => 123456,
|
261
|
+
:lang => "pt",
|
262
|
+
:fields => {:foo => "bar"}
|
263
|
+
} }
|
264
|
+
let(:parsed_json) { JSON.parse(described_class.new(attributes).to_json) }
|
265
|
+
|
266
|
+
context "when 'type' is 'add'" do
|
267
|
+
let(:type) { "add" }
|
268
|
+
|
269
|
+
it "includes the 'type' attribute" do
|
270
|
+
expect(parsed_json["type"]).to eq("add")
|
271
|
+
end
|
272
|
+
|
273
|
+
it "includes the 'id' attribute" do
|
274
|
+
expect(parsed_json["id"]).to eq("123abc")
|
275
|
+
end
|
276
|
+
|
277
|
+
it "includes the 'version' attribute" do
|
278
|
+
expect(parsed_json["version"]).to eq(123456)
|
279
|
+
end
|
280
|
+
|
281
|
+
it "includes the 'lang' attribute" do
|
282
|
+
expect(parsed_json["lang"]).to eq("pt")
|
283
|
+
end
|
284
|
+
|
285
|
+
it "includes the 'fields' attribute" do
|
286
|
+
expect(parsed_json["fields"]).to eq("foo" => "bar")
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
context "when 'type' is 'delete'" do
|
291
|
+
let(:type) { "delete" }
|
292
|
+
|
293
|
+
it "includes the 'type' attribute" do
|
294
|
+
expect(parsed_json["type"]).to eq("delete")
|
295
|
+
end
|
296
|
+
|
297
|
+
it "includes the 'id' attribute" do
|
298
|
+
expect(parsed_json["id"]).to eq("123abc")
|
299
|
+
end
|
300
|
+
|
301
|
+
it "includes the 'version' attribute" do
|
302
|
+
expect(parsed_json["version"]).to eq(123456)
|
303
|
+
end
|
304
|
+
|
305
|
+
it "does not include the 'lang' attribute" do
|
306
|
+
expect(parsed_json["lang"]).to be_nil
|
307
|
+
end
|
308
|
+
|
309
|
+
it "does not include the 'fields' attribute" do
|
310
|
+
expect(parsed_json["fields"]).to be_nil
|
311
|
+
end
|
312
|
+
end
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|