rsolr 0.13.0.pre → 1.0.0.beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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