├── VERSION ├── spec ├── spec_helper.rb └── api │ ├── error_spec.rb │ ├── connection_spec.rb │ ├── char_spec.rb │ ├── rsolr_spec.rb │ ├── pagination_spec.rb │ ├── uri_spec.rb │ ├── xml_spec.rb │ └── client_spec.rb ├── .gitignore ├── tasks ├── rsolr.rake ├── rdoc.rake ├── rcov.rake ├── jeweler.rake └── spec.rake ├── Rakefile ├── lib ├── rsolr.rb ├── rsolr │ ├── char.rb │ ├── uri.rb │ ├── connection.rb │ ├── error.rb │ ├── pagination.rb │ ├── xml.rb │ └── client.rb └── rsolr-direct.rb ├── LICENSE ├── rsolr.gemspec ├── example.rb └── README.rdoc /VERSION: -------------------------------------------------------------------------------- 1 | 1.0.0 -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require File.join(File.dirname(__FILE__), '..', 'lib', 'rsolr') -------------------------------------------------------------------------------- /spec/api/error_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | describe "RSolr::Error" do 3 | 4 | end -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | pkg/* 4 | 5 | jetty/solr/data/**/* 6 | 7 | coverage/ 8 | 9 | doc/ 10 | -------------------------------------------------------------------------------- /tasks/rsolr.rake: -------------------------------------------------------------------------------- 1 | namespace :rsolr do 2 | 3 | namespace :solr do 4 | desc "Starts the HTTP server (port 9999) used for running HTTP connection tests" 5 | task :start do 6 | system "cd jetty; java -jar start.jar" 7 | end 8 | end 9 | 10 | end -------------------------------------------------------------------------------- /tasks/rdoc.rake: -------------------------------------------------------------------------------- 1 | # Rdoc 2 | desc 'Generate documentation for the rsolr gem.' 3 | Rake::RDocTask.new(:doc) do |rdoc| 4 | rdoc.rdoc_dir = 'doc' 5 | rdoc.title = 'RSolr' 6 | rdoc.options << '--line-numbers' << '--inline-source' 7 | rdoc.rdoc_files.include('README.rdoc') 8 | rdoc.rdoc_files.include('lib/**/*.rb') 9 | end -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rake' 2 | require 'rake/testtask' 3 | require 'rake/rdoctask' 4 | require 'rake/gempackagetask' 5 | 6 | ENV['RUBYOPT'] = '-W1' 7 | 8 | task :environment do 9 | require File.dirname(__FILE__) + '/lib/rsolr' 10 | end 11 | 12 | Dir['tasks/**/*.rake'].each { |t| load t } 13 | 14 | task :default => ['spec:api'] -------------------------------------------------------------------------------- /spec/api/connection_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | describe "RSolr::Connection" do 3 | 4 | # context "execute" do 5 | # c = RSolr::Connection.new 6 | # base_url = "http://localhost:8983/solr" 7 | # client = RSolr::Client.new c, :url => base_url 8 | # c.execute client, {:method => :get, :uri => URI.parse(base_url + "/select")} 9 | # end 10 | 11 | end -------------------------------------------------------------------------------- /spec/api/char_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | describe "RSolr::Char" do 3 | 4 | let(:char){Object.new.extend RSolr::Char} 5 | 6 | it 'should escape everything that is not a word with \\' do 7 | (0..255).each do |ascii| 8 | chr = ascii.chr 9 | esc = char.escape(chr) 10 | if chr =~ /\W/ 11 | esc.to_s.should == "\\#{chr}" 12 | else 13 | esc.to_s.should == chr 14 | end 15 | end 16 | end 17 | 18 | end -------------------------------------------------------------------------------- /spec/api/rsolr_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | describe "RSolr" do 3 | 4 | it "has a version that can be read via #version or VERSION" do 5 | RSolr.version.should == RSolr::VERSION 6 | end 7 | 8 | it "can escape" do 9 | RSolr.should be_a(RSolr::Char) 10 | RSolr.escape("this string").should == "this\\ string" 11 | end 12 | 13 | context "connect" do 14 | it "should return a RSolr::Client instance" do 15 | RSolr.connect.should be_a(RSolr::Client) 16 | end 17 | end 18 | 19 | end -------------------------------------------------------------------------------- /lib/rsolr.rb: -------------------------------------------------------------------------------- 1 | $: << "#{File.dirname(__FILE__)}" unless $:.include? File.dirname(__FILE__) 2 | 3 | require 'rubygems' 4 | 5 | module RSolr 6 | 7 | %W(Char Client Error Connection Pagination Uri Xml).each{|n|autoload n.to_sym, "rsolr/#{n.downcase}"} 8 | 9 | def self.version 10 | @version ||= File.read(File.join(File.dirname(__FILE__), '..', 'VERSION')).chomp 11 | end 12 | 13 | VERSION = self.version 14 | 15 | def self.connect *args 16 | Client.new Connection.new, *args 17 | end 18 | 19 | # RSolr.escape 20 | extend Char 21 | 22 | end -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2008-2010 Matt Mitchell 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. -------------------------------------------------------------------------------- /lib/rsolr/char.rb: -------------------------------------------------------------------------------- 1 | # A module that contains (1) string related methods 2 | module RSolr::Char 3 | 4 | # backslash everything 5 | # that isn't a word character 6 | def escape value 7 | value.gsub /(\W)/, '\\\\\1' 8 | end 9 | 10 | # LUCENE_CHAR_RX = /([\+\-\!\(\)\[\]\^\"\~\*\?\:\\]+)/ 11 | # LUCENE_WORD_RX = /(OR|AND|NOT)/ 12 | # 13 | # # More specific/lucene escape sequence 14 | # def lucene_escape string 15 | # delim = " " 16 | # string.gsub(LUCENE_CHAR_RX, '\\\\\1').split(delim).map { |v| 17 | # v.gsub(LUCENE_WORD_RX, '\\\\\1') 18 | # }.join(delim) 19 | # end 20 | 21 | end -------------------------------------------------------------------------------- /tasks/rcov.rake: -------------------------------------------------------------------------------- 1 | # require 'rake' 2 | # require 'spec/rake/spectask' 3 | # 4 | # desc 'run specs with rcov' 5 | # Spec::Rake::SpecTask.new('rcov') do |t| 6 | # t.spec_files = FileList['spec/**/*_spec.rb'] 7 | # t.rcov = true 8 | # t.rcov_dir = File.join('coverage', 'all') 9 | # # --only-uncovered 10 | # t.rcov_opts.concat(['--exclude', 'spec', '--sort', 'coverage']) 11 | # end 12 | # 13 | # namespace :rcov do 14 | # desc 'run api specs with rcov' 15 | # Spec::Rake::SpecTask.new('api') do |t| 16 | # rm_f "coverage" 17 | # rm_f "coverage.data" 18 | # t.spec_files = FileList['spec/spec_helper.rb', 'spec/api/**/*_spec.rb'] 19 | # t.rcov = true 20 | # t.rcov_dir = File.join('coverage', 'api') 21 | # # --only-uncovered 22 | # t.rcov_opts.concat(['--exclude', 'spec', '--sort', 'coverage']) 23 | # end 24 | # 25 | # end -------------------------------------------------------------------------------- /tasks/jeweler.rake: -------------------------------------------------------------------------------- 1 | begin 2 | require 'jeweler' 3 | Jeweler::Tasks.new do |gemspec| 4 | gemspec.name = "rsolr" 5 | gemspec.summary = "A Ruby client for Apache Solr" 6 | gemspec.description = "RSolr aims to provide a simple and extensible library for working with Solr" 7 | gemspec.email = "goodieboy@gmail.com" 8 | gemspec.homepage = "http://github.com/mwmitchell/rsolr" 9 | gemspec.authors = ["Matt Mitchell"] 10 | 11 | gemspec.files = FileList['lib/**/*.rb', 'LICENSE', 'README.rdoc', 'CHANGES', 'VERSION']. 12 | exclude("rsolr-direct.rb") 13 | 14 | gemspec.test_files = ['spec/**/*.rb', 'Rakefile', 'tasks/spec.rake', 'tasks/rdoc.rake'] 15 | 16 | gemspec.add_dependency('builder', '>= 2.1.2') 17 | 18 | #require File.dirname(__FILE__) + '/../lib/rsolr' 19 | #gemspec.version = RSolr.version 20 | 21 | now = Time.now 22 | gemspec.date = "#{now.year}-#{now.month}-#{now.day}" 23 | 24 | gemspec.has_rdoc = true 25 | end 26 | 27 | # Jeweler::GemcutterTasks.new 28 | 29 | rescue LoadError 30 | puts "Jeweler not available. Install it with: gem install jeweler" 31 | end -------------------------------------------------------------------------------- /tasks/spec.rake: -------------------------------------------------------------------------------- 1 | gem 'rspec' 2 | 3 | # $stderr.puts `gem list` 4 | 5 | require 'spec' 6 | require 'spec/rake/spectask' 7 | 8 | namespace :spec do 9 | 10 | namespace :ruby do 11 | desc 'run api specs through the Ruby implementations' 12 | task :api do 13 | puts "Ruby 1.8.7" 14 | puts `rake spec:api` 15 | puts "Ruby 1.9" 16 | puts `rake1.9 spec:api` 17 | puts "JRuby" 18 | puts `jruby -S rake spec:api` 19 | end 20 | end 21 | 22 | desc 'run api specs (mock out Solr dependency)' 23 | Spec::Rake::SpecTask.new(:api) do |t| 24 | 25 | t.spec_files = [File.join('spec', 'spec_helper.rb')] 26 | t.spec_files += FileList[File.join('spec', 'api', '**', '*_spec.rb')] 27 | 28 | t.verbose = true 29 | t.spec_opts = ['--color'] 30 | end 31 | 32 | desc 'run integration specs' 33 | Spec::Rake::SpecTask.new(:integration) do |t| 34 | 35 | t.spec_files = [File.join('spec', 'spec_helper.rb')] 36 | t.spec_files += FileList[File.join('spec', 'integration', '**', '*_spec.rb')] 37 | 38 | t.verbose = true 39 | t.spec_opts = ['--color'] 40 | end 41 | 42 | end -------------------------------------------------------------------------------- /spec/api/pagination_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | describe "RSolr::Pagination" do 3 | context "calculate_start_and_rows" do 4 | it "should return an array with 2 ints" do 5 | values = RSolr::Pagination.calculate_start_and_rows 2, 10 6 | values[0].should == 10 7 | values[1].should == 10 8 | end 9 | it "should handle string values" do 10 | values = RSolr::Pagination.calculate_start_and_rows "1", "22" 11 | values[0].should == 0 12 | values[1].should == 22 13 | end 14 | end 15 | context "build_paginated_request" do 16 | it "should create the proper solr params and query string" do 17 | c = RSolr::Client.new(nil, {}).extend(RSolr::Pagination::Client) 18 | r = c.build_paginated_request 3, 25, "select", {:params => {:q => "test"}} 19 | r[:page].should == 3 20 | r[:per_page].should == 25 21 | r[:params][:start].should == 50 22 | r[:params][:rows].should == 25 23 | r[:uri].query.should =~ /rows=25/ 24 | r[:uri].query.should =~ /start=50/ 25 | end 26 | end 27 | context "paginate" do 28 | it "should build a paginated request context and call execute" do 29 | c = RSolr::Client.new(nil, {}).extend(RSolr::Pagination::Client) 30 | c.should_receive(:execute).with(hash_including({ 31 | :page => 1, 32 | :per_page => 10, 33 | :params => { 34 | :rows => 10, 35 | :start => 0, 36 | :wt => :ruby 37 | } 38 | })) 39 | c.paginate 1, 10, "select" 40 | end 41 | end 42 | end -------------------------------------------------------------------------------- /lib/rsolr/uri.rb: -------------------------------------------------------------------------------- 1 | require 'uri' 2 | 3 | module RSolr::Uri 4 | 5 | def create url 6 | ::URI.parse url[-1] == ?/ ? url : "#{url}/" 7 | end 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 | # Creates a Solr based query string. 30 | # Keys that have arrays values are set multiple times: 31 | # params_to_solr(:q => 'query', :fq => ['a', 'b']) 32 | # is converted to: 33 | # ?q=query&fq=a&fq=b 34 | def params_to_solr(params, escape = true) 35 | mapped = params.map do |k, v| 36 | next if v.to_s.empty? 37 | if v.class == Array 38 | params_to_solr(v.map { |x| [k, x] }, escape) 39 | else 40 | build_param k, v, escape 41 | end 42 | end 43 | mapped.compact.join("&") 44 | end 45 | 46 | # Performs URI escaping so that you can construct proper 47 | # query strings faster. Use this rather than the cgi.rb 48 | # version since it's faster. 49 | # (Stolen from Rack). 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(' ', '+') 54 | end 55 | 56 | extend self 57 | 58 | end -------------------------------------------------------------------------------- /rsolr.gemspec: -------------------------------------------------------------------------------- 1 | # Generated by jeweler 2 | # DO NOT EDIT THIS FILE DIRECTLY 3 | # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command 4 | # -*- encoding: utf-8 -*- 5 | 6 | Gem::Specification.new do |s| 7 | s.name = %q{rsolr} 8 | s.version = "1.0.0" 9 | 10 | s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= 11 | s.authors = ["Matt Mitchell"] 12 | s.date = %q{2011-01-05} 13 | s.description = %q{RSolr aims to provide a simple and extensible library for working with Solr} 14 | s.email = %q{goodieboy@gmail.com} 15 | s.extra_rdoc_files = [ 16 | "LICENSE", 17 | "README.rdoc" 18 | ] 19 | s.files = [ 20 | "LICENSE", 21 | "README.rdoc", 22 | "VERSION", 23 | "lib/rsolr.rb", 24 | "lib/rsolr/char.rb", 25 | "lib/rsolr/client.rb", 26 | "lib/rsolr/connection.rb", 27 | "lib/rsolr/error.rb", 28 | "lib/rsolr/pagination.rb", 29 | "lib/rsolr/uri.rb", 30 | "lib/rsolr/xml.rb" 31 | ] 32 | s.homepage = %q{http://github.com/mwmitchell/rsolr} 33 | s.rdoc_options = ["--charset=UTF-8"] 34 | s.require_paths = ["lib"] 35 | s.rubygems_version = %q{1.3.6} 36 | s.summary = %q{A Ruby client for Apache Solr} 37 | s.test_files = [ 38 | "spec/api/char_spec.rb", 39 | "spec/api/client_spec.rb", 40 | "spec/api/connection_spec.rb", 41 | "spec/api/error_spec.rb", 42 | "spec/api/pagination_spec.rb", 43 | "spec/api/rsolr_spec.rb", 44 | "spec/api/uri_spec.rb", 45 | "spec/api/xml_spec.rb", 46 | "spec/spec_helper.rb", 47 | "Rakefile", 48 | "tasks/spec.rake", 49 | "tasks/rdoc.rake" 50 | ] 51 | 52 | if s.respond_to? :specification_version then 53 | current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION 54 | s.specification_version = 3 55 | 56 | if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then 57 | s.add_runtime_dependency(%q, [">= 2.1.2"]) 58 | else 59 | s.add_dependency(%q, [">= 2.1.2"]) 60 | end 61 | else 62 | s.add_dependency(%q, [">= 2.1.2"]) 63 | end 64 | end 65 | 66 | -------------------------------------------------------------------------------- /spec/api/uri_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | describe "RSolr::Uri" do 3 | 4 | context "class-level methods" do 5 | 6 | let(:uri){ RSolr::Uri } 7 | 8 | it "should return a URI object with a trailing slash" do 9 | u = uri.create 'http://apache.org' 10 | u.path[0].should == ?/ 11 | end 12 | 13 | it "should return the bytesize of a string" do 14 | uri.bytesize("test").should == 4 15 | end 16 | 17 | it "should convert a solr query string from a hash w/o a starting ?" do 18 | hash = {:q => "gold", :fq => ["mode:one", "level:2"]} 19 | query = uri.params_to_solr hash 20 | query[0].should_not == ?? 21 | [/q=gold/, /fq=mode%3Aone/, /fq=level%3A2/].each do |p| 22 | query.should match p 23 | end 24 | query.split('&').size.should == 3 25 | end 26 | 27 | context "escape_query_value" do 28 | 29 | it 'should escape &' do 30 | uri.params_to_solr(:fq => "&").should == 'fq=%26' 31 | end 32 | 33 | it 'should convert spaces to +' do 34 | uri.params_to_solr(:fq => "me and you").should == 'fq=me+and+you' 35 | end 36 | 37 | it 'should escape comlex queries, part 1' do 38 | my_params = {'fq' => '{!raw f=field_name}crazy+\"field+value'} 39 | expected = 'fq=%7B%21raw+f%3Dfield_name%7Dcrazy%2B%5C%22field%2Bvalue' 40 | uri.params_to_solr(my_params).should == expected 41 | end 42 | 43 | it 'should escape complex queries, part 2' do 44 | my_params = {'q' => '+popularity:[10 TO *] +section:0'} 45 | expected = 'q=%2Bpopularity%3A%5B10+TO+%2A%5D+%2Bsection%3A0' 46 | uri.params_to_solr(my_params).should == expected 47 | end 48 | 49 | it 'should escape properly' do 50 | uri.escape_query_value('+').should == '%2B' 51 | uri.escape_query_value('This is a test').should == 'This+is+a+test' 52 | uri.escape_query_value('<>/\\').should == '%3C%3E%2F%5C' 53 | uri.escape_query_value('"').should == '%22' 54 | uri.escape_query_value(':').should == '%3A' 55 | end 56 | 57 | it 'should escape brackets' do 58 | uri.escape_query_value('{').should == '%7B' 59 | uri.escape_query_value('}').should == '%7D' 60 | end 61 | 62 | it 'should escape exclamation marks!' do 63 | uri.escape_query_value('!').should == '%21' 64 | end 65 | 66 | end 67 | 68 | end 69 | 70 | end -------------------------------------------------------------------------------- /lib/rsolr/connection.rb: -------------------------------------------------------------------------------- 1 | require 'net/http' 2 | require 'net/https' 3 | 4 | # The default/Net::Http adapter for RSolr. 5 | class RSolr::Connection 6 | 7 | # using the request_context hash, 8 | # send a request, 9 | # then return the standard rsolr response hash {:status, :body, :headers} 10 | def execute client, request_context 11 | h = http request_context[:uri], request_context[:proxy] 12 | request = setup_raw_request request_context 13 | request.body = request_context[:data] if request_context[:method] == :post and request_context[:data] 14 | begin 15 | response = h.request request 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 18 | rescue NoMethodError 19 | $!.message == "undefined method `closed?' for nil:NilClass" ? 20 | raise(Errno::ECONNREFUSED.new) : 21 | raise($!) 22 | end 23 | end 24 | 25 | protected 26 | 27 | # This returns a singleton of a Net::HTTP or Net::HTTP.Proxy request object. 28 | def http uri, proxy = nil 29 | @http ||= ( 30 | http = if proxy 31 | proxy_user, proxy_pass = proxy.userinfo.split(/:/) if proxy.userinfo 32 | Net::HTTP.Proxy(proxy.host, proxy.port, proxy_user, proxy_pass).new uri.host, uri.port 33 | else 34 | Net::HTTP.new uri.host, uri.port 35 | end 36 | http.use_ssl = uri.port == 443 || uri.instance_of?(URI::HTTPS) 37 | http 38 | ) 39 | end 40 | 41 | # 42 | def setup_raw_request request_context 43 | http_method = case request_context[:method] 44 | when :get 45 | Net::HTTP::Get 46 | when :post 47 | #require 'net/http/post/multipart' 48 | #File === request_context[:data] ? Net::HTTP::Post::Multipart : Net::HTTP::Post 49 | Net::HTTP::Post 50 | when :head 51 | Net::HTTP::Head 52 | else 53 | raise "Only :get, :post and :head http method types are allowed." 54 | end 55 | headers = request_context[:headers] || {} 56 | # if http_method.to_s == "Net::HTTP::Post::Multipart" 57 | # io = request_context[:data] 58 | # UploadIO.convert! io, request_context[:headers]["Content-Type"], io.path, io.path 59 | # raw_request = 60 | # Net::HTTP::Post::Multipart.new( 61 | # request_context[:path], 62 | # :file => io) 63 | # else 64 | raw_request = http_method.new request_context[:uri].to_s 65 | # end 66 | raw_request.initialize_http_header headers 67 | raw_request 68 | end 69 | 70 | end -------------------------------------------------------------------------------- /example.rb: -------------------------------------------------------------------------------- 1 | require "#{File.dirname(__FILE__)}/lib/rsolr" 2 | require 'rubygems' 3 | require 'builder' 4 | 5 | # puts RSolr::Uri.params_to_solr({:id => "test this - or this, '; ", :fq => [1, 2, 3]}, false) 6 | # puts RSolr.escape("OR this and +that but NOT this") 7 | 8 | solr = RSolr.connect :url => "http://localhost:8983/solr" 9 | 10 | r = solr.paginate 23, 10, "select", :params => {:q => "*:*"} 11 | 12 | puts r["response"]["docs"].inspect 13 | 14 | begin 15 | r = solr.get 'select', :params => {:q => '*:*', :wt => :ruby} 16 | rescue 17 | puts $! 18 | puts $!.backtrace 19 | exit 20 | end 21 | 22 | puts r["response"]["docs"].inspect 23 | 24 | r = solr.build_request "select", :params => {:q => "hello", :fq => ["one:1", "two:2"]}, :method => :post 25 | puts r[:uri] 26 | 27 | begin 28 | r = solr.select :params => {"q" => "*:*", "facet" => true, "facet.field" => "cat"} 29 | r["facet_counts"]["facet_fields"].each_pair do |field, hits| 30 | hits.each_slice 2 do |k,v| 31 | puts "#{k} : #{v}" 32 | end 33 | puts 34 | end 35 | rescue 36 | raise $! 37 | end 38 | 39 | begin 40 | r = solr.select :params => {:q => '*:*!', :fw => ["one", "two"]} 41 | rescue 42 | puts $! 43 | puts $!.backtrace.join("\n") 44 | end 45 | 46 | puts 47 | 48 | r = solr.build_request 'select', :params => {:q => '*:*', :fw => ["one", "two"]} 49 | puts r[:uri].inspect 50 | 51 | puts 52 | 53 | response = solr.get "select", :params => {:q => "*:*"} 54 | puts response["response"]["docs"].size 55 | 56 | r = solr.select( 57 | :params => {:q => '*:*'}, 58 | :headers => {"Cache-Control" => "max-age=0, no-cache, no-store, must-revalidate"} 59 | ) 60 | 61 | puts "basic select using RSolr::Client #method_missing" 62 | puts r.inspect 63 | puts 64 | 65 | # "blah/blah/blah" doesn't exists, so a http error is raised... 66 | begin 67 | solr.head "blah/blah/blah" 68 | rescue 69 | puts $!.request.inspect 70 | puts "=====================" 71 | # the error will have #request and #response attributes 72 | # puts "blah blah blah HEAD response: " + $!.response.inspect 73 | end 74 | 75 | puts 76 | 77 | # "admin" exists so we can check the return value's original response status code 78 | #puts "admin HEAD response: " + solr.head("admin").inspect 79 | #puts 80 | 81 | # add some shiz via solr.xml 82 | add_xml = solr.xml.add({:name_s => "blah blah", :id => Time.now.to_s}, :add_attributes => {:boost=>5.0, :commitWithin=>1}) do |xml| 83 | # can setup individual doc add attributes here... 84 | end 85 | 86 | solr.update :data => add_xml 87 | solr.commit 88 | solr.optimize 89 | 90 | begin 91 | result = solr.get 'select', :params => {:q => '*:*'} 92 | puts "response['docs']:" 93 | result['response']['docs'].each do |doc| 94 | puts doc.inspect 95 | end 96 | rescue 97 | puts $!.to_s 98 | end 99 | 100 | puts 101 | 102 | # puts "Deleting all!" 103 | # solr.delete_by_query "*:*" 104 | # solr.commit -------------------------------------------------------------------------------- /lib/rsolr/error.rb: -------------------------------------------------------------------------------- 1 | module RSolr::Error 2 | 3 | module SolrContext 4 | 5 | attr_accessor :request, :response 6 | 7 | def to_s 8 | m = "#{super.to_s}" 9 | if response 10 | m << " - #{response[:status]} #{Http::STATUS_CODES[response[:status].to_i]}" 11 | details = parse_solr_error_response response[:body] 12 | m << "\nError: #{details}\n" if details 13 | end 14 | p = "\nQuery: #{request[:path]}?#{request[:query]}" 15 | p = "\nRequest Headers: #{request[:headers].inspect}" if request[:headers] 16 | p = "\nRequest Data: #{request[:data].inspect}" if request[:data] 17 | p << "\n" 18 | p << "\nBacktrace: " + self.backtrace[0..10].join("\n") 19 | m << p 20 | m 21 | end 22 | 23 | protected 24 | 25 | def parse_solr_error_response body 26 | begin 27 | info = body.scan(/
(.*)<\/pre>/mi)[0]
 28 |         partial = info.to_s.split("\n")[0..10]
 29 |         partial.join("\n").gsub(">", ">").gsub("<", "<")
 30 |       rescue
 31 |         nil
 32 |       end
 33 |     end
 34 |     
 35 |   end
 36 |   
 37 |   class Http < RuntimeError
 38 |     
 39 |     include SolrContext
 40 |     
 41 |     # ripped right from ActionPack
 42 |     # Defines the standard HTTP status codes, by integer, with their
 43 |     # corresponding default message texts.
 44 |     # Source: http://www.iana.org/assignments/http-status-codes
 45 |     STATUS_CODES = {
 46 |       100 => "Continue",
 47 |       101 => "Switching Protocols",
 48 |       102 => "Processing",
 49 | 
 50 |       200 => "OK",
 51 |       201 => "Created",
 52 |       202 => "Accepted",
 53 |       203 => "Non-Authoritative Information",
 54 |       204 => "No Content",
 55 |       205 => "Reset Content",
 56 |       206 => "Partial Content",
 57 |       207 => "Multi-Status",
 58 |       226 => "IM Used",
 59 | 
 60 |       300 => "Multiple Choices",
 61 |       301 => "Moved Permanently",
 62 |       302 => "Found",
 63 |       303 => "See Other",
 64 |       304 => "Not Modified",
 65 |       305 => "Use Proxy",
 66 |       307 => "Temporary Redirect",
 67 | 
 68 |       400 => "Bad Request",
 69 |       401 => "Unauthorized",
 70 |       402 => "Payment Required",
 71 |       403 => "Forbidden",
 72 |       404 => "Not Found",
 73 |       405 => "Method Not Allowed",
 74 |       406 => "Not Acceptable",
 75 |       407 => "Proxy Authentication Required",
 76 |       408 => "Request Timeout",
 77 |       409 => "Conflict",
 78 |       410 => "Gone",
 79 |       411 => "Length Required",
 80 |       412 => "Precondition Failed",
 81 |       413 => "Request Entity Too Large",
 82 |       414 => "Request-URI Too Long",
 83 |       415 => "Unsupported Media Type",
 84 |       416 => "Requested Range Not Satisfiable",
 85 |       417 => "Expectation Failed",
 86 |       422 => "Unprocessable Entity",
 87 |       423 => "Locked",
 88 |       424 => "Failed Dependency",
 89 |       426 => "Upgrade Required",
 90 | 
 91 |       500 => "Internal Server Error",
 92 |       501 => "Not Implemented",
 93 |       502 => "Bad Gateway",
 94 |       503 => "Service Unavailable",
 95 |       504 => "Gateway Timeout",
 96 |       505 => "HTTP Version Not Supported",
 97 |       507 => "Insufficient Storage",
 98 |       510 => "Not Extended"
 99 |     }
