class RSolr::Client
attr_reader :connection, :uri, :proxy, :options
def initialize connection, options = {}
@connection = connection
url = options[:url] || 'http://127.0.0.1:8983/solr/'
url << "/" unless url[-1] == ?/
proxy_url = options[:proxy]
proxy_url << "/" unless proxy_url.nil? or proxy_url[-1] == ?/
@uri = RSolr::Uri.create url
@proxy = RSolr::Uri.create proxy_url if proxy_url
@options = options
extend RSolr::Pagination::Client
end
# returns the actual request uri object.
def base_request_uri
base_uri.request_uri
end
# returns the uri proxy if present,
# otherwise just the uri object.
def base_uri
@proxy || @uri
end
# Create the get, post, and head methods
%W(get post head).each do |meth|
class_eval <<-RUBY
def #{meth} path, opts = {}, &block
send_and_receive path, opts.merge(:method => :#{meth}), &block
end
RUBY
end
# POST XML messages to /update with optional params.
#
# http://wiki.apache.org/solr/UpdateXmlMessages#add.2BAC8-update
#
# If not set, opts[:headers] will be set to a hash with the key
# 'Content-Type' set to 'text/xml'
#
# +opts+ can/should contain:
#
# :data - posted data
# :headers - http headers
# :params - solr query parameter hash
#
def update opts = {}
opts[:headers] ||= {}
opts[:headers]['Content-Type'] ||= 'text/xml'
post 'update', opts
end
#
# +add+ creates xml "add" documents and sends the xml data to the +update+ method
#
# http://wiki.apache.org/solr/UpdateXmlMessages#add.2BAC8-update
#
# single record:
# solr.update(:id=>1, :name=>'one')
#
# update using an array
#
# solr.update(
# [{:id=>1, :name=>'one'}, {:id=>2, :name=>'two'}],
# :add_attributes => {:boost=>5.0, :commitWithin=>10}
# )
#
def add doc, opts = {}
add_attributes = opts.delete :add_attributes
update opts.merge(:data => xml.add(doc, add_attributes))
end
# send "commit" xml with opts
#
# http://wiki.apache.org/solr/UpdateXmlMessages#A.22commit.22_and_.22optimize.22
#
def commit opts = {}
commit_attrs = opts.delete :commit_attributes
update opts.merge(:data => xml.commit( commit_attrs ))
end
# send "optimize" xml with opts.
#
# http://wiki.apache.org/solr/UpdateXmlMessages#A.22commit.22_and_.22optimize.22
#
def optimize opts = {}
optimize_attributes = opts.delete :optimize_attributes
update opts.merge(:data => xml.optimize(optimize_attributes))
end
# send </rollback>
#
# http://wiki.apache.org/solr/UpdateXmlMessages#A.22rollback.22
#
# NOTE: solr 1.4 only
def rollback opts = {}
update opts.merge(:data => xml.rollback)
end
# Delete one or many documents by id
# solr.delete_by_id 10
# solr.delete_by_id([12, 41, 199])
def delete_by_id id, opts = {}
update opts.merge(:data => xml.delete_by_id(id))
end
# delete one or many documents by query.
#
# http://wiki.apache.org/solr/UpdateXmlMessages#A.22delete.22_by_ID_and_by_Query
#
# solr.delete_by_query 'available:0'
# solr.delete_by_query ['quantity:0', 'manu:"FQ"']
def delete_by_query query, opts = {}
update opts.merge(:data => xml.delete_by_query(query))
end
# shortcut to RSolr::Message::Generator
def xml
@xml ||= RSolr::Xml::Generator.new
end
# +send_and_receive+ is the main request method responsible for sending requests to the +connection+ object.
#
# "path" : A string value that usually represents a solr request handler
# "opt" : A hash, which can contain the following keys:
# :method : required - the http method (:get, :post or :head)
# :params : optional - the query string params in hash form
# :data : optional - post data -- if a hash is given, it's sent as "application/x-www-form-urlencoded"
# :headers : optional - hash of request headers
# All other options are passed right along to the connection's +send_and_receive+ method (:get, :post, or :head)
#
# +send_and_receive+ returns either a string or hash on a successful ruby request.
# When the :params[:wt] => :ruby, the response will be a hash, else a string.
#
# creates a request context hash,
# sends it to the connection's +execute+ method
# which returns a simple hash,
# then passes the request/response into +adapt_response+.
def send_and_receive path, opts
request_context = build_request path, opts
execute request_context
end
#
def execute request_context
raw_response = connection.execute self, request_context
adapt_response(request_context, raw_response) unless raw_response.nil?
end
# +build_request+ accepts a path and options hash,
# then prepares a normalized hash to return for sending
# to a solr connection driver.
# +build_request+ sets up the uri/query string
# and converts the +data+ arg to form-urlencoded,
# if the +data+ arg is a hash.
# returns a hash with the following keys:
# :method
# :params
# :headers
# :data
# :uri
# :path
# :query
def build_request path, opts
raise "path must be a string or symbol, not #{path.inspect}" unless [String,Symbol].include?(path.class)
path = path.to_s
opts[:proxy] = proxy unless proxy.nil?
opts[:method] ||= :get
raise "The :data option can only be used if :method => :post" if opts[:method] != :post and opts[:data]
opts[:params] = opts[:params].nil? ? {:wt => :ruby} : {:wt => :ruby}.merge(opts[:params])
query = RSolr::Uri.params_to_solr(opts[:params]) unless opts[:params].empty?
opts[:query] = query
if opts[:data].is_a? Hash
opts[:data] = RSolr::Uri.params_to_solr opts[:data]
opts[:headers] ||= {}
opts[:headers]['Content-Type'] ||= 'application/x-www-form-urlencoded'
end
opts[:path] = path
opts[:uri] = base_uri.merge(path.to_s + (query ? "?#{query}" : "")) if base_uri
opts
end
# A mixin for used by #adapt_response
# This module essentially
# allows the raw response access to
# the original response and request.
module Context
attr_accessor :request, :response
end
# This method will evaluate the :body value
# if the params[:uri].params[:wt] == :ruby
# ... otherwise, the body is returned as is.
# The return object has methods attached, :request and :response.
# These methods give you access to the original
# request and response from the connection.
#
# +adapt_response+ will raise an InvalidRubyResponse
# if :wt == :ruby and the body
# couldn't be evaluated.
def adapt_response request, response
raise "The response does not have the correct keys => :body, :headers, :status" unless
%W(body headers status) == response.keys.map{|k|k.to_s}.sort
raise RSolr::Error::Http.new request, response unless
[200,302].include? response[:status]
result = request[:params][:wt] == :ruby ? evaluate_ruby_response(request, response) : response[:body]
result.extend Context
result.request = request
result.response = response
result
end
protected
# converts the method name for the solr request handler path.
def method_missing name, *args
send_and_receive name, *args
end
# evaluates the response[:body],
# attemps to bring the ruby string to life.
# If a SyntaxError is raised, then
# this method intercepts and raises a
# RSolr::Error::InvalidRubyResponse
# instead, giving full access to the
# request/response objects.
def evaluate_ruby_response request, response
begin
Kernel.eval response[:body].to_s
rescue SyntaxError
raise RSolr::Error::InvalidRubyResponse.new request, response
end
end
end