gsolr 0.12.2

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.
@@ -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