rsolr 2.6.0 → 3.0.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2b7e170dd7fcd0bfd504562297b93a6fb504da5e95df0d356cd0d1c70a7515c8
4
- data.tar.gz: b4d04a24455588c421f72d29b8e9e7ab255fb4bfde9457bc6ab5f64bb64c76a4
3
+ metadata.gz: 7cd06ee9f0201b75d69b9f7d02f5ad6641272e5ec8d6d1eaa1b57aaedc90daef
4
+ data.tar.gz: 277248ac909e888160f8addac51fa01d44dc26cb089b8bfcbdb3ed93263015c9
5
5
  SHA512:
6
- metadata.gz: cd27dcd3369b5d1e4cd99604426927bf83a3a4c3ef7d8a06fb3cfc45676b33c920ce256ecb7fc577dedbbe910b6d50a45fdaa7dd0324516529c5705df8698385
7
- data.tar.gz: 5a488c3aa5e5ca93e8fe2647baf313d8b5a94229e99d6a49820a3bf80b65fe9c19fd8ab05678ee402d3674d7a043ded6110acdf4ada5a2a1752b96f4080ceec1
6
+ metadata.gz: e095fbdf8f63f68eeb5b9919b6cf61d745b1dd044d819f2a762dbf82bf3b9bf813b0b022c61709f57e3f080ce96104e453d22db120d2d62f1a94453cf58b145c
7
+ data.tar.gz: 8ff5849b11a1bb25f315cd5373354dd2479d7c0725d8092b76e8360fb31886f3d406f4d2cecf898003d11124af0566528aa6f9dd860680510875008a10afe046
@@ -2,28 +2,30 @@ name: CI
2
2
 
3
3
  on:
4
4
  push:
5
- branches: [ master ]
5
+ branches: [main]
6
6
  pull_request:
7
- branches: [ master ]
7
+ branches: [main]
8
8
 
9
9
  jobs:
10
10
  tests:
11
11
  runs-on: ubuntu-latest
12
12
  strategy:
13
13
  matrix:
14
- ruby: [jruby-9.3.3.0, '3.0', '3.1', '3.2', '3.3']
15
- faraday: ['~> 0.17', '~> 1', '~>2']
14
+ ruby: [jruby-10.0.3.0, "3.2", "3.3", "3.4", "4.0"]
15
+ faraday: ["~> 1", "~>2"]
16
16
  steps:
17
- - uses: actions/checkout@v2
18
- - name: Set up Ruby
19
- uses: ruby/setup-ruby@v1
20
- with:
21
- ruby-version: ${{ matrix.ruby }}
22
- - name: Install dependencies
23
- run: bundle install
24
- env:
25
- FARADAY_VERSION: ${{ matrix.faraday}}
26
- - name: Run tests
27
- run: bundle exec rake
28
- env:
29
- FARADAY_VERSION: ${{ matrix.faraday}}
17
+ - uses: actions/checkout@v7
18
+ - name: Set up Ruby
19
+ uses: ruby/setup-ruby@v1
20
+ with:
21
+ ruby-version: ${{ matrix.ruby }}
22
+ - name: Install dependencies
23
+ run: bundle install
24
+ env:
25
+ FARADAY_VERSION: ${{ matrix.faraday}}
26
+ - name: Start Solr
27
+ run: docker compose up -d --wait --wait-timeout 60
28
+ - name: Run tests
29
+ run: bundle exec rake
30
+ env:
31
+ FARADAY_VERSION: ${{ matrix.faraday}}
data/CHANGES.txt CHANGED
@@ -1,3 +1,13 @@
1
+ 3.0.0
2
+
3
+ - Drop faraday 0.x support by @jcoyne in https://github.com/rsolr/rsolr/pull/255
4
+ - Allow for setting a default http method by @cjcolvar in https://github.com/rsolr/rsolr/pull/252
5
+ - Move query params into data hash when using post and no data is provided by @cjcolvar in https://github.com/rsolr/rsolr/pull/253
6
+ - Support HTTP Basic Auth via a basic_auth: option (in addition to URL-embedded) by @maxkadel in https://github.com/rsolr/rsolr/pull/259
7
+ - Redact only basic auth credentials from Faraday's cause exception by @maxkadel in https://github.com/rsolr/rsolr/pull/261
8
+
9
+ **Full Changelog**: https://github.com/rsolr/rsolr/compare/v2.6.0...v3.0.0
10
+
1
11
  2.6.0
2
12
 
3
13
  - Stop testing on Ruby 2. https://github.com/rsolr/rsolr/pull/237
data/Gemfile CHANGED
@@ -4,10 +4,4 @@ gemspec
4
4
 
5
5
  gem "builder", ">= 2.1.2"
6
6
 
7
- if defined? JRUBY_VERSION
8
- # HTTP.rb (used by solr_wrapper to download solr for integration testing) fails
9
- # to download the full contents of files (under jruby)?
10
- gem "http", '< 5', platforms: :jruby
11
- end
12
-
13
7
  gem 'faraday', ENV['FARADAY_VERSION'] if ENV['FARADAY_VERSION']
