rsolr 1.1.2 → 2.3.0

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/rsolr.gemspec CHANGED
@@ -23,19 +23,23 @@ Gem::Specification.new do |s|
23
23
  s.email = ["goodieboy@gmail.com"]
24
24
  s.license = 'Apache-2.0'
25
25
  s.homepage = "https://github.com/rsolr/rsolr"
26
- s.rubyforge_project = "rsolr"
27
26
  s.files = `git ls-files`.split("\n")
28
27
  s.test_files = `git ls-files -- {spec}/*`.split("\n")
29
28
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
30
29
  s.require_paths = ["lib"]
31
30
 
32
31
  s.required_ruby_version = '>= 1.9.3'
32
+
33
+ s.requirements << 'Apache Solr'
33
34
 
34
35
  s.add_dependency 'builder', '>= 2.1.2'
36
+ s.add_dependency 'faraday', '>= 0.9.0'
37
+
35
38
  s.add_development_dependency 'activesupport'
36
39
  s.add_development_dependency 'nokogiri', '>= 1.4.0'
37
- s.add_development_dependency 'rake', '~> 10.0'
38
- s.add_development_dependency 'rdoc', '~> 4.0'
40
+ s.add_development_dependency 'rake', '>= 10.0'
41
+ s.add_development_dependency 'rdoc', '>= 4.0'
39
42
  s.add_development_dependency 'rspec', '~> 3.0'
43
+ s.add_development_dependency 'simplecov'
40
44
  s.add_development_dependency 'solr_wrapper'
41
45
  end
@@ -1,20 +1,16 @@
1
1
  require 'spec_helper'
2
- describe "RSolr::Client" do
3
2
 
4
- module ClientHelper
5
- def client
6
- @client ||= (
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(:url) { "http://localhost:9999/solr" }
6
+ let(:connection_options) { { url: url, read_timeout: 42, open_timeout: 43, update_format: :xml } }
11
7
 
12
- def client_with_proxy
13
- @client_with_proxy ||= (
14
- connection = RSolr::Connection.new
15
- RSolr::Client.new connection, :url => "http://localhost:9999/solr", :proxy => 'http://localhost:8080', :read_timeout => 42, :open_timeout=>43
16
- )
17
- end
8
+ let(:client) do
9
+ RSolr::Client.new connection, connection_options
10
+ end
11
+
12
+ let(:client_with_proxy) do
13
+ RSolr::Client.new connection, connection_options.merge(proxy: 'http://localhost:8080')
18
14
  end
19
15
 
20
16
  context "initialize" do
@@ -46,64 +42,40 @@ describe "RSolr::Client" do
46
42
  client = RSolr::Client.new(:whatevs, { proxy: false })
47
43
  expect(client.proxy).to eq(false)
48
44
  end
49
- end
50
45
 
51
- context "send_and_receive" do
52
- include ClientHelper
53
- it "should forward these method calls the #connection object" do
54
- [:get, :post, :head].each do |meth|
55
- expect(client.connection).to receive(:execute).
56
- and_return({:status => 200, :body => "{}", :headers => {}})
57
- client.send_and_receive '', :method => meth, :params => {}, :data => nil, :headers => {}
46
+ context "with an non-HTTP url" do
47
+ let(:url) { "fake://localhost:9999/solr" }
48
+
49
+ it "raises an argument error" do
50
+ expect { client }.to raise_error ArgumentError, "You must provide an HTTP(S) url."
58
51
  end
59
52
  end
60
53
 
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 => {}
54
+ context "with an HTTPS url" do
55
+ let(:url) { "https://localhost:9999/solr" }
56
+
57
+ it "creates a connection" do
58
+ expect(client.uri).to be_kind_of URI::HTTPS
65
59
  end
66
60
  end
61
+
67
62
  end
68
63
 
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)
64
+ context "send_and_receive" do
65
+ it "should forward these method calls the #connection object" do
66
+ [:get, :post, :head].each do |meth|
67
+ expect(client).to receive(:execute).
68
+ and_return({:status => 200, :body => "{}", :headers => {}})
69
+ client.send_and_receive '', :method => meth, :params => {}, :data => nil, :headers => {}
70
+ end
98
71
  end
99
72
  end
100
73
 
101
74
  context "post" do
102
- include ClientHelper
103
75
  it "should pass the expected params to the connection's #execute method" do
104
76
  request_opts = {:data => "the data", :method=>:post, :headers => {"Content-Type" => "text/plain"}}