100 |     
101 |     def initialize request, response
102 |       @request, @response = request, response
103 |     end
104 |     
105 |   end
106 |   
107 |   # Thrown if the :wt is :ruby
108 |   # but the body wasn't succesfully parsed/evaluated
109 |   class InvalidRubyResponse < Http
110 |     
111 |   end
112 |   
113 | end


--------------------------------------------------------------------------------
/lib/rsolr-direct.rb:
--------------------------------------------------------------------------------
  1 | require 'java'
  2 | require 'rubygems'
  3 | require 'rsolr'
  4 | 
  5 | #
  6 | # Connection for JRuby + DirectSolrConnection
  7 | #
  8 | module RSolr::Direct
  9 |   
 10 |   # load the java libs that ship with rsolr-direct
 11 |   # RSolr.load_java_libs
 12 |   # rsolr = RSolr.connect :direct, :solr_home => ''
 13 |   def self.load_java_libs apache_solr_dir
 14 |     @java_libs_loaded ||= (
 15 |       base_dir = File.expand_path(apache_solr_dir)
 16 |       ['lib', 'dist'].each do |sub|
 17 |         Dir[File.join(base_dir, sub, '*.jar')].each do |jar|
 18 |           require jar
 19 |         end
 20 |       end
 21 |       true
 22 |     )
 23 |   end
 24 |   
 25 |   RSolr.class_eval do
 26 |     # RSolr.direct_connect :solr_home => 'apache-solr/example/solr'
 27 |     # RSolr.direct_connect java_solr_core
 28 |     # RSolr.direct_connect java_direct_solr_connection
 29 |     def self.direct_connect *args, &blk
 30 |       client = RSolr::Client.new RSolr::Direct::Connection.new(*args), {:url => false}
 31 |       if block_given?
 32 |         yield client
 33 |         client.connection.close
 34 |         nil
 35 |       else
 36 |         client
 37 |       end
 38 |     end
 39 |   end
 40 |   
 41 |   class Connection
 42 |     
 43 |     attr_accessor :opts
 44 |     
 45 |     class MissingRequiredJavaLibs < RuntimeError
 46 |     end
 47 |     
 48 |     class InvalidSolrHome < RuntimeError
 49 |     end
 50 |     
 51 |     # opts can be an instance of org.apache.solr.servlet.DirectSolrConnection
 52 |     # if opts is NOT an instance of org.apache.solr.servlet.DirectSolrConnection
 53 |     # then...
 54 |     # required: opts[:solr_home] is absolute path to solr home (the directory with "data", "config" etc.)
 55 |     def initialize opts
 56 |       begin
 57 |         org.apache.solr.servlet.DirectSolrConnection
 58 |       rescue NameError
 59 |         raise MissingRequiredJavaLibs
 60 |       end
 61 |       if opts.is_a?(Hash) and opts[:solr_home]
 62 |         raise InvalidSolrHome unless File.exists?(opts[:solr_home])
 63 |         opts[:data_dir] ||= File.join(opts[:solr_home], 'data')
 64 |         @opts = opts
 65 |       elsif opts.class.to_s == "Java::OrgApacheSolrCore::SolrCore"
 66 |         @direct = org.apache.solr.servlet.DirectSolrConnection.new(opts)
 67 |       elsif opts.class.to_s == "Java::OrgApacheSolrServlet::DirectSolrConnection"
 68 |         @direct = opts
 69 |       end
 70 |       opts[:auto_connect] = true unless opts.key?(:auto_connect)
 71 |       self.direct if opts[:auto_connect]
 72 |     end
 73 |     
 74 |     # sets the @direct instance variable if it has not yet been set
 75 |     def direct
 76 |       @direct ||= org.apache.solr.servlet.DirectSolrConnection.new(opts[:solr_home], @opts[:data_dir], nil)
 77 |     end
 78 |     
 79 |     # rsolr.connection.open
 80 |     alias_method :open, :direct
 81 |     
 82 |     def close
 83 |       if @direct
 84 |         @direct.close
 85 |         @direct = nil
 86 |       end
 87 |     end
 88 |     
 89 |     # send a request to the connection
 90 |     def execute client, request_context
 91 |       #data = request_context[:data]
 92 |       #data = data.to_xml if data.respond_to?(:to_xml)
 93 |       url = [request_context[:path], request_context[:query]].join("?")
 94 |       url = "/" + url unless url[0].chr == "/"
 95 |       begin
 96 |         body = direct.request(url, request_context[:data])
 97 |       rescue
 98 |         $!.extend RSolr::Error::SolrContext
 99 |         $!.request = request_context
