mwmitchell-rsolr 0.5.7

Sign up to get free protection for your applications and to get access to all the features.
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