rsolr 1.0.0.beta3 → 1.0.0.beta4
Sign up to get free protection for your applications and to get access to all the features.
- 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
|