data/README.md ADDED
@@ -0,0 +1,370 @@
1
+ # RSolr
2
+
3
+ A simple, extensible Ruby client for Apache Solr.
4
+
5
+ ## Documentation
6
+
7
+ The code docs: http://www.rubydoc.info/gems/rsolr
8
+
9
+ ## Installation
10
+
11
+ ```
12
+ gem install rsolr
13
+ ```
14
+
15
+ ## Example
16
+
17
+ ```ruby
18
+ require 'rsolr'
19
+
20
+ # Direct connection
21
+ solr = RSolr.connect :url => 'http://solrserver.com'
22
+
23
+ # Connecting over a proxy server
24
+ solr = RSolr.connect :url => 'http://solrserver.com', :proxy=>'http://user:pass@proxy.example.com:8080'
25
+
26
+ # Using an alternate Faraday adapter
27
+ solr = RSolr.connect :url => 'http://solrserver.com', :adapter => :em_http
28
+
29
+ # Using a custom Faraday connection
30
+ conn = Faraday.new do |faraday|
31
+ faraday.response :logger # log requests to STDOUT
32
+ faraday.adapter Faraday.default_adapter # make requests with Net::HTTP
33
+ end
34
+ solr = RSolr.connect conn, :url => 'http://solrserver.com'
35
+
36
+ # send a request to /select
37
+ response = solr.get 'select', :params => {:q => '*:*'}
38
+
39
+ # send a request to /catalog
40
+ response = solr.get 'catalog', :params => {:q => '*:*'}
41
+ ```
42
+
43
+ When the Solr `:wt` is `:ruby`, then the response will be a Hash. This Hash is the same object returned by Solr, but evaluated as Ruby. If the `:wt` is not `:ruby`, then the response will be a String.
44
+
45
+ The response also exposes 2 attribute readers (for any `:wt` value), `:request` and `:response`. Both are Hash objects with symbolized keys.
46
+
47
+ The `:request` attribute contains the original request context. You can use this for debugging or logging. Some of the keys this object contains are `:uri`, `:query`, `:method` etc..
48
+
49
+ The `:response` attribute contains the original response. This object contains the `:status`, `:body` and `:headers` keys.
50
+
51
+ ## Basic Auth
52
+
53
+ Credentials can be embedded in the URL:
54
+
55
+ ```ruby
56
+ solr = RSolr.connect :url => 'http://user:pass@solrserver.com'
57
+ ```
58
+
59
+ or passed explicitly via `:basic_auth`, which is useful if you'd rather keep
60
+ secrets out of a URL string (logs, error messages, etc. tend to capture URLs
61
+ more casually than headers):
62
+
63
+ ```ruby
64
+ solr = RSolr.connect :url => 'http://solrserver.com', :basic_auth => { :user => 'user', :password => 'pass' }
65
+ ```
66
+
67
+ `:basic_auth` accepts string or symbol keys (`'user'`/`'password'`
68
+ work the same as `:user`/`:password`), so a hash loaded straight
69
+ from YAML config works without re-symbolizing it yourself.
70
+
71
+ If both are given, `:basic_auth` wins. Either way, this only protects the
72
+ credential from casual logging - it doesn't encrypt anything in transit, so
73
+ use `https://` if the connection isn't otherwise trusted (e.g. firewalled).
74
+
75
+ ## Request formats
76
+
77
+ By default, RSolr uses the Solr JSON command format for all requests.
78
+
79
+ ```ruby
80
+ RSolr.connect :url => 'http://solrserver.com', update_format: :json # the default
81
+ # or
82
+ RSolr.connect :url => 'http://solrserver.com', update_format: :xml
83
+ ```
84
+
85
+ ## Timeouts
86
+
87
+ The read and connect timeout settings can be set when creating a new instance of RSolr, and will
88
+ be passed on to underlying Faraday instance:
89
+
90
+ ```ruby
91
+ solr = RSolr.connect(:timeout => 120, :open_timeout => 120)
92
+ ```
93
+
94
+ ## Retry 503s
95
+
96
+ A 503 is usually a temporary error which RSolr may retry if requested. You may specify the number of retry attempts with the `:retry_503` option.
97
+
98
+ Only requests which specify a Retry-After header will be retried, after waiting the indicated retry interval, otherwise RSolr will treat the request as a 500. You may specify a maximum Retry-After interval to wait with the `:retry_after_limit` option (default: one second).
99
+
100
+ ```ruby
101
+ solr = RSolr.connect(:retry_503 => 1, :retry_after_limit => 1)
102
+ ```
103
+
104
+ For additional control, consider using a custom Faraday connection (see above) using its `retry` middleware.
105
+
106
+ ## Querying
107
+
108
+ Use the `#get` / `#post` method to send search requests to the /select handler:
109
+
110
+ ```ruby
111
+ response = solr.get 'select', :params => {
112
+ :q=>'washington',
113
+ :start=>0,
114
+ :rows=>10
115
+ }
116
+ response["response"]["docs"].each{|doc| puts doc["id"] }
117
+ ```
118
+
119
+ The `:params` sent into the method are sent to Solr as-is, which is to say they are converted to Solr url style, but no special mapping is used.
120
+ When an array is used, multiple parameters **with the same name** are generated for the Solr query. Example:
121
+
122
+ ```ruby
123
+ solr.get 'select', :params => {:q=>'roses', :fq=>['red', 'violet']}
124
+ ```
125
+
126
+ The above statement generates this Solr query:
127
+
128
+ ```
129
+ select?q=roses&fq=red&fq=violet
130
+ ```
131
+
132
+ ### Pagination
133
+
134
+ To paginate through a set of Solr documents, use the paginate method:
135
+
136
+ ```ruby
137
+ solr.paginate 1, 10, "select", :params => {:q => "test"}
138
+ ```
139
+
140
+ The first argument is the current page, the second is how many documents to return for each page. In other words, "page" is the "start" Solr param and "per-page" is the "rows" Solr param.
141
+
142
+ The paginate method returns WillPaginate ready "docs" objects, so for example in a Rails application, paginating is as simple as:
143
+
144
+ ```erb
145
+ <%= will_paginate @solr_response["response"]["docs"] %>
146
+ ```
147
+
148
+ ### Method Missing
149
+
150
+ The `RSolr::Client` class also uses `method_missing` for setting the request handler/path:
151
+
152
+ ```ruby
153
+ solr.paintings :params => {:q=>'roses', :fq=>['red', 'violet']}
154
+ ```
155
+
156
+ This is sent to Solr as:
157
+
158
+ ```
159
+ paintings?q=roses&fq=red&fq=violet
160
+ ```
161
+
162
+ This works with pagination as well:
163
+
164
+ ```ruby
165
+ solr.paginate_paintings 1, 10, {:q=>'roses', :fq=>['red', 'violet']}
166
+ ```
167
+
168
+ ### Using POST for Search Queries
169
+
170
+ There may be cases where the query string is too long for a GET request. RSolr solves this issue by converting hash objects into form-encoded strings:
171
+
172
+ ```ruby
173
+ response = solr.music :data => {:q => "*:*"}
174
+ ```
175
+
176
+ The `:data` hash is serialized as a form-encoded query string, and the correct content-type headers are sent along to Solr.
177
+
178
+ ### Sending HEAD Requests
179
+
180
+ There may be cases where you'd like to send a HEAD request to Solr:
181
+
182
+ ```ruby
183
+ solr.head("admin/ping").response[:status] == 200
184
+ ```
185
+
186
+ ## Sending HTTP Headers
187
+
188
+ Solr responds to the request headers listed here: http://wiki.apache.org/solr/SolrAndHTTPCaches
189
+ To send header information to Solr using RSolr, just use the `:headers` option:
190
+
191
+ ```ruby
192
+ response = solr.head "admin/ping", :headers => {"Cache-Control" => "If-None-Match"}
193
+ ```
194
+
195
+ ### Building a Request
196
+
197
+ `RSolr::Client` provides a method for building a request context, which can be useful for debugging or logging etc.:
198
+
199
+ ```ruby
200
+ request_context = solr.build_request "select", :data => {:q => "*:*"}, :method => :post, :headers => {}
201
+ ```
202
+
203
+ To build a paginated request use build_paginated_request:
204
+
205
+ ```ruby
206
+ request_context = solr.build_paginated_request 1, 10, "select", ...
207
+ ```
208
+
209
+ ## Updating Solr
210
+
211
+ Updating is done using native Ruby objects. Hashes are used for single documents and arrays are used for a collection of documents (hashes). These objects get turned into simple XML "messages". Raw XML strings can also be used.
212
+
213
+ Single document via `#add`
214
+
215
+ ```ruby
216
+ solr.add :id=>1, :price=>1.00
217
+ ```
218
+
219
+ Multiple documents via `#add`
220
+
221
+ ```ruby
222
+ documents = [{:id=>1, :price=>1.00}, {:id=>2, :price=>10.50}]
223
+ solr.add documents
224
+ ```
225
+
226
+ The optional `:add_attributes` hash can also be used to set Solr "add" document attributes:
227
+
228
+ ```ruby
229
+ solr.add documents, :add_attributes => {:commitWithin => 10}
230
+ ```
231
+
232
+ Raw commands via `#update`
233
+
234
+ ```ruby
235
+ solr.update data: '<commit/>', headers: { 'Content-Type' => 'text/xml' }
236
+ solr.update data: { optimize: true }.to_json, headers: { 'Content-Type' => 'application/json' }
237
+ ```
238
+
239
+ When adding, you can also supply "add" xml element attributes and/or a block for manipulating other "add" related elements (docs and fields) by calling the `xml` method directly:
240
+
241
+ ```ruby
242
+ doc = {:id=>1, :price=>1.00}
243
+ add_attributes = {:allowDups=>false, :commitWithin=>10}
244
+ add_xml = solr.xml.add(doc, add_attributes) do |doc|
245
+ # boost each document
246
+ doc.attrs[:boost] = 1.5
247
+ # boost the price field:
248
+ doc.field_by_name(:price).attrs[:boost] = 2.0
249
+ end
250
+ ```
251
+
252
+ Now the "add_xml" object can be sent to Solr like:
253
+
254
+ ```ruby
255
+ solr.update :data => add_xml
256
+ ```
257
+
258
+ ### Deleting
259
+
260
+ Delete by id
261
+
262
+ ```ruby
263
+ solr.delete_by_id 1
264
+ ```
265
+
266
+ or an array of ids
267
+
268
+ ```ruby
269
+ solr.delete_by_id [1, 2, 3, 4]
270
+ ```
271
+
272
+ Delete by query:
273
+
274
+ ```ruby
275
+ solr.delete_by_query 'price:1.00'
276
+ ```
277
+
278
+ Delete by array of queries
279
+
280
+ ```ruby
281
+ solr.delete_by_query ['price:1.00', 'price:10.00']
282
+ ```
283
+
284
+ ### Commit / Optimize
285
+
286
+ ```ruby
287
+ solr.commit, :commit_attributes => {}
288
+ solr.optimize, :optimize_attributes => {}
289
+ ```
290
+
291
+ ## Response Formats
292
+
293
+ The default response format is Ruby. When the `:wt` param is set to `:ruby`, the response is eval'd resulting in a Hash. You can get a raw response by setting the `:wt` to `"ruby"` - notice, the string -- not a symbol. RSolr will eval the Ruby string ONLY if the :wt value is :ruby. All other response formats are available as expected, `:wt=>'xml'` etc..
294
+
295
+ ### Evaluated Ruby:
296
+
297
+ ```ruby
298
+ solr.get 'select', :params => {:wt => :ruby} # notice :ruby is a Symbol
299
+ ```
300
+
301
+ ### Raw Ruby:
302
+
303
+ ```ruby
304
+ solr.get 'select', :params => {:wt => 'ruby'} # notice 'ruby' is a String
305
+ ```
306
+
307
+ ### XML:
308
+
309
+ ```ruby
310
+ solr.get 'select', :params => {:wt => :xml}
311
+ ```
312
+
313
+ ### JSON (default):
314
+
315
+ ```ruby
316
+ solr.get 'select', :params => {:wt => :json}
317
+ ```
318
+
319
+ ## Related Resources & Projects
320
+
321
+ * [RSolr Google Group](http://groups.google.com/group/rsolr) -- The RSolr discussion group
322
+ * [rsolr-ext](http://github.com/mwmitchell/rsolr-ext) -- An extension kit for RSolr
323
+ * [rsolr-direct](http://github.com/mwmitchell/rsolr-direct) -- JRuby direct connection for RSolr
324
+ * [rsolr-nokogiri](http://github.com/mwmitchell/rsolr-nokogiri) -- Gives RSolr Nokogiri for XML generation.
325
+ * [SunSpot](http://github.com/sunspot/sunspot) -- An awesome Solr DSL, built with RSolr
326
+ * [Blacklight](http://blacklightopac.org) -- A "next generation" Library OPAC, built with RSolr
327
+ * [java_bin](http://github.com/kennyj/java_bin) -- Provides javabin/binary parsing for RSolr
328
+ * [Solr](http://lucene.apache.org/solr/) -- The Apache Solr project
329
+ * [solr-ruby](http://wiki.apache.org/solr/solr-ruby) -- The original Solr Ruby Gem!
330
+
331
+ ## Running the tests
332
+
333
+ The integration specs (`spec/integration`) need a Solr instance;
334
+ run `docker compose up -d` before `bundle exec rspec` or
335
+ `bundle exec rake`.
336
+
337
+ ## Note on Patches/Pull Requests
338
+
339
+ * Fork the project.
340
+ * Make your feature addition or bug fix.
341
+ * Add tests for it. This is important so I don't break it in a future version unintentionally.
342
+ * Commit, do not mess with rakefile, version, or history
343
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
344
+ * Send me a pull request. Bonus points for topic branches.
345
+
346
+ ## Contributors
347
+
348
+ * Nathan Witmer
349
+ * Magnus Bergmark
350
+ * shima
351
+ * Randy Souza
352
+ * Mat Brown
353
+ * Jeremy Hinegardner
354
+ * Denis Goeury
355
+ * shairon toledo
356
+ * Rob Di Marco
357
+ * Peter Kieltyka
358
+ * Mike Perham
359
+ * Lucas Souza
360
+ * Dmitry Lihachev
361
+ * Antoine Latter
362
+ * Naomi Dushay
363
+
364
+ ## Author
365
+
366
+ Matt Mitchell <goodieboy@gmail.com>
367
+
368
+ ## Copyright
369
+
370
+ Copyright (c) 2008-2010 Matt Mitchell. See LICENSE for details.
data/Rakefile CHANGED
@@ -14,6 +14,6 @@ RDoc::Task.new(:doc) do |rdoc|
14
14
  rdoc.rdoc_dir = 'doc'
15
15
  rdoc.title = 'RSolr'
16
16
  rdoc.options << '--line-numbers' << '--inline-source'
17
- rdoc.rdoc_files.include('README.rdoc')
17
+ rdoc.rdoc_files.include('README.md')
18
18
  rdoc.rdoc_files.include('lib/**/*.rb')
19
19
  end
@@ -0,0 +1,32 @@
1
+ services:
2
+ solr:
3
+ image: solr:9
4
+ ports:
5
+ - "8983:8983"
6
+ volumes:
7
+ - ./spec/fixtures/basic_configs:/basic_configs_config/conf:ro
8
+ command: ["solr-precreate", "basic_configs", "/basic_configs_config"]
9
+ healthcheck:
10
+ test: ["CMD", "curl", "-sf", "http://localhost:8983/solr/basic_configs/admin/ping"]
11
+ interval: 2s
12
+ timeout: 5s
13
+ retries: 30
14
+
15
+ solr_auth:
16
+ image: solr:9
17
+ ports:
18
+ - "8984:8983"
19
+ volumes:
20
+ - ./spec/fixtures/basic_configs:/basic_configs_config/conf:ro
21
+ - ./spec/fixtures/security/security.json:/security-fixture/security.json:ro
22
+ entrypoint: ["bash", "-c"]
23
+ command:
24
+ - |
25
+ mkdir -p /var/solr/data &&
26
+ cp /security-fixture/security.json /var/solr/data/security.json &&
27
+ exec /opt/solr/docker/scripts/solr-precreate basic_configs /basic_configs_config
28
+ healthcheck:
29
+ test: ["CMD", "curl", "-sf", "-u", "rsolr:RSolrTest123", "http://localhost:8983/solr/basic_configs/admin/ping"]
30
+ interval: 2s
31
+ timeout: 5s
32
+ retries: 30
data/lib/rsolr/client.rb CHANGED
@@ -3,10 +3,24 @@
3
3
  require 'json'
4
4
  require 'faraday'
5
5
  require 'uri'
6
+ require 'base64'
6
7
 
7
8
  class RSolr::Client
9
+ include RSolr::Error::URICleanup
10
+
8
11
  DEFAULT_URL = 'http://127.0.0.1:8983/solr/'
9
12
 
13
+ # HTTP Basic Auth credentials, and how to render them as an Authorization header.
14
+ BasicAuth = Struct.new(:user, :password) do
15
+ def present?
16
+ !!(user && password)
17
+ end
18
+
19
+ def header_value
20
+ "Basic #{Base64.strict_encode64("#{user}:#{password}")}"
21
+ end
22
+ end
23
+
10
24
  class << self
11
25
  def default_wt
12
26
  @default_wt ||= :json
@@ -15,6 +29,14 @@ class RSolr::Client
15
29
  def default_wt= value
16
30
  @default_wt = value
17
31
  end
32
+
33
+ def default_http_method
34
+ @default_http_method ||= :get
35
+ end
36
+
37
+ def default_http_method= method
38
+ @default_http_method = method
39
+ end
18
40
  end
19
41
 
20
42
  attr_reader :uri, :proxy, :update_format, :options, :update_path
@@ -213,11 +235,11 @@ class RSolr::Client
213
235
 
214
236
  { status: response.status.to_i, headers: response.headers, body: response.body.force_encoding('utf-8') }
215
237
  rescue Faraday::TimeoutError => e
216
- raise RSolr::Error::Timeout.new(request_context, e.response)
238
+ raise RSolr::Error::Timeout.new(request_context, e.response), cause: redact_basic_auth(e)
217
239
  rescue Errno::ECONNREFUSED, defined?(Faraday::ConnectionFailed) ? Faraday::ConnectionFailed : Faraday::Error::ConnectionFailed
218
240
  raise RSolr::Error::ConnectionRefused.new(request_context)
219
241
  rescue Faraday::Error => e
220
- raise RSolr::Error::Http.new(request_context, e.response)
242
+ raise RSolr::Error::Http.new(request_context, e.response), cause: redact_basic_auth(e)
221
243
  end
222
244
  adapt_response(request_context, raw_response) unless raw_response.nil?
223
245
  end
@@ -240,11 +262,17 @@ class RSolr::Client
240
262
  raise "path must be a string or symbol, not #{path.inspect}" unless [String,Symbol].include?(path.class)
241
263
  path = path.to_s
242
264
  opts[:proxy] = proxy unless proxy.nil?
243
- opts[:method] ||= :get
265
+ opts[:method] ||= default_http_method
244
266
  raise "The :data option can only be used if :method => :post" if opts[:method] != :post and opts[:data]
245
267
  opts[:params] = params_with_wt(opts[:params])
246
- query = RSolr::Uri.params_to_solr(opts[:params]) unless opts[:params].empty?
247
- opts[:query] = query
268
+ if opts[:method] == :post && (opts[:data].nil? || opts[:data].empty?)
269
+ # If no data hash is provided move query into data hash
270
+ # This is useful if the query string is larger than the max url length
271
+ opts[:data] = opts[:params]
272
+ else
273
+ query = RSolr::Uri.params_to_solr(opts[:params]) unless opts[:params].empty?
274
+ opts[:query] = query
275
+ end
248
276
  if opts[:data].is_a? Hash
249
277
  opts[:data] = RSolr::Uri.params_to_solr opts[:data]
250
278
  opts[:headers] ||= {}
@@ -314,28 +342,48 @@ class RSolr::Client
314
342
  conn_opts[:request][:params_encoder] = Faraday::FlatParamsEncoder
315
343
 
316
344
  Faraday.new(conn_opts) do |conn|
317
- if uri.user && uri.password
318
- case Faraday::VERSION
319
- when /^0/
320
- conn.basic_auth uri.user, uri.password
321
- when /^1/
322
- conn.request :basic_auth, uri.user, uri.password
323
- else
324
- conn.request :authorization, :basic_auth, uri.user, uri.password
325
- end
326
- end
345
+ conn.headers['Authorization'] = basic_auth.header_value if basic_auth.present?
327
346
 
328
347
  conn.response :raise_error
329
348
  conn.request :retry, max: options[:retry_after_limit], interval: 0.05,
330
349
  interval_randomness: 0.5, backoff_factor: 2,
331
350
  exceptions: ['Faraday::Error', 'Timeout::Error'] if options[:retry_503]
332
- conn.adapter options[:adapter] || Faraday.default_adapter || :net_http
351
+ conn.adapter(*Array(options[:adapter] || Faraday.default_adapter || :net_http))
333
352
  end
334
353
  end
335
354
  end
336
355
 
337
356
  protected
338
357
 
358
+ def basic_auth
359
+ if options[:basic_auth]
360
+ auth = options[:basic_auth]
361
+ BasicAuth.new(auth[:user] || auth['user'], auth[:password] || auth['password'])
362
+ else
363
+ BasicAuth.new(uri.user, uri.password)
364
+ end
365
+ end
366
+
367
+ # Faraday's own exception (kept as +cause+) embeds the request unredacted; rebuild it with just the credentials scrubbed.
368
+ def redact_basic_auth(error)
369
+ response = error.response
370
+ return error unless response.is_a?(Hash) && response[:request].is_a?(Hash)
371
+
372
+ request = response[:request].dup
373
+ request[:url] = clean_uri(request[:url]) if request[:url].respond_to?(:user)
374
+ request[:headers] = redact_authorization_header(request[:headers]) if request[:headers]
375
+
376
+ error.class.new(response.merge(request: request))
377
+ end
378
+
379
+ def redact_authorization_header(headers)
380
+ return headers unless headers['Authorization']
381
+
382
+ headers = headers.dup
383
+ headers['Authorization'] = 'REDACTED'
384
+ headers
385
+ end
386
+
339
387
  # converts the method name for the solr request handler path.
340
388
  def method_missing name, *args
341
389
  if name.to_s =~ /^paginated?_(.+)$/
@@ -369,4 +417,8 @@ class RSolr::Client
369
417
  def default_wt
370
418
  self.options[:default_wt] || self.class.default_wt
371
419
  end
420
+
421
+ def default_http_method
422
+ self.options[:default_http_method] || self.class.default_http_method
423
+ end
372
424
  end
data/lib/rsolr/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  module RSolr
2
- VERSION = "2.6.0"
2
+ VERSION = "3.0.0"
3
3
 
4
4
  def self.version
5
5
  VERSION
data/rsolr.gemspec CHANGED
@@ -33,7 +33,7 @@ Gem::Specification.new do |s|
33
33
  s.requirements << 'Apache Solr'
34
34
 
35
35
  s.add_dependency 'builder', '>= 2.1.2'
36
- s.add_dependency 'faraday', '>= 0.9', '!= 2.0.0', '< 3'
36
+ s.add_dependency 'faraday', '>= 1.0', '< 3'
37
37
 
38
38
  s.add_development_dependency 'activesupport'
39
39
  s.add_development_dependency 'nokogiri', '>= 1.4.0'
@@ -41,5 +41,4 @@ Gem::Specification.new do |s|
41
41
  s.add_development_dependency 'rdoc', '>= 4.0'
42
42
  s.add_development_dependency 'rspec', '~> 3.0'
43
43
  s.add_development_dependency 'simplecov'
44
- s.add_development_dependency 'solr_wrapper'
45
44
  end
@@ -1,4 +1,5 @@
1
1
  require 'spec_helper'
2
+ require 'base64'
2
3
 
3
4
  RSpec.describe RSolr::Client do
4
5
  let(:connection) { nil }
@@ -13,6 +14,106 @@ RSpec.describe RSolr::Client do
13
14
  RSolr::Client.new connection, connection_options.merge(proxy: 'http://localhost:8080')
14
15
  end
15
16
 
17
+ context "basic auth" do
18
+ let(:stubs) { Faraday::Adapter::Test::Stubs.new }
19
+
20
+ def authorization_header_for(url:, **client_opts)
21
+ seen_header = nil
22
+ stubs.get('/solr/admin/ping') do |env|
23
+ seen_header = env.request_headers['Authorization']
24
+ [200, {}, '']
25
+ end
26
+
27
+ RSolr::Client.new(nil, { url: url, adapter: [:test, stubs] }.merge(client_opts)).get('admin/ping')
28
+ stubs.verify_stubbed_calls
29
+ seen_header
30
+ end
31
+
32
+ it "sends credentials embedded in the URL as a Basic Authorization header" do
33
+ header = authorization_header_for(url: "http://someuser:somepass@localhost:9999/solr")
34
+ expect(header).to eq("Basic #{Base64.strict_encode64('someuser:somepass')}")
35
+ end
36
+
37
+ it "sends no Authorization header when the URL has no credentials" do
38
+ header = authorization_header_for(url: "http://localhost:9999/solr")
39
+ expect(header).to be_nil
40
+ end
41
+
42
+ it "sends credentials given via the basic_auth: option" do
43
+ header = authorization_header_for(
44
+ url: "http://localhost:9999/solr",
45
+ basic_auth: { user: 'optuser', password: 'optpass' }
46
+ )
47
+ expect(header).to eq("Basic #{Base64.strict_encode64('optuser:optpass')}")
48
+ end
49
+
50
+ it "prefers the basic_auth: option over credentials embedded in the URL" do
51
+ header = authorization_header_for(
52
+ url: "http://urluser:urlpass@localhost:9999/solr",
53
+ basic_auth: { user: 'optuser', password: 'optpass' }
54
+ )
55
+ expect(header).to eq("Basic #{Base64.strict_encode64('optuser:optpass')}")
56
+ end
57
+
58
+ it "accepts a basic_auth: option with string keys, as loaded from YAML" do
59
+ header = authorization_header_for(
60
+ url: "http://localhost:9999/solr",
61
+ basic_auth: { 'user' => 'optuser', 'password' => 'optpass' }
62
+ )
63
+ expect(header).to eq("Basic #{Base64.strict_encode64('optuser:optpass')}")
64
+ end
65
+
66
+ context 'when a connection error is raised' do
67
+ def error_raised_for(url:, **client_opts)
68
+ stubs.get('/solr/admin/ping') { |_env| [500, {}, 'boom'] }
69
+ RSolr::Client.new(nil, { url: url, adapter: [:test, stubs] }.merge(client_opts)).get('admin/ping')
70
+ nil
71
+ rescue RSolr::Error::Http => e
72
+ e
73
+ end
74
+
75
+ context 'with credentials in url' do
76
+ it 'already redacts the url in RSolr::Error::Http#message' do
77
+ error = error_raised_for(url: "http://someuser:somepass@localhost:9999/solr")
78
+
79
+ expect(error.message).not_to include('somepass')
80
+ expect(error.message).to include('REDACTED')
81
+ end
82
+
83
+ it 'does not leak basic auth info' do
84
+ error = error_raised_for(url: "http://someuser:somepass@localhost:9999/solr")
85
+
86
+ expect(error.cause.message).not_to include('somepass')
87
+ expect(error.cause.inspect).not_to include('somepass')
88
+ expect(error.cause.inspect).to include('REDACTED')
89
+ end
90
+ end
91
+
92
+ context 'with credentials in header' do
93
+ it 'already omits credentials from RSolr::Error::Http#message' do
94
+ error = error_raised_for(
95
+ url: "http://localhost:9999/solr",
96
+ basic_auth: { user: 'optuser', password: 'optpass' }
97
+ )
98
+ encoded_credentials = Base64.strict_encode64('optuser:optpass')
99
+
100
+ expect(error.message).not_to include(encoded_credentials)
101
+ end
102
+
103
+ it 'does not leak basic auth info' do
104
+ error = error_raised_for(
105
+ url: "http://localhost:9999/solr",
106
+ basic_auth: { user: 'optuser', password: 'optpass' }
107
+ )
108
+ encoded_credentials = Base64.strict_encode64('optuser:optpass')
109
+
110
+ expect(error.cause.inspect).not_to include(encoded_credentials)
111
+ expect(error.cause.inspect).to include('REDACTED')
112
+ end
113
+ end
114
+ end
115
+ end
116
+
16
117
  context "initialize" do
17
118
  it "should accept whatevs and set it as the @connection" do
18
119
  expect(RSolr::Client.new(:whatevs).connection).to eq(:whatevs)
@@ -92,7 +193,7 @@ RSpec.describe RSolr::Client do
92
193
  it "removes credentials from uri" do
93
194
  expect {
94
195
  client.execute({ uri: uri })
95
- }.to raise_error(RSolr::Error::ConnectionRefused, /http:\/\/REDACTED:REDACTED@hostname\.local:8983/)
196
+ }.to raise_error(RSolr::Error::ConnectionRefused, /http:\/\/(?:REDACTED:)?REDACTED@hostname\.local:8983/)
96
197
  end
97
198
  end
98
199
  end
@@ -411,6 +512,18 @@ RSpec.describe RSolr::Client do
411
512
  end
412
513
  end
413
514
 
515
+ context "when post but no hash is passed in as data" do
516
+ let(:data) { nil }
517
+
518
+ it "sets the Content-Type header to application/x-www-form-urlencoded; charset=UTF-8" do
519
+ expect(subject[:query]).to be_nil
520
+ [/fq=0/, /fq=1/, /q=test/, /wt=json/].each do |pattern|
521
+ expect(subject[:data]).to match pattern
522
+ end
523
+ expect(subject[:headers]).to eq({"Content-Type" => "application/x-www-form-urlencoded; charset=UTF-8"})
524
+ end
525
+ end
526
+
414
527
  it "should properly handle proxy configuration" do
415
528
  result = client_with_proxy.build_request('select',
416
529
  :method => :post,
@@ -419,5 +532,24 @@ RSpec.describe RSolr::Client do
419
532
  )
420
533
  expect(result[:uri].to_s).to match %r{^http://localhost:9999/solr/}
421
534
  end
535
+
536
+ context "http method" do
537
+ it "uses get by default" do
538
+ expect(client.build_request('select', { params: params })[:method]).to eq :get
539
+ end
540
+ it "uses the instance default if set" do
541
+ client = RSolr::Client.new connection, connection_options.merge(default_http_method: :post)
542
+ expect(client.build_request('select', { data: params })[:method]).to eq :post
543
+ end
544
+ it "uses the class default if set" do
545
+ RSolr::Client.default_http_method = :post
546
+ expect(client.build_request('select', { data: params })[:method]).to eq :post
547
+ RSolr::Client.default_http_method = nil
548
+ end
549
+ it "uses the method passed in opts" do
550
+ expect(client.build_request('select', { params: params, method: :get })[:method]).to eq :get
551
+ expect(client.build_request('select', { data: params, method: :post })[:method]).to eq :post
552
+ end
553
+ end
422
554
  end
423
555
  end
@@ -148,11 +148,13 @@ RSpec.describe RSolr::Error do
148
148
  end
149
149
 
150
150
  context "when request uri contains credentials" do
151
- let(:request) { { uri: URI.parse('http://admin:admin@hostname.local:8983/solr/admin/update?wt=json&q=test') } }
151
+ let(:request) { { uri: URI.parse('http://user:pass@hostname.local:8983/solr/admin/update?wt=json&q=test') } }
152
152
 
153
153
 
154
154
  it 'includes redacted url' do
155
- expect(subject).to include 'http://REDACTED:REDACTED@hostname.local:8983/solr/admin/update?wt=json&q=test'
155
+ expect(subject).to include 'REDACTED@hostname.local:8983/solr/admin/update?wt=json&q=test'
156
+ expect(subject).not_to include 'user'
157
+ expect(subject).not_to include 'pass'
156
158
  end
157
159
  end
158
160
  end
@@ -68,32 +68,10 @@ RSpec.describe RSolr::JSON do
68
68
  },
69
69
  ]
70
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
71
  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
72
+
73
+ expect(request).to match(/"add":{"boost":1,"doc":{"id":"1","name":"matt"}}/)
74
+ expect(request).to match(/"add":{"boost":1,"doc":{"id":"2","name":"sam"}}/)
97
75
  end
98
76
  end
99
77
 
@@ -498,7 +498,7 @@
498
498
  <fieldType name="point" class="solr.PointType" dimension="2" subFieldSuffix="_d"/>
499
499
 
500
500
  <!-- A specialized field for geospatial search. If indexed, this fieldType must not be multivalued. -->
501
- <fieldType name="location" class="solr.LatLonType" subFieldSuffix="_coordinate"/>
501
+ <fieldType name="location" class="solr.LatLonPointSpatialField"/>
502
502
 
503
503
  <!-- An alternative geospatial field type new to Solr 4. It supports multiValued and polygon shapes.
504
504
  For more information about this and other Spatial fields new to Solr 4, see:
@@ -315,8 +315,8 @@
315
315
  background concurrently. An error is returned if this limit
316
316
  is exceeded.
317
317
 
318
- Recommend values of 1-2 for read-only slaves, higher for
319
- masters w/o cache warming.
318
+ Recommend values of 1-2 for read-only followers, higher for
319
+ leaders w/o cache warming.
320
320
  -->
321
321
  <maxWarmingSearchers>2</maxWarmingSearchers>
322
322
 
@@ -0,0 +1,16 @@
1
+ {
2
+ "authentication": {
3
+ "blockUnknown": true,
4
+ "class": "solr.BasicAuthPlugin",
5
+ "credentials": {
6
+ "rsolr": "yBxMT8Js4iwRuqWHqW7eQcF1iNDaSbI8ZQtQc5nZyYg= 57WDp52Vgp2LrB9BT5/iFnGY7txUMdTlyM9rVT9XwcA="
7
+ },
8
+ "realm": "rsolr test",
9
+ "forwardCredentials": false
10
+ },
11
+ "authorization": {
12
+ "class": "solr.RuleBasedAuthorizationPlugin",
13
+ "permissions": [{ "name": "security-edit", "role": "admin" }],
14
+ "user-role": { "rsolr": "admin" }
15
+ }
16
+ }
@@ -0,0 +1,29 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe "Solr basic auth" do
4
+ let(:port) { ENV.fetch('SOLR_AUTH_TEST_PORT', 8984) }
5
+ let(:base_url) { "http://localhost:#{port}/solr/basic_configs/" }
6
+ let(:user) { 'rsolr' }
7
+ let(:password) { 'RSolrTest123' }
8
+
9
+ it "succeeds using the basic_auth: option" do
10
+ solr = RSolr.connect(url: base_url, basic_auth: { user: user, password: password })
11
+ expect { solr.head('admin/ping') }.not_to raise_error
12
+ end
13
+
14
+ it "succeeds using credentials embedded in the URL" do
15
+ url = "http://#{user}:#{password}@localhost:#{port}/solr/basic_configs/"
16
+ solr = RSolr.connect(url: url)
17
+ expect { solr.head('admin/ping') }.not_to raise_error
18
+ end
19
+
20
+ it "raises RSolr::Error::Http with no credentials" do
21
+ solr = RSolr.connect(url: base_url)
22
+ expect { solr.head('admin/ping') }.to raise_error(RSolr::Error::Http, /401/)
23
+ end
24
+
25
+ it "raises RSolr::Error::Http with the wrong credentials" do
26
+ solr = RSolr.connect(url: base_url, basic_auth: { user: user, password: 'wrong' })
27
+ expect { solr.head('admin/ping') }.to raise_error(RSolr::Error::Http, /401/)
28
+ end
29
+ end
@@ -1,22 +1,9 @@
1
1
  require 'spec_helper'
2
- require 'solr_wrapper'
3
2
 
4
3
  RSpec.describe "Solr basic_configs" do
5
- SolrWrapper.default_instance_options = {
6
- port: SolrWrapper.default_solr_port,
7
- version: '8.11.3'
8
- }
9
- SOLR_INSTANCE = SolrWrapper.default_instance({})
10
- before(:all) { SOLR_INSTANCE.start }
11
- after(:all) { SOLR_INSTANCE.stop }
12
-
13
4
  context "basic configs" do
14
- subject { RSolr.connect url: "http://localhost:#{SOLR_INSTANCE.port}/solr/basic_configs/"}
15
- around(:each) do |example|
16
- SOLR_INSTANCE.with_collection(name: "basic_configs", dir: File.join(FIXTURES_DIR, "basic_configs")) do |coll|
17
- example.run
18
- end
19
- end
5
+ subject { RSolr.connect url: "http://localhost:#{ENV.fetch('SOLR_TEST_PORT', 8983)}/solr/basic_configs/" }
6
+
20
7
  describe "HEAD admin/ping" do
21
8
  it "should not raise an exception" do
22
9
  expect { subject.head('admin/ping') }.not_to raise_error
data/spec/spec_helper.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  require 'simplecov'
2
2
  SimpleCov.start do
3
- add_filter "/spec/"
3
+ skip "/spec/"
4
4
  end
5
5
 
6
6
  require 'rsolr'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rsolr
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.6.0
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Antoine Latter
@@ -26,10 +26,9 @@ authors:
26
26
  - Nathan Witmer
27
27
  - Naomi Dushay
28
28
  - '"shima"'
29
- autorequire:
30
29
  bindir: bin
31
30
  cert_chain: []
32
- date: 2024-03-25 00:00:00.000000000 Z
31
+ date: 1980-01-02 00:00:00.000000000 Z
33
32
  dependencies:
34
33
  - !ruby/object:Gem::Dependency
35
34
  name: builder
@@ -51,10 +50,7 @@ dependencies:
51
50
  requirements:
52
51
  - - ">="
53
52
  - !ruby/object:Gem::Version
54
- version: '0.9'
55
- - - "!="
56
- - !ruby/object:Gem::Version
57
- version: 2.0.0
53
+ version: '1.0'
58
54
  - - "<"
59
55
  - !ruby/object:Gem::Version
60
56
  version: '3'
@@ -64,10 +60,7 @@ dependencies:
64
60
  requirements:
65
61
  - - ">="
66
62
  - !ruby/object:Gem::Version
67
- version: '0.9'
68
- - - "!="
69
- - !ruby/object:Gem::Version
70
- version: 2.0.0
63
+ version: '1.0'
71
64
  - - "<"
72
65
  - !ruby/object:Gem::Version
73
66
  version: '3'
@@ -155,20 +148,6 @@ dependencies:
155
148
  - - ">="
156
149
  - !ruby/object:Gem::Version
157
150
  version: '0'
158
- - !ruby/object:Gem::Dependency
159
- name: solr_wrapper
160
- requirement: !ruby/object:Gem::Requirement
161
- requirements:
162
- - - ">="
163
- - !ruby/object:Gem::Version
164
- version: '0'
165
- type: :development
166
- prerelease: false
167
- version_requirements: !ruby/object:Gem::Requirement
168
- requirements:
169
- - - ">="
170
- - !ruby/object:Gem::Version
171
- version: '0'
172
151
  description: RSolr aims to provide a simple and extensible library for working with
173
152
  Solr
174
153
  email:
@@ -183,8 +162,9 @@ files:
183
162
  - CHANGES.txt
184
163
  - Gemfile
185
164
  - LICENSE
186
- - README.rdoc
165
+ - README.md
187
166
  - Rakefile
167
+ - docker-compose.yaml
188
168
  - lib/rsolr.rb
189
169
  - lib/rsolr/char.rb
190
170
  - lib/rsolr/client.rb
@@ -214,6 +194,8 @@ files:
214
194
  - spec/fixtures/basic_configs/solrconfig.xml
215
195
  - spec/fixtures/basic_configs/stopwords.txt
216
196
  - spec/fixtures/basic_configs/synonyms.txt
197
+ - spec/fixtures/security/security.json
198
+ - spec/integration/basic_auth_spec.rb
217
199
  - spec/integration/solr5_spec.rb
218
200
  - spec/lib/rsolr/client_spec.rb
219
201
  - spec/spec_helper.rb
@@ -221,7 +203,6 @@ homepage: https://github.com/rsolr/rsolr
221
203
  licenses:
222
204
  - Apache-2.0
223
205
  metadata: {}
224
- post_install_message:
225
206
  rdoc_options: []
226
207
  require_paths:
227
208
  - lib
@@ -237,8 +218,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
237
218
  version: '0'
238
219
  requirements:
239
220
  - Apache Solr
240
- rubygems_version: 3.4.10
241
- signing_key:
221
+ rubygems_version: 4.0.18
242
222
  specification_version: 4
243
223
  summary: A Ruby client for Apache Solr
244
224
  test_files: []
data/README.rdoc DELETED
@@ -1,227 +0,0 @@
1
- =RSolr
2
-
3
- A simple, extensible Ruby client for Apache Solr.
4
-
5
- ==Documentation
6
- The code docs http://www.rubydoc.info/gems/rsolr
7
-
8
- == Installation:
9
- gem install rsolr
10
-
11
- == Example:
12
- require 'rsolr'
13
-
14
- # Direct connection
15
- solr = RSolr.connect :url => 'http://solrserver.com'
16
-
17
- # Connecting over a proxy server
18
- solr = RSolr.connect :url => 'http://solrserver.com', :proxy=>'http://user:pass@proxy.example.com:8080'
19
-
20
- # Using an alternate Faraday adapter
21
- solr = RSolr.connect :url => 'http://solrserver.com', :adapter => :em_http
22
-
23
- # Using a custom Faraday connection
24
- conn = Faraday.new do |faraday|
25
- faraday.response :logger # log requests to STDOUT
26
- faraday.adapter Faraday.default_adapter # make requests with Net::HTTP
27
- end
28
- solr = RSolr.connect conn, :url => 'http://solrserver.com'
29
-
30
- # send a request to /select
31
- response = solr.get 'select', :params => {:q => '*:*'}
32
-
33
- # send a request to /catalog
34
- response = solr.get 'catalog', :params => {:q => '*:*'}
35
-
36
- When the Solr +:wt+ is +:ruby+, then the response will be a Hash. This Hash is the same object returned by Solr, but evaluated as Ruby. If the +:wt+ is not +:ruby+, then the response will be a String.
37
-
38
- The response also exposes 2 attribute readers (for any +:wt+ value), +:request+ and +:response+. Both are Hash objects with symbolized keys.
39
-
40
- The +:request+ attribute contains the original request context. You can use this for debugging or logging. Some of the keys this object contains are +:uri+, +:query+, +:method+ etc..
41
-
42
- The +:response+ attribute contains the original response. This object contains the +:status+, +:body+ and +:headers+ keys.
43
-
44
- == Request formats
45
-
46
- By default, RSolr uses the Solr JSON command format for all requests.
47
-
48
- RSolr.connect :url => 'http://solrserver.com', update_format: :json # the default
49
- # or
50
- RSolr.connect :url => 'http://solrserver.com', update_format: :xml
51
-
52
- == Timeouts
53
- The read and connect timeout settings can be set when creating a new instance of RSolr, and will
54
- be passed on to underlying Faraday instance:
55
-
56
- solr = RSolr.connect(:timeout => 120, :open_timeout => 120)
57
-
58
- == Retry 503s
59
- A 503 is usually a temporary error which RSolr may retry if requested. You may specify the number of retry attempts with the +:retry_503+ option.
60
-
61
- Only requests which specify a Retry-After header will be retried, after waiting the indicated retry interval, otherwise RSolr will treat the request as a 500. You may specify a maximum Retry-After interval to wait with the +:retry_after_limit+ option (default: one second).
62
- solr = RSolr.connect(:retry_503 => 1, :retry_after_limit => 1)
63
-
64
- For additional control, consider using a custom Faraday connection (see above) using its `retry` middleware.
65
-
66
- == Querying
67
- Use the #get / #post method to send search requests to the /select handler:
68
- response = solr.get 'select', :params => {
69
- :q=>'washington',
70
- :start=>0,
71
- :rows=>10
72
- }
73
- response["response"]["docs"].each{|doc| puts doc["id"] }
74
-
75
- The +:params+ sent into the method are sent to Solr as-is, which is to say they are converted to Solr url style, but no special mapping is used.
76
- When an array is used, multiple parameters *with the same name* are generated for the Solr query. Example:
77
-
78
- solr.get 'select', :params => {:q=>'roses', :fq=>['red', 'violet']}
79
-
80
- The above statement generates this Solr query:
81
-
82
- select?q=roses&fq=red&fq=violet
83
-
84
- ===Pagination
85
- To paginate through a set of Solr documents, use the paginate method:
86
- solr.paginate 1, 10, "select", :params => {:q => "test"}
87
-
88
- The first argument is the current page, the second is how many documents to return for each page. In other words, "page" is the "start" Solr param and "per-page" is the "rows" Solr param.
89
-
90
- The paginate method returns WillPaginate ready "docs" objects, so for example in a Rails application, paginating is as simple as:
91
- <%= will_paginate @solr_response["response"]["docs"] %>
92
-
93
- ===Method Missing
94
- The +RSolr::Client+ class also uses +method_missing+ for setting the request handler/path:
95
-
96
- solr.paintings :params => {:q=>'roses', :fq=>['red', 'violet']}
97
-
98
- This is sent to Solr as:
99
- paintings?q=roses&fq=red&fq=violet
100
-
101
- This works with pagination as well:
102
-
103
- solr.paginate_paintings 1, 10, {:q=>'roses', :fq=>['red', 'violet']}
104
-
105
- ===Using POST for Search Queries
106
- There may be cases where the query string is too long for a GET request. RSolr solves this issue by converting hash objects into form-encoded strings:
107
- response = solr.music :data => {:q => "*:*"}
108
-
109
- The +:data+ hash is serialized as a form-encoded query string, and the correct content-type headers are sent along to Solr.
110
-
111
- ===Sending HEAD Requests
112
- There may be cases where you'd like to send a HEAD request to Solr:
113
- solr.head("admin/ping").response[:status] == 200
114
-
115
- ==Sending HTTP Headers
116
- Solr responds to the request headers listed here: http://wiki.apache.org/solr/SolrAndHTTPCaches
117
- To send header information to Solr using RSolr, just use the +:headers+ option:
118
- response = solr.head "admin/ping", :headers => {"Cache-Control" => "If-None-Match"}
119
-
120
- ===Building a Request
121
- +RSolr::Client+ provides a method for building a request context, which can be useful for debugging or logging etc.:
122
- request_context = solr.build_request "select", :data => {:q => "*:*"}, :method => :post, :headers => {}
123
-
124
- To build a paginated request use build_paginated_request:
125
- request_context = solr.build_paginated_request 1, 10, "select", ...
126
-
127
- == Updating Solr
128
- Updating is done using native Ruby objects. Hashes are used for single documents and arrays are used for a collection of documents (hashes). These objects get turned into simple XML "messages". Raw XML strings can also be used.
129
-
130
- Single document via #add
131
- solr.add :id=>1, :price=>1.00
132
-
133
- Multiple documents via #add
134
- documents = [{:id=>1, :price=>1.00}, {:id=>2, :price=>10.50}]
135
- solr.add documents
136
-
137
- The optional +:add_attributes+ hash can also be used to set Solr "add" document attributes:
138
- solr.add documents, :add_attributes => {:commitWithin => 10}
139
-
140
- Raw commands via #update
141
- solr.update data: '<commit/>', headers: { 'Content-Type' => 'text/xml' }
142
- solr.update data: { optimize: true }.to_json, headers: { 'Content-Type' => 'application/json' }
143
-
144
- When adding, you can also supply "add" xml element attributes and/or a block for manipulating other "add" related elements (docs and fields) by calling the +xml+ method directly:
145
-
146
- doc = {:id=>1, :price=>1.00}
147
- add_attributes = {:allowDups=>false, :commitWithin=>10}
148
- add_xml = solr.xml.add(doc, add_attributes) do |doc|
149
- # boost each document
150
- doc.attrs[:boost] = 1.5
151
- # boost the price field:
152
- doc.field_by_name(:price).attrs[:boost] = 2.0
153
- end
154
-
155
- Now the "add_xml" object can be sent to Solr like:
156
- solr.update :data => add_xml
157
-
158
- ===Deleting
159
- Delete by id
160
- solr.delete_by_id 1
161
- or an array of ids
162
- solr.delete_by_id [1, 2, 3, 4]
163
-
164
- Delete by query:
165
- solr.delete_by_query 'price:1.00'
166
- Delete by array of queries
167
- solr.delete_by_query ['price:1.00', 'price:10.00']
168
-
169
- ===Commit / Optimize
170
- solr.commit, :commit_attributes => {}
171
- solr.optimize, :optimize_attributes => {}
172
-
173
- == Response Formats
174
- The default response format is Ruby. When the +:wt+ param is set to +:ruby+, the response is eval'd resulting in a Hash. You can get a raw response by setting the +:wt+ to +"ruby"+ - notice, the string -- not a symbol. RSolr will eval the Ruby string ONLY if the :wt value is :ruby. All other response formats are available as expected, +:wt=>'xml'+ etc..
175
-
176
- ===Evaluated Ruby:
177
- solr.get 'select', :params => {:wt => :ruby} # notice :ruby is a Symbol
178
- ===Raw Ruby:
179
- solr.get 'select', :params => {:wt => 'ruby'} # notice 'ruby' is a String
180
- ===XML:
181
- solr.get 'select', :params => {:wt => :xml}
182
- ===JSON (default):
183
- solr.get 'select', :params => {:wt => :json}
184
-
185
- ==Related Resources & Projects
186
- * {RSolr Google Group}[http://groups.google.com/group/rsolr] -- The RSolr discussion group
187
- * {rsolr-ext}[http://github.com/mwmitchell/rsolr-ext] -- An extension kit for RSolr
188
- * {rsolr-direct}[http://github.com/mwmitchell/rsolr-direct] -- JRuby direct connection for RSolr
189
- * {rsolr-nokogiri}[http://github.com/mwmitchell/rsolr-nokogiri] -- Gives RSolr Nokogiri for XML generation.
190
- * {SunSpot}[http://github.com/sunspot/sunspot] -- An awesome Solr DSL, built with RSolr
191
- * {Blacklight}[http://blacklightopac.org] -- A "next generation" Library OPAC, built with RSolr
192
- * {java_bin}[http://github.com/kennyj/java_bin] -- Provides javabin/binary parsing for RSolr
193
- * {Solr}[http://lucene.apache.org/solr/] -- The Apache Solr project
194
- * {solr-ruby}[http://wiki.apache.org/solr/solr-ruby] -- The original Solr Ruby Gem!
195
-
196
- == Note on Patches/Pull Requests
197
- * Fork the project.
198
- * Make your feature addition or bug fix.
199
- * Add tests for it. This is important so I don't break it in a future version unintentionally.
200
- * Commit, do not mess with rakefile, version, or history
201
- (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
202
- * Send me a pull request. Bonus points for topic branches.
203
-
204
- ==Contributors
205
- * Nathan Witmer
206
- * Magnus Bergmark
207
- * shima
208
- * Randy Souza
209
- * Mat Brown
210
- * Jeremy Hinegardner
211
- * Denis Goeury
212
- * shairon toledo
213
- * Rob Di Marco
214
- * Peter Kieltyka
215
- * Mike Perham
216
- * Lucas Souza
217
- * Dmitry Lihachev
218
- * Antoine Latter
219
- * Naomi Dushay
220
-
221
- ==Author
222
-
223
- Matt Mitchell <mailto:goodieboy@gmail.com>
224
-
225
- ==Copyright
226
-
227
- Copyright (c) 2008-2010 Matt Mitchell. See LICENSE for details.