rsolr 1.0.8 → 1.1.2

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.
@@ -15,11 +15,13 @@ class RSolr::Connection
15
15
  response = h.request request
16
16
  charset = response.type_params["charset"]
17
17
  {:status => response.code.to_i, :headers => response.to_hash, :body => force_charset(response.body, charset)}
18
+ rescue Errno::ECONNREFUSED
19
+ raise RSolr::Error::ConnectionRefused, request_context.inspect
18
20
  # catch the undefined closed? exception -- this is a confirmed ruby bug
19
- rescue NoMethodError
20
- $!.message == "undefined method `closed?' for nil:NilClass" ?
21
- raise(Errno::ECONNREFUSED.new) :
22
- raise($!)
21
+ rescue NoMethodError => e
22
+ e.message == "undefined method `closed?' for nil:NilClass" ?
23
+ raise(RSolr::Error::ConnectionRefused, request_context.inspect) :
24
+ raise(e)
23
25
  end
24
26
  end
25
27
 
@@ -27,18 +29,20 @@ class RSolr::Connection
27
29
 
28
30
  # This returns a singleton of a Net::HTTP or Net::HTTP.Proxy request object.
29
31
  def http uri, proxy = nil, read_timeout = nil, open_timeout = nil
30
- @http ||= (
31
- http = if proxy
32
- proxy_user, proxy_pass = proxy.userinfo.split(/:/) if proxy.userinfo
33
- Net::HTTP.Proxy(proxy.host, proxy.port, proxy_user, proxy_pass).new uri.host, uri.port
34
- else
35
- Net::HTTP.new uri.host, uri.port
36
- end
37
- http.use_ssl = uri.port == 443 || uri.instance_of?(URI::HTTPS)
38
- http.read_timeout = read_timeout if read_timeout
39
- http.open_timeout = open_timeout if open_timeout
40
- http
41
- )
32
+ http = if proxy
33
+ proxy_user, proxy_pass = proxy.userinfo.split(/:/) if proxy.userinfo
34
+ Net::HTTP.Proxy(proxy.host, proxy.port, proxy_user, proxy_pass).new uri.host, uri.port
35
+ elsif proxy == false
36
+ # If explicitly passing in false, make sure we set proxy_addr to nil
37
+ # to tell Net::HTTP to *not* use the environment proxy variables.
38
+ Net::HTTP.new uri.host, uri.port, nil
39
+ else
40
+ Net::HTTP.new uri.host, uri.port
41
+ end
42
+ http.use_ssl = uri.port == 443 || uri.instance_of?(URI::HTTPS)
43
+ http.read_timeout = read_timeout if read_timeout
44
+ http.open_timeout = open_timeout if open_timeout
45
+ http
42
46
  end
43
47
 
44
48
  #
@@ -67,4 +71,4 @@ class RSolr::Connection
67
71
  body.force_encoding(charset)
68
72
  end
69
73
 
70
- end
74
+ end
data/lib/rsolr/error.rb CHANGED
@@ -1,9 +1,9 @@
1
1
  module RSolr::Error
2
-
2
+
3
3
  module SolrContext
4
-
4
+
5
5
  attr_accessor :request, :response
6
-
6
+
7
7
  def to_s
8
8
  m = "#{super.to_s}"
9
9
  if response
@@ -12,22 +12,25 @@ module RSolr::Error
12
12
  m << "\nError: #{details}\n" if details
13
13
  end
14
14
  p = "\nURI: #{request[:uri].to_s}"
15
- p = "\nRequest Headers: #{request[:headers].inspect}" if request[:headers]
16
- p = "\nRequest Data: #{request[:data].inspect}" if request[:data]
15
+ p << "\nRequest Headers: #{request[:headers].inspect}" if request[:headers]
16
+ p << "\nRequest Data: #{request[:data].inspect}" if request[:data]
17
17
  p << "\n"
18
18
  p << "\nBacktrace: " + self.backtrace[0..10].join("\n")
19
19
  m << p
20
20
  m
21
21
  end
22
-
22
+
23
23
  protected
24
-
24
+
25
25
  def parse_solr_error_response body
26
26
  begin
27
- info = body.scan(/<pre>(.*)<\/pre>/mi)[0]
27
+ if body =~ /<pre>/
28
+ info = body.scan(/<pre>(.*)<\/pre>/mi)[0]
29
+ elsif body =~ /'msg'=>/
30
+ info = body.scan(/'msg'=>(.*)/)[0]
31
+ end
28
32
  info = info.join if info.respond_to? :join
29
-
30
- info ||= body # body may not contain <pre> elements
33
+ info ||= body # body might not contain <pre> or msg elements
31
34
 
32
35
  partial = info.to_s.split("\n")[0..10]
