rsolr 1.0.0.beta3 → 1.0.0.beta4
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/VERSION +1 -1
- data/lib/rsolr/client.rb +137 -40
- data/lib/rsolr/{http.rb → connection.rb} +11 -51
- data/lib/rsolr/pagination.rb +127 -0
- data/lib/rsolr.rb +2 -2
- data/spec/api/client_spec.rb +83 -54
- data/spec/api/http_spec.rb +0 -25
- metadata +5 -6
- data/lib/rsolr/connectable.rb +0 -97
- data/spec/api/connectable_spec.rb +0 -56
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.0.0.
|
1
|
+
1.0.0.beta4
|
data/lib/rsolr/client.rb
CHANGED
@@ -1,24 +1,42 @@
|
|
1
1
|
class RSolr::Client
|
2
2
|
|
3
|
-
attr_reader :connection
|
3
|
+
attr_reader :connection, :uri, :proxy, :options
|
4
4
|
|
5
|
-
def initialize connection
|
5
|
+
def initialize connection, options = {}
|
6
6
|
@connection = connection
|
7
|
+
url = options[:url] || 'http://127.0.0.1:8983/solr/'
|
8
|
+
url << "/" unless url[-1] == ?/
|
9
|
+
proxy_url = options[:proxy]
|
10
|
+
proxy_url << "/" unless proxy_url.nil? or proxy_url[-1] == ?/
|
11
|
+
@uri = RSolr::Uri.create url
|
12
|
+
@proxy = RSolr::Uri.create proxy_url if proxy_url
|
13
|
+
@options = options
|
14
|
+
extend RSolr::Pagination::Client
|
7
15
|
end
|
8
16
|
|
17
|
+
# returns the actual request uri object.
|
18
|
+
def base_request_uri
|
19
|
+
base_uri.request_uri
|
20
|
+
end
|
21
|
+
|
22
|
+
# returns the uri proxy if present,
|
23
|
+
# otherwise just the uri object.
|
24
|
+
def base_uri
|
25
|
+
@proxy || @uri
|
26
|
+
end
|
27
|
+
|
28
|
+
# Create the get, post, and head methods
|
9
29
|
%W(get post head).each do |meth|
|
10
30
|
class_eval <<-RUBY
|
11
31
|
def #{meth} path, opts = {}, &block
|
12
|
-
|
32
|
+
send_and_receive path, opts.merge(:method => :#{meth}), &block
|
13
33
|
end
|
14
34
|
RUBY
|
15
35
|
end
|
16
36
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
# POST XML messages to /update with optional params
|
37
|
+
# POST XML messages to /update with optional params.
|
38
|
+
#
|
39
|
+
# http://wiki.apache.org/solr/UpdateXmlMessages#add.2BAC8-update
|
22
40
|
#
|
23
41
|
# If not set, opts[:headers] will be set to a hash with the key
|
24
42
|
# 'Content-Type' set to 'text/xml'
|
@@ -27,7 +45,7 @@ class RSolr::Client
|
|
27
45
|
#
|
28
46
|
# :data - posted data
|
29
47
|
# :headers - http headers
|
30
|
-
# :params - query parameter hash
|
48
|
+
# :params - solr query parameter hash
|
31
49
|
#
|
32
50
|
def update opts = {}
|
33
51
|
opts[:headers] ||= {}
|
@@ -37,6 +55,9 @@ class RSolr::Client
|
|
37
55
|
|
38
56
|
#
|
39
57
|
# +add+ creates xml "add" documents and sends the xml data to the +update+ method
|
58
|
+
#
|
59
|
+
# http://wiki.apache.org/solr/UpdateXmlMessages#add.2BAC8-update
|
60
|
+
#
|
40
61
|
# single record:
|
41
62
|
# solr.update(:id=>1, :name=>'one')
|
42
63
|
#
|
@@ -54,14 +75,7 @@ class RSolr::Client
|
|
54
75
|
|
55
76
|
# send "commit" xml with opts
|
56
77
|
#
|
57
|
-
#
|
58
|
-
#
|
59
|
-
# :maxSegments => N - optimizes down to at most N number of segments
|
60
|
-
# :waitFlush => true|false - do not return until changes are flushed to disk
|
61
|
-
# :waitSearcher => true|false - do not return until a new searcher is opened and registered
|
62
|
-
# :expungeDeletes => true|false - merge segments with deletes into other segments #NOT
|
63
|
-
#
|
64
|
-
# *NOTE* :expungeDeletes is Solr 1.4 only
|
78
|
+
# http://wiki.apache.org/solr/UpdateXmlMessages#A.22commit.22_and_.22optimize.22
|
65
79
|
#
|
66
80
|
def commit opts = {}
|
67
81
|
commit_attrs = opts.delete :commit_attributes
|
@@ -70,14 +84,7 @@ class RSolr::Client
|
|
70
84
|
|
71
85
|
# send "optimize" xml with opts.
|
72
86
|
#
|
73
|
-
#
|
74
|
-
#
|
75
|
-
# :maxSegments => N - optimizes down to at most N number of segments
|
76
|
-
# :waitFlush => true|false - do not return until changes are flushed to disk
|
77
|
-
# :waitSearcher => true|false - do not return until a new searcher is opened and registered
|
78
|
-
# :expungeDeletes => true|false - merge segments with deletes into other segments
|
79
|
-
#
|
80
|
-
# *NOTE* :expungeDeletes is Solr 1.4 only
|
87
|
+
# http://wiki.apache.org/solr/UpdateXmlMessages#A.22commit.22_and_.22optimize.22
|
81
88
|
#
|
82
89
|
def optimize opts = {}
|
83
90
|
optimize_attributes = opts.delete :optimize_attributes
|
@@ -85,11 +92,14 @@ class RSolr::Client
|
|
85
92
|
end
|
86
93
|
|
87
94
|
# send </rollback>
|
95
|
+
#
|
96
|
+
# http://wiki.apache.org/solr/UpdateXmlMessages#A.22rollback.22
|
97
|
+
#
|
88
98
|
# NOTE: solr 1.4 only
|
89
99
|
def rollback opts = {}
|
90
100
|
update opts.merge(:data => xml.rollback)
|
91
101
|
end
|
92
|
-
|
102
|
+
|
93
103
|
# Delete one or many documents by id
|
94
104
|
# solr.delete_by_id 10
|
95
105
|
# solr.delete_by_id([12, 41, 199])
|
@@ -97,7 +107,10 @@ class RSolr::Client
|
|
97
107
|
update opts.merge(:data => xml.delete_by_id(id))
|
98
108
|
end
|
99
109
|
|
100
|
-
# delete one or many documents by query
|
110
|
+
# delete one or many documents by query.
|
111
|
+
#
|
112
|
+
# http://wiki.apache.org/solr/UpdateXmlMessages#A.22delete.22_by_ID_and_by_Query
|
113
|
+
#
|
101
114
|
# solr.delete_by_query 'available:0'
|
102
115
|
# solr.delete_by_query ['quantity:0', 'manu:"FQ"']
|
103
116
|
def delete_by_query query, opts = {}
|
@@ -109,7 +122,7 @@ class RSolr::Client
|
|
109
122
|
@xml ||= RSolr::Xml::Generator.new
|
110
123
|
end
|
111
124
|
|
112
|
-
# +
|
125
|
+
# +send_and_receive+ is the main request method responsible for sending requests to the +connection+ object.
|
113
126
|
#
|
114
127
|
# "path" : A string value that usually represents a solr request handler
|
115
128
|
# "opt" : A hash, which can contain the following keys:
|
@@ -117,25 +130,109 @@ class RSolr::Client
|
|
117
130
|
# :params : optional - the query string params in hash form
|
118
131
|
# :data : optional - post data -- if a hash is given, it's sent as "application/x-www-form-urlencoded"
|
119
132
|
# :headers : optional - hash of request headers
|
120
|
-
# All other options are passed right along to the connection
|
133
|
+
# All other options are passed right along to the connection's +send_and_receive+ method (:get, :post, or :head)
|
121
134
|
#
|
122
|
-
# +
|
135
|
+
# +send_and_receive+ returns either a string or hash on a successful ruby request.
|
123
136
|
# When the :params[:wt] => :ruby, the response will be a hash, else a string.
|
137
|
+
#
|
138
|
+
# creates a request context hash,
|
139
|
+
# sends it to the connection's +execute+ method
|
140
|
+
# which returns a simple hash,
|
141
|
+
# then passes the request/response into +adapt_response+.
|
142
|
+
def send_and_receive path, opts
|
143
|
+
request_context = build_request path, opts
|
144
|
+
execute request_context
|
145
|
+
end
|
146
|
+
|
124
147
|
#
|
125
|
-
def
|
126
|
-
connection.
|
148
|
+
def execute request_context
|
149
|
+
raw_response = connection.execute self, request_context
|
150
|
+
adapt_response(request_context, raw_response) unless raw_response.nil?
|
151
|
+
end
|
152
|
+
|
153
|
+
# +build_request+ accepts a path and options hash,
|
154
|
+
# then prepares a normalized hash to return for sending
|
155
|
+
# to a solr connection driver.
|
156
|
+
# +build_request+ sets up the uri/query string
|
157
|
+
# and converts the +data+ arg to form-urlencoded,
|
158
|
+
# if the +data+ arg is a hash.
|
159
|
+
# returns a hash with the following keys:
|
160
|
+
# :method
|
161
|
+
# :params
|
162
|
+
# :headers
|
163
|
+
# :data
|
164
|
+
# :uri
|
165
|
+
# :path
|
166
|
+
# :query
|
167
|
+
def build_request path, opts
|
168
|
+
raise "path must be a string or symbol, not #{path.inspect}" unless [String,Symbol].include?(path.class)
|
169
|
+
path = path.to_s
|
170
|
+
opts[:proxy] = proxy unless proxy.nil?
|
171
|
+
opts[:method] ||= :get
|
172
|
+
raise "The :data option can only be used if :method => :post" if opts[:method] != :post and opts[:data]
|
173
|
+
opts[:params] = opts[:params].nil? ? {:wt => :ruby} : {:wt => :ruby}.merge(opts[:params])
|
174
|
+
query = RSolr::Uri.params_to_solr(opts[:params]) unless opts[:params].empty?
|
175
|
+
opts[:query] = query
|
176
|
+
if opts[:data].is_a? Hash
|
177
|
+
opts[:data] = RSolr::Uri.params_to_solr opts[:data]
|
178
|
+
opts[:headers] ||= {}
|
179
|
+
opts[:headers]['Content-Type'] ||= 'application/x-www-form-urlencoded'
|
180
|
+
end
|
181
|
+
opts[:path] = path
|
182
|
+
opts[:uri] = base_uri.merge(path.to_s + (query ? "?#{query}" : "")) if base_uri
|
183
|
+
opts
|
127
184
|
end
|
128
185
|
|
129
|
-
#
|
130
|
-
#
|
131
|
-
|
132
|
-
|
186
|
+
# A mixin for used by #adapt_response
|
187
|
+
# This module essentially
|
188
|
+
# allows the raw response access to
|
189
|
+
# the original response and request.
|
190
|
+
module Context
|
191
|
+
attr_accessor :request, :response
|
133
192
|
end
|
134
193
|
|
135
|
-
#
|
136
|
-
#
|
137
|
-
|
138
|
-
|
194
|
+
# This method will evaluate the :body value
|
195
|
+
# if the params[:uri].params[:wt] == :ruby
|
196
|
+
# ... otherwise, the body is returned as is.
|
197
|
+
# The return object has methods attached, :request and :response.
|
198
|
+
# These methods give you access to the original
|
199
|
+
# request and response from the connection.
|
200
|
+
#
|
201
|
+
# +adapt_response+ will raise an InvalidRubyResponse
|
202
|
+
# if :wt == :ruby and the body
|
203
|
+
# couldn't be evaluated.
|
204
|
+
def adapt_response request, response
|
205
|
+
raise "The response does not have the correct keys => :body, :headers, :status" unless
|
206
|
+
%W(body headers status) == response.keys.map{|k|k.to_s}.sort
|
207
|
+
raise RSolr::Error::Http.new request, response unless
|
208
|
+
[200,302].include? response[:status]
|
209
|
+
result = request[:params][:wt] == :ruby ? evaluate_ruby_response(request, response) : response[:body]
|
210
|
+
result.extend Context
|
211
|
+
result.request = request
|
212
|
+
result.response = response
|
213
|
+
result
|
214
|
+
end
|
215
|
+
|
216
|
+
protected
|
217
|
+
|
218
|
+
# converts the method name for the solr request handler path.
|
219
|
+
def method_missing name, *args
|
220
|
+
send_and_receive name, *args
|
221
|
+
end
|
222
|
+
|
223
|
+
# evaluates the response[:body],
|
224
|
+
# attemps to bring the ruby string to life.
|
225
|
+
# If a SyntaxError is raised, then
|
226
|
+
# this method intercepts and raises a
|
227
|
+
# RSolr::Error::InvalidRubyResponse
|
228
|
+
# instead, giving full access to the
|
229
|
+
# request/response objects.
|
230
|
+
def evaluate_ruby_response request, response
|
231
|
+
begin
|
232
|
+
Kernel.eval response[:body].to_s
|
233
|
+
rescue SyntaxError
|
234
|
+
raise RSolr::Error::InvalidRubyResponse.new request, response
|
235
|
+
end
|
139
236
|
end
|
140
237
|
|
141
238
|
end
|
@@ -1,24 +1,20 @@
|
|
1
1
|
require 'net/http'
|
2
2
|
require 'net/https'
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
include RSolr::Connectable
|
7
|
-
|
8
|
-
def initialize *args, &block
|
9
|
-
# call the initialize method from RSolr::Connectable
|
10
|
-
super
|
11
|
-
end
|
4
|
+
# The default/Net::Http adapter for RSolr.
|
5
|
+
class RSolr::Connection
|
12
6
|
|
13
7
|
# using the request_context hash,
|
14
|
-
#
|
8
|
+
# send a request,
|
15
9
|
# then return the standard rsolr response hash {:status, :body, :headers}
|
16
|
-
def execute request_context
|
10
|
+
def execute client, request_context
|
11
|
+
h = http request_context[:uri], request_context[:proxy]
|
17
12
|
request = setup_raw_request request_context
|
18
13
|
request.body = request_context[:data] if request_context[:method] == :post and request_context[:data]
|
19
14
|
begin
|
20
|
-
response =
|
15
|
+
response = h.request request
|
21
16
|
{:status => response.code.to_i, :headers => response.to_hash, :body => response.body}
|
17
|
+
# catch the undefined closed? exception -- this is a confirmed ruby bug
|
22
18
|
rescue NoMethodError
|
23
19
|
$!.message == "undefined method `closed?' for nil:NilClass" ?
|
24
20
|
raise(Errno::ECONNREFUSED.new) :
|
@@ -28,7 +24,8 @@ class RSolr::Http
|
|
28
24
|
|
29
25
|
protected
|
30
26
|
|
31
|
-
|
27
|
+
# This returns a singleton of a Net::HTTP or Net::HTTP.Proxy request object.
|
28
|
+
def http uri, proxy = nil
|
32
29
|
@http ||= (
|
33
30
|
http = if proxy
|
34
31
|
proxy_user, proxy_pass = proxy.userinfo.split(/:/) if proxy.userinfo
|
@@ -36,30 +33,12 @@ class RSolr::Http
|
|
36
33
|
else
|
37
34
|
Net::HTTP.new uri.host, uri.port
|
38
35
|
end
|
39
|
-
|
40
|
-
http.use_ssl = uri.port == 443 || uri.instance_of?(URI::HTTPS)
|
41
|
-
|
42
|
-
if options[:timeout] && options[:timeout].is_a?(Integer)
|
43
|
-
http.open_timeout = options[:timeout]
|
44
|
-
http.read_timeout = options[:timeout]
|
45
|
-
end
|
46
|
-
|
47
|
-
if options[:pem] && http.use_ssl?
|
48
|
-
http.cert = OpenSSL::X509::Certificate.new(options[:pem])
|
49
|
-
http.key = OpenSSL::PKey::RSA.new(options[:pem])
|
50
|
-
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
51
|
-
else
|
52
|
-
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
53
|
-
end
|
54
|
-
|
55
|
-
if options[:debug_output]
|
56
|
-
http.set_debug_output(options[:debug_output])
|
57
|
-
end
|
58
|
-
|
36
|
+
http.use_ssl = uri.port == 443 || uri.instance_of?(URI::HTTPS)
|
59
37
|
http
|
60
38
|
)
|
61
39
|
end
|
62
40
|
|
41
|
+
#
|
63
42
|
def setup_raw_request request_context
|
64
43
|
http_method = case request_context[:method]
|
65
44
|
when :get
|
@@ -74,26 +53,7 @@ class RSolr::Http
|
|
74
53
|
headers = request_context[:headers] || {}
|
75
54
|
raw_request = http_method.new request_context[:uri].to_s
|
76
55
|
raw_request.initialize_http_header headers
|
77
|
-
raw_request.basic_auth username, password if options[:basic_auth]
|
78
|
-
if options[:digest_auth]
|
79
|
-
res = http.head(request_context[:uri].to_s, headers)
|
80
|
-
if res['www-authenticate'] != nil && res['www-authenticate'].length > 0
|
81
|
-
raw_request.digest_auth username, password, res
|
82
|
-
end
|
83
|
-
end
|
84
56
|
raw_request
|
85
57
|
end
|
86
58
|
|
87
|
-
def credentials
|
88
|
-
options[:basic_auth] || options[:digest_auth]
|
89
|
-
end
|
90
|
-
|
91
|
-
def username
|
92
|
-
credentials[:username]
|
93
|
-
end
|
94
|
-
|
95
|
-
def password
|
96
|
-
credentials[:password]
|
97
|
-
end
|
98
|
-
|
99
59
|
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
module RSolr::Pagination
|
2
|
+
|
3
|
+
# Calculates the "start" and "rows" Solr params
|
4
|
+
# by inspecting the :per_page and :page params.
|
5
|
+
def self.calculate_start_and_rows page, per_page
|
6
|
+
per_page ||= 10
|
7
|
+
page = page.to_s.to_i-1
|
8
|
+
page = page < 1 ? 0 : page
|
9
|
+
start = page * per_page
|
10
|
+
[start, per_page]
|
11
|
+
end
|
12
|
+
|
13
|
+
# A mixin module for RSolr::Client
|
14
|
+
# -- note, this must mixed-in via
|
15
|
+
# "extend" on a RSolr::Client instance.
|
16
|
+
module Client
|
17
|
+
|
18
|
+
# A paginated request method.
|
19
|
+
def paginate page, per_page, path, opts = {}
|
20
|
+
request_context = build_paginated_request page, per_page, path, opts = {}
|
21
|
+
puts request_context.inspect
|
22
|
+
execute request_context
|
23
|
+
end
|
24
|
+
|
25
|
+
# Just like RSolr::Client #build_request
|
26
|
+
# but converts the page and per_page
|
27
|
+
# arguments into :rows and :start.
|
28
|
+
def build_paginated_request page, per_page, path, opts = {}
|
29
|
+
opts[:page] = page
|
30
|
+
opts[:per_page] = per_page
|
31
|
+
opts[:params] ||= {}
|
32
|
+
values = RSolr::Pagination.calculate_start_and_rows(page, per_page)
|
33
|
+
opts[:params][:start] = values[0]
|
34
|
+
opts[:params][:rows] = values[1]
|
35
|
+
build_request path, opts
|
36
|
+
end
|
37
|
+
|
38
|
+
protected
|
39
|
+
|
40
|
+
# Checks if the called method starts
|
41
|
+
# with "paginate_" and
|
42
|
+
# converts the paginate_* to the solr
|
43
|
+
# request path. It then calls paginate
|
44
|
+
# with the appropriate arguments.
|
45
|
+
# If the called method doesn't
|
46
|
+
# start with "paginate_",
|
47
|
+
# the original/super
|
48
|
+
# RSolr::Client #method_missing
|
49
|
+
# method is called.
|
50
|
+
def method_missing name, *args
|
51
|
+
if name.to_s =~ /^paginate_(.+)$/
|
52
|
+
paginate args[0], args[1], $1, *args[2..-1]
|
53
|
+
else
|
54
|
+
super name, *args
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Overrides the RSolr::Client #evaluate_ruby_response method.
|
59
|
+
# Calls the original/super
|
60
|
+
# RSolr::Client #evaluate_ruby_response method.
|
61
|
+
# Mixes in the PaginatedResponse if
|
62
|
+
# the request[:page] and request[:per_page]
|
63
|
+
# opts are set.
|
64
|
+
def evaluate_ruby_response request, response
|
65
|
+
result = super request, response
|
66
|
+
result.extend(PaginatedResponse) if request[:page] && request[:per_page]
|
67
|
+
result
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
module PaginatedResponse
|
73
|
+
# TODO: self["responseHeader"]["params"]["rows"]
|
74
|
+
# will not be available if omitHeader is false...
|
75
|
+
# so, a simple "extend" probably isn't going to cut it.
|
76
|
+
def self.extended base
|
77
|
+
return unless base["response"] && base["response"]["docs"]
|
78
|
+
d = base['response']['docs']
|
79
|
+
d.extend PaginatedDocSet
|
80
|
+
d.per_page = self["responseHeader"]["params"]["rows"].to_s.to_i rescue 10
|
81
|
+
d.start = base["response"]["start"].to_s.to_i
|
82
|
+
d.total = base["response"]["numFound"].to_s.to_i
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# A response module which gets mixed into the solr ["response"]["docs"] array.
|
87
|
+
module PaginatedDocSet
|
88
|
+
|
89
|
+
attr_accessor :start, :per_page, :total
|
90
|
+
|
91
|
+
# Returns the current page calculated from 'rows' and 'start'
|
92
|
+
# WillPaginate hook
|
93
|
+
def current_page
|
94
|
+
return 1 if start < 1
|
95
|
+
per_page_normalized = per_page < 1 ? 1 : per_page
|
96
|
+
@current_page ||= (start / per_page_normalized).ceil + 1
|
97
|
+
end
|
98
|
+
|
99
|
+
# Calcuates the total pages from 'numFound' and 'rows'
|
100
|
+
# WillPaginate hook
|
101
|
+
def total_pages
|
102
|
+
@total_pages ||= per_page > 0 ? (total / per_page.to_f).ceil : 1
|
103
|
+
end
|
104
|
+
|
105
|
+
# returns the previous page number or 1
|
106
|
+
# WillPaginate hook
|
107
|
+
def previous_page
|
108
|
+
@previous_page ||= (current_page > 1) ? current_page - 1 : 1
|
109
|
+
end
|
110
|
+
|
111
|
+
# returns the next page number or the last
|
112
|
+
# WillPaginate hook
|
113
|
+
def next_page
|
114
|
+
@next_page ||= (current_page == total_pages) ? total_pages : current_page+1
|
115
|
+
end
|
116
|
+
|
117
|
+
def has_next?
|
118
|
+
current_page < total_pages
|
119
|
+
end
|
120
|
+
|
121
|
+
def has_previous?
|
122
|
+
current_page > 1
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
126
|
+
|
127
|
+
end
|
data/lib/rsolr.rb
CHANGED
@@ -4,7 +4,7 @@ require 'rubygems'
|
|
4
4
|
|
5
5
|
module RSolr
|
6
6
|
|
7
|
-
%W(Char Client
|
7
|
+
%W(Char Client Error Connection Pagination Uri Xml).each{|n|autoload n.to_sym, "rsolr/#{n.downcase}"}
|
8
8
|
|
9
9
|
def self.version
|
10
10
|
@version ||= File.read(File.join(File.dirname(__FILE__), '..', 'VERSION')).chomp
|
@@ -13,7 +13,7 @@ module RSolr
|
|
13
13
|
VERSION = self.version
|
14
14
|
|
15
15
|
def self.connect *args
|
16
|
-
Client.new
|
16
|
+
Client.new Connection.new, *args
|
17
17
|
end
|
18
18
|
|
19
19
|
# RSolr.escape
|
data/spec/api/client_spec.rb
CHANGED
@@ -4,8 +4,8 @@ describe "RSolr::Client" do
|
|
4
4
|
module ClientHelper
|
5
5
|
def client
|
6
6
|
@client ||= (
|
7
|
-
connection = RSolr::
|
8
|
-
RSolr::Client.new connection
|
7
|
+
connection = RSolr::Connection.new
|
8
|
+
RSolr::Client.new connection, :url => "http://localhost:9999/solr"
|
9
9
|
)
|
10
10
|
end
|
11
11
|
end
|
@@ -16,13 +16,13 @@ describe "RSolr::Client" do
|
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
19
|
-
context "
|
19
|
+
context "send_and_receive" do
|
20
20
|
include ClientHelper
|
21
21
|
it "should forward these method calls the #connection object" do
|
22
22
|
[:get, :post, :head].each do |meth|
|
23
|
-
client.connection.should_receive(:
|
23
|
+
client.connection.should_receive(:execute).
|
24
24
|
and_return({:status => 200, :body => "{}", :headers => {}})
|
25
|
-
client.
|
25
|
+
client.send_and_receive '', :method => meth, :params => {}, :data => nil, :headers => {}
|
26
26
|
end
|
27
27
|
end
|
28
28
|
end
|
@@ -30,19 +30,16 @@ describe "RSolr::Client" do
|
|
30
30
|
context "post" do
|
31
31
|
include ClientHelper
|
32
32
|
it "should pass the expected params to the connection's #post method" do
|
33
|
-
client.connection.should_receive(:
|
33
|
+
client.connection.should_receive(:execute).
|
34
34
|
with(
|
35
|
-
"update",
|
35
|
+
client, hash_including({:path => "update", :headers=>{"Content-Type"=>"text/plain"}, :method=>:post, :data=>"the data"})
|
36
36
|
).
|
37
37
|
and_return(
|
38
|
-
:
|
39
|
-
:
|
40
|
-
:
|
41
|
-
:data=>"the data",
|
42
|
-
:method=>:post,
|
43
|
-
:headers=>{"Content-Type"=>"text/plain"}
|
38
|
+
:body => "",
|
39
|
+
:status => 200,
|
40
|
+
:headers => {"Content-Type"=>"text/plain"}
|
44
41
|
)
|
45
|
-
client.post "update", :data => "the data", :headers => {"Content-Type" => "text/plain"}
|
42
|
+
client.post "update", :data => "the data", :method=>:post, :headers => {"Content-Type" => "text/plain"}
|
46
43
|
end
|
47
44
|
end
|
48
45
|
|
@@ -56,17 +53,14 @@ describe "RSolr::Client" do
|
|
56
53
|
context "add" do
|
57
54
|
include ClientHelper
|
58
55
|
it "should send xml to the connection's #post method" do
|
59
|
-
client.connection.should_receive(:
|
56
|
+
client.connection.should_receive(:execute).
|
60
57
|
with(
|
61
|
-
"update",
|
58
|
+
client, hash_including({:path => "update", :headers=>{"Content-Type"=>"text/xml"}, :method=>:post, :data=>"<xml/>"})
|
62
59
|
).
|
63
60
|
and_return(
|
64
|
-
:
|
65
|
-
:
|
66
|
-
:headers => {"Content-Type"=>"text/xml"}
|
67
|
-
:method => :post,
|
68
|
-
:query => "wt=ruby",
|
69
|
-
:params => {:wt=>:ruby}
|
61
|
+
:body => "",
|
62
|
+
:status => 200,
|
63
|
+
:headers => {"Content-Type"=>"text/xml"}
|
70
64
|
)
|
71
65
|
# the :xml attr is lazy loaded... so load it up first
|
72
66
|
client.xml
|
@@ -80,17 +74,14 @@ describe "RSolr::Client" do
|
|
80
74
|
context "update" do
|
81
75
|
include ClientHelper
|
82
76
|
it "should send data to the connection's #post method" do
|
83
|
-
client.connection.should_receive(:
|
77
|
+
client.connection.should_receive(:execute).
|
84
78
|
with(
|
85
|
-
"update",
|
79
|
+
client, hash_including({:path => "update", :headers=>{"Content-Type"=>"text/xml"}, :method=>:post, :data=>"<optimize/>"})
|
86
80
|
).
|
87
81
|
and_return(
|
88
|
-
:
|
89
|
-
:
|
90
|
-
:headers => {"Content-Type"=>"text/xml"}
|
91
|
-
:method => :post,
|
92
|
-
:query => "wt=ruby",
|
93
|
-
:params => {:wt=>:ruby}
|
82
|
+
:body => "",
|
83
|
+
:status => 200,
|
84
|
+
:headers => {"Content-Type"=>"text/xml"}
|
94
85
|
)
|
95
86
|
client.update(:data => "<optimize/>")
|
96
87
|
end
|
@@ -100,17 +91,14 @@ describe "RSolr::Client" do
|
|
100
91
|
include ClientHelper
|
101
92
|
[:commit, :optimize, :rollback].each do |meth|
|
102
93
|
it "should send a #{meth} message to the connection's #post method" do
|
103
|
-
client.connection.should_receive(:
|
94
|
+
client.connection.should_receive(:execute).
|
104
95
|
with(
|
105
|
-
"update",
|
96
|
+
client, hash_including({:path => "update", :headers=>{"Content-Type"=>"text/xml"}, :method=>:post, :data=>"<?xml version=\"1.0\" encoding=\"UTF-8\"?><#{meth}/>"})
|
106
97
|
).
|
107
98
|
and_return(
|
108
|
-
:
|
109
|
-
:
|
110
|
-
:headers => {"Content-Type"=>"text/xml"}
|
111
|
-
:method => :post,
|
112
|
-
:query => "wt=ruby",
|
113
|
-
:params => {:wt=>:ruby}
|
99
|
+
:body => "",
|
100
|
+
:status => 200,
|
101
|
+
:headers => {"Content-Type"=>"text/xml"}
|
114
102
|
)
|
115
103
|
client.send meth
|
116
104
|
end
|
@@ -120,17 +108,14 @@ describe "RSolr::Client" do
|
|
120
108
|
context "delete_by_id" do
|
121
109
|
include ClientHelper
|
122
110
|
it "should send data to the connection's #post method" do
|
123
|
-
client.connection.should_receive(:
|
111
|
+
client.connection.should_receive(:execute).
|
124
112
|
with(
|
125
|
-
"update",
|
113
|
+
client, hash_including({:path => "update", :headers=>{"Content-Type"=>"text/xml"}, :method=>:post, :data=>"<?xml version=\"1.0\" encoding=\"UTF-8\"?><delete><id>1</id></delete>"})
|
126
114
|
).
|
127
115
|
and_return(
|
128
|
-
:
|
129
|
-
:
|
130
|
-
:headers => {"Content-Type"=>"text/xml"}
|
131
|
-
:method => :post,
|
132
|
-
:query => "wt=ruby",
|
133
|
-
:params => {:wt=>:ruby}
|
116
|
+
:body => "",
|
117
|
+
:status => 200,
|
118
|
+
:headers => {"Content-Type"=>"text/xml"}
|
134
119
|
)
|
135
120
|
client.delete_by_id 1
|
136
121
|
end
|
@@ -139,20 +124,64 @@ describe "RSolr::Client" do
|
|
139
124
|
context "delete_by_query" do
|
140
125
|
include ClientHelper
|
141
126
|
it "should send data to the connection's #post method" do
|
142
|
-
client.connection.should_receive(:
|
127
|
+
client.connection.should_receive(:execute).
|
143
128
|
with(
|
144
|
-
"update",
|
129
|
+
client, hash_including({:path => "update", :headers=>{"Content-Type"=>"text/xml"}, :method=>:post, :data=>"<?xml version=\"1.0\" encoding=\"UTF-8\"?><delete><query fq=\"category:"trash"\"/></delete>"})
|
145
130
|
).
|
146
131
|
and_return(
|
147
|
-
:
|
148
|
-
:
|
149
|
-
:headers => {"Content-Type"=>"text/xml"}
|
150
|
-
:method => :post,
|
151
|
-
:query => "wt=ruby",
|
152
|
-
:params => {:wt=>:ruby}
|
132
|
+
:body => "",
|
133
|
+
:status => 200,
|
134
|
+
:headers => {"Content-Type"=>"text/xml"}
|
153
135
|
)
|
154
136
|
client.delete_by_query :fq => "category:\"trash\""
|
155
137
|
end
|
156
138
|
end
|
157
139
|
|
140
|
+
context "adapt_response" do
|
141
|
+
include ClientHelper
|
142
|
+
it 'should not try to evaluate ruby when the :qt is not :ruby' do
|
143
|
+
body = '{:time=>"NOW"}'
|
144
|
+
result = client.adapt_response({:params=>{}}, {:status => 200, :body => body, :headers => {}})
|
145
|
+
result.should be_a(String)
|
146
|
+
result.should == body
|
147
|
+
end
|
148
|
+
|
149
|
+
it 'should evaluate ruby responses when the :wt is :ruby' do
|
150
|
+
body = '{:time=>"NOW"}'
|
151
|
+
result = client.adapt_response({:params=>{:wt=>:ruby}}, {:status => 200, :body => body, :headers => {}})
|
152
|
+
result.should be_a(Hash)
|
153
|
+
result.should == {:time=>"NOW"}
|
154
|
+
end
|
155
|
+
|
156
|
+
it "ought raise a RSolr::Error::InvalidRubyResponse when the ruby is indeed frugged" do
|
157
|
+
lambda {
|
158
|
+
client.adapt_response({:params=>{:wt => :ruby}}, {:status => 200, :body => "<woops/>", :headers => {}})
|
159
|
+
}.should raise_error RSolr::Error::InvalidRubyResponse
|
160
|
+
end
|
161
|
+
|
162
|
+
end
|
163
|
+
|
164
|
+
context "build_request" do
|
165
|
+
include ClientHelper
|
166
|
+
it 'should return a request context array' do
|
167
|
+
result = client.build_request 'select', :method => :post, :params => {:q=>'test', :fq=>[0,1]}, :data => "data", :headers => {}
|
168
|
+
[/fq=0/, /fq=1/, /q=test/, /wt=ruby/].each do |pattern|
|
169
|
+
result[:query].should match pattern
|
170
|
+
end
|
171
|
+
result[:data].should == "data"
|
172
|
+
result[:headers].should == {}
|
173
|
+
end
|
174
|
+
|
175
|
+
it "should set the Content-Type header to application/x-www-form-urlencoded if a hash is passed in to the data arg" do
|
176
|
+
result = client.build_request 'select', :method => :post, :data => {:q=>'test', :fq=>[0,1]}, :headers => {}
|
177
|
+
result[:query].should == "wt=ruby"
|
178
|
+
[/fq=0/, /fq=1/, /q=test/].each do |pattern|
|
179
|
+
result[:data].should match pattern
|
180
|
+
end
|
181
|
+
result[:data].should_not match /wt=ruby/
|
182
|
+
result[:headers].should == {"Content-Type" => "application/x-www-form-urlencoded"}
|
183
|
+
end
|
184
|
+
|
185
|
+
end
|
186
|
+
|
158
187
|
end
|
data/spec/api/http_spec.rb
CHANGED
@@ -1,29 +1,4 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
describe "RSolr::Http" do
|
3
|
-
it "Should be an RSolr::Connectable and implement an execute method" do
|
4
|
-
http = RSolr::Http.new
|
5
|
-
http.should be_a(RSolr::Connectable)
|
6
|
-
http.should respond_to(:execute)
|
7
|
-
end
|
8
3
|
|
9
|
-
context "execute" do
|
10
|
-
it "should require a request_context hash" do
|
11
|
-
http = RSolr::Http.new
|
12
|
-
lambda {
|
13
|
-
http.execute
|
14
|
-
}.should raise_error(ArgumentError)
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
# it "should raise an Http error if the response status code aint right" do
|
19
|
-
# client.connection.should_receive(:get).
|
20
|
-
# and_return({:status => 400, :body => "", :headers => {}})
|
21
|
-
# lambda{
|
22
|
-
# client.send_request '', :method => :get
|
23
|
-
# }.should raise_error(RSolr::Error::Http) {|error|
|
24
|
-
# error.should be_a(RSolr::Error::Http)
|
25
|
-
# error.should respond_to(:request)
|
26
|
-
# error.should respond_to(:response)
|
27
|
-
# }
|
28
|
-
# end
|
29
4
|
end
|
metadata
CHANGED
@@ -6,8 +6,8 @@ version: !ruby/object:Gem::Version
|
|
6
6
|
- 1
|
7
7
|
- 0
|
8
8
|
- 0
|
9
|
-
-
|
10
|
-
version: 1.0.0.
|
9
|
+
- beta4
|
10
|
+
version: 1.0.0.beta4
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Matt Mitchell
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2010-
|
18
|
+
date: 2010-10-17 00:00:00 -04:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
@@ -48,9 +48,9 @@ files:
|
|
48
48
|
- lib/rsolr.rb
|
49
49
|
- lib/rsolr/char.rb
|
50
50
|
- lib/rsolr/client.rb
|
51
|
-
- lib/rsolr/
|
51
|
+
- lib/rsolr/connection.rb
|
52
52
|
- lib/rsolr/error.rb
|
53
|
-
- lib/rsolr/
|
53
|
+
- lib/rsolr/pagination.rb
|
54
54
|
- lib/rsolr/uri.rb
|
55
55
|
- lib/rsolr/xml.rb
|
56
56
|
has_rdoc: true
|
@@ -88,7 +88,6 @@ summary: A Ruby client for Apache Solr
|
|
88
88
|
test_files:
|
89
89
|
- spec/api/char_spec.rb
|
90
90
|
- spec/api/client_spec.rb
|
91
|
-
- spec/api/connectable_spec.rb
|
92
91
|
- spec/api/error_spec.rb
|
93
92
|
- spec/api/http_spec.rb
|
94
93
|
- spec/api/uri_spec.rb
|
data/lib/rsolr/connectable.rb
DELETED
@@ -1,97 +0,0 @@
|
|
1
|
-
# Connectable is designed to be shared across solr driver implementations.
|
2
|
-
# If the driver uses an http url/proxy and returns the standard http respon
|
3
|
-
# data (status, body, headers) then this module could be used.
|
4
|
-
module RSolr::Connectable
|
5
|
-
|
6
|
-
attr_reader :uri, :proxy, :options
|
7
|
-
|
8
|
-
def initialize options = {}
|
9
|
-
url = options[:url] || 'http://127.0.0.1:8983/solr/'
|
10
|
-
url << "/" unless url[-1] == ?/
|
11
|
-
proxy_url = options[:proxy]
|
12
|
-
proxy_url << "/" unless proxy_url.nil? or proxy_url[-1] == ?/
|
13
|
-
@uri = RSolr::Uri.create url
|
14
|
-
@proxy = RSolr::Uri.create proxy_url if proxy_url
|
15
|
-
@options = options
|
16
|
-
end
|
17
|
-
|
18
|
-
#
|
19
|
-
def base_request_uri
|
20
|
-
base_uri.request_uri
|
21
|
-
end
|
22
|
-
|
23
|
-
def base_uri
|
24
|
-
@proxy || @uri
|
25
|
-
end
|
26
|
-
|
27
|
-
# creates a request context hash,
|
28
|
-
# sends it to the connection.execute method
|
29
|
-
# which returns a simple hash,
|
30
|
-
# then passes the request/response into adapt_response.
|
31
|
-
def send_request path, opts
|
32
|
-
request_context = build_request path, opts
|
33
|
-
raw_response = execute request_context
|
34
|
-
adapt_response request_context, raw_response
|
35
|
-
end
|
36
|
-
|
37
|
-
# all connection imlementations that use this mixin need to create an execute method
|
38
|
-
def execute request_context
|
39
|
-
raise "You gotta implement this method and return a hash like => {:status => <integer>, :body => <string>, :headers => <hash>}"
|
40
|
-
end
|
41
|
-
|
42
|
-
# build_request sets up the uri/query string
|
43
|
-
# and converts the +data+ arg to form-urlencoded
|
44
|
-
# if the +data+ arg is a hash.
|
45
|
-
# returns a hash with the following keys:
|
46
|
-
# :method
|
47
|
-
# :params
|
48
|
-
# :headers
|
49
|
-
# :data
|
50
|
-
# :uri
|
51
|
-
# :path
|
52
|
-
# :query
|
53
|
-
def build_request path, opts
|
54
|
-
raise "path must be a string or symbol, not #{path.inspect}" unless [String,Symbol].include?(path.class)
|
55
|
-
path = path.to_s
|
56
|
-
opts[:method] ||= :get
|
57
|
-
raise "The :data option can only be used if :method => :post" if opts[:method] != :post and opts[:data]
|
58
|
-
opts[:params] = opts[:params].nil? ? {:wt => :ruby} : {:wt => :ruby}.merge(opts[:params])
|
59
|
-
query = RSolr::Uri.params_to_solr(opts[:params]) unless opts[:params].empty?
|
60
|
-
opts[:query] = query
|
61
|
-
if opts[:data].is_a? Hash
|
62
|
-
opts[:data] = RSolr::Uri.params_to_solr opts[:data]
|
63
|
-
opts[:headers] ||= {}
|
64
|
-
opts[:headers]['Content-Type'] ||= 'application/x-www-form-urlencoded'
|
65
|
-
end
|
66
|
-
opts[:path] = path
|
67
|
-
opts[:uri] = base_uri.merge(path.to_s + (query ? "?#{query}" : "")) if base_uri
|
68
|
-
opts
|
69
|
-
end
|
70
|
-
|
71
|
-
# This method will evaluate the :body value
|
72
|
-
# if the params[:uri].params[:wt] == :ruby
|
73
|
-
# ... otherwise, the body is returned as is.
|
74
|
-
# The return object has methods attached, :request and :response.
|
75
|
-
# These methods give you access to the original
|
76
|
-
# request and response from the connection.
|
77
|
-
#
|
78
|
-
# +adapt_response+ will raise an InvalidRubyResponse
|
79
|
-
# if :wt == :ruby and the body
|
80
|
-
# couldn't be evaluated.
|
81
|
-
def adapt_response request, response
|
82
|
-
raise "The response does not have the correct keys => :body, :headers, :status" unless
|
83
|
-
%W(body headers status) == response.keys.map{|k|k.to_s}.sort
|
84
|
-
raise RSolr::Error::Http.new request, response unless
|
85
|
-
[200,302].include? response[:status]
|
86
|
-
data = response[:body]
|
87
|
-
if request[:params][:wt] == :ruby
|
88
|
-
begin
|
89
|
-
data = Kernel.eval data.to_s
|
90
|
-
rescue SyntaxError
|
91
|
-
raise RSolr::Error::InvalidRubyResponse.new request, response
|
92
|
-
end
|
93
|
-
end
|
94
|
-
data
|
95
|
-
end
|
96
|
-
|
97
|
-
end
|
@@ -1,56 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
describe "RSolr::Connectable" do
|
4
|
-
|
5
|
-
def connectable
|
6
|
-
Object.new.extend RSolr::Connectable
|
7
|
-
end
|
8
|
-
|
9
|
-
context "adapt_response" do
|
10
|
-
|
11
|
-
it 'should not try to evaluate ruby when the :qt is not :ruby' do
|
12
|
-
body = '{:time=>"NOW"}'
|
13
|
-
result = connectable.adapt_response({:params=>{}}, {:status => 200, :body => body, :headers => {}})
|
14
|
-
result.should be_a(String)
|
15
|
-
result.should == body
|
16
|
-
end
|
17
|
-
|
18
|
-
it 'should evaluate ruby responses when the :wt is :ruby' do
|
19
|
-
body = '{:time=>"NOW"}'
|
20
|
-
result = connectable.adapt_response({:params=>{:wt=>:ruby}}, {:status => 200, :body => body, :headers => {}})
|
21
|
-
result.should be_a(Hash)
|
22
|
-
result.should == {:time=>"NOW"}
|
23
|
-
end
|
24
|
-
|
25
|
-
it "ought raise a RSolr::Error::InvalidRubyResponse when the ruby is indeed frugged" do
|
26
|
-
lambda {
|
27
|
-
connectable.adapt_response({:params=>{:wt => :ruby}}, {:status => 200, :body => "<woops/>", :headers => {}})
|
28
|
-
}.should raise_error RSolr::Error::InvalidRubyResponse
|
29
|
-
end
|
30
|
-
|
31
|
-
end
|
32
|
-
|
33
|
-
context "build_request" do
|
34
|
-
|
35
|
-
it 'should return a request context array' do
|
36
|
-
result = connectable.build_request 'select', :method => :post, :params => {:q=>'test', :fq=>[0,1]}, :data => "data", :headers => {}
|
37
|
-
[/fq=0/, /fq=1/, /q=test/, /wt=ruby/].each do |pattern|
|
38
|
-
result[:query].should match pattern
|
39
|
-
end
|
40
|
-
result[:data].should == "data"
|
41
|
-
result[:headers].should == {}
|
42
|
-
end
|
43
|
-
|
44
|
-
it "should set the Content-Type header to application/x-www-form-urlencoded if a hash is passed in to the data arg" do
|
45
|
-
result = connectable.build_request 'select', :method => :post, :data => {:q=>'test', :fq=>[0,1]}, :headers => {}
|
46
|
-
result[:query].should == "wt=ruby"
|
47
|
-
[/fq=0/, /fq=1/, /q=test/].each do |pattern|
|
48
|
-
result[:data].should match pattern
|
49
|
-
end
|
50
|
-
result[:data].should_not match /wt=ruby/
|
51
|
-
result[:headers].should == {"Content-Type" => "application/x-www-form-urlencoded"}
|
52
|
-
end
|
53
|
-
|
54
|
-
end
|
55
|
-
|
56
|
-
end
|