105
- expect(client.connection).to receive(:execute).
106
- with(client, hash_including(request_opts)).
77
+ expect(client).to receive(:execute).
78
+ with(hash_including(request_opts)).
107
79
  and_return(
108
80
  :body => "",
109
81
  :status => 200,
@@ -113,19 +85,11 @@ describe "RSolr::Client" do
113
85
  end
114
86
  end
115
87
 
116
- context "xml" do
117
- include ClientHelper
118
- it "should return an instance of RSolr::Xml::Generator" do
119
- expect(client.xml).to be_a RSolr::Xml::Generator
120
- end
121
- end
122
-
123
88
  context "add" do
124
- include ClientHelper
125
89
  it "should send xml to the connection's #post method" do
126
- expect(client.connection).to receive(:execute).
90
+ expect(client).to receive(:execute).
127
91
  with(
128
- client, hash_including({
92
+ hash_including({
129
93
  :path => "update",
130
94
  :headers => {"Content-Type"=>"text/xml"},
131
95
  :method => :post,
@@ -137,19 +101,59 @@ describe "RSolr::Client" do
137
101
  :status => 200,
138
102
  :headers => {"Content-Type"=>"text/xml"}
139
103
  )
140
- expect(client.xml).to receive(:add).
104
+ expect(client.builder).to receive(:add).
141
105
  with({:id=>1}, {:commitWith=>10}).
142
106
  and_return("<xml/>")
143
107
  client.add({:id=>1}, :add_attributes => {:commitWith=>10})
144
108
  end
109
+
110
+ context 'when the client is configured for json updates' do
111
+ let(:client) do
112
+ RSolr::Client.new nil, :url => "http://localhost:9999/solr", :read_timeout => 42, :open_timeout=>43, :update_format => :json
113
+ end
114
+ it "should send json to the connection's #post method" do
115
+ expect(client).to receive(:execute).
116
+ with(hash_including({
117
+ :path => 'update',
118
+ :headers => {"Content-Type" => 'application/json'},
119
+ :method => :post,
120
+ :data => '{"hello":"this is json"}'
121
+ })
122
+ ).
123
+ and_return(
124
+ :body => "",
125
+ :status => 200,
126
+ :headers => {"Content-Type"=>"text/xml"}
127
+ )
128
+ expect(client.builder).to receive(:add).
129
+ with({:id => 1}, {:commitWith=>10}).
130
+ and_return('{"hello":"this is json"}')
131
+ client.add({:id=>1}, :add_attributes => {:commitWith=>10})
132
+ end
133
+
134
+ it "should send json to the connection's #post method" do
135
+ expect(client).to receive(:execute).
136
+ with(hash_including({
137
+ :path => 'update',
138
+ :headers => {'Content-Type'=>'application/json'},
139
+ :method => :post,
140
+ :data => '{"optimise" : {}}'
141
+ })
142
+ ).
143
+ and_return(
144
+ :body => "",
145
+ :status => 200,
146
+ :headers => {"Content-Type"=>"text/xml"}
147
+ )
148
+ client.update(:data => '{"optimise" : {}}')
149
+ end
150
+ end
145
151
  end
146
152
 
147
153
  context "update" do
148
- include ClientHelper
149
154
  it "should send data to the connection's #post method" do
150
- expect(client.connection).to receive(:execute).
151
- with(
152
- client, hash_including({
155
+ expect(client).to receive(:execute).
156
+ with(hash_including({
153
157
  :path => "update",
154
158
  :headers => {"Content-Type"=>"text/xml"},
155
159
  :method => :post,
@@ -178,12 +182,10 @@ describe "RSolr::Client" do
178
182
  end
179
183
 
180
184
  context "post based helper methods:" do
181
- include ClientHelper
182
185
  [:commit, :optimize, :rollback].each do |meth|
183
186
  it "should send a #{meth} message to the connection's #post method" do
184
- expect(client.connection).to receive(:execute).
185
- with(
186
- client, hash_including({
187
+ expect(client).to receive(:execute).
188
+ with(hash_including({
187
189
  :path => "update",
188
190
  :headers => {"Content-Type"=>"text/xml"},
189
191
  :method => :post,
@@ -201,11 +203,10 @@ describe "RSolr::Client" do
201
203
  end
202
204
 
203
205
  context "delete_by_id" do
204
- include ClientHelper
205
206
  it "should send data to the connection's #post method" do
206
- expect(client.connection).to receive(:execute).
207
+ expect(client).to receive(:execute).
207
208
  with(
208
- client, hash_including({
209
+ hash_including({
209
210
  :path => "update",
210
211
  :headers => {"Content-Type"=>"text/xml"},
211
212
  :method => :post,
@@ -222,11 +223,10 @@ describe "RSolr::Client" do
222
223
  end
223
224
 
224
225
  context "delete_by_query" do
225
- include ClientHelper
226
226
  it "should send data to the connection's #post method" do
227
- expect(client.connection).to receive(:execute).
227
+ expect(client).to receive(:execute).
228
228
  with(
229
- client, hash_including({
229
+ hash_including({
230
230
  :path => "update",
231
231
  :headers => {"Content-Type"=>"text/xml"},
232
232
  :method => :post,
@@ -243,7 +243,6 @@ describe "RSolr::Client" do
243
243
  end
244
244
 
245
245
  context "adapt_response" do
246
- include ClientHelper
247
246
  it 'should not try to evaluate ruby when the :qt is not :ruby' do
248
247
  body = '{"time"=>"NOW"}'
249
248
  result = client.adapt_response({:params=>{}}, {:status => 200, :body => body, :headers => {}})
@@ -281,9 +280,9 @@ describe "RSolr::Client" do
281
280
  end
282
281
 
283
282
  context "indifferent access" do
284
- include ClientHelper
285
283
  it "should raise a RuntimeError if the #with_indifferent_access extension isn't loaded" do
286
- hide_const("HashWithIndifferentAccess")
284
+ hide_const("::RSolr::HashWithIndifferentAccessWithResponse")
285
+ hide_const("ActiveSupport::HashWithIndifferentAccess")
287
286
  body = "{'foo'=>'bar'}"
288
287
  result = client.adapt_response({:params=>{:wt=>:ruby}}, {:status => 200, :body => body, :headers => {}})
289
288
  expect { result.with_indifferent_access }.to raise_error RuntimeError
@@ -306,7 +305,6 @@ describe "RSolr::Client" do
306
305
  end
307
306
 
308
307
  context "build_request" do
309
- include ClientHelper
310
308
  let(:data) { 'data' }
311
309
  let(:params) { { q: 'test', fq: [0,1] } }
312
310
  let(:options) { { method: :post, params: params, data: data, headers: {} } }
@@ -314,7 +312,7 @@ describe "RSolr::Client" do
314
312
 
315
313
  context "when params are symbols" do
316
314
  it 'should return a request context array' do
317
- [/fq=0/, /fq=1/, /q=test/, /wt=ruby/].each do |pattern|
315
+ [/fq=0/, /fq=1/, /q=test/, /wt=json/].each do |pattern|
318
316
  expect(subject[:query]).to match pattern
319
317
  end
320
318
  expect(subject[:data]).to eq("data")
@@ -336,11 +334,11 @@ describe "RSolr::Client" do
336
334
  let(:options) { { method: :post, data: data, headers: {} } }
337
335
 
338
336
  it "sets the Content-Type header to application/x-www-form-urlencoded; charset=UTF-8" do
339
- expect(subject[:query]).to eq("wt=ruby")
337
+ expect(subject[:query]).to eq("wt=json")
340
338
  [/fq=0/, /fq=1/, /q=test/].each do |pattern|
341
339
  expect(subject[:data]).to match pattern
342
340
  end
343
- expect(subject[:data]).not_to match /wt=ruby/
341
+ expect(subject[:data]).not_to match(/wt=json/)
344
342
  expect(subject[:headers]).to eq({"Content-Type" => "application/x-www-form-urlencoded; charset=UTF-8"})
345
343
  end
346
344
  end
@@ -351,7 +349,7 @@ describe "RSolr::Client" do
351
349
  :data => {:q=>'test', :fq=>[0,1]},
352
350
  :headers => {}
353
351
  )
354
- expect(result[:uri].to_s).to match /^http:\/\/localhost:9999\/solr\//
355
- end
352
+ expect(result[:uri].to_s).to match %r{^http://localhost:9999/solr/}
353
+ end
356
354
  end
357
355
  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
@@ -1,5 +1,6 @@
1
1
  require 'spec_helper'
2
- describe "RSolr::Error" do
2
+
3
+ RSpec.describe RSolr::Error do
3
4
  def generate_error_with_backtrace(request, response)
4
5
  raise RSolr::Error::Http.new request, response
5
6
  rescue RSolr::Error::Http => exception
@@ -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