mwmitchell-rsolr 0.5.7

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.
Files changed (42) hide show
  1. data/CHANGES.txt +41 -0
  2. data/LICENSE +201 -0
  3. data/README.rdoc +191 -0
  4. data/Rakefile +40 -0
  5. data/examples/direct.rb +20 -0
  6. data/examples/http.rb +16 -0
  7. data/lib/core_ext.rb +8 -0
  8. data/lib/rsolr.rb +34 -0
  9. data/lib/rsolr/connection.rb +7 -0
  10. data/lib/rsolr/connection/adapter.rb +7 -0
  11. data/lib/rsolr/connection/adapter/common_methods.rb +46 -0
  12. data/lib/rsolr/connection/adapter/direct.rb +80 -0
  13. data/lib/rsolr/connection/adapter/http.rb +51 -0
  14. data/lib/rsolr/connection/base.rb +121 -0
  15. data/lib/rsolr/connection/search_ext.rb +126 -0
  16. data/lib/rsolr/http_client.rb +115 -0
  17. data/lib/rsolr/http_client/adapter.rb +6 -0
  18. data/lib/rsolr/http_client/adapter/curb.rb +51 -0
  19. data/lib/rsolr/http_client/adapter/net_http.rb +48 -0
  20. data/lib/rsolr/indexer.rb +23 -0
  21. data/lib/rsolr/mapper.rb +62 -0
  22. data/lib/rsolr/mapper/rss.rb +29 -0
  23. data/lib/rsolr/message.rb +73 -0
  24. data/lib/rsolr/response.rb +8 -0
  25. data/lib/rsolr/response/base.rb +33 -0
  26. data/lib/rsolr/response/index_info.rb +22 -0
  27. data/lib/rsolr/response/query.rb +170 -0
  28. data/lib/rsolr/response/update.rb +4 -0
  29. data/test/connection/direct_test.rb +22 -0
  30. data/test/connection/http_test.rb +19 -0
  31. data/test/connection/search_ext_test_methods.rb +17 -0
  32. data/test/connection/test_methods.rb +122 -0
  33. data/test/http_client/curb_test.rb +19 -0
  34. data/test/http_client/net_http_test.rb +13 -0
  35. data/test/http_client/test_methods.rb +40 -0
  36. data/test/http_client/util_test.rb +40 -0
  37. data/test/mapper_test.rb +123 -0
  38. data/test/message_test.rb +87 -0
  39. data/test/pagination_test.rb +58 -0
  40. data/test/ruby-lang.org.rss.xml +391 -0
  41. data/test/test_helpers.rb +39 -0
  42. metadata +107 -0
