cloud_search 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Build Status](https://secure.travis-ci.org/willian/cloud_search.png)](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
|
+
|