100 |         raise $!
101 |       end
102 |       {
103 |         :status => 200,
104 |         :body => body,
105 |         :headers => {}
106 |       }
107 |     end
108 |     
109 |   end
110 |   
111 | end


--------------------------------------------------------------------------------
/lib/rsolr/pagination.rb:
--------------------------------------------------------------------------------
  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 = per_page.to_s.to_i
  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 = nil
 20 |       request_context = build_paginated_request page, per_page, path, opts
 21 |       execute request_context
 22 |     end
 23 |     
 24 |     # Just like RSolr::Client #build_request
 25 |     # but converts the page and per_page
 26 |     # arguments into :rows and :start.
 27 |     def build_paginated_request page, per_page, path, opts = nil
 28 |       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 * 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 =~ /^paginated?_(.+)$/
 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 PaginatedDocSet 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 |       if request[:page] && request[:per_page] && result["response"] && result["response"]["docs"]
 67 |         d = result['response']['docs'].extend PaginatedDocSet
 68 |         d.per_page = request[:per_page]
 69 |         d.start = request[:params][:start]
 70 |         d.total = result["response"]["numFound"].to_s.to_i
 71 |       end
 72 |       result
 73 |     end
 74 |     
 75 |   end
 76 |   
 77 |   # A response module which gets mixed into the solr ["response"]["docs"] array.
 78 |   module PaginatedDocSet
 79 | 
 80 |     attr_accessor :start, :per_page, :total
 81 | 
 82 |     # Returns the current page calculated from 'rows' and 'start'
 83 |     # WillPaginate hook
 84 |     def current_page
 85 |       return 1 if start < 1
 86 |       per_page_normalized = per_page < 1 ? 1 : per_page
 87 |       @current_page ||= (start / per_page_normalized).ceil + 1
 88 |     end
 89 | 
 90 |     # Calcuates the total pages from 'numFound' and 'rows'
 91 |     # WillPaginate hook
 92 |     def total_pages
 93 |       @total_pages ||= per_page > 0 ? (total / per_page.to_f).ceil : 1
 94 |     end
 95 | 
 96 |     # returns the previous page number or 1
 97 |     # WillPaginate hook
 98 |     def previous_page
 99 |       @previous_page ||= (current_page > 1) ? current_page - 1 : 1
