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