backupify-rsolr-nokogiri 0.12.1.1

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,8 @@
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
@@ -0,0 +1,48 @@
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
@@ -0,0 +1,20 @@
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
@@ -0,0 +1,91 @@
1
+ class RSolr::Message::Generator
2
+
3
+ def build &block
4
+ require 'nokogiri'
5
+ b = Nokogiri::XML::Builder.new(:encoding => 'UTF-8')
6
+ yield(b) if block_given?
7
+ b.to_xml(:indent => 0)
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
+ xml.doc_(doc.attrs) {
43
+ doc.fields.each do |field_obj|
44
+ xml.field(field_obj.attrs) {
45
+ text field_obj.value
46
+ }
47
+ end
48
+ }
49
+ end
50
+ }
51
+ end
52
+ end
53
+
54
+ # generates a <commit/> message
55
+ def commit(opts={})
56
+ build {|xml| xml.commit opts}
57
+ end
58
+
59
+ # generates a <optimize/> message
60
+ def optimize(opts={})
61
+ build {|xml| xml.optimize opts}
62
+ end
63
+
64
+ # generates a <rollback/> message
65
+ def rollback opts={}
66
+ build {|xml| xml.rollback opts}
67
+ end
68
+
69
+ # generates a <delete><id>ID</id></delete> message
70
+ # "ids" can be a single value or array of values
71
+ def delete_by_id(ids)
72
+ ids = [ids] unless ids.is_a?(Array)
73
+ build do |xml|
74
+ xml.delete do |delete_node|
75
+ ids.each { |id| delete_node.id(id) }
76
+ end
77
+ end
78
+ end
79
+
80
+ # generates a <delete><query>ID</query></delete> message
81
+ # "queries" can be a single value or an array of values
82
+ def delete_by_query(queries)
83
+ queries = [queries] unless queries.is_a?(Array)
84
+ build do |xml|
85
+ xml.delete do |delete_node|
86
+ queries.each { |query| delete_node.query query }
87
+ end
88
+ end
89
+ end
90
+
91
+ end
@@ -0,0 +1,93 @@
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\"?>\n<add>\n<doc>\n<field name=\"id\">1</field>\n</doc>\n</add>\n"
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\"?>\n<commit/>\n")
33
+ client.commit
34
+ end
35
+
36
+ it 'should forward #optimize calls to #update' do
37
+ client.should_receive(:update).
38
+ with("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<optimize/>\n")
39
+ client.optimize
40
+ end
41
+
42
+ it 'should forward #rollback calls to #update' do
43
+ client.should_receive(:update).
44
+ with("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<rollback/>\n")
45
+ client.rollback
46
+ end
47
+
48
+ it 'should forward #delete_by_id calls to #update' do
49
+ client.should_receive(:update).
50
+ with("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<delete>\n<id>1</id>\n</delete>\n")
51
+ client.delete_by_id 1
52
+ end
53
+
54
+ it 'should forward #delete_by_query calls to #update' do
55
+ client.should_receive(:update).
56
+ with("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<delete>\n<query>blah</query>\n</delete>\n")
57
+ client.delete_by_query 'blah'
58
+ end
59
+
60
+ end
61
+
62
+ context :request do
63
+
64
+ it 'should forward #request calls to the connection' do
65
+ client.connection.should_receive(:request).
66
+ with('/music', :q=>'Coltrane', :wt=>:ruby).
67
+ # empty params so that Client doesn't try to evalulate to Ruby;
68
+ # -- this happens if the :wt equal :ruby
69
+ and_return(:params=>{})
70
+ client.request '/music', :q=>'Coltrane'
71
+ end
72
+
73
+ end
74
+
75
+ context :adapt_response do
76
+
77
+ it 'should not try to evaluate ruby when the :qt is not :ruby' do
78
+ body = '{:time=>"NOW"}'
79
+ result = client.send(:adapt_response, {:body=>body, :params=>{}})
80
+ result.should be_a(String)
81
+ result.should == body
82
+ end
83
+
84
+ it 'should evaluate ruby responses when the :wt is :ruby' do
85
+ body = '{:time=>"NOW"}'
86
+ result = client.send(:adapt_response, {:body=>body, :params=>{:wt=>:ruby}})
87
+ result.should be_a(Hash)
88
+ result.should == {:time=>"NOW"}
89
+ end
90
+
91
+ end
92
+
93
+ end
@@ -0,0 +1,148 @@
1
+ describe RSolr::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){ RSolr::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 RSolr::Connection::Requestable do
2
+
3
+ # calls #let to set "net_http" as method accessor
4
+ class R
5
+ include RSolr::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