@@ -0,0 +1,22 @@
1
+ # response for /admin/luke
2
+ class RSolr::Response::IndexInfo < RSolr::Response::Base
3
+
4
+ attr_reader :index, :directory, :has_deletions, :optimized, :current, :max_doc, :num_docs, :version
5
+
6
+ alias :has_deletions? :has_deletions
7
+ alias :optimized? :optimized
8
+ alias :current? :current
9
+
10
+ def initialize(data)
11
+ super(data)
12
+ @index = @data['index']
13
+ @directory = @data['directory']
14
+ @has_deletions = @index['hasDeletions']
15
+ @optimized = @index['optimized']
16
+ @current = @index['current']
17
+ @max_doc = @index['maxDoc']
18
+ @num_docs = @index['numDocs']
19
+ @version = @index['version']
20
+ end
21
+
22
+ end
@@ -0,0 +1,170 @@
1
+ # response module for queries
2
+ module RSolr::Response::Query
3
+
4
+ # module for adding helper methods to each Hash document
5
+ module DocExt
6
+
7
+ def self.extended(base)
8
+ base.keys.each do |k,v|
9
+ base.instance_eval <<-EOF
10
+ def #{k}; self['#{k.to_s}']; end
11
+ EOF
12
+ end
13
+ end
14
+
15
+ # Helper method to check if value/multi-values exist for a given key.
16
+ # The value can be a string, or a RegExp
17
+ # Example:
18
+ # doc.has?(:location_facet)
19
+ # doc.has?(:location_facet, 'Clemons')
20
+ # doc.has?(:id, 'h009', /^u/i)
21
+ def has?(k, *values)
22
+ return if self[k].nil?
23
+ return true if self.has_key?(k) and values.empty?
24
+ target = self[k]
25
+ if target.is_a?(Array)
26
+ values.each do |val|
27
+ return target.any?{|tv| val.is_a?(Regexp) ? (tv =~ val) : (tv==val)}
28
+ end
29
+ else
30
+ return values.any? {|val| val.is_a?(Regexp) ? (target =~ val) : (target == val)}
31
+ end
32
+ end
33
+
34
+ end
35
+
36
+ # from the delsolr project -> http://github.com/avvo/delsolr/tree/master/lib/delsolr/response.rb
37
+ module Facets
38
+
39
+ def facets
40
+ @facets ||= data['facet_counts'] || {}
41
+ end
42
+
43
+ # Returns the hash of all the facet_fields (ie: {'instock_b' => ['true', 123, 'false', 20]}
44
+ def facet_fields
45
+ @facet_fields ||= facets['facet_fields'] || {}
46
+ end
47
+
48
+ # Returns all of the facet queries
49
+ def facet_queries
50
+ @facet_queries ||= facets['facet_queries'] || {}
51
+ end
52
+
53
+ # Returns a hash of hashs rather than a hash of arrays (ie: {'instock_b' => {'true' => 123', 'false', => 20} })
54
+ def facet_fields_by_hash
55
+ @facet_fields_by_hash ||= begin
56
+ f = {}
57
+ if facet_fields
58
+ facet_fields.each do |field,value_and_counts|
59
+ f[field] = {}
60
+ value_and_counts.each_with_index do |v, i|
61
+ if i % 2 == 0
62
+ f[field][v] = value_and_counts[i+1]
63
+ end
64
+ end
65
+ end
66
+ end
67
+ f
68
+ end
69
+ end
70
+
71
+ # Returns an array of value/counts for a given field (ie: ['true', 123, 'false', 20]
72
+ def facet_field(field)
73
+ facet_fields[field.to_s]
74
+ end
75
+
76
+ # Returns the array of field values for the given field in the order they were returned from solr
77
+ def facet_field_values(field)
78
+ facet_field_values ||= {}
79
+ facet_field_values[field.to_s] ||= begin
80
+ a = []
81
+ return unless facet_field(field)
82
+ facet_field(field).each_with_index do |val_or_count, i|
83
+ a << val_or_count if i % 2 == 0 && facet_field(field)[i+1] > 0
84
+ end
85
+ a
86
+ end
87
+ end
88
+
89
+ # Returns a hash of value/counts for a given field (ie: {'true' => 123, 'false' => 20}
90
+ def facet_field_by_hash(field)
91
+ facet_fields_by_hash[field.to_s]
92
+ end
93
+
94
+ # Returns the count for the given field/value pair
95
+ def facet_field_count(field, value)
96
+ facet_fields_by_hash[field.to_s][value.to_s] if facet_fields_by_hash[field.to_s]
97
+ end
98
+
99
+ # Returns the counts for a given facet_query_name
100
+ def facet_query_count_by_name(facet_query_name)
101
+ query_string = query_builder.facet_query_by_name(facet_query_name)
102
+ facet_queries[query_string] if query_string
103
+ end
104
+
105
+ end
106
+
107
+ #
108
+ #
109
+ #
110
+ module Pagination
111
+
112
+ # alias to the Solr param, 'rows'
113
+ def per_page
114
+ @per_page ||= params['rows'].to_s.to_i
115
+ end
116
+
117
+ # Returns the current page calculated from 'rows' and 'start'
118
+ # WillPaginate hook
119
+ def current_page
120
+ @current_page ||= (self.start / self.per_page).ceil + 1
121
+ end
122
+
123
+ # Calcuates the total pages from 'numFound' and 'rows'
124
+ # WillPaginate hook
125
+ def total_pages
126
+ @total_pages ||= self.per_page > 0 ? (self.total / self.per_page.to_f).ceil : 1
127
+ end
128
+
129
+ # returns the previous page number or 1
130
+ # WillPaginate hook
131
+ def previous_page
132
+ @previous_page ||= (current_page > 1) ? current_page - 1 : 1
133
+ end
134
+
135
+ # returns the next page number or the last
136
+ # WillPaginate hook
137
+ def next_page
138
+ @next_page ||= (current_page < total_pages) ? current_page + 1 : total_pages
139
+ end
140
+
141
+ end
142
+
143
+ # The base query response class
144
+ # adds to the Solr::Response::Base class by defining a few more attributes,
145
+ # includes the Pagination module, and extends each of the doc hashes
146
+ # with Solr::Response::Query::DocExt
147
+ class Base < RSolr::Response::Base
148
+
149
+ include RSolr::Response::Query::Pagination
150
+ include RSolr::Response::Query::Facets
151
+
152
+ attr_reader :response, :docs, :num_found, :start
153
+
154
+ alias :total :num_found
155
+ alias :offset :start
156
+
157
+ def initialize(data)
158
+ super(data)
159
+ @response = @data['response']
160
+ @docs = @response['docs']
161
+ @docs.each do |d|
162
+ d.extend RSolr::Response::Query::DocExt
163
+ end
164
+ @num_found = @response['numFound']
165
+ @start = @response['start']
166
+ end
167
+
168
+ end
169
+
170
+ end
@@ -0,0 +1,4 @@
1
+ # response class for update requests - not sure if this is needed yet?
2
+ class RSolr::Response::Update < RSolr::Response::Base
3
+
4
+ end
@@ -0,0 +1,22 @@
1
+ if defined?(JRUBY_VERSION)
2
+
3
+ require File.join(File.dirname(__FILE__), '..', 'test_helpers')
4
+
5
+ require File.join(File.dirname(__FILE__), 'test_methods')
6
+
7
+ class ConnectionDirectTest < RSolrBaseTest
8
+
9
+ include ConnectionTestMethods
10
+
11
+ def setup
12
+ base = File.expand_path( File.dirname(__FILE__) )
13
+ dist = File.join(base, '..', '..', 'apache-solr')
14
+ home = File.join(dist, 'example', 'rsolr')
15
+ @solr = RSolr.connect(:direct, :home_dir=>home, :dist_dir=>dist)
16
+ @solr.delete_by_query('*:*')
17
+ @solr.commit
18
+ end
19
+
20
+ end
21
+
22
+ end
@@ -0,0 +1,19 @@
1
+ unless defined?(JRUBY_VERSION)
2
+
3
+ require File.join(File.dirname(__FILE__), '..', 'test_helpers')
4
+
5
+ require File.join(File.dirname(__FILE__), 'test_methods')
6
+
7
+ class AdapterHTTPTest < RSolrBaseTest
8
+
9
+ include ConnectionTestMethods
10
+
11
+ def setup
12
+ @solr = RSolr.connect :http
13
+ @solr.delete_by_query('*:*')
14
+ @solr.commit
15
+ end
16
+
17
+ end
18
+
19
+ end
@@ -0,0 +1,17 @@
1
+ raise 'Not yet implemented!'
2
+
3
+ module SearchExtTestMethods
4
+
5
+ def test_facet_response_methods
6
+ @response.facets
7
+ @response.facet_fields
8
+ @response.facet_queries
9
+ @response.facet_fields_by_hash
10
+ @response.facet_field(:feed_language_facet)
11
+ @response.facet_field_values(:feed_language_facet)
12
+ @response.facet_field_by_hash(:feed_language_facet)
13
+ @response.facet_field_by_hash(:feed_language_facet)
14
+ @response.facet_field_count(:feed_title_facet, 'ScienceDaily: Latest Science News')
15
+ end
16
+
17
+ end
@@ -0,0 +1,122 @@
1
+ # These are all of the test methods used by the various connection + adapter tests
2
+ # Currently: Direct and HTTP
3
+ # By sharing these tests, we can make sure the adapters are doing what they're suppossed to
4
+ # while staying "dry"
5
+
6
+ module ConnectionTestMethods
7
+
8
+ #def teardown
9
+ # @solr.delete_by_query('id:[* TO *]')
10
+ # @solr.commit
11
+ # assert_equal 0, @solr.query(:q=>'*:*').docs.size
12
+ #end
13
+
14
+ def test_default_options
15
+ target = {
16
+ :select_path => '/select',
17
+ :update_path => '/update',
18
+ :luke_path => '/admin/luke'
19
+ }
20
+ assert_equal target, @solr.adapter.default_options
21
+ end
22
+
23
+ # setting adapter options in Solr.connect method should set them in the adapter
24
+ def test_set_adapter_options
25
+ solr = RSolr.connect(:http, :select_path=>'/select2')
26
+ assert_equal '/select2', solr.adapter.opts[:select_path]
27
+ end
28
+
29
+ # setting connection options in Solr.connect method should set them in the connection
30
+ def test_set_connection_options
31
+ solr = RSolr.connect(:http, :default_wt=>:json)
32
+ assert_equal :json, solr.opts[:default_wt]
33
+ end
34
+
35
+ # If :wt is NOT :ruby, the format doesn't get wrapped in a Solr::Response class
36
+ # Raw ruby can be returned by using :wt=>'ruby', not :ruby
37
+ def test_raw_response_formats
38
+ ruby_response = @solr.query(:q=>'*:*', :wt=>'ruby')
39
+ assert ruby_response[:body].is_a?(String)
40
+ assert ruby_response[:body]=~%r('wt'=>'ruby')
41
+ # xml?
42
+ xml_response = @solr.query(:q=>'*:*', :wt=>'xml')
43
+ assert xml_response[:body]=~%r(<str name="wt">xml</str>)
44
+ # json?
45
+ json_response = @solr.query(:q=>'*:*', :wt=>'json')
46
+ assert json_response[:body]=~%r("wt":"json")
47
+ end
48
+
49
+ def test_query_responses
50
+ r = @solr.query(:q=>'*:*')
51
+ assert r.is_a?(RSolr::Response::Query::Base)
52
+ # catch exceptions for bad queries
53
+ assert_raise RSolr::RequestError do
54
+ @solr.query(:q=>'!')
55
+ end
56
+ end
57
+
58
+ def test_query_response_docs
59
+ @solr.add(:id=>1, :price=>1.00, :cat=>['electronics', 'something else'])
60
+ @solr.commit
61
+ r = @solr.query(:q=>'*:*')
62
+ assert r.is_a?(RSolr::Response::Query::Base)
63
+ assert_equal Array, r.docs.class
64
+ first = r.docs.first
65
+ assert first.respond_to?(:price)
66
+ assert first.respond_to?(:cat)
67
+ assert first.respond_to?(:id)
68
+ assert first.respond_to?(:timestamp)
69
+
70
+ # test the has? method
71
+ assert first.has?('price', 1.00)
72
+ assert first.has?('cat', 'electronics')
73
+ assert first.has?('cat', 'something else')
74
+
75
+ assert first.has?('cat', /something/)
76
+
77
+ # has? only works with strings at this time
78
+ assert_nil first.has?(:cat)
79
+
80
+ assert false == first.has?('cat', /zxcv/)
81
+ end
82
+
83
+ def test_add
84
+ assert_equal 0, @solr.query(:q=>'*:*').total
85
+ response = @solr.add(:id=>100)
86
+ @solr.commit
87
+ assert_equal 1, @solr.query(:q=>'*:*').total
88
+ assert response.is_a?(RSolr::Response::Update)
89
+ end
90
+
91
+ def test_delete_by_id
92
+ @solr.add(:id=>100)
93
+ @solr.commit
94
+ total = @solr.query(:q=>'*:*').total
95
+ assert_equal 1, total
96
+ delete_response = @solr.delete_by_id(100)
97
+ @solr.commit
98
+ assert delete_response.is_a?(RSolr::Response::Update)
99
+ total = @solr.query(:q=>'*:*').total
100
+ assert_equal 0, total
101
+ end
102
+
103
+ def test_delete_by_query
104
+ @solr.add(:id=>1, :name=>'BLAH BLAH BLAH')
105
+ @solr.commit
106
+ assert_equal 1, @solr.query(:q=>'*:*').total
107
+ response = @solr.delete_by_query('name:BLAH BLAH BLAH')
108
+ @solr.commit
109
+ assert response.is_a?(RSolr::Response::Update)
110
+ assert_equal 0, @solr.query(:q=>'*:*').total
111
+ end
112
+
113
+ def test_index_info
114
+ response = @solr.index_info
115
+ assert response.is_a?(RSolr::Response::IndexInfo)
116
+ # make sure the ? methods are true/false
117
+ assert [true, false].include?(response.current?)
118
+ assert [true, false].include?(response.optimized?)
119
+ assert [true, false].include?(response.has_deletions?)
120
+ end
121
+
122
+ end
@@ -0,0 +1,19 @@
1
+ # don't run this test in jruby,
2
+ # the curb gem is a c extension based gem, jruby has no support for this
3
+ unless defined?(JRUBY_VERSION)
4
+
5
+ require File.join(File.dirname(__FILE__), '..', 'test_helpers')
6
+
7
+ require File.join(File.dirname(__FILE__), 'test_methods')
8
+
9
+ class CurbTest < RSolrBaseTest
10
+
11
+ def setup
12
+ @c ||= RSolr::HTTPClient.connect(URL, :curb)
13
+ end
14
+
15
+ include HTTPClientTestMethods
16
+
17
+ end
18
+
19
+ end
@@ -0,0 +1,13 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'test_helpers')
2
+
3
+ require File.join(File.dirname(__FILE__), 'test_methods')
4
+
5
+ class NetHTTPTest < RSolrBaseTest
6
+
7
+ def setup
8
+ @c ||= RSolr::HTTPClient.connect(URL, :net_http)
9
+ end
10
+
11
+ include HTTPClientTestMethods
12
+
13
+ end
@@ -0,0 +1,40 @@
1
+ module HTTPClientTestMethods
2
+
3
+ URL = 'http://localhost:8983/solr/'
4
+
5
+ def test_raise_unknown_adapter
6
+ assert_raise RSolr::HTTPClient::UnkownAdapterError do
7
+ c = RSolr::HTTPClient.connect(URL, :blah)
8
+ end
9
+ end
10
+
11
+ # the responses from the HTTPClient adapter should return the same hash structure
12
+ def test_get_response
13
+ headers = {}
14
+ data = nil
15
+ response = @c.get('select', :q=>'*:*')
16
+ assert_equal data, response[:data]
17
+ assert_equal 200, response[:status_code]
18
+ expected_params = {:q=>'*:*'}
19
+ assert_equal expected_params, response[:params]
20
+ assert_equal 'select', response[:path]
21
+ assert response[:body] =~ /name="responseHeader"/
22
+ assert_equal 'http://localhost:8983/solr/select?q=%2A%3A%2A', response[:url]
23
+ assert_equal headers, response[:headers]
24
+ end
25
+
26
+ def test_post_response
27
+ headers = {"Content-Type" => 'text/xml; charset=utf-8'}
28
+ data = '<add><doc><field name="id">1</field></doc></add>'
29
+ response = @c.post('update', data, {}, headers)
30
+ assert_equal data, response[:data]
31
+ assert_equal 200, response[:status_code]
32
+ expected_params = {}
33
+ assert_equal expected_params, response[:params]
34
+ assert_equal 'update', response[:path]
35
+ assert response[:body] =~ /name="responseHeader"/
36
+ assert_equal 'http://localhost:8983/solr/update', response[:url]
37
+ assert_equal headers, response[:headers]
38
+ end
39
+
40
+ end