33
36
  partial.join("\n").gsub("&gt;", ">").gsub("&lt;", "<")
@@ -35,13 +38,17 @@ module RSolr::Error
35
38
  nil
36
39
  end
37
40
  end
38
-
41
+
42
+
43
+ end
44
+
45
+ class ConnectionRefused < ::Errno::ECONNREFUSED
39
46
  end
40
-
47
+
41
48
  class Http < RuntimeError
42
-
49
+
43
50
  include SolrContext
44
-
51
+
45
52
  # ripped right from ActionPack
46
53
  # Defines the standard HTTP status codes, by integer, with their
47
54
  # corresponding default message texts.
@@ -101,17 +108,29 @@ module RSolr::Error
101
108
  507 => "Insufficient Storage",
102
109
  510 => "Not Extended"
103
110
  }
104
-
111
+
105
112
  def initialize request, response
106
113
  @request, @response = request, response
107
114
  end
108
-
115
+
116
+ end
117
+
118
+ # Thrown if the :wt is :ruby
119
+ # but the body wasn't succesfully parsed/evaluated
120
+ class InvalidResponse < Http
121
+
122
+ end
123
+
124
+ # Thrown if the :wt is :ruby
125
+ # but the body wasn't succesfully parsed/evaluated
126
+ class InvalidJsonResponse < InvalidResponse
127
+
109
128
  end
110
-
129
+
111
130
  # Thrown if the :wt is :ruby
112
131
  # but the body wasn't succesfully parsed/evaluated
113
- class InvalidRubyResponse < Http
114
-
132
+ class InvalidRubyResponse < InvalidResponse
133
+
115
134
  end
116
-
135
+
117
136
  end
@@ -1,20 +1,55 @@
1
1
  module RSolr::Response
2
-
3
- def self.extended base
4
- if base["response"] && base["response"]["docs"]
5
- base["response"]["docs"].tap do |d|
6
- d.extend PaginatedDocSet
7
- d.per_page = base.request[:params]["rows"]
8
- d.start = base.request[:params]["start"]
9
- d.total = base["response"]["numFound"].to_s.to_i
2
+
3
+ def self.included(base)
4
+ unless base < Hash
5
+ raise ArgumentError, "RSolr::Response expects to included only in (sub)classes of Hash; got included in '#{base}' instead."
6
+ end
7
+ base.send(:attr_reader, :request, :response)
8
+ end
9
+
10
+ def initialize_rsolr_response(request, response, result)
11
+ @request = request
12
+ @response = response
13
+ self.merge!(result)
14
+ if self["response"] && self["response"]["docs"].is_a?(Array)
15
+ docs = PaginatedDocSet.new(self["response"]["docs"])
16
+ docs.per_page = request[:params]["rows"]
17
+ docs.page_start = request[:params]["start"]
18
+ docs.page_total = self["response"]["numFound"].to_s.to_i
19
+ self["response"]["docs"] = docs
20
+ end
21
+ end
22
+
23
+ def with_indifferent_access
24
+ if defined?(::RSolr::HashWithIndifferentAccessWithResponse)
25
+ ::RSolr::HashWithIndifferentAccessWithResponse.new(request, response, self)
26
+ else
27
+ if defined?(ActiveSupport::HashWithIndifferentAccess)
28
+ RSolr.const_set("HashWithIndifferentAccessWithResponse", Class.new(ActiveSupport::HashWithIndifferentAccess))
29
+ RSolr::HashWithIndifferentAccessWithResponse.class_eval <<-eos
30
+ include RSolr::Response
31
+ def initialize(request, response, result)
32
+ super()
33
+ initialize_rsolr_response(request, response, result)
34
+ end
35
+ eos
36
+ ::RSolr::HashWithIndifferentAccessWithResponse.new(request, response, self)
37
+ else
38
+ raise RuntimeError, "HashWithIndifferentAccess is not currently defined"
10
39
  end
11
40
  end
12
41
  end
13
-
42
+
14
43
  # A response module which gets mixed into the solr ["response"]["docs"] array.
15
- module PaginatedDocSet
44
+ class PaginatedDocSet < Array
16
45
 
17
- attr_accessor :start, :per_page, :total
46
+ attr_accessor :page_start, :per_page, :page_total
47
+ if not (Object.const_defined?("RUBY_ENGINE") and Object::RUBY_ENGINE=='rbx')
48
+ alias_method(:start,:page_start)
49
+ alias_method(:start=,:page_start=)
50
+ alias_method(:total,:page_total)
51
+ alias_method(:total=,:page_total=)
52
+ end
18
53
 
19
54
  # Returns the current page calculated from 'rows' and 'start'
20
55
  def current_page
@@ -47,5 +82,14 @@ module RSolr::Response
47
82
  end
