telvue-rsolr 2.2.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +13 -0
- data/.rspec +2 -0
- data/.travis.yml +14 -0
- data/CHANGES.txt +47 -0
- data/Gemfile +5 -0
- data/LICENSE +13 -0
- data/README.rdoc +229 -0
- data/Rakefile +19 -0
- data/lib/rsolr.rb +52 -0
- data/lib/rsolr/char.rb +6 -0
- data/lib/rsolr/client.rb +342 -0
- data/lib/rsolr/document.rb +59 -0
- data/lib/rsolr/error.rb +136 -0
- data/lib/rsolr/field.rb +87 -0
- data/lib/rsolr/generator.rb +5 -0
- data/lib/rsolr/json.rb +60 -0
- data/lib/rsolr/response.rb +95 -0
- data/lib/rsolr/uri.rb +25 -0
- data/lib/rsolr/version.rb +7 -0
- data/lib/rsolr/xml.rb +150 -0
- data/rsolr.gemspec +46 -0
- data/spec/api/client_spec.rb +355 -0
- data/spec/api/document_spec.rb +48 -0
- data/spec/api/error_spec.rb +47 -0
- data/spec/api/json_spec.rb +198 -0
- data/spec/api/pagination_spec.rb +31 -0
- data/spec/api/rsolr_spec.rb +31 -0
- data/spec/api/uri_spec.rb +37 -0
- data/spec/api/xml_spec.rb +255 -0
- data/spec/fixtures/basic_configs/_rest_managed.json +1 -0
- data/spec/fixtures/basic_configs/currency.xml +67 -0
- data/spec/fixtures/basic_configs/lang/stopwords_en.txt +54 -0
- data/spec/fixtures/basic_configs/protwords.txt +21 -0
- data/spec/fixtures/basic_configs/schema.xml +530 -0
- data/spec/fixtures/basic_configs/solrconfig.xml +572 -0
- data/spec/fixtures/basic_configs/stopwords.txt +14 -0
- data/spec/fixtures/basic_configs/synonyms.txt +29 -0
- data/spec/integration/solr5_spec.rb +34 -0
- data/spec/spec_helper.rb +94 -0
- metadata +232 -0
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe RSolr::Document do
|
4
|
+
describe RSolr::Field do
|
5
|
+
describe ".instance" do
|
6
|
+
subject { RSolr::Field }
|
7
|
+
|
8
|
+
it "uses the class name of the field value" do
|
9
|
+
expect(subject.instance({}, Time.new)).to be_a_kind_of(RSolr::TimeField)
|
10
|
+
end
|
11
|
+
|
12
|
+
it "uses the provided type option when it is a class" do
|
13
|
+
expect(subject.instance({:type => RSolr::TimeField}, nil)).to be_a_kind_of(RSolr::TimeField)
|
14
|
+
end
|
15
|
+
|
16
|
+
it "uses the provided type option when it is a string" do
|
17
|
+
expect(subject.instance({:type => 'Time'}, nil)).to be_a_kind_of(RSolr::TimeField)
|
18
|
+
end
|
19
|
+
|
20
|
+
it "falls back to the base Field class" do
|
21
|
+
expect(subject.instance({:type => 'UndefinedType'}, nil)).to be_a_kind_of(RSolr::Field)
|
22
|
+
end
|
23
|
+
|
24
|
+
it "defaults to the base Field class" do
|
25
|
+
expect(subject.instance({}, nil)).to be_a_kind_of(RSolr::Field)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe RSolr::TimeField do
|
31
|
+
it "convert value to string" do
|
32
|
+
time_value = Time.utc(2013, 9, 11, 18, 10, 0)
|
33
|
+
expect(RSolr::Field.instance({}, time_value).value).to eq '2013-09-11T18:10:00Z'
|
34
|
+
end
|
35
|
+
|
36
|
+
it "convert time to UTC" do
|
37
|
+
time_value = Time.new(2013, 9, 11, 18, 10, 0, '+02:00')
|
38
|
+
expect(RSolr::Field.instance({}, time_value).value).to eq '2013-09-11T16:10:00Z'
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe RSolr::DateField do
|
43
|
+
it "convert value to string" do
|
44
|
+
date_value = Date.new(2013, 9, 11)
|
45
|
+
expect(RSolr::Field.instance({}, date_value).value).to eq '2013-09-11T00:00:00Z'
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe RSolr::Error do
|
4
|
+
def generate_error_with_backtrace(request, response)
|
5
|
+
raise RSolr::Error::Http.new request, response
|
6
|
+
rescue RSolr::Error::Http => exception
|
7
|
+
exception
|
8
|
+
end
|
9
|
+
let (:response_lines) { (1..15).to_a.map { |i| "line #{i}" } }
|
10
|
+
let(:request) { double :[] => "mocked" }
|
11
|
+
let(:response_body) { response_lines.join("\n") }
|
12
|
+
let(:response) {{
|
13
|
+
:body => response_body,
|
14
|
+
:status => 400
|
15
|
+
}}
|
16
|
+
subject { generate_error_with_backtrace(request, response).to_s }
|
17
|
+
|
18
|
+
context "when the response body is wrapped in a <pre> element" do
|
19
|
+
let(:response_body) { "<pre>" + response_lines.join("\n") + "</pre>" }
|
20
|
+
|
21
|
+
it "only shows the first eleven lines of the response" do
|
22
|
+
expect(subject).to match(/line 1\n.+line 11\n\n/m)
|
23
|
+
end
|
24
|
+
|
25
|
+
context "when the response is one line long" do
|
26
|
+
let(:response_body) { "<pre>failed</pre>" }
|
27
|
+
it { should match(/Error: failed/) }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
context "when the response body is not wrapped in a <pre> element" do
|
32
|
+
|
33
|
+
it "only shows the first eleven lines of the response" do
|
34
|
+
expect(subject).to match(/line 1\n.+line 11\n\n/m)
|
35
|
+
end
|
36
|
+
|
37
|
+
context "when the response is one line long" do
|
38
|
+
let(:response_body) { 'failed' }
|
39
|
+
it { should match(/Error: failed/) }
|
40
|
+
end
|
41
|
+
context "when the response body contains a msg key" do
|
42
|
+
let(:msg) { "'org.apache.solr.search.SyntaxError: Cannot parse \\':false\\': Encountered \" \":\" \": \"\" at line 1, column 0.'" }
|
43
|
+
let(:response_body) { (response_lines << "'error'=>{'msg'=> #{msg}").join("\n") }
|
44
|
+
it { should include msg }
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,198 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe RSolr::JSON do
|
4
|
+
|
5
|
+
let(:generator){ RSolr::JSON::Generator.new }
|
6
|
+
|
7
|
+
context :add do
|
8
|
+
# add a single hash ("doc")
|
9
|
+
it 'should create an add from a hash' do
|
10
|
+
data = {
|
11
|
+
:id=>"1",
|
12
|
+
:name=>'matt'
|
13
|
+
}
|
14
|
+
message = JSON.parse(generator.add(data), symbolize_names: true)
|
15
|
+
expect(message.length).to eq 1
|
16
|
+
expect(message.first).to eq data
|
17
|
+
end
|
18
|
+
|
19
|
+
# add an array of hashes
|
20
|
+
it 'should create many adds from an array of hashes' do
|
21
|
+
data = [
|
22
|
+
{
|
23
|
+
:id=>"1",
|
24
|
+
:name=>'matt'
|
25
|
+
},
|
26
|
+
{
|
27
|
+
:id=>"2",
|
28
|
+
:name=>'sam'
|
29
|
+
}
|
30
|
+
]
|
31
|
+
message = JSON.parse(generator.add(data), symbolize_names: true)
|
32
|
+
expect(message).to eq data
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'should yield a Document object when #add is called with a block' do
|
36
|
+
documents = [{:id=>1, :name=>'sam', :cat=>['cat 1', 'cat 2']}]
|
37
|
+
result = generator.add(documents) do |doc|
|
38
|
+
doc.field_by_name(:name).attrs[:boost] = 10
|
39
|
+
end
|
40
|
+
|
41
|
+
message = JSON.parse(result, symbolize_names: true)
|
42
|
+
|
43
|
+
expect(message.length).to eq 1
|
44
|
+
expect(message.first).to include name: { boost: 10, value: 'sam' }
|
45
|
+
end
|
46
|
+
|
47
|
+
context 'with add_attr' do
|
48
|
+
it 'should create an add command with the attributes from a hash' do
|
49
|
+
data = {
|
50
|
+
:id=>"1",
|
51
|
+
:name=>'matt'
|
52
|
+
}
|
53
|
+
message = JSON.parse(generator.add(data, boost: 1), symbolize_names: true)
|
54
|
+
expect(message).to include :add
|
55
|
+
expect(message[:add][:doc]).to eq data
|
56
|
+
expect(message[:add][:boost]).to eq 1
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'should create multiple add command with the attributes from a hash' do
|
60
|
+
data = [
|
61
|
+
{
|
62
|
+
:id=>"1",
|
63
|
+
:name=>'matt'
|
64
|
+
},
|
65
|
+
{
|
66
|
+
:id=>"2",
|
67
|
+
:name=>'sam'
|
68
|
+
},
|
69
|
+
]
|
70
|
+
|
71
|
+
# custom JSON object class to handle Solr's non-standard JSON command format
|
72
|
+
tmp = Class.new do
|
73
|
+
def initialize
|
74
|
+
@source ||= {}
|
75
|
+
end
|
76
|
+
|
77
|
+
def []=(k, v)
|
78
|
+
if k == :add
|
79
|
+
@source[k] ||= []
|
80
|
+
@source[k] << v.to_h
|
81
|
+
elsif v.class == self.class
|
82
|
+
@source[k] = v.to_h
|
83
|
+
else
|
84
|
+
@source[k] = v
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def to_h
|
89
|
+
@source
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
request = generator.add(data, boost: 1)
|
94
|
+
message = JSON.parse(request, object_class: tmp, symbolize_names: true).to_h
|
95
|
+
expect(message[:add].length).to eq 2
|
96
|
+
expect(message[:add].map { |x| x[:doc] }).to eq data
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'allows for atomic updates' do
|
101
|
+
data = {
|
102
|
+
foo: { set: 'Bar' }
|
103
|
+
}
|
104
|
+
|
105
|
+
message = JSON.parse(generator.add(data), symbolize_names: true)
|
106
|
+
expect(message.length).to eq 1
|
107
|
+
expect(message.first).to eq data
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'supports nested child documents' do
|
111
|
+
data = {
|
112
|
+
_childDocuments_: [
|
113
|
+
{
|
114
|
+
id: 1
|
115
|
+
},
|
116
|
+
{
|
117
|
+
id: 2
|
118
|
+
}
|
119
|
+
]
|
120
|
+
}
|
121
|
+
|
122
|
+
message = JSON.parse(generator.add(data), symbolize_names: true)
|
123
|
+
expect(message.length).to eq 1
|
124
|
+
expect(message.first).to eq data
|
125
|
+
end
|
126
|
+
|
127
|
+
it 'supports nested child documents with only a single document' do
|
128
|
+
data = {
|
129
|
+
_childDocuments_: [
|
130
|
+
{
|
131
|
+
id: 1
|
132
|
+
}
|
133
|
+
]
|
134
|
+
}
|
135
|
+
|
136
|
+
message = JSON.parse(generator.add(data), symbolize_names: true)
|
137
|
+
expect(message.length).to eq 1
|
138
|
+
expect(message.first).to eq data
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
it 'should create multiple fields from array values' do
|
143
|
+
data = {
|
144
|
+
:id => "1",
|
145
|
+
:name => ['matt1', 'matt2']
|
146
|
+
}
|
147
|
+
message = JSON.parse(generator.add(data), symbolize_names: true)
|
148
|
+
expect(message.length).to eq 1
|
149
|
+
expect(message.first).to eq data
|
150
|
+
end
|
151
|
+
|
152
|
+
it 'should create multiple fields from array values with options' do
|
153
|
+
test_values = [nil, 'matt1', 'matt2']
|
154
|
+
message = JSON.parse(
|
155
|
+
generator.add(id: '1') { |doc| doc.add_field(:name, test_values, boost: 3) },
|
156
|
+
symbolize_names: true
|
157
|
+
)
|
158
|
+
expect(message).to eq [{ id: '1', name: { boost: 3, value: test_values } }]
|
159
|
+
end
|
160
|
+
|
161
|
+
describe '#commit' do
|
162
|
+
it 'generates a commit command' do
|
163
|
+
expect(JSON.parse(generator.commit, symbolize_names: true)).to eq(commit: {})
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
describe '#optimize' do
|
168
|
+
it 'generates a optimize command' do
|
169
|
+
expect(JSON.parse(generator.optimize, symbolize_names: true)).to eq(optimize: {})
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
describe '#rollback' do
|
174
|
+
it 'generates a rollback command' do
|
175
|
+
expect(JSON.parse(generator.rollback, symbolize_names: true)).to eq(rollback: {})
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
describe '#delete_by_id' do
|
180
|
+
it 'generates a delete_by_id command for single documents' do
|
181
|
+
expect(JSON.parse(generator.delete_by_id('x'), symbolize_names: true)).to eq(delete: 'x')
|
182
|
+
end
|
183
|
+
|
184
|
+
it 'generates a delete_by_id command for an array of documents' do
|
185
|
+
expect(JSON.parse(generator.delete_by_id(%w(a b c)), symbolize_names: true)).to eq(delete: %w(a b c))
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
describe '#delete_by_query' do
|
190
|
+
it 'generates a delete_by_id command for single documents' do
|
191
|
+
expect(JSON.parse(generator.delete_by_query('id:x'), symbolize_names: true)).to eq(delete: { query: 'id:x'})
|
192
|
+
end
|
193
|
+
|
194
|
+
it 'generates a delete_by_id command for an array of documents' do
|
195
|
+
expect(JSON.parse(generator.delete_by_id(%w(a b c)), symbolize_names: true)).to eq(delete: %w(a b c))
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe RSolr::Client do
|
4
|
+
context "build_paginated_request" do
|
5
|
+
it "should create the proper solr params and query string" do
|
6
|
+
c = RSolr::Client.new(nil, {})#.extend(RSolr::Pagination::Client)
|
7
|
+
r = c.build_paginated_request 3, 25, "select", {:params => {:q => "test"}}
|
8
|
+
#r[:page].should == 3
|
9
|
+
#r[:per_page].should == 25
|
10
|
+
expect(r[:params]["start"]).to eq(50)
|
11
|
+
expect(r[:params]["rows"]).to eq(25)
|
12
|
+
expect(r[:uri].query).to match(/rows=25/)
|
13
|
+
expect(r[:uri].query).to match(/start=50/)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
context "paginate" do
|
17
|
+
it "should build a paginated request context and call execute" do
|
18
|
+
c = RSolr::Client.new(nil, {})#.extend(RSolr::Pagination::Client)
|
19
|
+
expect(c).to receive(:execute).with(hash_including({
|
20
|
+
#:page => 1,
|
21
|
+
#:per_page => 10,
|
22
|
+
:params => {
|
23
|
+
"rows" => 10,
|
24
|
+
"start" => 0,
|
25
|
+
:wt => :json
|
26
|
+
}
|
27
|
+
}))
|
28
|
+
c.paginate 1, 10, "select"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe RSolr do
|
4
|
+
|
5
|
+
it "has a version that can be read via #version or VERSION" do
|
6
|
+
expect(RSolr.version).to eq(RSolr::VERSION)
|
7
|
+
end
|
8
|
+
|
9
|
+
context "connect" do
|
10
|
+
it "should return a RSolr::Client instance" do
|
11
|
+
expect(RSolr.connect).to be_a(RSolr::Client)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
context '.solr_escape' do
|
16
|
+
it "adds backslash to Solr query syntax chars" do
|
17
|
+
# per http://lucene.apache.org/core/4_0_0/queryparser/org/apache/lucene/queryparser/classic/package-summary.html#Escaping_Special_Characters
|
18
|
+
special_chars = [ "+", "-", "&", "|", "!", "(", ")", "{", "}", "[", "]", "^", '"', "~", "*", "?", ":", "\\", "/" ]
|
19
|
+
escaped_str = RSolr.solr_escape("aa#{special_chars.join('aa')}aa")
|
20
|
+
special_chars.each { |c|
|
21
|
+
# note that the ruby code sending the query to Solr will un-escape the backslashes
|
22
|
+
# so the result sent to Solr is ultimately a single backslash in front of the particular character
|
23
|
+
expect(escaped_str).to match "\\#{c}"
|
24
|
+
}
|
25
|
+
end
|
26
|
+
it "leaves other chars alone" do
|
27
|
+
str = "nothing to see here; let's move along people."
|
28
|
+
expect(RSolr.solr_escape(str)).to eq str
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe RSolr::Uri do
|
4
|
+
|
5
|
+
let(:uri) { RSolr::Uri }
|
6
|
+
|
7
|
+
context '.params_to_solr' do
|
8
|
+
it "converts Hash to Solr query string w/o a starting ?" do
|
9
|
+
hash = {:q => "gold", :fq => ["mode:one", "level:2"]}
|
10
|
+
query = uri.params_to_solr hash
|
11
|
+
expect(query[0]).not_to eq(??)
|
12
|
+
[/q=gold/, /fq=mode%3Aone/, /fq=level%3A2/].each do |p|
|
13
|
+
expect(query).to match p
|
14
|
+
end
|
15
|
+
expect(query.split('&').size).to eq(3)
|
16
|
+
end
|
17
|
+
it 'should URL escape &' do
|
18
|
+
expect(uri.params_to_solr(:fq => "&")).to eq('fq=%26')
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'should convert spaces to +' do
|
22
|
+
expect(uri.params_to_solr(:fq => "me and you")).to eq('fq=me+and+you')
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'should URL escape complex queries, part 1' do
|
26
|
+
my_params = {'fq' => '{!raw f=field_name}crazy+\"field+value'}
|
27
|
+
expected = 'fq=%7B%21raw+f%3Dfield_name%7Dcrazy%2B%5C%22field%2Bvalue'
|
28
|
+
expect(uri.params_to_solr(my_params)).to eq(expected)
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'should URL escape complex queries, part 2' do
|
32
|
+
my_params = {'q' => '+popularity:[10 TO *] +section:0'}
|
33
|
+
expected = 'q=%2Bpopularity%3A%5B10+TO+*%5D+%2Bsection%3A0'
|
34
|
+
expect(uri.params_to_solr(my_params)).to eq(expected)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,255 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'builder'
|
3
|
+
require 'nokogiri'
|
4
|
+
|
5
|
+
RSpec.describe RSolr::Xml do
|
6
|
+
let(:generator){ RSolr::Xml::Generator.new }
|
7
|
+
|
8
|
+
builder_engines = {
|
9
|
+
:builder => { :val => false, :class => Builder::XmlMarkup, :engine => Builder::XmlMarkup.new(:indent => 0, :margin => 0, :encoding => 'UTF-8') },
|
10
|
+
:nokogiri => { :val => true, :class => Nokogiri::XML::Builder, :engine => Nokogiri::XML::Builder.new }
|
11
|
+
}
|
12
|
+
|
13
|
+
[:builder,:nokogiri].each do |engine_name|
|
14
|
+
describe engine_name do
|
15
|
+
before :all do
|
16
|
+
@engine = builder_engines[engine_name]
|
17
|
+
@old_ng_setting = RSolr::Xml::Generator.use_nokogiri
|
18
|
+
RSolr::Xml::Generator.use_nokogiri = @engine[:val]
|
19
|
+
end
|
20
|
+
|
21
|
+
after :all do
|
22
|
+
RSolr::Xml::Generator.use_nokogiri = @old_ng_setting
|
23
|
+
end
|
24
|
+
|
25
|
+
before :each do
|
26
|
+
builder_engines.each_pair do |name,spec|
|
27
|
+
expect(spec[:class]).not_to receive(:new) unless name == engine_name
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
context :xml_engine do
|
32
|
+
it "should use #{engine_name}" do
|
33
|
+
expect(@engine[:class]).to receive(:new).and_return(@engine[:engine])
|
34
|
+
generator.send(:commit)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# call all of the simple methods...
|
39
|
+
# make sure the xml string is valid
|
40
|
+
# ensure the class is actually Solr::XML
|
41
|
+
[:optimize, :rollback, :commit].each do |meth|
|
42
|
+
it "#{meth} should generator xml" do
|
43
|
+
result = generator.send(meth)
|
44
|
+
expect(result).to eq("<?xml version=\"1.0\" encoding=\"UTF-8\"?><#{meth}/>")
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
context :add do
|
49
|
+
|
50
|
+
it 'should yield a Message::Document object when #add is called with a block' do
|
51
|
+
documents = [{:id=>1, :name=>'sam', :cat=>['cat 1', 'cat 2']}]
|
52
|
+
add_attrs = {:boost=>200.00}
|
53
|
+
result = generator.add(documents, add_attrs) do |doc|
|
54
|
+
doc.field_by_name(:name).attrs[:boost] = 10
|
55
|
+
end
|
56
|
+
expect(result).to match(%r(name="cat">cat 1</field>))
|
57
|
+
expect(result).to match(%r(name="cat">cat 2</field>))
|
58
|
+
expect(result).to match(%r(<add boost="200.0">))
|
59
|
+
expect(result).to match(%r(boost="10"))
|
60
|
+
expect(result).to match(%r(<field name="id">1</field>))
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'should work for values that yield enumerators' do
|
64
|
+
documents = [{id: 1, cat: ['cat 1', 'cat 2'].to_enum}]
|
65
|
+
result = generator.add(documents)
|
66
|
+
|
67
|
+
expect(result).to match(%r(name="cat">cat 1</field>))
|
68
|
+
expect(result).to match(%r(name="cat">cat 2</field>))
|
69
|
+
end
|
70
|
+
|
71
|
+
# add a single hash ("doc")
|
72
|
+
it 'should create an add from a hash' do
|
73
|
+
data = {
|
74
|
+
:id=>1,
|
75
|
+
:name=>'matt'
|
76
|
+
}
|
77
|
+
result = generator.add(data)
|
78
|
+
expect(result).to match(/<field name="name">matt<\/field>/)
|
79
|
+
expect(result).to match(/<field name="id">1<\/field>/)
|
80
|
+
end
|
81
|
+
|
82
|
+
# add a single hash ("doc")
|
83
|
+
it 'should create an add from a hash formatted for atomic updates' do
|
84
|
+
data = {
|
85
|
+
:id=>1,
|
86
|
+
:name=> { set: 'matt' }
|
87
|
+
}
|
88
|
+
result = generator.add(data)
|
89
|
+
expect(result).to match(/<field name="name" update="set">matt<\/field>/)
|
90
|
+
expect(result).to match(/<field name="id">1<\/field>/)
|
91
|
+
end
|
92
|
+
it 'should remove a field from a hash formatted for atomic updates' do
|
93
|
+
data = {
|
94
|
+
:id => 1,
|
95
|
+
:name => nil
|
96
|
+
}
|
97
|
+
result = generator.add(data)
|
98
|
+
expect(result).to match(%r{<field name="name" null="true"})
|
99
|
+
expect(result).to match(/<field name="id">1<\/field>/)
|
100
|
+
end
|
101
|
+
|
102
|
+
# add an array of hashes
|
103
|
+
it 'should create many adds from an array of hashes' do
|
104
|
+
data = [
|
105
|
+
{
|
106
|
+
:id=>1,
|
107
|
+
:name=>'matt'
|
108
|
+
},
|
109
|
+
{
|
110
|
+
:id=>2,
|
111
|
+
:name=>'sam'
|
112
|
+
}
|
113
|
+
]
|
114
|
+
message = generator.add(data)
|
115
|
+
expect(message).to match %r{<field name="name">matt</field>}
|
116
|
+
expect(message).to match %r{<field name="name">sam</field>}
|
117
|
+
end
|
118
|
+
|
119
|
+
# multiValue field support test, thanks to Fouad Mardini!
|
120
|
+
it 'should create multiple fields from array values' do
|
121
|
+
data = {
|
122
|
+
:id => 1,
|
123
|
+
:name => ['matt1', 'matt2']
|
124
|
+
}
|
125
|
+
result = generator.add(data)
|
126
|
+
expect(result).to match(/<field name="name">matt1<\/field>/)
|
127
|
+
expect(result).to match(/<field name="name">matt2<\/field>/)
|
128
|
+
end
|
129
|
+
|
130
|
+
it 'should allow for objects which can be casted to an array' do
|
131
|
+
name = double("name", to_ary: ['matt1', 'matt2'])
|
132
|
+
data = {
|
133
|
+
:id => 1,
|
134
|
+
:name => name
|
135
|
+
}
|
136
|
+
result = generator.add(data)
|
137
|
+
expect(result).to match(/<field name="name">matt1<\/field>/)
|
138
|
+
expect(result).to match(/<field name="name">matt2<\/field>/)
|
139
|
+
end
|
140
|
+
|
141
|
+
it 'should create an add from a single Message::Document' do
|
142
|
+
document = RSolr::Document.new
|
143
|
+
document.add_field('id', 1)
|
144
|
+
document.add_field('name', 'matt', :boost => 2.0)
|
145
|
+
result = generator.add(document)
|
146
|
+
expect(result).to match(Regexp.escape('<?xml version="1.0" encoding="UTF-8"?>'))
|
147
|
+
expect(result).to match(/<field name="id">1<\/field>/)
|
148
|
+
expect(result).to match Regexp.escape('boost="2.0"')
|
149
|
+
expect(result).to match Regexp.escape('name="name"')
|
150
|
+
expect(result).to match Regexp.escape('matt</field>')
|
151
|
+
end
|
152
|
+
|
153
|
+
it 'should create adds from multiple Message::Documents' do
|
154
|
+
documents = (1..2).map do |i|
|
155
|
+
doc = RSolr::Document.new
|
156
|
+
doc.add_field('id', i)
|
157
|
+
doc.add_field('name', "matt#{i}")
|
158
|
+
doc
|
159
|
+
end
|
160
|
+
result = generator.add(documents)
|
161
|
+
expect(result).to match(/<field name="name">matt1<\/field>/)
|
162
|
+
expect(result).to match(/<field name="name">matt2<\/field>/)
|
163
|
+
end
|
164
|
+
|
165
|
+
it 'supports nested child documents' do
|
166
|
+
data = {
|
167
|
+
:_childDocuments_ => [
|
168
|
+
{
|
169
|
+
:id => 1
|
170
|
+
},
|
171
|
+
{
|
172
|
+
:id => 2
|
173
|
+
}
|
174
|
+
]
|
175
|
+
}
|
176
|
+
|
177
|
+
result = generator.add(data)
|
178
|
+
expect(result).to match(%r{<add><doc><doc>})
|
179
|
+
expect(result).to match(%r{<doc><field name="id">1</field></doc>})
|
180
|
+
expect(result).to match(%r{<doc><field name="id">2</field></doc>})
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
context :delete_by_id do
|
185
|
+
|
186
|
+
it 'should create a doc id delete' do
|
187
|
+
expect(generator.delete_by_id(10)).to eq("<?xml version=\"1.0\" encoding=\"UTF-8\"?><delete><id>10</id></delete>")
|
188
|
+
end
|
189
|
+
|
190
|
+
it 'should create many doc id deletes' do
|
191
|
+
expect(generator.delete_by_id([1, 2, 3])).to eq("<?xml version=\"1.0\" encoding=\"UTF-8\"?><delete><id>1</id><id>2</id><id>3</id></delete>")
|
192
|
+
end
|
193
|
+
|
194
|
+
end
|
195
|
+
|
196
|
+
context :delete_by_query do
|
197
|
+
it 'should create a query delete' do
|
198
|
+
expect(generator.delete_by_query('status:"LOST"')).to eq("<?xml version=\"1.0\" encoding=\"UTF-8\"?><delete><query>status:\"LOST\"</query></delete>")
|
199
|
+
end
|
200
|
+
|
201
|
+
it 'should create many query deletes' do
|
202
|
+
expect(generator.delete_by_query(['status:"LOST"', 'quantity:0'])).to eq("<?xml version=\"1.0\" encoding=\"UTF-8\"?><delete><query>status:\"LOST\"</query><query>quantity:0</query></delete>")
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
context :formatting do
|
210
|
+
it 'should format date objects into ISO 8601' do
|
211
|
+
data = {
|
212
|
+
dt: Date.new(1992, 03, 15)
|
213
|
+
}
|
214
|
+
result = generator.add(data)
|
215
|
+
expect(result).to match(/<field name="dt">1992-03-15T00:00:00Z<\/field>/)
|
216
|
+
end
|
217
|
+
|
218
|
+
it 'should format time objects into ISO 8601' do
|
219
|
+
data = {
|
220
|
+
dt: Time.new(1992, 03, 15, 16, 23, 55, 3600)
|
221
|
+
}
|
222
|
+
result = generator.add(data)
|
223
|
+
expect(result).to match(/<field name="dt">1992-03-15T15:23:55Z<\/field>/)
|
224
|
+
end
|
225
|
+
|
226
|
+
it 'should format datetime objects into ISO 8601' do
|
227
|
+
data = {
|
228
|
+
dt: DateTime.new(1992, 03, 15, 16, 23, 55, '+1')
|
229
|
+
}
|
230
|
+
result = generator.add(data)
|
231
|
+
expect(result).to match(/<field name="dt">1992-03-15T15:23:55Z<\/field>/)
|
232
|
+
end
|
233
|
+
|
234
|
+
it 'passes through other values' do
|
235
|
+
data = {
|
236
|
+
whatever: 'some string'
|
237
|
+
}
|
238
|
+
|
239
|
+
result = generator.add(data)
|
240
|
+
expect(result).to match(/<field name="whatever">some string<\/field>/)
|
241
|
+
end
|
242
|
+
|
243
|
+
# rails monkey-patches String to add a #to_time casting..
|
244
|
+
context 'with rails monkey patching' do
|
245
|
+
it 'passes through string values' do
|
246
|
+
data = {
|
247
|
+
whatever: double(to_s: 'some string', to_time: nil)
|
248
|
+
}
|
249
|
+
|
250
|
+
result = generator.add(data)
|
251
|
+
expect(result).to match(/<field name="whatever">some string<\/field>/)
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|