rsolr 0.13.0.pre → 1.0.0.beta

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,101 +0,0 @@
1
- # A module that defines the interface and top-level logic for http based connection classes.
2
- # Httpable provides URL parsing and handles proxy logic.
3
-
4
- module RSolr::Connection::Httpable
5
-
6
- include RSolr::Connection::Utils
7
-
8
- attr_reader :opts, :uri, :proxy
9
-
10
- # opts can have:
11
- # :url => 'http://localhost:8080/solr'
12
- def initialize opts={}
13
- opts[:url] ||= 'http://127.0.0.1:8983/solr'
14
- @opts = opts
15
- @uri = URI.parse opts[:url]
16
- @proxy = URI.parse opts[:proxy] if opts[:proxy]
17
- end
18
-
19
- # send a request to the connection
20
- # request '/select', :q=>'*:*'
21
- #
22
- # request '/update', {:wt=>:xml}, '</commit>'
23
- #
24
- # force a post where the post body is the param query
25
- # request '/update', "<optimize/>", :method=>:post
26
- #
27
- def request path, params={}, *extra
28
- extra = extra.dup
29
- opts = extra[-1].kind_of?(Hash) ? extra.pop : {}
30
- data = extra[0]
31
-
32
- context = create_http_context path, params, data, opts
33
-
34
- error = nil
35
-
36
- begin
37
-
38
- # if data is being sent, this is a POST
39
- if context[:data]
40
- response = self.post context[:path], context[:data], context[:headers]
41
- # use POST if :method => :post
42
- elsif opts[:method] == :post
43
- response = self.post context[:path], context[:query], context[:headers]
44
- # GET
45
- else
46
- response = self.get context[:path]
47
- end
48
-
49
- # spray out our response into variables...
50
- body, status_code, message = response
51
-
52
- # merge the response into the http context
53
- context.merge!(:body => body, :status_code => status_code, :message => message)
54
-
55
- rescue
56
- # throw RequestError?
57
- context[:message] = $!.to_s
58
- end
59
-
60
- # if no :message but a non-200, throw a "SolrRequestError" ?
61
- unless context[:status_code] == 200
62
- error = context[:message] || "Non-200 Response Status Code"
63
- raise RSolr::RequestError.new("#{error} -> #{context.inspect}")
64
- end
65
-
66
- context
67
- end
68
-
69
- # -> should this stuff be in a "ReqResContext" class? ->
70
-
71
- # creates a Hash based "context"
72
- # that contains all of the information sent to Solr
73
- # The keys are:
74
- # :host, :path, :params, :query, :data, :headers
75
- def create_http_context path, params, data=nil, opts={}
76
- context = {:host => base_url, :path => build_url(path), :params => params, :query => hash_to_query(params), :data => data}
77
- if opts[:method] == :post
78
- raise "Don't send POST data when using :method => :post" unless data.to_s.empty?
79
- # force a POST, use the query string as the POST body
80
- context.merge! :data => hash_to_query(params), :headers => {'Content-Type' => 'application/x-www-form-urlencoded'}
81
- elsif data
82
- # standard POST, using "data" as the POST body
83
- context.merge! :headers => {'Content-Type' => 'text/xml; charset=utf-8'}
84
- else
85
- context.merge! :path => build_url(path, params)
86
- end
87
- context
88
- end
89
-
90
- # accepts a path/string and optional hash of query params
91
- # returns a string that represents the full url
92
- def build_url path, params={}
93
- full_path = @uri.path + path
94
- super full_path, params, @uri.query
95
- end
96
-
97
- def base_url
98
- "#{@uri.scheme}://#{@uri.host}" + (@uri.port ? ":#{@uri.port}" : "")
99
- end
100
-
101
- end
@@ -1,30 +0,0 @@
1
- require 'net/http'
2
-
3
- #
4
- # Connection for standard HTTP Solr server
5
- #
6
- class RSolr::Connection::NetHttp
7
-
8
- include RSolr::Connection::Httpable
9
-
10
- def connection
11
- if @proxy
12
- proxy_user, proxy_pass = @proxy.userinfo.split(/:/) if @proxy.userinfo
13
- @connection ||= Net::HTTP.Proxy(@proxy.host, @proxy.port, proxy_user, proxy_pass).new(@uri.host, @uri.port)
14
- else
15
- @connection ||= Net::HTTP.new(@uri.host, @uri.port)
16
- end
17
- end
18
-
19
- # maybe follow Rack and do [status, headers, body]
20
- def get url
21
- net_http_response = self.connection.get url
22
- [net_http_response.code.to_i, net_http_response.message, net_http_response.body]
23
- end
24
-
25
- def post url, data, headers={}
26
- net_http_response = self.connection.post url, data, headers
27
- [net_http_response.code.to_i, net_http_response.message, net_http_response.body]
28
- end
29
-
30
- end
@@ -1,74 +0,0 @@
1
- # Helpful utility methods for building queries to a Solr server
2
- # This includes helpers that the Direct connection can use -- not specific to http, for example
3
- module RSolr::Connection::Utils
4
-
5
- # Performs URI escaping so that you can construct proper
6
- # query strings faster. Use this rather than the cgi.rb
7
- # version since it's faster. (Stolen from Rack).
8
- def escape(s)
9
- s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/n) {
10
- #'%'+$1.unpack('H2'*$1.size).join('%').upcase
11
- '%'+$1.unpack('H2'*bytesize($1)).join('%').upcase
12
- }.tr(' ', '+')
13
- end
14
-
15
- # encodes the string as utf-8 in Ruby 1.9
16
- # returns the unaltered string in Ruby 1.8
17
- def encode_utf8 string
18
- (string.respond_to?(:force_encoding) and string.respond_to?(:encoding)) ?
19
- string.force_encoding(Encoding::UTF_8) : string
20
- end
21
-
22
- # Return the bytesize of String; uses String#size under Ruby 1.8 and
23
- # String#bytesize under 1.9.
24
- if ''.respond_to?(:bytesize)
25
- def bytesize(string)
26
- string.bytesize
27
- end
28
- else
29
- def bytesize(string)
30
- string.size
31
- end
32
- end
33
-
34
- # creates and returns a url as a string
35
- # "url" is the base url
36
- # "params" is an optional hash of GET style query params
37
- # "string_query" is an extra query string that will be appended to the
38
- # result of "url" and "params".
39
- def build_url url='', params={}, string_query=''
40
- queries = [string_query, hash_to_query(params)]
41
- queries.delete_if{|i| i.to_s.empty?}
42
- url += "?#{queries.join('&')}" unless queries.empty?
43
- url
44
- end
45
-
46
- # converts a key value pair to an escaped string:
47
- # Example:
48
- # build_param(:id, 1) == "id=1"
49
- def build_param(k,v)
50
- "#{escape(k)}=#{escape(v)}"
51
- end
52
-
53
- #
54
- # converts hash into URL query string, keys get an alpha sort
55
- # if a value is an array, the array values get mapped to the same key:
56
- # hash_to_query(:q=>'blah-query', :fq=>['f1', 'f1'], :facet=>true, 'facet.field'=>['location_facet', 'format_facet'])
57
- # returns:
58
- # ?q=blah-query&fq=f1&fq=f2&facet=true&facet.field=location_facet&facet.field=format.facet
59
- #
60
- # if a value is empty/nil etc., it is tossed out
61
- #
62
- def hash_to_query(params)
63
- mapped = params.map do |k, v|
64
- next if v.to_s.empty?
65
- if v.class == Array
66
- hash_to_query(v.map { |x| [k, x] })
67
- else
68
- build_param k, v
69
- end
70
- end
71
- mapped.compact.join("&")
72
- end
73
-
74
- end
@@ -1,9 +0,0 @@
1
- require 'uri'
2
-
3
- module RSolr::Connection
4
-
5
- autoload :NetHttp, 'rsolr/connection/net_http'
6
- autoload :Utils, 'rsolr/connection/utils'
7
- autoload :Httpable, 'rsolr/connection/httpable'
8
-
9
- end
@@ -1,48 +0,0 @@
1
- # A class that represents a "doc" xml element for a solr update
2
- class RSolr::Message::Document
3
-
4
- # "attrs" is a hash for setting the "doc" xml attributes
5
- # "fields" is an array of Field objects
6
- attr_accessor :attrs, :fields
7
-
8
- # "doc_hash" must be a Hash/Mash object
9
- # If a value in the "doc_hash" is an array,
10
- # a field object is created for each value...
11
- def initialize(doc_hash = {})
12
- @fields = []
13
- doc_hash.each_pair do |field,values|
14
- # create a new field for each value (multi-valued)
15
- # put non-array values into an array
16
- values = [values] unless values.is_a?(Array)
17
- values.each do |v|
18
- next if v.to_s.empty?
19
- @fields << RSolr::Message::Field.new({:name=>field}, v.to_s)
20
- end
21
- end
22
- @attrs={}
23
- end
24
-
25
- # returns an array of fields that match the "name" arg
26
- def fields_by_name(name)
27
- @fields.select{|f|f.name==name}
28
- end
29
-
30
- # returns the *first* field that matches the "name" arg
31
- def field_by_name(name)
32
- @fields.detect{|f|f.name==name}
33
- end
34
-
35
- #
36
- # Add a field value to the document. Options map directly to
37
- # XML attributes in the Solr <field> node.
38
- # See http://wiki.apache.org/solr/UpdateXmlMessages#head-8315b8028923d028950ff750a57ee22cbf7977c6
39
- #
40
- # === Example:
41
- #
42
- # document.add_field('title', 'A Title', :boost => 2.0)
43
- #
44
- def add_field(name, value, options = {})
45
- @fields << RSolr::Message::Field.new(options.merge({:name=>name}), value)
46
- end
47
-
48
- end
@@ -1,20 +0,0 @@
1
- # A class that represents a "doc"/"field" xml element for a solr update
2
- class RSolr::Message::Field
3
-
4
- # "attrs" is a hash for setting the "doc" xml attributes
5
- # "value" is the text value for the node
6
- attr_accessor :attrs, :value
7
-
8
- # "attrs" must be a hash
9
- # "value" should be something that responds to #_to_s
10
- def initialize(attrs, value)
11
- @attrs = attrs
12
- @value = value
13
- end
14
-
15
- # the value of the "name" attribute
16
- def name
17
- @attrs[:name]
18
- end
19
-
20
- end
@@ -1,89 +0,0 @@
1
- class RSolr::Message::Generator
2
-
3
- def build &block
4
- require 'builder'
5
- b = ::Builder::XmlMarkup.new(:indent=>0, :margin=>0, :encoding => 'UTF-8')
6
- b.instruct!
7
- block_given? ? yield(b) : b
8
- end
9
-
10
- # generates "add" xml for updating solr
11
- # "data" can be a hash or an array of hashes.
12
- # - each hash should be a simple key=>value pair representing a solr doc.
13
- # If a value is an array, multiple fields will be created.
14
- #
15
- # "add_attrs" can be a hash for setting the add xml element attributes.
16
- #
17
- # This method can also accept a block.
18
- # The value yielded to the block is a Message::Document; for each solr doc in "data".
19
- # You can set xml element attributes for each "doc" element or individual "field" elements.
20
- #
21
- # For example:
22
- #
23
- # solr.add({:id=>1, :nickname=>'Tim'}, {:boost=>5.0, :commitWithin=>1.0}) do |doc_msg|
24
- # doc_msg.attrs[:boost] = 10.00 # boost the document
25
- # nickname = doc_msg.field_by_name(:nickname)
26
- # nickname.attrs[:boost] = 20 if nickname.value=='Tim' # boost a field
27
- # end
28
- #
29
- # would result in an add element having the attributes boost="10.0"
30
- # and a commitWithin="1.0".
31
- # Each doc element would have a boost="10.0".
32
- # The "nickname" field would have a boost="20.0"
33
- # if the doc had a "nickname" field with the value of "Tim".
34
- #
35
- def add data, add_attrs={}, &block
36
- data = [data] unless data.is_a?(Array)
37
- build do |xml|
38
- xml.add(add_attrs) do |add_node|
39
- data.each do |doc|
40
- doc = RSolr::Message::Document.new(doc) if doc.respond_to?(:each_pair)
41
- yield doc if block_given?
42
- add_node.doc(doc.attrs) do |doc_node|
43
- doc.fields.each do |field_obj|
44
- doc_node.field field_obj.value, field_obj.attrs
45
- end
46
- end
47
- end
48
- end
49
- end
50
- end
51
-
52
- # generates a <commit/> message
53
- def commit(opts={})
54
- build {|xml| xml.commit opts}
55
- end
56
-
57
- # generates a <optimize/> message
58
- def optimize(opts={})
59
- build {|xml| xml.optimize opts}
60
- end
61
-
62
- # generates a <rollback/> message
63
- def rollback opts={}
64
- build {|xml| xml.rollback opts}
65
- end
66
-
67
- # generates a <delete><id>ID</id></delete> message
68
- # "ids" can be a single value or array of values
69
- def delete_by_id(ids)
70
- ids = [ids] unless ids.is_a?(Array)
71
- build do |xml|
72
- xml.delete do |delete_node|
73
- ids.each { |id| delete_node.id(id) }
74
- end
75
- end
76
- end
77
-
78
- # generates a <delete><query>ID</query></delete> message
79
- # "queries" can be a single value or an array of values
80
- def delete_by_query(queries)
81
- queries = [queries] unless queries.is_a?(Array)
82
- build do |xml|
83
- xml.delete do |delete_node|
84
- queries.each { |query| delete_node.query query }
85
- end
86
- end
87
- end
88
-
89
- end
data/lib/rsolr/message.rb DELETED
@@ -1,8 +0,0 @@
1
- # The Solr::Message::Generator class is the XML generation module for sending updates to Solr.
2
- module RSolr::Message
3
-
4
- autoload :Document, 'rsolr/message/document'
5
- autoload :Field, 'rsolr/message/field'
6
- autoload :Generator, 'rsolr/message/generator'
7
-
8
- end
@@ -1,115 +0,0 @@
1
- describe RSolr::Client do
2
-
3
- let(:client){ RSolr::Client.new('') }
4
-
5
- context :method_missing do
6
-
7
- it 'a non-existent method should be forwarded to #method_missing and then to #request' do
8
- client.should_receive(:request).
9
- with('/music', :q=>'Coltrane')
10
- client.music :q=>'Coltrane'
11
- end
12
-
13
- end
14
-
15
- context :update do
16
-
17
- it 'should forward /update to #request("/update")' do
18
- client.should_receive(:request)#.
19
- # with('/update', {:wt=>:ruby}, "my xml message")
20
- client.update "my xml message"
21
- end
22
-
23
- it 'should forward #add calls to #update' do
24
- client.should_receive(:update) {|value,params|
25
- value.should == "<?xml version=\"1.0\" encoding=\"UTF-8\"?><add><doc><field name=\"id\">1</field></doc></add>"
26
- }
27
- client.add(:id=>1)
28
- end
29
-
30
- it 'should forward #commit calls to #update' do
31
- client.should_receive(:update).
32
- with("<?xml version=\"1.0\" encoding=\"UTF-8\"?><commit/>")
33
- client.commit
34
- end
35
-
36
- it 'should forward #commit calls with options to #update' do
37
- opts = {:waitFlush => false, :waitSearcher => false, :expungeDeletes => true}
38
- # when client.commit is called, it eventually calls update
39
- client.should_receive(:update).
40
- with(opts)
41
- # client.message is calls to create the xml
42
- client.message.should_receive(:commit).
43
- and_return(opts)
44
- client.commit(opts)
45
- end
46
-
47
- it 'should forward #optimize calls to #update' do
48
- client.should_receive(:update).
49
- with("<?xml version=\"1.0\" encoding=\"UTF-8\"?><optimize/>")
50
- client.optimize
51
- end
52
-
53
- it 'should forward #optimize calls with options to #update' do
54
- opts = {:maxSegments => 5, :waitFlush => false}
55
- # when client.commit is called, it eventually calls update
56
- client.should_receive(:update).
57
- with(opts)
58
- # client.message is calls to create the xml
59
- client.message.should_receive(:optimize).
60
- and_return(opts)
61
- client.optimize(opts)
62
- end
63
-
64
- it 'should forward #rollback calls to #update' do
65
- client.should_receive(:update).
66
- with("<?xml version=\"1.0\" encoding=\"UTF-8\"?><rollback/>")
67
- client.rollback
68
- end
69
-
70
- it 'should forward #delete_by_id calls to #update' do
71
- client.should_receive(:update).
72
- with("<?xml version=\"1.0\" encoding=\"UTF-8\"?><delete><id>1</id></delete>")
73
- client.delete_by_id 1
74
- end
75
-
76
- it 'should forward #delete_by_query calls to #update' do
77
- client.should_receive(:update).
78
- with("<?xml version=\"1.0\" encoding=\"UTF-8\"?><delete><query>blah</query></delete>")
79
- client.delete_by_query 'blah'
80
- end
81
-
82
- end
83
-
84
- context :request do
85
-
86
- it 'should forward #request calls to the connection' do
87
- client.connection.should_receive(:request).
88
- with('/music', :q=>'Coltrane', :wt=>:ruby).
89
- # empty params so that Client doesn't try to evalulate to Ruby;
90
- # -- this happens if the :wt equal :ruby
91
- and_return(:params=>{})
92
- client.request '/music', :q=>'Coltrane'
93
- end
94
-
95
- end
96
-
97
- context :adapt_response do
98
-
99
- it 'should not try to evaluate ruby when the :qt is not :ruby' do
100
- body = '{:time=>"NOW"}'
101
- result = client.send(:adapt_response, {:body=>body, :params=>{}})
102
- result.should be_a(String)
103
- result.should == body
104
- end
105
-
106
- it 'should evaluate ruby responses when the :wt is :ruby' do
107
- body = '{:time=>"NOW"}'
108
- result = client.send(:adapt_response, {:body=>body, :params=>{:wt=>:ruby}})
109
- result.should be_a(Hash)
110
- result.should == {:time=>"NOW"}
111
- end
112
-
113
- end
114
-
115
- end
@@ -1,157 +0,0 @@
1
- describe RSolr::Connection::Httpable do
2
-
3
- # calls #let to set "net_http" as method accessor
4
- class R
5
- include RSolr::Connection::Httpable
6
- end
7
-
8
- module HttpableHelper
9
- def self.included base
10
- base.let(:httpable){R.new}
11
- end
12
- end
13
-
14
- context 'new' do
15
-
16
- include HttpableHelper
17
-
18
- it 'should define an intialize method that accepts a hash argument' do
19
- lambda{R.new({})}.should_not raise_error
20
- end
21
-
22
- it 'should have an #opts attribute after creation' do
23
- httpable.should respond_to(:opts)
24
- end
25
-
26
- it 'should have an #uri attribute after creation' do
27
- httpable.should respond_to(:uri)
28
- end
29
-
30
- end
31
-
32
- context 'opts and uri' do
33
-
34
- it 'should allow setting the opts' do
35
- opts = {:url=>'blah'}
36
- r = R.new(opts)
37
- r.opts.should == opts
38
- end
39
-
40
- it 'should parser the url option into a URI object' do
41
- opts = {:url => 'http://xyz:1010/solr'}
42
- r = R.new(opts)
43
- r.uri.should be_a(URI)
44
- r.uri.host.should == 'xyz'
45
- end
46
-
47
- end
48
-
49
- context :build_url do
50
-
51
- include HttpableHelper
52
-
53
- it 'should build a full path and create a query string' do
54
- r = httpable
55
- r.build_url("/select", :q=>1).should == '/solr/select?q=1'
56
- end
57
-
58
- it 'should build a full path without a query string' do
59
- r = httpable
60
- r.build_url("/select").should == '/solr/select'
61
- end
62
-
63
- end
64
-
65
- context :create_http_context do
66
-
67
- include HttpableHelper
68
-
69
- it "should build a simple GET context" do
70
- r = httpable
71
- result = r.create_http_context('/select', :q=>'a', :fq=>'b')
72
- expected = {:path=>"/solr/select?q=a&fq=b", :params=>{:q=>"a", :fq=>"b"}, :data=>nil, :query=>"q=a&fq=b", :host=>"http://127.0.0.1:8983"}
73
-
74
- result.keys.all? {|v| expected.keys.include?(v) }
75
- result.values.all? {|v| expected.values.include?(v) }
76
- end
77
-
78
- it "should build a POST context" do
79
- r = httpable
80
- result = r.create_http_context('/select', {:wt => :xml}, '<commit/>')
81
- expected = {:path=>"/solr/select", :params=>{:wt=>:xml}, :headers=>{"Content-Type"=>"text/xml; charset=utf-8"}, :data=>"<commit/>", :query=>"wt=xml", :host=>"http://127.0.0.1:8983"}
82
- result.should == expected
83
- end
84
-
85
- it "should raise an exception when trying to use POST data AND :method => :post" do
86
- r = httpable
87
- lambda{
88
- r.create_http_context('/select', {:wt => :xml}, '<commit/>', :method => :post)
89
- }.should raise_error("Don't send POST data when using :method => :post")
90
- end
91
-
92
- it "should form-encoded POST context" do
93
- r = httpable
94
- result = r.create_http_context('/select', {:q => 'some gigantic query string that is too big for GET (for example)'}, nil, :method => :post)
95
- result.should == {:path=>"/solr/select", :params=>{:q=>"some gigantic query string that is too big for GET (for example)"}, :headers=>{"Content-Type"=>"application/x-www-form-urlencoded"}, :data=>"q=some+gigantic+query+string+that+is+too+big+for+GET+%28for+example%29", :query=>"q=some+gigantic+query+string+that+is+too+big+for+GET+%28for+example%29", :host=>"http://127.0.0.1:8983"}
96
- end
97
-
98
- end
99
-
100
- context :request do
101
-
102
- include HttpableHelper
103
-
104
- it "should be able to build a request context, pass the url to #get and return a full context" do
105
- httpable.should_receive(:create_http_context).
106
- with("/admin/ping", {}, nil, {}).
107
- and_return({:path => '/solr/admin/ping'})
108
- httpable.should_receive(:get).
109
- with('/solr/admin/ping').
110
- and_return(["asdfasdf", 200, "OK"])
111
- response = httpable.request '/admin/ping'
112
- response.should == {:status_code=>200, :message=>"OK", :path=>"/solr/admin/ping", :body=>"asdfasdf"}
113
- end
114
-
115
- it 'should send a get to itself with params' do
116
- httpable.should_receive(:get).
117
- with("/solr/blahasdf?id=1").
118
- and_return(["", 200, "OK"])
119
- r = httpable.request('/blahasdf', :id=>1)
120
- r.should == {:status_code=>200, :path=>"/solr/blahasdf?id=1", :params=>{:id=>1}, :message=>"OK", :data=>nil, :query=>"id=1", :host=>"http://127.0.0.1:8983", :body=>""}
121
- end
122
-
123
- it 'should raise an error if the status_code is not 200' do
124
- httpable.should_receive(:get).
125
- with("/solr/blah?id=1").
126
- and_return( ["", 404, "Not Found"] )
127
- lambda{
128
- httpable.request('/blah', :id=>1).should == true
129
- }.should raise_error(/Not Found/)
130
- end
131
-
132
- it 'should send a post to itself if data is supplied' do
133
- httpable.should_receive(:post).
134
- with("/solr/blah", "<commit/>", {"Content-Type"=>"text/xml; charset=utf-8"}).
135
- and_return(["", 200, "OK"])
136
- httpable.request('/blah', {:id=>1}, "<commit/>")#.should == expected_response
137
- end
138
-
139
- it 'should send a post to itself when :method=>:post is set even if no POST data is supplied' do
140
- httpable.should_receive(:post).
141
- with("/solr/blah", "q=testing", {"Content-Type"=>"application/x-www-form-urlencoded"}).
142
- and_return(["", 200, "OK"])
143
- response = httpable.request('/blah', {:q => "testing"}, :method => :post)#.should == expected_response
144
- response[:body].should == ""
145
- response[:path].should == "/solr/blah"
146
- response[:message].should == "OK"
147
- response[:status_code].should == 200
148
- response[:params].should == {:q=>"testing"}
149
- response[:headers].should == {"Content-Type"=>"application/x-www-form-urlencoded"}
150
- response[:data].should == "q=testing"
151
- response[:query].should == "q=testing"
152
- response[:host].should == "http://127.0.0.1:8983"
153
- end
154
-
155
- end
156
-
157
- end