48
83
 
49
84
  end
50
-
51
- end
85
+
86
+ end
87
+
88
+ class RSolr::HashWithResponse < Hash
89
+ include RSolr::Response
90
+
91
+ def initialize(request, response, result)
92
+ super()
93
+ initialize_rsolr_response(request, response, result || {})
94
+ end
95
+ end
data/lib/rsolr/uri.rb CHANGED
@@ -3,56 +3,73 @@ require 'uri'
3
3
  module RSolr::Uri
4
4
 
5
5
  def create url
6
- ::URI.parse url[-1] == ?/ ? url : "#{url}/"
6
+ ::URI.parse (url[-1] == '/' || URI.parse(url).query) ? url : "#{url}/"
7
7
  end
8
8
 
9
- # Returns a query string param pair as a string.
10
- # Both key and value are escaped.
11
- def build_param(k,v, escape = true)
12
- escape ?
13
- "#{escape_query_value(k)}=#{escape_query_value(v)}" :
14
- "#{k}=#{v}"
15
- end
16
-
17
- # Return the bytesize of String; uses String#size under Ruby 1.8 and
18
- # String#bytesize under 1.9.
19
- if ''.respond_to?(:bytesize)
20
- def bytesize(string)
21
- string.bytesize
22
- end
23
- else
24
- def bytesize(string)
25
- string.size
26
- end
27
- end
28
-
29
9
  # Creates a Solr based query string.
30
10
  # Keys that have arrays values are set multiple times:
31
11
  # params_to_solr(:q => 'query', :fq => ['a', 'b'])
32
12
  # is converted to:
33
13
  # ?q=query&fq=a&fq=b
14
+ # @param [boolean] escape false if no URI escaping is to be performed. Default true.
15
+ # @return [String] Solr query params as a String, suitable for use in a url
34
16
  def params_to_solr(params, escape = true)
17
+ return URI.encode_www_form(params.reject{|k,v| k.to_s.empty? || v.to_s.empty?}) if escape
18
+
19
+ # escape = false if we are here
35
20
  mapped = params.map do |k, v|
36
21
  next if v.to_s.empty?
37
22
  if v.class == Array
38
- params_to_solr(v.map { |x| [k, x] }, escape)
23
+ params_to_solr(v.map { |x| [k, x] }, false)
39
24
  else
40
- build_param k, v, escape
25
+ "#{k}=#{v}"
41
26
  end
42
27
  end
43
28
  mapped.compact.join("&")
44
29
  end
45
30
 
31
+ # Returns a query string param pair as a string.
32
+ # Both key and value are URI escaped, unless third param is false
33
+ # @param [boolean] escape false if no URI escaping is to be performed. Default true.
34
+ # @deprecated - used to be called from params_to_solr before 2015-02-25
35
+ def build_param(k, v, escape = true)
36
+ warn "[DEPRECATION] `RSolr::Uri.build_param` is deprecated. Use `URI.encode_www_form_component` or k=v instead."
37
+ escape ?
38
+ "#{URI.encode_www_form_component(k)}=#{URI.encode_www_form_component(v)}" :
39
+ "#{k}=#{v}"
40
+ end
41
+
42
+ # 2015-02 Deprecated: use URI.encode_www_form_component(s)
43
+ #
46
44
  # Performs URI escaping so that you can construct proper
47
45
  # query strings faster. Use this rather than the cgi.rb
48
46
  # version since it's faster.
49
47
  # (Stolen from Rack).
48
+ # http://www.rubydoc.info/github/rack/rack/URI.encode_www_form_component
49
+ # @deprecated
50
50
  def escape_query_value(s)
51
- s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/u) {
52
- '%'+$1.unpack('H2'*bytesize($1)).join('%').upcase
53
- }.tr(' ', '+')
51
+ warn "[DEPRECATION] `RSolr::Uri.escape_query_value` is deprecated. Use `URI.encode_www_form_component` instead."
52
+ URI.encode_www_form_component(s)
53
+ # s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/u) {
54
+ # '%'+$1.unpack('H2'*bytesize($1)).join('%').upcase
55
+ # }.tr(' ', '+')
54
56
  end
55
-
57
+
58
+ # Return the bytesize of String; uses String#size under Ruby 1.8 and
59
+ # String#bytesize under 1.9.
60
+ # @deprecated as bytesize was only used by escape_query_value which is itself deprecated
61
+ if ''.respond_to?(:bytesize)
62
+ def bytesize(string)
63
+ warn "[DEPRECATION] `RSolr::Uri.bytesize` is deprecated. Use String.bytesize"
64
+ string.bytesize
65
+ end
66
+ else
67
+ def bytesize(string)
68
+ warn "[DEPRECATION] `RSolr::Uri.bytesize` is deprecated. Use String.size"
69
+ string.size
70
+ end
71
+ end
72
+
56
73
  extend self
