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.
- data/CHANGES.txt +41 -0
- data/LICENSE +201 -0
- data/README.rdoc +191 -0
- data/Rakefile +40 -0
- data/examples/direct.rb +20 -0
- data/examples/http.rb +16 -0
- data/lib/core_ext.rb +8 -0
- data/lib/rsolr.rb +34 -0
- data/lib/rsolr/connection.rb +7 -0
- data/lib/rsolr/connection/adapter.rb +7 -0
- data/lib/rsolr/connection/adapter/common_methods.rb +46 -0
- data/lib/rsolr/connection/adapter/direct.rb +80 -0
- data/lib/rsolr/connection/adapter/http.rb +51 -0
- data/lib/rsolr/connection/base.rb +121 -0
- data/lib/rsolr/connection/search_ext.rb +126 -0
- data/lib/rsolr/http_client.rb +115 -0
- data/lib/rsolr/http_client/adapter.rb +6 -0
- data/lib/rsolr/http_client/adapter/curb.rb +51 -0
- data/lib/rsolr/http_client/adapter/net_http.rb +48 -0
- data/lib/rsolr/indexer.rb +23 -0
- data/lib/rsolr/mapper.rb +62 -0
- data/lib/rsolr/mapper/rss.rb +29 -0
- data/lib/rsolr/message.rb +73 -0
- data/lib/rsolr/response.rb +8 -0
- data/lib/rsolr/response/base.rb +33 -0
- data/lib/rsolr/response/index_info.rb +22 -0
- data/lib/rsolr/response/query.rb +170 -0
- data/lib/rsolr/response/update.rb +4 -0
- data/test/connection/direct_test.rb +22 -0
- data/test/connection/http_test.rb +19 -0
- data/test/connection/search_ext_test_methods.rb +17 -0
- data/test/connection/test_methods.rb +122 -0
- data/test/http_client/curb_test.rb +19 -0
- data/test/http_client/net_http_test.rb +13 -0
- data/test/http_client/test_methods.rb +40 -0
- data/test/http_client/util_test.rb +40 -0
- data/test/mapper_test.rb +123 -0
- data/test/message_test.rb +87 -0
- data/test/pagination_test.rb +58 -0
- data/test/ruby-lang.org.rss.xml +391 -0
- data/test/test_helpers.rb +39 -0
- 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,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
|