rsolr 1.1.2 → 2.0.0.pre1
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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.rspec +2 -0
- data/README.rdoc +3 -4
- data/Rakefile +16 -3
- data/lib/rsolr/char.rb +3 -21
- data/lib/rsolr/client.rb +66 -66
- data/lib/rsolr/document.rb +161 -0
- data/lib/rsolr/json.rb +52 -0
- data/lib/rsolr/uri.rb +1 -51
- data/lib/rsolr/version.rb +1 -1
- data/lib/rsolr/xml.rb +24 -98
- data/lib/rsolr.rb +8 -9
- data/rsolr.gemspec +2 -0
- data/spec/api/client_spec.rb +77 -85
- data/spec/api/document_spec.rb +48 -0
- data/spec/api/error_spec.rb +2 -1
- data/spec/api/json_spec.rb +175 -0
- data/spec/api/pagination_spec.rb +3 -9
- data/spec/api/rsolr_spec.rb +3 -11
- data/spec/api/uri_spec.rb +2 -93
- data/spec/api/xml_spec.rb +36 -10
- data/spec/integration/solr5_spec.rb +1 -1
- data/spec/spec_helper.rb +83 -2
- metadata +23 -10
- data/lib/rsolr/connection.rb +0 -74
- data/spec/api/char_spec.rb +0 -23
- data/spec/api/connection_spec.rb +0 -140
- data/tasks/rdoc.rake +0 -11
- data/tasks/rsolr.rake +0 -10
- data/tasks/spec.rake +0 -2
data/lib/rsolr/xml.rb
CHANGED
@@ -2,100 +2,11 @@ begin; require 'nokogiri'; rescue LoadError; end
|
|
2
2
|
require 'time'
|
3
3
|
|
4
4
|
module RSolr::Xml
|
5
|
-
|
6
|
-
class Document
|
7
|
-
|
8
|
-
# "attrs" is a hash for setting the "doc" xml attributes
|
9
|
-
# "fields" is an array of Field objects
|
10
|
-
attr_accessor :attrs, :fields
|
11
|
-
|
12
|
-
# "doc_hash" must be a Hash/Mash object
|
13
|
-
# If a value in the "doc_hash" is an array,
|
14
|
-
# a field object is created for each value...
|
15
|
-
def initialize(doc_hash = {})
|
16
|
-
@fields = []
|
17
|
-
doc_hash.each_pair do |field,values|
|
18
|
-
# create a new field for each value (multi-valued)
|
19
|
-
wrap(values).each do |v|
|
20
|
-
v = format_value(v)
|
21
|
-
next if v.empty?
|
22
|
-
@fields << RSolr::Xml::Field.new({:name=>field}, v)
|
23
|
-
end
|
24
|
-
end
|
25
|
-
@attrs={}
|
26
|
-
end
|
5
|
+
require 'rsolr/document'
|
27
6
|
|
28
|
-
|
29
|
-
|
30
|
-
@fields.select{|f|f.name==name}
|
31
|
-
end
|
32
|
-
|
33
|
-
# returns the *first* field that matches the "name" arg
|
34
|
-
def field_by_name(name)
|
35
|
-
@fields.detect{|f|f.name==name}
|
36
|
-
end
|
37
|
-
|
38
|
-
#
|
39
|
-
# Add a field value to the document. Options map directly to
|
40
|
-
# XML attributes in the Solr <field> node.
|
41
|
-
# See http://wiki.apache.org/solr/UpdateXmlMessages#head-8315b8028923d028950ff750a57ee22cbf7977c6
|
42
|
-
#
|
43
|
-
# === Example:
|
44
|
-
#
|
45
|
-
# document.add_field('title', 'A Title', :boost => 2.0)
|
46
|
-
#
|
47
|
-
def add_field(name, value, options = {})
|
48
|
-
@fields << RSolr::Xml::Field.new(options.merge({:name=>name}), value)
|
49
|
-
end
|
50
|
-
|
51
|
-
private
|
52
|
-
|
53
|
-
def format_value(v)
|
54
|
-
case v
|
55
|
-
when Time
|
56
|
-
v.getutc.iso8601
|
57
|
-
when DateTime
|
58
|
-
v.to_time.getutc.iso8601
|
59
|
-
when Date
|
60
|
-
Time.utc(v.year, v.mon, v.mday).iso8601
|
61
|
-
else
|
62
|
-
v.to_s
|
63
|
-
end
|
64
|
-
end
|
7
|
+
Document = RSolr::Document
|
8
|
+
Field = RSolr::Field
|
65
9
|
|
66
|
-
def wrap(object)
|
67
|
-
if object.nil?
|
68
|
-
[]
|
69
|
-
elsif object.respond_to?(:to_ary)
|
70
|
-
object.to_ary || [object]
|
71
|
-
elsif object.is_a? Enumerable
|
72
|
-
object
|
73
|
-
else
|
74
|
-
[object]
|
75
|
-
end
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
|
-
class Field
|
80
|
-
|
81
|
-
# "attrs" is a hash for setting the "doc" xml attributes
|
82
|
-
# "value" is the text value for the node
|
83
|
-
attr_accessor :attrs, :value
|
84
|
-
|
85
|
-
# "attrs" must be a hash
|
86
|
-
# "value" should be something that responds to #_to_s
|
87
|
-
def initialize(attrs, value)
|
88
|
-
@attrs = attrs
|
89
|
-
@value = value
|
90
|
-
end
|
91
|
-
|
92
|
-
# the value of the "name" attribute
|
93
|
-
def name
|
94
|
-
@attrs[:name]
|
95
|
-
end
|
96
|
-
|
97
|
-
end
|
98
|
-
|
99
10
|
class Generator
|
100
11
|
class << self
|
101
12
|
attr_accessor :use_nokogiri
|
@@ -162,13 +73,9 @@ module RSolr::Xml
|
|
162
73
|
build do |xml|
|
163
74
|
xml.add(add_attrs) do |add_node|
|
164
75
|
data.each do |doc|
|
165
|
-
doc = RSolr::
|
76
|
+
doc = RSolr::Document.new(doc) if doc.respond_to?(:each_pair)
|
166
77
|
yield doc if block_given?
|
167
|
-
doc_node_builder =
|
168
|
-
doc.fields.each do |field_obj|
|
169
|
-
doc_node.field field_obj.value, field_obj.attrs
|
170
|
-
end
|
171
|
-
end
|
78
|
+
doc_node_builder = to_xml(doc)
|
172
79
|
self.class.use_nokogiri ? add_node.doc_(doc.attrs,&doc_node_builder) : add_node.doc(doc.attrs,&doc_node_builder)
|
173
80
|
end
|
174
81
|
end
|
@@ -215,5 +122,24 @@ module RSolr::Xml
|
|
215
122
|
end
|
216
123
|
end
|
217
124
|
end
|
125
|
+
|
126
|
+
private
|
127
|
+
|
128
|
+
def to_xml(doc)
|
129
|
+
lambda do |doc_node|
|
130
|
+
doc.fields.each do |field_obj|
|
131
|
+
value = field_obj.value
|
132
|
+
if field_obj.name.to_s == RSolr::Document::CHILD_DOCUMENT_KEY
|
133
|
+
child_node_builder = to_xml(field_obj.value)
|
134
|
+
self.class.use_nokogiri ? doc_node.doc_(&child_node_builder) : doc_node.doc(&child_node_builder)
|
135
|
+
elsif value.is_a?(Hash) && value.length == 1 && field_obj.attrs[:update].nil?
|
136
|
+
update_attr, real_value = value.first
|
137
|
+
doc_node.field real_value, field_obj.attrs.merge(update: update_attr)
|
138
|
+
else
|
139
|
+
doc_node.field field_obj.value, field_obj.attrs
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
218
144
|
end
|
219
145
|
end
|
data/lib/rsolr.rb
CHANGED
@@ -1,16 +1,15 @@
|
|
1
1
|
module RSolr
|
2
|
-
|
3
2
|
Dir.glob(File.expand_path("../rsolr/*.rb", __FILE__)).each{|rb_file| require(rb_file)}
|
4
|
-
|
3
|
+
|
5
4
|
def self.connect *args
|
6
|
-
|
7
|
-
opts
|
8
|
-
|
5
|
+
opts = args.pop if args.last.is_a?(::Hash)
|
6
|
+
opts ||= {}
|
7
|
+
|
8
|
+
connection = args.first
|
9
|
+
|
10
|
+
Client.new connection, opts
|
9
11
|
end
|
10
|
-
|
11
|
-
# RSolr.escape, which is deprecated as of 2015-02
|
12
|
-
extend Char
|
13
|
-
|
12
|
+
|
14
13
|
# backslash escape characters that have special meaning to Solr query parser
|
15
14
|
# per http://lucene.apache.org/core/4_0_0/queryparser/org/apache/lucene/queryparser/classic/package-summary.html#Escaping_Special_Characters
|
16
15
|
# + - & | ! ( ) { } [ ] ^ " ~ * ? : \ /
|
data/rsolr.gemspec
CHANGED
@@ -32,6 +32,8 @@ Gem::Specification.new do |s|
|
|
32
32
|
s.required_ruby_version = '>= 1.9.3'
|
33
33
|
|
34
34
|
s.add_dependency 'builder', '>= 2.1.2'
|
35
|
+
s.add_dependency 'faraday'
|
36
|
+
|
35
37
|
s.add_development_dependency 'activesupport'
|
36
38
|
s.add_development_dependency 'nokogiri', '>= 1.4.0'
|
37
39
|
s.add_development_dependency 'rake', '~> 10.0'
|
data/spec/api/client_spec.rb
CHANGED
@@ -1,20 +1,15 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
-
describe "RSolr::Client" do
|
3
2
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
connection = RSolr::Connection.new
|
8
|
-
RSolr::Client.new connection, :url => "http://localhost:9999/solr", :read_timeout => 42, :open_timeout=>43
|
9
|
-
)
|
10
|
-
end
|
3
|
+
RSpec.describe RSolr::Client do
|
4
|
+
let(:connection) { nil }
|
5
|
+
let(:connection_options) { { url: "http://localhost:9999/solr", read_timeout: 42, open_timeout: 43, update_format: :xml } }
|
11
6
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
7
|
+
let(:client) do
|
8
|
+
RSolr::Client.new connection, connection_options
|
9
|
+
end
|
10
|
+
|
11
|
+
let(:client_with_proxy) do
|
12
|
+
RSolr::Client.new connection, connection_options.merge(proxy: 'http://localhost:8080')
|
18
13
|
end
|
19
14
|
|
20
15
|
context "initialize" do
|
@@ -49,61 +44,20 @@ describe "RSolr::Client" do
|
|
49
44
|
end
|
50
45
|
|
51
46
|
context "send_and_receive" do
|
52
|
-
include ClientHelper
|
53
47
|
it "should forward these method calls the #connection object" do
|
54
48
|
[:get, :post, :head].each do |meth|
|
55
|
-
expect(client
|
49
|
+
expect(client).to receive(:execute).
|
56
50
|
and_return({:status => 200, :body => "{}", :headers => {}})
|
57
51
|
client.send_and_receive '', :method => meth, :params => {}, :data => nil, :headers => {}
|
58
52
|
end
|
59
53
|
end
|
60
|
-
|
61
|
-
it "should be timeout aware" do
|
62
|
-
[:get, :post, :head].each do |meth|
|
63
|
-
expect(client.connection).to receive(:execute).with(client, hash_including(:read_timeout => 42, :open_timeout=>43))
|
64
|
-
client.send_and_receive '', :method => meth, :params => {}, :data => nil, :headers => {}
|
65
|
-
end
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
context "execute" do
|
70
|
-
include ClientHelper
|
71
|
-
let :request_context do
|
72
|
-
{
|
73
|
-
:method => :post,
|
74
|
-
:params => {},
|
75
|
-
:data => nil,
|
76
|
-
:headers => {},
|
77
|
-
:path => '',
|
78
|
-
:uri => client.base_uri,
|
79
|
-
:retry_503 => 1
|
80
|
-
}
|
81
|
-
end
|
82
|
-
it "should retry 503s if requested" do
|
83
|
-
expect(client.connection).to receive(:execute).exactly(2).times.and_return(
|
84
|
-
{:status => 503, :body => "{}", :headers => {'Retry-After' => 0}},
|
85
|
-
{:status => 200, :body => "{}", :headers => {}}
|
86
|
-
)
|
87
|
-
client.execute request_context
|
88
|
-
end
|
89
|
-
it "should not retry a 503 if the retry-after is too large" do
|
90
|
-
expect(client.connection).to receive(:execute).exactly(1).times.and_return(
|
91
|
-
{:status => 503, :body => "{}", :headers => {'Retry-After' => 10}}
|
92
|
-
)
|
93
|
-
expect {
|
94
|
-
Timeout.timeout(0.5) do
|
95
|
-
client.execute({:retry_after_limit => 0}.merge(request_context))
|
96
|
-
end
|
97
|
-
}.to raise_error(RSolr::Error::Http)
|
98
|
-
end
|
99
54
|
end
|
100
55
|
|
101
56
|
context "post" do
|
102
|
-
include ClientHelper
|
103
57
|
it "should pass the expected params to the connection's #execute method" do
|
104
58
|
request_opts = {:data => "the data", :method=>:post, :headers => {"Content-Type" => "text/plain"}}
|
105
|
-
expect(client
|
106
|
-
with(
|
59
|
+
expect(client).to receive(:execute).
|
60
|
+
with(hash_including(request_opts)).
|
107
61
|
and_return(
|
108
62
|
:body => "",
|
109
63
|
:status => 200,
|
@@ -114,18 +68,22 @@ describe "RSolr::Client" do
|
|
114
68
|
end
|
115
69
|
|
116
70
|
context "xml" do
|
117
|
-
include ClientHelper
|
118
71
|
it "should return an instance of RSolr::Xml::Generator" do
|
119
72
|
expect(client.xml).to be_a RSolr::Xml::Generator
|
120
73
|
end
|
121
74
|
end
|
122
75
|
|
76
|
+
context "json" do
|
77
|
+
it "should return an instance of RSolr::JSON::Generator" do
|
78
|
+
expect(client.json).to be_a RSolr::JSON::Generator
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
123
82
|
context "add" do
|
124
|
-
include ClientHelper
|
125
83
|
it "should send xml to the connection's #post method" do
|
126
|
-
expect(client
|
84
|
+
expect(client).to receive(:execute).
|
127
85
|
with(
|
128
|
-
|
86
|
+
hash_including({
|
129
87
|
:path => "update",
|
130
88
|
:headers => {"Content-Type"=>"text/xml"},
|
131
89
|
:method => :post,
|
@@ -142,14 +100,54 @@ describe "RSolr::Client" do
|
|
142
100
|
and_return("<xml/>")
|
143
101
|
client.add({:id=>1}, :add_attributes => {:commitWith=>10})
|
144
102
|
end
|
103
|
+
|
104
|
+
context 'when the client is configured for json updates' do
|
105
|
+
let(:client) do
|
106
|
+
RSolr::Client.new nil, :url => "http://localhost:9999/solr", :read_timeout => 42, :open_timeout=>43, :update_format => :json
|
107
|
+
end
|
108
|
+
it "should send json to the connection's #post method" do
|
109
|
+
expect(client).to receive(:execute).
|
110
|
+
with(hash_including({
|
111
|
+
:path => 'update',
|
112
|
+
:headers => {"Content-Type" => 'application/json'},
|
113
|
+
:method => :post,
|
114
|
+
:data => '{"hello":"this is json"}'
|
115
|
+
})
|
116
|
+
).
|
117
|
+
and_return(
|
118
|
+
:body => "",
|
119
|
+
:status => 200,
|
120
|
+
:headers => {"Content-Type"=>"text/xml"}
|
121
|
+
)
|
122
|
+
expect(client.json).to receive(:add).
|
123
|
+
with({:id => 1}, {:commitWith=>10}).
|
124
|
+
and_return('{"hello":"this is json"}')
|
125
|
+
client.add({:id=>1}, :add_attributes => {:commitWith=>10})
|
126
|
+
end
|
127
|
+
|
128
|
+
it "should send json to the connection's #post method" do
|
129
|
+
expect(client).to receive(:execute).
|
130
|
+
with(hash_including({
|
131
|
+
:path => 'update',
|
132
|
+
:headers => {'Content-Type'=>'application/json'},
|
133
|
+
:method => :post,
|
134
|
+
:data => '{"optimise" : {}}'
|
135
|
+
})
|
136
|
+
).
|
137
|
+
and_return(
|
138
|
+
:body => "",
|
139
|
+
:status => 200,
|
140
|
+
:headers => {"Content-Type"=>"text/xml"}
|
141
|
+
)
|
142
|
+
client.update(:data => '{"optimise" : {}}')
|
143
|
+
end
|
144
|
+
end
|
145
145
|
end
|
146
146
|
|
147
147
|
context "update" do
|
148
|
-
include ClientHelper
|
149
148
|
it "should send data to the connection's #post method" do
|
150
|
-
expect(client
|
151
|
-
with(
|
152
|
-
client, hash_including({
|
149
|
+
expect(client).to receive(:execute).
|
150
|
+
with(hash_including({
|
153
151
|
:path => "update",
|
154
152
|
:headers => {"Content-Type"=>"text/xml"},
|
155
153
|
:method => :post,
|
@@ -178,12 +176,10 @@ describe "RSolr::Client" do
|
|
178
176
|
end
|
179
177
|
|
180
178
|
context "post based helper methods:" do
|
181
|
-
include ClientHelper
|
182
179
|
[:commit, :optimize, :rollback].each do |meth|
|
183
180
|
it "should send a #{meth} message to the connection's #post method" do
|
184
|
-
expect(client
|
185
|
-
with(
|
186
|
-
client, hash_including({
|
181
|
+
expect(client).to receive(:execute).
|
182
|
+
with(hash_including({
|
187
183
|
:path => "update",
|
188
184
|
:headers => {"Content-Type"=>"text/xml"},
|
189
185
|
:method => :post,
|
@@ -201,11 +197,10 @@ describe "RSolr::Client" do
|
|
201
197
|
end
|
202
198
|
|
203
199
|
context "delete_by_id" do
|
204
|
-
include ClientHelper
|
205
200
|
it "should send data to the connection's #post method" do
|
206
|
-
expect(client
|
201
|
+
expect(client).to receive(:execute).
|
207
202
|
with(
|
208
|
-
|
203
|
+
hash_including({
|
209
204
|
:path => "update",
|
210
205
|
:headers => {"Content-Type"=>"text/xml"},
|
211
206
|
:method => :post,
|
@@ -222,11 +217,10 @@ describe "RSolr::Client" do
|
|
222
217
|
end
|
223
218
|
|
224
219
|
context "delete_by_query" do
|
225
|
-
include ClientHelper
|
226
220
|
it "should send data to the connection's #post method" do
|
227
|
-
expect(client
|
221
|
+
expect(client).to receive(:execute).
|
228
222
|
with(
|
229
|
-
|
223
|
+
hash_including({
|
230
224
|
:path => "update",
|
231
225
|
:headers => {"Content-Type"=>"text/xml"},
|
232
226
|
:method => :post,
|
@@ -243,7 +237,6 @@ describe "RSolr::Client" do
|
|
243
237
|
end
|
244
238
|
|
245
239
|
context "adapt_response" do
|
246
|
-
include ClientHelper
|
247
240
|
it 'should not try to evaluate ruby when the :qt is not :ruby' do
|
248
241
|
body = '{"time"=>"NOW"}'
|
249
242
|
result = client.adapt_response({:params=>{}}, {:status => 200, :body => body, :headers => {}})
|
@@ -281,9 +274,9 @@ describe "RSolr::Client" do
|
|
281
274
|
end
|
282
275
|
|
283
276
|
context "indifferent access" do
|
284
|
-
include ClientHelper
|
285
277
|
it "should raise a RuntimeError if the #with_indifferent_access extension isn't loaded" do
|
286
|
-
hide_const("
|
278
|
+
hide_const("::RSolr::HashWithIndifferentAccessWithResponse")
|
279
|
+
hide_const("ActiveSupport::HashWithIndifferentAccess")
|
287
280
|
body = "{'foo'=>'bar'}"
|
288
281
|
result = client.adapt_response({:params=>{:wt=>:ruby}}, {:status => 200, :body => body, :headers => {}})
|
289
282
|
expect { result.with_indifferent_access }.to raise_error RuntimeError
|
@@ -306,7 +299,6 @@ describe "RSolr::Client" do
|
|
306
299
|
end
|
307
300
|
|
308
301
|
context "build_request" do
|
309
|
-
include ClientHelper
|
310
302
|
let(:data) { 'data' }
|
311
303
|
let(:params) { { q: 'test', fq: [0,1] } }
|
312
304
|
let(:options) { { method: :post, params: params, data: data, headers: {} } }
|
@@ -314,7 +306,7 @@ describe "RSolr::Client" do
|
|
314
306
|
|
315
307
|
context "when params are symbols" do
|
316
308
|
it 'should return a request context array' do
|
317
|
-
[/fq=0/, /fq=1/, /q=test/, /wt=
|
309
|
+
[/fq=0/, /fq=1/, /q=test/, /wt=json/].each do |pattern|
|
318
310
|
expect(subject[:query]).to match pattern
|
319
311
|
end
|
320
312
|
expect(subject[:data]).to eq("data")
|
@@ -336,11 +328,11 @@ describe "RSolr::Client" do
|
|
336
328
|
let(:options) { { method: :post, data: data, headers: {} } }
|
337
329
|
|
338
330
|
it "sets the Content-Type header to application/x-www-form-urlencoded; charset=UTF-8" do
|
339
|
-
expect(subject[:query]).to eq("wt=
|
331
|
+
expect(subject[:query]).to eq("wt=json")
|
340
332
|
[/fq=0/, /fq=1/, /q=test/].each do |pattern|
|
341
333
|
expect(subject[:data]).to match pattern
|
342
334
|
end
|
343
|
-
expect(subject[:data]).not_to match
|
335
|
+
expect(subject[:data]).not_to match(/wt=json/)
|
344
336
|
expect(subject[:headers]).to eq({"Content-Type" => "application/x-www-form-urlencoded; charset=UTF-8"})
|
345
337
|
end
|
346
338
|
end
|
@@ -351,7 +343,7 @@ describe "RSolr::Client" do
|
|
351
343
|
:data => {:q=>'test', :fq=>[0,1]},
|
352
344
|
:headers => {}
|
353
345
|
)
|
354
|
-
expect(result[:uri].to_s).to match
|
355
|
-
end
|
346
|
+
expect(result[:uri].to_s).to match %r{^http://localhost:9999/solr/}
|
347
|
+
end
|
356
348
|
end
|
357
349
|
end
|
@@ -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
|
data/spec/api/error_spec.rb
CHANGED
@@ -0,0 +1,175 @@
|
|
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
|
+
end
|
127
|
+
|
128
|
+
it 'should create multiple fields from array values' do
|
129
|
+
data = {
|
130
|
+
:id => "1",
|
131
|
+
:name => ['matt1', 'matt2']
|
132
|
+
}
|
133
|
+
message = JSON.parse(generator.add(data), symbolize_names: true)
|
134
|
+
expect(message.length).to eq 1
|
135
|
+
expect(message.first).to eq data
|
136
|
+
end
|
137
|
+
|
138
|
+
describe '#commit' do
|
139
|
+
it 'generates a commit command' do
|
140
|
+
expect(JSON.parse(generator.commit, symbolize_names: true)).to eq(commit: {})
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
describe '#optimize' do
|
145
|
+
it 'generates a optimize command' do
|
146
|
+
expect(JSON.parse(generator.optimize, symbolize_names: true)).to eq(optimize: {})
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
describe '#rollback' do
|
151
|
+
it 'generates a rollback command' do
|
152
|
+
expect(JSON.parse(generator.rollback, symbolize_names: true)).to eq(rollback: {})
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
describe '#delete_by_id' do
|
157
|
+
it 'generates a delete_by_id command for single documents' do
|
158
|
+
expect(JSON.parse(generator.delete_by_id('x'), symbolize_names: true)).to eq(delete: 'x')
|
159
|
+
end
|
160
|
+
|
161
|
+
it 'generates a delete_by_id command for an array of documents' do
|
162
|
+
expect(JSON.parse(generator.delete_by_id(%w(a b c)), symbolize_names: true)).to eq(delete: %w(a b c))
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
describe '#delete_by_query' do
|
167
|
+
it 'generates a delete_by_id command for single documents' do
|
168
|
+
expect(JSON.parse(generator.delete_by_query('id:x'), symbolize_names: true)).to eq(delete: { query: 'id:x'})
|
169
|
+
end
|
170
|
+
|
171
|
+
it 'generates a delete_by_id command for an array of documents' do
|
172
|
+
expect(JSON.parse(generator.delete_by_id(%w(a b c)), symbolize_names: true)).to eq(delete: %w(a b c))
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|