57
74
 
58
75
  end
@@ -0,0 +1,7 @@
1
+ module RSolr
2
+ VERSION = "1.1.2"
3
+
4
+ def self.version
5
+ VERSION
6
+ end
7
+ end
data/lib/rsolr/xml.rb CHANGED
@@ -1,4 +1,5 @@
1
- require 'builder'
1
+ begin; require 'nokogiri'; rescue LoadError; end
2
+ require 'time'
2
3
 
3
4
  module RSolr::Xml
4
5
 
@@ -15,11 +16,10 @@ module RSolr::Xml
15
16
  @fields = []
16
17
  doc_hash.each_pair do |field,values|
17
18
  # create a new field for each value (multi-valued)
18
- # put non-array values into an array
19
- values = [values] unless values.is_a?(Array)
20
- values.each do |v|
21
- next if v.to_s.empty?
22
- @fields << RSolr::Xml::Field.new({:name=>field}, v.to_s)
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
23
  end
24
24
  end
25
25
  @attrs={}
@@ -47,9 +47,35 @@ module RSolr::Xml
47
47
  def add_field(name, value, options = {})
48
48
  @fields << RSolr::Xml::Field.new(options.merge({:name=>name}), value)
49
49
  end
50
-
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
65
+
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
51
77
  end
52
-
78
+
53
79
  class Field
54
80
 
55
81
  # "attrs" is a hash for setting the "doc" xml attributes
@@ -71,13 +97,40 @@ module RSolr::Xml
71
97
  end
72
98
 
73
99
  class Generator
100
+ class << self
101
+ attr_accessor :use_nokogiri
102
+
103
+ def builder_proc
104
+ if use_nokogiri
105
+ require 'nokogiri' unless defined?(::Nokogiri::XML::Builder)
106
+ :nokogiri_build
107
+ else
108
+ require 'builder' unless defined?(::Builder::XmlMarkup)
109
+ :builder_build
110
+ end
111
+ end
112
+ end
113
+ self.use_nokogiri = (defined?(::Nokogiri::XML::Builder) and not defined?(JRuby)) ? true : false
114
+
115
+ def nokogiri_build &block
116
+ b = ::Nokogiri::XML::Builder.new do |xml|
117
+ block_given? ? yield(xml) : xml
118
+ end
119
+ '<?xml version="1.0" encoding="UTF-8"?>'+b.to_xml(:indent => 0, :encoding => 'UTF-8', :save_with => ::Nokogiri::XML::Node::SaveOptions::AS_XML | ::Nokogiri::XML::Node::SaveOptions::NO_DECLARATION).strip
120
+ end
121
+ protected :nokogiri_build
74
122
 
75
- def build &block
123
+ def builder_build &block
76
124
  b = ::Builder::XmlMarkup.new(:indent => 0, :margin => 0, :encoding => 'UTF-8')
77
125
  b.instruct!
78
126
  block_given? ? yield(b) : b
79
127
  end
80
-
128
+ protected :builder_build
129
+
130
+ def build &block
131
+ self.send(self.class.builder_proc,&block)
132
+ end
133
+
81
134
  # generates "add" xml for updating solr
82
135
  # "data" can be a hash or an array of hashes.
83
136
  # - each hash should be a simple key=>value pair representing a solr doc.
@@ -111,11 +164,12 @@ module RSolr::Xml
111
164
  data.each do |doc|
112
165
  doc = RSolr::Xml::Document.new(doc) if doc.respond_to?(:each_pair)
113
166
  yield doc if block_given?
114
- add_node.doc(doc.attrs) do |doc_node|
167
+ doc_node_builder = lambda do |doc_node|
115
168
  doc.fields.each do |field_obj|
116
169
  doc_node.field field_obj.value, field_obj.attrs
117
170
  end
118
171
  end
172
+ self.class.use_nokogiri ? add_node.doc_(doc.attrs,&doc_node_builder) : add_node.doc(doc.attrs,&doc_node_builder)
119
173
  end
120
174
  end
121
175
  end
@@ -144,7 +198,9 @@ module RSolr::Xml
144
198
  ids = [ids] unless ids.is_a?(Array)
145
199
  build do |xml|
146
200
  xml.delete do |delete_node|
147
- ids.each { |id| delete_node.id(id) }
201
+ ids.each do |id|
202
+ self.class.use_nokogiri ? delete_node.id_(id) : delete_node.id(id)
203
+ end
148
204
  end
149
205
  end
150
206
  end
@@ -159,7 +215,5 @@ module RSolr::Xml
159
215
  end
160
216
  end
161
217
  end
162
-
163
218
  end
164
-
165
- end
219
+ end