gsolr 0.12.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,94 @@
1
+ module GSolr
2
+ module Message
3
+ class Generator
4
+
5
+ def build(&block)
6
+ require 'builder'
7
+ b = ::Builder::XmlMarkup.new(:indent=>0, :margin=>0, :encoding => 'UTF-8')
8
+ b.instruct!
9
+ block_given? ? yield(b) : b
10
+ end
11
+
12
+ # generates "add" xml for updating solr
13
+ # "data" can be a hash or an array of hashes.
14
+ # - each hash should be a simple key=>value pair representing a solr doc.
15
+ # If a value is an array, multiple fields will be created.
16
+ #
17
+ # "add_attrs" can be a hash for setting the add xml element attributes.
18
+ #
19
+ # This method can also accept a block.
20
+ # The value yielded to the block is a Message::Document; for each solr doc in "data".
21
+ # You can set xml element attributes for each "doc" element or individual "field" elements.
22
+ #
23
+ # For example:
24
+ #
25
+ # solr.add({:id=>1, :nickname=>'Tim'}, {:boost=>5.0, :commitWithin=>1.0}) do |doc_msg|
26
+ # doc_msg.attrs[:boost] = 10.00 # boost the document
27
+ # nickname = doc_msg.field_by_name(:nickname)
28
+ # nickname.attrs[:boost] = 20 if nickname.value=='Tim' # boost a field
29
+ # end
30
+ #
31
+ # would result in an add element having the attributes boost="10.0"
32
+ # and a commitWithin="1.0".
33
+ # Each doc element would have a boost="10.0".
34
+ # The "nickname" field would have a boost="20.0"
35
+ # if the doc had a "nickname" field with the value of "Tim".
36
+ #
37
+ def add(data, add_attrs={}, &block)
38
+ data = [data] unless data.is_a?(Array)
39
+
40
+ build do |xml|
41
+ xml.add(add_attrs) do |add_node|
42
+ data.each do |doc|
43
+ doc = GSolr::Message::Document.new(doc) if doc.respond_to?(:each_pair)
44
+ yield doc if block_given?
45
+ add_node.doc(doc.attrs) do |doc_node|
46
+ doc.fields.each do |field_obj|
47
+ doc_node.field field_obj.value, field_obj.attrs
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+ # generates a <commit/> message
56
+ def commit(opts={})
57
+ build {|xml| xml.commit opts}
58
+ end
59
+
60
+ # generates a <optimize/> message
61
+ def optimize(opts={})
62
+ build {|xml| xml.optimize opts}
63
+ end
64
+
65
+ # generates a <rollback/> message
66
+ def rollback opts={}
67
+ build {|xml| xml.rollback opts}
68
+ end
69
+
70
+ # generates a <delete><id>ID</id></delete> message
71
+ # "ids" can be a single value or array of values
72
+ def delete_by_id(ids)
73
+ ids = [ids] unless ids.is_a?(Array)
74
+ build do |xml|
75
+ xml.delete do |delete_node|
76
+ ids.each { |id| delete_node.id(id) }
77
+ end
78
+ end
79
+ end
80
+
81
+ # generates a <delete><query>ID</query></delete> message
82
+ # "queries" can be a single value or an array of values
83
+ def delete_by_query(queries)
84
+ queries = [queries] unless queries.is_a?(Array)
85
+ build do |xml|
86
+ xml.delete do |delete_node|
87
+ queries.each { |query| delete_node.query query }
88
+ end
89
+ end
90
+ end
91
+
92
+ end # class Generator
93
+ end # module Message
94
+ end # module GSolr
@@ -0,0 +1,8 @@
1
+ # The Solr::Message::Generator class is the XML generation module for sending updates to Solr.
2
+ module GSolr
3
+ module Message
4
+ autoload :Document, 'gsolr/message/document'
5
+ autoload :Field, 'gsolr/message/field'
6
+ autoload :Generator, 'gsolr/message/generator'
7
+ end
8
+ end
@@ -0,0 +1,3 @@
1
+ module Gsolr
2
+ VERSION = "0.12.2"
3
+ end
data/lib/gsolr.rb ADDED
@@ -0,0 +1,39 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless
2
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
3
+
4
+ require 'json'
5
+
6
+ module GSolr
7
+ autoload :Message, 'gsolr/message'
8
+ autoload :Client, 'gsolr/client'
9
+ autoload :Connection, 'gsolr/connection'
10
+
11
+ module Connectable
12
+ def connect(opts={})
13
+ Client.new Connection::NetHttp.new(opts)
14
+ end
15
+ end
16
+ extend Connectable
17
+
18
+ # A module that contains string related methods
19
+ module Char
20
+ # escape - from the solr-ruby library
21
+ # GSolr.escape('asdf')
22
+ # backslash everything that isn't a word character
23
+ def escape(value)
24
+ value.gsub(/(\W)/, '\\\\\1')
25
+ end
26
+ end
27
+
28
+ # send the escape method into the Connection class ->
29
+ # solr = GSolr.connect
30
+ # solr.escape('asdf')
31
+ GSolr::Client.send(:include, Char)
32
+
33
+ # bring escape into this module (GSolr) -> GSolr.escape('asdf')
34
+ extend Char
35
+
36
+ # RequestError is a common/generic exception class used by the adapters
37
+ class RequestError < RuntimeError
38
+ end
39
+ end
@@ -0,0 +1,97 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'spec_helper')
2
+
3
+ describe GSolr::Client do
4
+
5
+ let(:client) {
6
+ GSolr::Client.new('')
7
+ }
8
+
9
+ context :method_missing do
10
+
11
+ it 'a non-existent method should be forwarded to #method_missing and then to #request' do
12
+ client.should_receive(:request).
13
+ with('/music', :q=>'Coltrane')
14
+ client.music :q=>'Coltrane'
15
+ end
16
+
17
+ end
18
+
19
+ context :update do
20
+
21
+ it 'should forward /update to #request("/update")' do
22
+ client.should_receive(:request)#.
23
+ # with('/update', {:wt=>:json}, "my xml message")
24
+ client.update "my xml message"
25
+ end
26
+
27
+ it 'should forward #add calls to #update' do
28
+ client.should_receive(:update) {|value,params|
29
+ value.should == "<?xml version=\"1.0\" encoding=\"UTF-8\"?><add><doc><field name=\"id\">1</field></doc></add>"
30
+ }
31
+ client.add(:id=>1)
32
+ end
33
+
34
+ it 'should forward #commit calls to #update' do
35
+ client.should_receive(:update).
36
+ with("<?xml version=\"1.0\" encoding=\"UTF-8\"?><commit/>")
37
+ client.commit
38
+ end
39
+
40
+ it 'should forward #optimize calls to #update' do
41
+ client.should_receive(:update).
42
+ with("<?xml version=\"1.0\" encoding=\"UTF-8\"?><optimize/>")
43
+ client.optimize
44
+ end
45
+
46
+ it 'should forward #rollback calls to #update' do
47
+ client.should_receive(:update).
48
+ with("<?xml version=\"1.0\" encoding=\"UTF-8\"?><rollback/>")
49
+ client.rollback
50
+ end
51
+
52
+ it 'should forward #delete_by_id calls to #update' do
53
+ client.should_receive(:update).
54
+ with("<?xml version=\"1.0\" encoding=\"UTF-8\"?><delete><id>1</id></delete>")
55
+ client.delete_by_id 1
56
+ end
57
+
58
+ it 'should forward #delete_by_query calls to #update' do
59
+ client.should_receive(:update).
60
+ with("<?xml version=\"1.0\" encoding=\"UTF-8\"?><delete><query>blah</query></delete>")
61
+ client.delete_by_query 'blah'
62
+ end
63
+
64
+ end
65
+
66
+ context :request do
67
+
68
+ it 'should forward #request calls to the connection' do
69
+ client.connection.should_receive(:request).
70
+ with('/music', :q=>'Coltrane', :wt=>:json).
71
+ # empty params so that Client doesn't try to evalulate to Ruby;
72
+ # -- this happens if the :wt equal :ruby
73
+ and_return(:params=>{})
74
+ client.request '/music', :q=>'Coltrane'
75
+ end
76
+
77
+ end
78
+
79
+ context :adapt_response do
80
+
81
+ it 'should not try to evaluate ruby when the :qt is not :ruby' do
82
+ body = '{:time=>"NOW"}'
83
+ result = client.send(:adapt_response, {:body=>body, :params=>{}})
84
+ result.should be_a(String)
85
+ result.should == body
86
+ end
87
+
88
+ it 'should evaluate ruby responses when the :wt is :ruby' do
89
+ body = '{:time=>"NOW"}'
90
+ result = client.send(:adapt_response, {:body=>body, :params=>{:wt=>:ruby}})
91
+ result.should be_a(Hash)
92
+ result.should == {:time=>"NOW"}
93
+ end
94
+
95
+ end
96
+
97
+ end
@@ -0,0 +1,148 @@
1
+ describe GSolr::Connection::NetHttp do
2
+
3
+ # calls #let to set "net_http" as method accessor
4
+ module NetHttpHelper
5
+ def self.included base
6
+ base.let(:net_http){ GSolr::Connection::NetHttp.new }
7
+ end
8
+ end
9
+
10
+ context '#request' do
11
+
12
+ include NetHttpHelper
13
+
14
+ it 'should forward simple, non-data calls to #get' do
15
+ net_http.should_receive(:get).
16
+ with('/select', :q=>'a').
17
+ and_return({:status_code=>200})
18
+ net_http.request('/select', :q=>'a')
19
+ end
20
+
21
+ it 'should forward :method=>:post calls to #post with a special header' do
22
+ net_http.should_receive(:post).
23
+ with('/select', 'q=a', {}, {"Content-Type"=>"application/x-www-form-urlencoded"}).
24
+ and_return({:status_code=>200})
25
+ net_http.request('/select', {:q=>'a'}, :method=>:post)
26
+ end
27
+
28
+ it 'should forward data calls to #post' do
29
+ net_http.should_receive(:post).
30
+ with("/update", "<optimize/>", {}, {"Content-Type"=>"text/xml; charset=utf-8"}).
31
+ and_return({:status_code=>200})
32
+ net_http.request('/update', {}, '<optimize/>')
33
+ end
34
+
35
+ end
36
+
37
+ context 'connection' do
38
+
39
+ include NetHttpHelper
40
+
41
+ it 'will create an instance of Net::HTTP' do
42
+ net_http.send(:connection).should be_a(Net::HTTP)
43
+ end
44
+
45
+ end
46
+
47
+ context 'get/post' do
48
+
49
+ include NetHttpHelper
50
+
51
+ it 'should make a GET request as expected' do
52
+ net_http_response = mock('net_http_response')
53
+ net_http_response.should_receive(:code).
54
+ and_return(200)
55
+ net_http_response.should_receive(:body).
56
+ and_return('The Response')
57
+ net_http_response.should_receive(:message).
58
+ and_return('OK')
59
+ c = net_http.send(:connection)
60
+ c.should_receive(:get).
61
+ with('/solr/select?q=1').
62
+ and_return(net_http_response)
63
+
64
+ context = net_http.send(:get, '/select', :q=>1)
65
+ context.should be_a(Hash)
66
+
67
+ keys = [:data, :body, :status_code, :path, :url, :headers, :params, :message]
68
+ context.keys.size.should == keys.size
69
+ context.keys.all?{|key| keys.include?(key) }.should == true
70
+
71
+ context[:data].should == nil
72
+ context[:body].should == 'The Response'
73
+ context[:status_code].should == 200
74
+ context[:path].should == '/select'
75
+ context[:url].should == 'http://127.0.0.1:8983/solr/select?q=1'
76
+ context[:headers].should == {}
77
+ context[:params].should == {:q=>1}
78
+ context[:message].should == 'OK'
79
+ end
80
+
81
+ it 'should make a POST request as expected' do
82
+ net_http_response = mock('net_http_response')
83
+ net_http_response.should_receive(:code).
84
+ and_return(200)
85
+ net_http_response.should_receive(:body).
86
+ and_return('The Response')
87
+ net_http_response.should_receive(:message).
88
+ and_return('OK')
89
+ c = net_http.send(:connection)
90
+ c.should_receive(:post).
91
+ with('/solr/update', '<rollback/>', {}).
92
+ and_return(net_http_response)
93
+ context = net_http.send(:post, '/update', '<rollback/>')
94
+ context.should be_a(Hash)
95
+
96
+ keys = [:data, :body, :status_code, :path, :url, :headers, :params, :message]
97
+ context.keys.size.should == keys.size
98
+ context.keys.all?{|key| keys.include?(key) }.should == true
99
+
100
+ context[:data].should == '<rollback/>'
101
+ context[:body].should == 'The Response'
102
+ context[:status_code].should == 200
103
+ context[:path].should == '/update'
104
+ context[:url].should == 'http://127.0.0.1:8983/solr/update'
105
+ context[:headers].should == {}
106
+ context[:params].should == {}
107
+ context[:message].should == 'OK'
108
+ end
109
+
110
+ end
111
+
112
+ context 'build_url' do
113
+
114
+ include NetHttpHelper
115
+
116
+ it 'should incude the base path to solr' do
117
+ result = net_http.send(:build_url, '/select', :q=>'*:*', :check=>'{!}')
118
+ # this is a non-ordered hash work around,
119
+ # -- the order of the parameters in the resulting url will be different depending on the ruby distribution/platform
120
+ # yuk.
121
+ begin
122
+ result.should == '/solr/select?check=%7B%21%7D&q=%2A%3A%2A'
123
+ rescue
124
+ result.should == '/solr/select?q=%2A%3A%2A&check=%7B%21%7D'
125
+ end
126
+ end
127
+
128
+ end
129
+
130
+ context 'encode_utf8' do
131
+
132
+ include NetHttpHelper
133
+
134
+ it 'should encode response body as utf-8' do
135
+ string = 'testing'
136
+ if RUBY_VERSION =~ /1\.9/
137
+ string.encoding.should == Encoding::US_ASCII
138
+ encoded_string = net_http.send(:encode_utf8, string)
139
+ string.encoding.should == Encoding::UTF_8
140
+ else
141
+ encoded_string = net_http.send(:encode_utf8, string)
142
+ encoded_string.should == string
143
+ end
144
+ end
145
+
146
+ end
147
+
148
+ end
@@ -0,0 +1,92 @@
1
+ describe GSolr::Connection::Requestable do
2
+
3
+ # calls #let to set "net_http" as method accessor
4
+ class R
5
+ include GSolr::Connection::Requestable
6
+ end
7
+
8
+ module RequestableHelper
9
+ def self.included base
10
+ base.let(:requestable){R.new}
11
+ end
12
+ end
13
+
14
+ context 'new' do
15
+
16
+ include RequestableHelper
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
+ requestable.should respond_to(:opts)
24
+ end
25
+
26
+ it 'should have an #uri attribute after creation' do
27
+ requestable.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 :request do
50
+
51
+ include RequestableHelper
52
+
53
+ it 'should send a get to itself' do
54
+ expected_response = {:status_code => 200}
55
+ requestable.should_receive(:get).
56
+ with('/blah', {:id=>1}).
57
+ and_return(expected_response)
58
+ requestable.request('/blah', :id=>1).should == expected_response
59
+ end
60
+
61
+ it 'should raise an error if the status_code is not 200' do
62
+ expected_response = {:status_code => 503}
63
+ requestable.should_receive(:get).
64
+ with('/blah', {:id=>1}).
65
+ and_return(expected_response)
66
+ lambda{
67
+ requestable.request('/blah', :id=>1)
68
+ }.should raise_error
69
+ end
70
+
71
+ it 'should send a post to itself if data is supplied' do
72
+ expected_response = {:status_code => 200}
73
+ my_data = "<commit/>"
74
+ post_headers = {"Content-Type"=>"text/xml; charset=utf-8"}
75
+ requestable.should_receive(:post).
76
+ with('/blah', my_data, {:id=>1}, post_headers).
77
+ and_return(expected_response)
78
+ requestable.request('/blah', {:id=>1}, my_data).should == expected_response
79
+ end
80
+
81
+ it 'should send a post to itself when :method=>:post is set even if no POST data is supplied' do
82
+ expected_response = {:status_code => 200}
83
+ post_headers = {"Content-Type"=>"application/x-www-form-urlencoded"}
84
+ requestable.should_receive(:post).
85
+ with('/blah', "", {}, post_headers).
86
+ and_return(expected_response)
87
+ requestable.request('/blah', {}, :method => :post).should == expected_response
88
+ end
89
+
90
+ end
91
+
92
+ end
@@ -0,0 +1,84 @@
1
+ describe GSolr::Connection::Utils do
2
+
3
+ # calls #let to set "utils" as a method accessor
4
+ module UtilsHelper
5
+ def self.included base
6
+ base.let(:utils){ nil.extend GSolr::Connection::Utils }
7
+ end
8
+ end
9
+
10
+ context 'hash_to_query method' do
11
+
12
+ include UtilsHelper
13
+
14
+ it "should build a query string from a hash, converting arrays to multi-params and removing nils/emptys" do
15
+ test_params = {
16
+ :z=>'should be whatever',
17
+ :q=>'test',
18
+ :item => [1, 2, 3, nil],
19
+ :nil=>nil
20
+ }
21
+ result = utils.hash_to_query(test_params)
22
+ [/z=should\+be\+whatever/, /q=test/, /item=1/, /item=2/, /item=3/].each do |regexp|
23
+ result.should match(regexp)
24
+ end
25
+ result.split('&').size.should == 5
26
+ end
27
+
28
+ it 'should escape &' do
29
+ utils.hash_to_query(:fq => "&").should == 'fq=%26'
30
+ end
31
+
32
+ it 'should convert spaces to +' do
33
+ utils.hash_to_query(:fq => "me and you").should == 'fq=me+and+you'
34
+ end
35
+
36
+ it 'should escape comlex queries, part 1' do
37
+ my_params = {'fq' => '{!raw f=field_name}crazy+\"field+value'}
38
+ expected = 'fq=%7B%21raw+f%3Dfield_name%7Dcrazy%2B%5C%22field%2Bvalue'
39
+ utils.hash_to_query(my_params).should == expected
40
+ end
41
+
42
+ it 'should escape comlex queries, part 2' do
43
+ my_params = {'q' => '+popularity:[10 TO *] +section:0'}
44
+ expected = 'q=%2Bpopularity%3A%5B10+TO+%2A%5D+%2Bsection%3A0'
45
+ utils.hash_to_query(my_params).should == expected
46
+ end
47
+
48
+ end
49
+
50
+ context 'escape method' do
51
+
52
+ include UtilsHelper
53
+
54
+ it 'should escape properly' do
55
+ utils.escape('+').should == '%2B'
56
+ utils.escape('This is a test').should == 'This+is+a+test'
57
+ utils.escape('<>/\\').should == '%3C%3E%2F%5C'
58
+ utils.escape('"').should == '%22'
59
+ utils.escape(':').should == '%3A'
60
+ end
61
+
62
+ it 'should escape brackets' do
63
+ utils.escape('{').should == '%7B'
64
+ utils.escape('}').should == '%7D'
65
+ end
66
+
67
+ it 'should escape exclamation marks!' do
68
+ utils.escape('!').should == '%21'
69
+ end
70
+
71
+ end
72
+
73
+ context 'build_url method' do
74
+
75
+ include UtilsHelper
76
+
77
+ it 'should build correctly' do
78
+ url = utils.build_url '/solr/select', {:q=>'test'}, 'blah=blah'
79
+ url.should == '/solr/select?blah=blah&q=test'
80
+ end
81
+
82
+ end
83
+
84
+ end
@@ -0,0 +1,29 @@
1
+ describe GSolr do
2
+
3
+ context :connect do
4
+
5
+ it 'does not care about valid/live URLs yet' do
6
+ lambda{GSolr.connect :url=>'http://blah.blah.blah:666/solr'}.should_not raise_error
7
+ end
8
+
9
+ it 'should create an instance of GSolr::Connection::NetHttp as the #connection' do
10
+ expected_class = GSolr::Connection::NetHttp
11
+ GSolr.connect.connection.should be_a(expected_class)
12
+ GSolr.connect(:url=>'blah').connection.should be_a(expected_class)
13
+ end
14
+
15
+ end
16
+
17
+ context :escape do
18
+
19
+ it "should escape properly" do
20
+ GSolr.escape('Trying & % different "characters" here!').should == "Trying\\ \\&\\ \\%\\ different\\ \\\"characters\\\"\\ here\\!"
21
+ end
22
+
23
+ it 'should escape' do
24
+ expected = "http\\:\\/\\/lucene\\.apache\\.org\\/solr"
25
+ GSolr.escape("http://lucene.apache.org/solr").should == expected
26
+ end
27
+ end
28
+
29
+ end