100 |     end
101 | 
102 |     # returns the next page number or the last
103 |     # WillPaginate hook
104 |     def next_page
105 |       @next_page ||= (current_page == total_pages) ? total_pages : current_page+1
106 |     end
107 | 
108 |     def has_next?
109 |       current_page < total_pages
110 |     end
111 | 
112 |     def has_previous?
113 |       current_page > 1
114 |     end
115 | 
116 |   end
117 |   
118 | end


--------------------------------------------------------------------------------
/spec/api/xml_spec.rb:
--------------------------------------------------------------------------------
  1 | require 'spec_helper'
  2 | describe "RSolr::Xml" do
  3 |   
  4 |   let(:generator){ RSolr::Xml::Generator.new }
  5 |   
  6 |   # call all of the simple methods...
  7 |   # make sure the xml string is valid
  8 |   # ensure the class is actually Solr::XML
  9 |   [:optimize, :rollback, :commit].each do |meth|
 10 |     it "#{meth} should generator xml" do
 11 |       result = generator.send(meth)
 12 |       result.should == "<#{meth}/>"
 13 |     end
 14 |   end
 15 |   
 16 |   context :add do
 17 |     
 18 |     it 'should yield a Message::Document object when #add is called with a block' do
 19 |       documents = [{:id=>1, :name=>'sam', :cat=>['cat 1', 'cat 2']}]
 20 |       add_attrs = {:boost=>200.00}
 21 |       result = generator.add(documents, add_attrs) do |doc|
 22 |         doc.field_by_name(:name).attrs[:boost] = 10
 23 |         doc.fields.size.should == 4
 24 |         doc.fields_by_name(:cat).size.should == 2
 25 |       end
 26 |       result.should match(%r(name="cat">cat 1))
 27 |       result.should match(%r(name="cat">cat 2))
 28 |       result.should match(%r())
 29 |       result.should match(%r(boost="10"))
 30 |       result.should match(%r(1))
 31 |     end
 32 |     
 33 |     # add a single hash ("doc")
 34 |     it 'should create an add from a hash' do
 35 |       data = {
 36 |         :id=>1,
 37 |         :name=>'matt'
 38 |       }
 39 |       result = generator.add(data)
 40 |       result.should match(/matt<\/field>/)
 41 |       result.should match(/1<\/field>/)
 42 |     end
 43 | 
 44 |     # add an array of hashes
 45 |     it 'should create many adds from an array of hashes' do
 46 |       data = [
 47 |         {
 48 |           :id=>1,
 49 |           :name=>'matt'
 50 |         },
 51 |         {
 52 |           :id=>2,
 53 |           :name=>'sam'
 54 |         }
 55 |       ]
 56 |       message = generator.add(data)
 57 |       expected = "1matt2sam"
 58 |       message.should match(/matt<\/field>/)
 59 |       message.should match(/sam<\/field>/)
 60 |     end
 61 | 
 62 |     # multiValue field support test, thanks to Fouad Mardini!
 63 |     it 'should create multiple fields from array values' do
 64 |       data = {
 65 |         :id   => 1,
 66 |         :name => ['matt1', 'matt2']
 67 |       }
 68 |       result = generator.add(data)
 69 |       result.should match(/matt1<\/field>/)
 70 |       result.should match(/matt2<\/field>/)
 71 |     end
 72 | 
 73 |     it 'should create an add from a single Message::Document' do
 74 |       document = RSolr::Xml::Document.new
 75 |       document.add_field('id', 1)
 76 |       document.add_field('name', 'matt', :boost => 2.0)
 77 |       result = generator.add(document)
 78 |       result.should match(Regexp.escape(''))
 79 |       result.should match(/1<\/field>/)
 80 |       result.should match Regexp.escape('boost="2.0"')
 81 |       result.should match Regexp.escape('name="name"')
 82 |       result.should match Regexp.escape('matt')
 83 |     end
 84 |     
 85 |     it 'should create adds from multiple Message::Documents' do
 86 |       documents = (1..2).map do |i|
 87 |         doc = RSolr::Xml::Document.new
 88 |         doc.add_field('id', i)
 89 |         doc.add_field('name', "matt#{i}")
 90 |         doc
 91 |       end
 92 |       result = generator.add(documents)
 93 |       result.should match(/matt1<\/field>/)
 94 |       result.should match(/matt2<\/field>/)
 95 |     end
 96 |     
 97 |   end
 98 |   
 99 |   context :delete_by_id do
100 |     
101 |     it 'should create a doc id delete' do
102 |       generator.delete_by_id(10).should == "10"
103 |     end
104 |     
105 |     it 'should create many doc id deletes' do
106 |       generator.delete_by_id([1, 2, 3]).should == "123"
107 |     end
108 |     
109 |   end
110 |   
111 |   context :delete_by_query do
112 |     it 'should create a query delete' do
113 |       generator.delete_by_query('status:"LOST"').should == "status:\"LOST\""
114 |     end
115 |     
116 |     it 'should create many query deletes' do
117 |       generator.delete_by_query(['status:"LOST"', 'quantity:0']).should == "status:\"LOST\"quantity:0"
118 |     end
119 |   end
120 |   
121 | end


--------------------------------------------------------------------------------
/lib/rsolr/xml.rb:
--------------------------------------------------------------------------------
  1 | require 'builder'
  2 | 
  3 | module RSolr::Xml
  4 |   
  5 |   class Document
  6 |     
  7 |     # "attrs" is a hash for setting the "doc" xml attributes
  8 |     # "fields" is an array of Field objects
  9 |     attr_accessor :attrs, :fields
 10 | 
 11 |     # "doc_hash" must be a Hash/Mash object
 12 |     # If a value in the "doc_hash" is an array,
 13 |     # a field object is created for each value...
 14 |     def initialize(doc_hash = {})
 15 |       @fields = []
 16 |       doc_hash.each_pair do |field,values|
 17 |         # 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)
 23 |         end
 24 |       end
 25 |       @attrs={}
 26 |     end
 27 | 
 28 |     # returns an array of fields that match the "name" arg
 29 |     def fields_by_name(name)
 30 |       @fields.select{|f|f.name==name}
 31 |     end
 32 | 
 33 |     # returns the *first* field that matches the "name" arg
 34 |     def field_by_name(name)
 35 |       @fields.detect{|f|f.name==name}
 36 |     end
 37 | 
 38 |     #
 39 |     # Add a field value to the document. Options map directly to
 40 |     # XML attributes in the Solr  node.
 41 |     # See http://wiki.apache.org/solr/UpdateXmlMessages#head-8315b8028923d028950ff750a57ee22cbf7977c6
 42 |     #
 43 |     # === Example:
 44 |     #
 45 |     #   document.add_field('title', 'A Title', :boost => 2.0)
 46 |     #
 47 |     def add_field(name, value, options = {})
 48 |       @fields << RSolr::Xml::Field.new(options.merge({:name=>name}), value)
 49 |     end
 50 |     
 51 |   end
 52 |   
 53 |   class Field
 54 |     
 55 |     # "attrs" is a hash for setting the "doc" xml attributes
 56 |     # "value" is the text value for the node
 57 |     attr_accessor :attrs, :value
 58 | 
 59 |     # "attrs" must be a hash
 60 |     # "value" should be something that responds to #_to_s
 61 |     def initialize(attrs, value)
 62 |       @attrs = attrs
 63 |       @value = value
 64 |     end
 65 | 
 66 |     # the value of the "name" attribute
 67 |     def name
 68 |       @attrs[:name]
 69 |     end
 70 |     
 71 |   end
 72 |   
 73 |   class Generator
 74 |     
 75 |     def build &block
 76 |       require 'builder'
 77 |       b = ::Builder::XmlMarkup.new(:indent => 0, :margin => 0, :encoding => 'UTF-8')
 78 |       b.instruct!
 79 |       block_given? ? yield(b) : b
 80 |     end
 81 | 
 82 |     # generates "add" xml for updating solr
 83 |     # "data" can be a hash or an array of hashes.
 84 |     # - each hash should be a simple key=>value pair representing a solr doc.
 85 |     # If a value is an array, multiple fields will be created.
 86 |     #
 87 |     # "add_attrs" can be a hash for setting the add xml element attributes.
 88 |     # 
 89 |     # This method can also accept a block.
 90 |     # The value yielded to the block is a Message::Document; for each solr doc in "data".
 91 |     # You can set xml element attributes for each "doc" element or individual "field" elements.
 92 |     #
 93 |     # For example:
 94 |     #
 95 |     # solr.add({:id=>1, :nickname=>'Tim'}, {:boost=>5.0, :commitWithin=>1.0}) do |doc_msg|
 96 |     #   doc_msg.attrs[:boost] = 10.00 # boost the document
 97 |     #   nickname = doc_msg.field_by_name(:nickname)
 98 |     #   nickname.attrs[:boost] = 20 if nickname.value=='Tim' # boost a field
 99 |     # end
100 |     #
101 |     # would result in an add element having the attributes boost="10.0"
102 |     # and a commitWithin="1.0".
103 |     # Each doc element would have a boost="10.0".
104 |     # The "nickname" field would have a boost="20.0"
105 |     # if the doc had a "nickname" field with the value of "Tim".
106 |     #
107 |     def add data, add_attrs = nil, &block
108 |       add_attrs ||= {}
109 |       data = [data] unless data.is_a?(Array)
110 |       build do |xml|
111 |         xml.add(add_attrs) do |add_node|
112 |           data.each do |doc|
113 |             doc = RSolr::Xml::Document.new(doc) if doc.respond_to?(:each_pair)
114 |             yield doc if block_given?
115 |             add_node.doc(doc.attrs) do |doc_node|
116 |               doc.fields.each do |field_obj|
117 |                 doc_node.field field_obj.value, field_obj.attrs
118 |               end
119 |             end
120 |           end
121 |         end
122 |       end
123 |     end
124 |     
125 |     # generates a  message
126 |     def commit opts = nil
127 |       opts ||= {}
128 |       build {|xml| xml.commit(opts) }
129 |     end
130 |     
131 |     # generates a  message
132 |     def optimize opts = nil
133 |       opts ||= {}
134 |       build {|xml| xml.optimize(opts) }
135 |     end
136 |     
137 |     # generates a  message
138 |     def rollback
139 |       build {|xml| xml.rollback({}) }
140 |     end
141 | 
142 |     # generates a ID message
143 |     # "ids" can be a single value or array of values
144 |     def delete_by_id ids
145 |       ids = [ids] unless ids.is_a?(Array)
146 |       build do |xml|
147 |         xml.delete do |delete_node|
148 |           ids.each { |id| delete_node.id(id) }
149 |         end
150 |       end
151 |     end
152 | 
153 |     # generates a ID message
154 |     # "queries" can be a single value or an array of values
155 |     def delete_by_query(queries)
156 |       queries = [queries] unless queries.is_a?(Array)
157 |       build do |xml|
158 |         xml.delete do |delete_node|
159 |           queries.each { |query| delete_node.query(query) }
160 |         end
161 |       end
162 |     end
163 |     
164 |   end
165 |   
166 | end


--------------------------------------------------------------------------------
/spec/api/client_spec.rb:
--------------------------------------------------------------------------------
  1 | require 'spec_helper'
  2 | describe "RSolr::Client" do
  3 |   
  4 |   module ClientHelper
  5 |     def client
  6 |       @client ||= (
  7 |         connection = RSolr::Connection.new
  8 |         RSolr::Client.new connection, :url => "http://localhost:9999/solr"
  9 |       )
 10 |     end
 11 |   end
 12 |   
 13 |   context "initialize" do
 14 |     it "should accept whatevs and set it as the @connection" do
 15 |       RSolr::Client.new(:whatevs).connection.should == :whatevs
 16 |     end
 17 |   end
 18 |   
 19 |   context "send_and_receive" do
 20 |     include ClientHelper
 21 |     it "should forward these method calls the #connection object" do
 22 |       [:get, :post, :head].each do |meth|
 23 |         client.connection.should_receive(:execute).
 24 |             and_return({:status => 200, :body => "{}", :headers => {}})
 25 |         client.send_and_receive '', :method => meth, :params => {}, :data => nil, :headers => {}
 26 |       end
 27 |     end
 28 |   end
 29 | 
 30 |   context "post" do
 31 |     include ClientHelper
 32 |     it "should pass the expected params to the connection's #execute method" do
 33 |       request_opts = {:data => "the data", :method=>:post, :headers => {"Content-Type" => "text/plain"}}
 34 |       client.connection.should_receive(:execute).
 35 |         with(client, hash_including(request_opts)).
 36 |         and_return(
 37 |           :body => "",
 38 |           :status => 200,
 39 |           :headers => {"Content-Type"=>"text/plain"}
 40 |         )
 41 |       client.post "update", request_opts
 42 |     end
 43 |   end
 44 |   
 45 |   context "xml" do
 46 |     include ClientHelper
 47 |     it "should return an instance of RSolr::Xml::Generator" do
 48 |       client.xml.should be_a RSolr::Xml::Generator
 49 |     end
 50 |   end
 51 |   
 52 |   context "add" do
 53 |     include ClientHelper
 54 |     it "should send xml to the connection's #post method" do
 55 |       client.connection.should_receive(:execute).
 56 |         with(
 57 |           client, hash_including({
 58 |             :path => "update",
 59 |             :headers => {"Content-Type"=>"text/xml"},
 60 |             :method => :post,
 61 |             :data => ""
 62 |           })
 63 |         ).
 64 |           and_return(
 65 |             :body => "",
 66 |             :status => 200,
 67 |             :headers => {"Content-Type"=>"text/xml"}
 68 |           )
 69 |       client.xml.should_receive(:add).
 70 |         with({:id=>1}, {:commitWith=>10}).
 71 |           and_return("")
 72 |       client.add({:id=>1}, :add_attributes => {:commitWith=>10})
 73 |     end
 74 |   end
 75 |   
 76 |   context "update" do
 77 |     include ClientHelper
 78 |     it "should send data to the connection's #post method" do
 79 |       client.connection.should_receive(:execute).
 80 |         with(
 81 |           client, hash_including({
 82 |             :path => "update",
 83 |             :headers => {"Content-Type"=>"text/xml"},
 84 |             :method => :post,
 85 |             :data => ""
 86 |           })
 87 |         ).
 88 |           and_return(
 89 |             :body => "",
 90 |             :status => 200,
 91 |             :headers => {"Content-Type"=>"text/xml"}
 92 |           )
 93 |       client.update(:data => "")
 94 |     end
 95 |   end
 96 |   
 97 |   context "post based helper methods:" do
 98 |     include ClientHelper
 99 |     [:commit, :optimize, :rollback].each do |meth|
100 |       it "should send a #{meth} message to the connection's #post method" do
101 |         client.connection.should_receive(:execute).
102 |           with(
103 |             client, hash_including({
104 |               :path => "update",
105 |               :headers => {"Content-Type"=>"text/xml"},
106 |               :method => :post,
107 |               :data => "<#{meth}/>"
108 |             })
109 |           ).
110 |             and_return(
111 |               :body => "",
112 |               :status => 200,
113 |               :headers => {"Content-Type"=>"text/xml"}
114 |             )
115 |         client.send meth
116 |       end
117 |     end
118 |   end
119 |   
120 |   context "delete_by_id" do
121 |     include ClientHelper
122 |     it "should send data to the connection's #post method" do
123 |       client.connection.should_receive(:execute).
124 |         with(
125 |           client, hash_including({
126 |             :path => "update",
127 |             :headers => {"Content-Type"=>"text/xml"},
128 |             :method => :post,
129 |             :data => "1"
130 |           })
131 |         ).
132 |           and_return(
133 |             :body => "",
134 |             :status => 200,
135 |             :headers => {"Content-Type"=>"text/xml"}
136 |           )
137 |       client.delete_by_id 1
138 |     end
139 |   end
140 |   
141 |   context "delete_by_query" do
142 |     include ClientHelper
143 |     it "should send data to the connection's #post method" do
144 |       client.connection.should_receive(:execute).
145 |         with(
146 |           client, hash_including({
147 |             :path => "update",
148 |             :headers => {"Content-Type"=>"text/xml"},
149 |             :method => :post,
150 |             :data => ""
151 |           })
152 |         ).
153 |           and_return(
154 |             :body => "",
155 |             :status => 200,
156 |             :headers => {"Content-Type"=>"text/xml"}
157 |           )
158 |       client.delete_by_query :fq => "category:\"trash\""
159 |     end
160 |   end
161 |   
162 |   context "adapt_response" do
163 |     include ClientHelper
164 |     it 'should not try to evaluate ruby when the :qt is not :ruby' do
165 |       body = '{:time=>"NOW"}'
166 |       result = client.adapt_response({:params=>{}}, {:status => 200, :body => body, :headers => {}})
167 |       result.should == body
168 |     end
169 |     
170 |     it 'should evaluate ruby responses when the :wt is :ruby' do
171 |       body = '{:time=>"NOW"}'
172 |       result = client.adapt_response({:params=>{:wt=>:ruby}}, {:status => 200, :body => body, :headers => {}})
173 |       result.should == {:time=>"NOW"}
174 |     end
175 |     
176 |     it "ought raise a RSolr::Error::InvalidRubyResponse when the ruby is indeed frugged, or even fruggified" do
177 |       lambda {
178 |         client.adapt_response({:params=>{:wt => :ruby}}, {:status => 200, :body => "", :headers => {}})
179 |       }.should raise_error RSolr::Error::InvalidRubyResponse
180 |     end
181 |   
182 |   end
183 |   
184 |   context "build_request" do
185 |     include ClientHelper
186 |     it 'should return a request context array' do
187 |       result = client.build_request('select',
188 |         :method => :post,
189 |         :params => {:q=>'test', :fq=>[0,1]},
190 |         :data => "data",
191 |         :headers => {}
192 |       )
193 |       [/fq=0/, /fq=1/, /q=test/, /wt=ruby/].each do |pattern|
194 |         result[:query].should match pattern
195 |       end
196 |       result[:data].should == "data"
197 |       result[:headers].should == {}
198 |     end
199 |     
200 |     it "should set the Content-Type header to application/x-www-form-urlencoded if a hash is passed in to the data arg" do
201 |       result = client.build_request('select',
202 |         :method => :post,
203 |         :data => {:q=>'test', :fq=>[0,1]},
204 |         :headers => {}
205 |       )
206 |       result[:query].should == "wt=ruby"
207 |       [/fq=0/, /fq=1/, /q=test/].each do |pattern|
208 |         result[:data].should match pattern
209 |       end
210 |       result[:data].should_not match /wt=ruby/
211 |       result[:headers].should == {"Content-Type" => "application/x-www-form-urlencoded"}
212 |     end
213 |     
214 |   end
215 |   
216 | end


--------------------------------------------------------------------------------
/lib/rsolr/client.rb:
--------------------------------------------------------------------------------
  1 | class RSolr::Client
  2 |   
  3 |   attr_reader :connection, :uri, :proxy, :options
  4 |   
  5 |   def initialize connection, options = {}
  6 |     @connection = connection
  7 |     unless false === options[:url]
  8 |       url = options[:url].dup || 'http://127.0.0.1:8983/solr/'
  9 |       url << "/" unless url[-1] == ?/
 10 |       proxy_url = options[:proxy]
 11 |       proxy_url << "/" unless proxy_url.nil? or proxy_url[-1] == ?/
 12 |       @uri = RSolr::Uri.create url
 13 |       @proxy = RSolr::Uri.create proxy_url if proxy_url
 14 |     end
 15 |     @options = options
 16 |     extend RSolr::Pagination::Client
 17 |   end
 18 |   
 19 |   # returns the actual request uri object.
 20 |   def base_request_uri
 21 |     base_uri.request_uri if base_uri
 22 |   end
 23 |   
 24 |   # returns the uri proxy if present,
 25 |   # otherwise just the uri object.
 26 |   def base_uri
 27 |     @proxy || @uri
 28 |   end
 29 |   
 30 |   # Create the get, post, and head methods
 31 |   %W(get post head).each do |meth|
 32 |     class_eval <<-RUBY
 33 |     def #{meth} path, opts = {}, &block
 34 |       send_and_receive path, opts.merge(:method => :#{meth}), &block
 35 |     end
 36 |     RUBY
 37 |   end
 38 |   
 39 |   # POST XML messages to /update with optional params.
 40 |   # 
 41 |   # http://wiki.apache.org/solr/UpdateXmlMessages#add.2BAC8-update
 42 |   #
 43 |   # If not set, opts[:headers] will be set to a hash with the key
 44 |   # 'Content-Type' set to 'text/xml'
 45 |   #
 46 |   # +opts+ can/should contain:
 47 |   #
 48 |   #  :data - posted data
 49 |   #  :headers - http headers
 50 |   #  :params - solr query parameter hash
 51 |   #
 52 |   def update opts = {}
 53 |     opts[:headers] ||= {}
 54 |     opts[:headers]['Content-Type'] ||= 'text/xml'
 55 |     post 'update', opts
 56 |   end
 57 |   
 58 |   # 
 59 |   # +add+ creates xml "add" documents and sends the xml data to the +update+ method
 60 |   # 
 61 |   # http://wiki.apache.org/solr/UpdateXmlMessages#add.2BAC8-update
 62 |   # 
 63 |   # single record:
 64 |   # solr.update(:id=>1, :name=>'one')
 65 |   #
 66 |   # update using an array
 67 |   # 
 68 |   # solr.update(
 69 |   #   [{:id=>1, :name=>'one'}, {:id=>2, :name=>'two'}],
 70 |   #   :add_attributes => {:boost=>5.0, :commitWithin=>10}
 71 |   # )
 72 |   # 
 73 |   def add doc, opts = {}
 74 |     add_attributes = opts.delete :add_attributes
 75 |     update opts.merge(:data => xml.add(doc, add_attributes))
 76 |   end
 77 | 
 78 |   # send "commit" xml with opts
 79 |   #
 80 |   # http://wiki.apache.org/solr/UpdateXmlMessages#A.22commit.22_and_.22optimize.22
 81 |   #
 82 |   def commit opts = {}
 83 |     commit_attrs = opts.delete :commit_attributes
 84 |     update opts.merge(:data => xml.commit( commit_attrs ))
 85 |   end
 86 | 
 87 |   # send "optimize" xml with opts.
 88 |   #
 89 |   # http://wiki.apache.org/solr/UpdateXmlMessages#A.22commit.22_and_.22optimize.22
 90 |   #
 91 |   def optimize opts = {}
 92 |     optimize_attributes = opts.delete :optimize_attributes
 93 |     update opts.merge(:data => xml.optimize(optimize_attributes))
 94 |   end
 95 |   
 96 |   # send 
 97 |   # 
 98 |   # http://wiki.apache.org/solr/UpdateXmlMessages#A.22rollback.22
 99 |   # 
100 |   # NOTE: solr 1.4 only
101 |   def rollback opts = {}
102 |     update opts.merge(:data => xml.rollback)
103 |   end
104 |   
105 |   # Delete one or many documents by id
106 |   #   solr.delete_by_id 10
107 |   #   solr.delete_by_id([12, 41, 199])
108 |   def delete_by_id id, opts = {}
109 |     update opts.merge(:data => xml.delete_by_id(id))
110 |   end
111 | 
112 |   # delete one or many documents by query.
113 |   # 
114 |   # http://wiki.apache.org/solr/UpdateXmlMessages#A.22delete.22_by_ID_and_by_Query
115 |   # 
116 |   #   solr.delete_by_query 'available:0'
117 |   #   solr.delete_by_query ['quantity:0', 'manu:"FQ"']
118 |   def delete_by_query query, opts = {}
119 |     update opts.merge(:data => xml.delete_by_query(query))
120 |   end
121 |   
122 |   # shortcut to RSolr::Xml::Generator
123 |   def xml
124 |     @xml ||= RSolr::Xml::Generator.new
125 |   end
126 |   
127 |   # +send_and_receive+ is the main request method responsible for sending requests to the +connection+ object.
128 |   # 
129 |   # "path" : A string value that usually represents a solr request handler
130 |   # "opts" : A hash, which can contain the following keys:
131 |   #   :method : required - the http method (:get, :post or :head)
132 |   #   :params : optional - the query string params in hash form
133 |   #   :data : optional - post data -- if a hash is given, it's sent as "application/x-www-form-urlencoded"
134 |   #   :headers : optional - hash of request headers
135 |   # All other options are passed right along to the connection's +send_and_receive+ method (:get, :post, or :head)
136 |   # 
137 |   # +send_and_receive+ returns either a string or hash on a successful ruby request.
138 |   # When the :params[:wt] => :ruby, the response will be a hash, else a string.
139 |   #
140 |   # creates a request context hash,
141 |   # sends it to the connection's +execute+ method
142 |   # which returns a simple hash,
143 |   # then passes the request/response into +adapt_response+.
144 |   def send_and_receive path, opts
145 |     request_context = build_request path, opts
146 |     execute request_context
147 |   end
148 |   
149 |   # 
150 |   def execute_with_timeout *args
151 |     max_execution_time = defined?(RSOLR_MAX_EXECUTION_TIME) ? RSOLR_MAX_EXECUTION_TIME : 0
152 |     Timeout.timeout(max_execution_time) do
153 |       execute_without_timeout *args
154 |     end
155 |   end
156 |   
157 |   def execute_without_timeout request_context
158 |     raw_response = connection.execute self, request_context
159 |     adapt_response(request_context, raw_response) unless raw_response.nil?
160 |   end
161 |   
162 |   alias :execute, :execute_with_timeout
163 |   
164 |   # +build_request+ accepts a path and options hash,
165 |   # then prepares a normalized hash to return for sending
166 |   # to a solr connection driver.
167 |   # +build_request+ sets up the uri/query string
168 |   # and converts the +data+ arg to form-urlencoded,
169 |   # if the +data+ arg is a hash.
170 |   # returns a hash with the following keys:
171 |   #   :method
172 |   #   :params
173 |   #   :headers
174 |   #   :data
175 |   #   :uri
176 |   #   :path
177 |   #   :query
178 |   def build_request path, opts
179 |     raise "path must be a string or symbol, not #{path.inspect}" unless [String,Symbol].include?(path.class)
180 |     path = path.to_s
181 |     opts[:proxy] = proxy unless proxy.nil?
182 |     opts[:method] ||= :get
183 |     raise "The :data option can only be used if :method => :post" if opts[:method] != :post and opts[:data]
184 |     opts[:params] = opts[:params].nil? ? {:wt => :ruby} : {:wt => :ruby}.merge(opts[:params])
185 |     query = RSolr::Uri.params_to_solr(opts[:params]) unless opts[:params].empty?
186 |     opts[:query] = query
187 |     if opts[:data].is_a? Hash
188 |       opts[:data] = RSolr::Uri.params_to_solr opts[:data]
189 |       opts[:headers] ||= {}
190 |       opts[:headers]['Content-Type'] ||= 'application/x-www-form-urlencoded'
191 |     end
192 |     opts[:path] = path
193 |     opts[:uri] = base_uri.merge(path.to_s + (query ? "?#{query}" : "")) if base_uri
194 |     opts
195 |   end
196 |   
197 |   #  A mixin for used by #adapt_response
198 |   # This module essentially
199 |   # allows the raw response access to
200 |   # the original response and request.
201 |   module Context
202 |     attr_accessor :request, :response
203 |   end
204 |   
205 |   # This method will evaluate the :body value
206 |   # if the params[:uri].params[:wt] == :ruby
207 |   # ... otherwise, the body is returned as is.
208 |   # The return object has methods attached, :request and :response.
209 |   # These methods give you access to the original
210 |   # request and response from the connection.
211 |   #
212 |   # +adapt_response+ will raise an InvalidRubyResponse
213 |   # if :wt == :ruby and the body
214 |   # couldn't be evaluated.
215 |   def adapt_response request, response
216 |     raise "The response does not have the correct keys => :body, :headers, :status" unless
217 |       %W(body headers status) == response.keys.map{|k|k.to_s}.sort
218 |     raise RSolr::Error::Http.new request, response unless
219 |       [200,302].include? response[:status]
220 |     result = request[:params][:wt] == :ruby ? evaluate_ruby_response(request, response) : response[:body]
221 |     result.extend Context
222 |     result.request = request
223 |     result.response = response
224 |     result
225 |   end
226 |   
227 |   protected
228 |   
229 |   # converts the method name for the solr request handler path.
230 |   def method_missing name, *args
231 |     send_and_receive name, *args
232 |   end
233 |   
234 |   # evaluates the response[:body],
235 |   # attemps to bring the ruby string to life.
236 |   # If a SyntaxError is raised, then
237 |   # this method intercepts and raises a
238 |   # RSolr::Error::InvalidRubyResponse
239 |   # instead, giving full access to the
240 |   # request/response objects.
241 |   def evaluate_ruby_response request, response
242 |     begin
243 |       Kernel.eval response[:body].to_s
244 |     rescue SyntaxError
245 |       raise RSolr::Error::InvalidRubyResponse.new request, response
246 |     end
247 |   end
248 |   
249 | end


--------------------------------------------------------------------------------
/README.rdoc:
--------------------------------------------------------------------------------
  1 | =RSolr
  2 | 
  3 | A simple, extensible Ruby client for Apache Solr.
  4 | 
  5 | Notice: This document is only for the the 1.0 in the master. The last pre-1.0 gem release documentation can be found here: http://github.com/mwmitchell/rsolr/tree/v0.12.1
  6 | 
  7 | ==Documentation
  8 | The code docs can be viewed here : http://rdoc.info/projects/mwmitchell/rsolr
  9 | 
 10 | == Installation:
 11 |   sudo gem install rsolr --pre
 12 | 
 13 | == Example:
 14 |   require 'rubygems'
 15 |   require 'rsolr'
 16 |   
 17 |   # Direct connection
 18 |   solr = RSolr.connect :url => 'http://solrserver.com'
 19 |   
 20 |   # Connecting over a proxy server
 21 |   solr = RSolr.connect :url => 'http://solrserver.com', :proxy=>'http://user:pass@proxy.example.com:8080'
 22 |   
 23 |   # send a request to /select
 24 |   response = solr.get 'select', :params => {:q => '*:*'}
 25 |   
 26 |   # send a request to /catalog
 27 |   response = solr.get 'catalog', :params => {:q => '*:*'}
 28 | 
 29 | When the Solr :wt is :ruby, then the response will be a Hash. This Hash is the same object returned by Solr, but evaluated as Ruby. If the :wt is not :ruby, then the response will be a String.
 30 | 
 31 | The response also exposes 2 attribute readers (for any :wt value), :request and :response. Both are Hash objects with symbolized keys.
 32 | 
 33 | The :request attribute contains the original request context. You can use this for debugging or logging. Some of the keys this object contains are :uri, :query, :method etc..
 34 | 
 35 | The :response attribute contains the original response. This object contains the :status, :body and :headers keys.
 36 | 
 37 | == Querying
 38 | Use the #get / #post method to send search requests to the /select handler:
 39 |   response = solr.get 'select', :params => {
 40 |     :q=>'washington',
 41 |     :start=>0,
 42 |     :rows=>10
 43 |   }
 44 |   response["response"]["docs"].each{|doc| puts doc["id"] }
 45 | 
 46 | The :params sent into the method are sent to Solr as-is, which is to say they are converted to Solr url style, but no special mapping is used.
 47 | When an array is used, multiple parameters *with the same name* are generated for the Solr query. Example:
 48 |   
 49 |   solr.get 'select', :params => {:q=>'roses', :fq=>['red', 'violet']}
 50 | 
 51 | The above statement generates this Solr query:
 52 |   
 53 |   select?q=roses&fq=red&fq=violet
 54 | 
 55 | ===Pagination
 56 | To paginate through a set of Solr documents, use the paginate method:
 57 |   solr.paginate 1, 10, "select", :params => {:q => "test"}
 58 | 
 59 | The first argument is the current page, the second is how many documents to return for each page. In other words, "page" is the "start" Solr param and "per-page" is the "rows" Solr param.
 60 | 
 61 | The paginate method returns WillPaginate ready "docs" objects, so for example in a Rails application, paginating is as simple as:
 62 |   <%= will_paginate @solr_response["response"]["docs"] %>
 63 | 
 64 | ===Method Missing
 65 | The RSolr::Client class also uses method_missing for setting the request handler/path:
 66 |   
 67 |   solr.paintings :params => {:q=>'roses', :fq=>['red', 'violet']}
 68 |   
 69 | This is sent to Solr as:
 70 |   paintings?q=roses&fq=red&fq=violet
 71 | 
 72 | This works with pagination as well:
 73 |   
 74 |   solr.paginate_paintings 1, 10, {:q=>'roses', :fq=>['red', 'violet']}
 75 | 
 76 | ===Using POST for Search Queries
 77 | There may be cases where the query string is too long for a GET request. RSolr solves this issue by converting hash objects into form-encoded strings:
 78 |   response = solr.music :data => {:q => "*:*"}
 79 | 
 80 | The :data hash is serialized as a form-encoded query string, and the correct content-type headers are sent along to Solr.
 81 | 
 82 | ===Sending HEAD Requests
 83 | There may be cases where you'd like to send a HEAD request to Solr:
 84 |   solr.head("admin/ping").response[:status] == 200
 85 | 
 86 | ==Sending HTTP Headers
 87 | Solr responds to the request headers listed here: http://wiki.apache.org/solr/SolrAndHTTPCaches
 88 | To send header information to Solr using RSolr, just use the :headers option:
 89 |   response = solr.head "admin/ping", :headers => {"Cache-Control" => "If-None-Match"}
 90 | 
 91 | ===Building a Request
 92 | RSolr::Client provides a method for building a request context, which can be useful for debugging or logging etc.:
 93 |   request_context = solr.build_request "select", :data => {:q => "*:*"}, :method => :post, :headers => {}
 94 |   
 95 | To build a paginated request use build_paginated_request:
 96 |   request_context = solr.build_paginated_request 1, 10, "select", ...
 97 |   
 98 | == Updating Solr
 99 | Updating is done using native Ruby objects. Hashes are used for single documents and arrays are used for a collection of documents (hashes). These objects get turned into simple XML "messages". Raw XML strings can also be used.
100 | 
101 | Single document via #add
102 |   solr.add :id=>1, :price=>1.00
103 | 
104 | Multiple documents via #add
105 |   documents = [{:id=>1, :price=>1.00}, {:id=>2, :price=>10.50}]
106 |   solr.add documents
107 | 
108 | The optional :add_attributes hash can also be used to set Solr "add" document attributes:
109 |   solr.add documents, :add_attributes => {:commitWithin => 10}
110 | 
111 | Raw XML via  #update
112 |   solr.update :data => ''
113 |   solr.update :data => ''
114 | 
115 | When adding, you can also supply "add" xml element attributes and/or a block for manipulating other "add" related elements (docs and fields) by calling the +xml+ method directly:
116 |   
117 |   doc = {:id=>1, :price=>1.00}
118 |   add_attributes = {:allowDups=>false, :commitWithin=>10}
119 |   add_xml = solr.xml.add(doc, add_attributes) do |doc|
120 |     # boost each document
121 |     doc.attrs[:boost] = 1.5
122 |     # boost the price field:
123 |     doc.field_by_name(:price).attrs[:boost] = 2.0
124 |   end
125 | 
126 | Now the "add_xml" object can be sent to Solr like:
127 |   solr.update :data => add_xml
128 | 
129 | ===Deleting
130 | Delete by id
131 |   solr.delete_by_id 1
132 | or an array of ids
133 |   solr.delete_by_id [1, 2, 3, 4]
134 | 
135 | Delete by query:
136 |   solr.delete_by_query 'price:1.00'
137 | Delete by array of queries
138 |   solr.delete_by_query ['price:1.00', 'price:10.00']
139 | 
140 | ===Commit / Optimize
141 |   solr.commit, :commit_attributes => {}
142 |   solr.optimize, :optimize_attributes => {}
143 | 
144 | == Response Formats
145 | The default response format is Ruby. When the :wt param is set to :ruby, the response is eval'd resulting in a Hash. You can get a raw response by setting the :wt to "ruby" - notice, the string -- not a symbol. RSolr will eval the Ruby string ONLY if the :wt value is :ruby. All other response formats are available as expected, :wt=>'xml' etc..
146 | 
147 | ===Evaluated Ruby (default)
148 |   solr.get 'select', :params => {:wt => :ruby} # notice :ruby is a Symbol
149 | ===Raw Ruby
150 |   solr.get 'select', :params => {:wt => 'ruby'} # notice 'ruby' is a String
151 | 
152 | ===XML:
153 |   solr.get 'select', :params => {:wt => :xml}
154 | ===JSON:
155 |   solr.get 'select', :params => {:wt => :json}
156 | 
157 | ==Related Resources & Projects
158 | * {RSolr Google Group}[http://groups.google.com/group/rsolr] -- The RSolr discussion group
159 | * {rsolr-ext}[http://github.com/mwmitchell/rsolr-ext] -- An extension kit for RSolr
160 | * {rsolr-direct}[http://github.com/mwmitchell/rsolr-direct] -- JRuby direct connection for RSolr
161 | * {rsolr-nokogiri}[http://github.com/mwmitchell/rsolr-nokogiri] -- Gives RSolr Nokogiri for XML generation.
162 | * {SunSpot}[http://github.com/outoftime/sunspot] -- An awesome Solr DSL, built with RSolr
163 | * {Blacklight}[http://blacklightopac.org] -- A "next generation" Library OPAC, built with RSolr
164 | * {java_bin}[http://github.com/kennyj/java_bin] -- Provides javabin/binary parsing for RSolr
165 | * {Solr}[http://lucene.apache.org/solr/] -- The Apache Solr project
166 | * {solr-ruby}[http://wiki.apache.org/solr/solr-ruby] -- The original Solr Ruby Gem!
167 | 
168 | == Note on Patches/Pull Requests
169 | * Fork the project.
170 | * Make your feature addition or bug fix.
171 | * Add tests for it. This is important so I don't break it in a future version unintentionally.
172 | * Commit, do not mess with rakefile, version, or history
173 |   (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
174 | * Send me a pull request. Bonus points for topic branches.
175 | 
176 | == Note on Patches/Pull Requests
177 |  
178 | * Fork the project.
179 | * Make your feature addition or bug fix.
180 | * Add tests for it. This is important so I don't break it in a
181 |   future version unintentionally.
182 | * Commit, do not mess with rakefile, version, or history.
183 |   (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
184 | * Send me a pull request. Bonus points for topic branches.
185 | 
186 | ==Contributors
187 | * Colin Steele
188 | * Lorenzo Riccucci
189 | * Mike Perham
190 | * Mat Brown
191 | * Shairon Toledo
192 | * Matthew Rudy
193 | * Fouad Mardini
194 | * Jeremy Hinegardner
195 | * Nathan Witmer
196 | * Craig Smith
197 | 
198 | ==Author
199 | 
200 | Matt Mitchell 
201 | 
202 | ==Copyright
203 | 
204 | Copyright (c) 2008-2010 Matt Mitchell. See LICENSE for details.


--------------------------------------------------------------------------------