delsolr 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ == 0.0.1 2008-09-08
2
+
3
+ * 1 major enhancement:
4
+ * Initial release
5
+ * No support for POST
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 FIXME full name
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,11 @@
1
+ History.txt
2
+ License.txt
3
+ Manifest.txt
4
+ README.txt
5
+ Rakefile
6
+ lib/delsolr.rb
7
+ lib/delsolr/configuration.rb
8
+ lib/delsolr/extensions.rb
9
+ lib/delsolr/query_builder.rb
10
+ lib/delsolr/response.rb
11
+ lib/delsolr/version.rb
@@ -0,0 +1,49 @@
1
+ = delsolr
2
+
3
+ http://delsolr.rubyforge.org
4
+
5
+ == DESCRIPTION:
6
+
7
+ DelSolr is a light weight ruby wrapper for solr. It's intention is to expose the full power of solr queries
8
+ while keeping the interface as ruby-esque as possible.
9
+
10
+ == FEATURES/PROBLEMS:
11
+
12
+ * Only supports querying (GET), no indexing (POST) support yet
13
+
14
+ == SYNOPSIS:
15
+
16
+ See http://delsolr.rubyforge.org for more info
17
+
18
+ == REQUIREMENTS:
19
+
20
+ You need Solr installed somewhere so you can query it ;)
21
+
22
+ == INSTALL:
23
+
24
+ sudo gem install delsolr
25
+
26
+ == LICENSE:
27
+
28
+ (The MIT License)
29
+
30
+ Copyright (c) 2008 FIXME full name
31
+
32
+ Permission is hereby granted, free of charge, to any person obtaining
33
+ a copy of this software and associated documentation files (the
34
+ 'Software'), to deal in the Software without restriction, including
35
+ without limitation the rights to use, copy, modify, merge, publish,
36
+ distribute, sublicense, and/or sell copies of the Software, and to
37
+ permit persons to whom the Software is furnished to do so, subject to
38
+ the following conditions:
39
+
40
+ The above copyright notice and this permission notice shall be
41
+ included in all copies or substantial portions of the Software.
42
+
43
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
44
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
45
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
46
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
47
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
48
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
49
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,4 @@
1
+ require 'config/requirements'
2
+ require 'config/hoe' # setup Hoe + all gem configuration
3
+
4
+ Dir['tasks/**/*.rake'].each { |rake| load rake }
@@ -0,0 +1,97 @@
1
+ #
2
+ # DelSolr
3
+ #
4
+ # ben@avvo.com 9.1.2008
5
+ #
6
+ # see README.txt
7
+ #
8
+
9
+ require 'net/http'
10
+
11
+ require 'digest/md5'
12
+
13
+ require File.join(File.dirname(__FILE__), 'delsolr', 'version')
14
+ require File.join(File.dirname(__FILE__), 'delsolr', 'response')
15
+ require File.join(File.dirname(__FILE__), 'delsolr', 'configuration')
16
+ require File.join(File.dirname(__FILE__), 'delsolr', 'query_builder')
17
+ require File.join(File.dirname(__FILE__), 'delsolr', 'extensions')
18
+
19
+ module DelSolr
20
+
21
+ class Client
22
+
23
+ attr_reader :configuration, :connection, :logger
24
+
25
+ # options
26
+ # :server - the server you want to connect to
27
+ # :port - the port you want to connect to
28
+ # :cache - [optional] a cache instance (any object the supports get and set)
29
+ # :shortcuts - [options] a list of values in the doc fields that generate short cuts for (defaults to [:id, :unique_id, :score]).
30
+ # With the response you will then be able to do rsp.scores and have it return an array of scores.
31
+ def initialize(opts = {})
32
+ @configuration = DelSolr::Client::Configuration.new(opts[:server], opts[:port])
33
+ @cache = opts[:cache]
34
+ @shortcuts = opts[:shortcuts]
35
+ end
36
+
37
+ #
38
+ #
39
+ # query_name - type of query to perform (should match up w/ queries defined in solrconfig.xml)
40
+ #
41
+ #
42
+ # Possible options
43
+ # :query - [required] unescape user input or solr query (can also be a hash {:field_name => value}).
44
+ # :filters - [optional] array, string, or hash of additional filters to apply
45
+ # :facet - [optional] array of hashes for all the facet params (ie: {:field => 'instock_b', :limit => 15, :mincount => 5})
46
+ # You can also specify facets using a query (ie: {:query => 'city_idm:seattle', :name => 'seattle'} or even
47
+ # {:query => {:city => 'seattle'}, :name => 'seattle'}) and then get counts for that facet by calling
48
+ # rsp.facet_query_count_by_name('seattle').
49
+ #
50
+ # :sorts - [optional] array or string of sorts
51
+ # :limit - [optional] number to return (defaults to 10)
52
+ # :offset - [optional] offset (defaults to 0)
53
+ # :enable_caching - [optional] switch to control whether or not to use the cache (for fetching or setting)
54
+ #
55
+ # Returns a DelSolr::Client::Response instance
56
+ def query(query_name, opts = {})
57
+
58
+ raise "query_name must be supplied" if query_name.blank?
59
+
60
+ enable_caching = opts.delete(:enable_caching) && !@cache.nil?
61
+ ttl = opts.delete(:ttl) || 1.hours
62
+
63
+ query_builder = DelSolr::Client::QueryBuilder.new(query_name, opts)
64
+
65
+ # it's important that the QueryBuilder returns strings in a deterministic fashion
66
+ # so that the cache keys will match for the same query.
67
+ cache_key = Digest::MD5.hexdigest(query_builder.request_string)
68
+ from_cache = false
69
+
70
+ # if we're caching, first try looking in the cache
71
+ if enable_caching
72
+ body = @cache.get(cache_key) rescue body = nil
73
+ from_cache = true unless body.blank?
74
+ end
75
+
76
+ if body.blank? # cache miss (or wasn't enabled)
77
+
78
+ # only bother to create the connection if we know we failed to hit the cache
79
+ @connection ||= Net::HTTP.new(configuration.server, configuration.port)
80
+ raise "Failed to connect to #{configuration.server}:#{configuration.port}" if @connection.nil?
81
+
82
+ header, body = @connection.get(query_builder.request_string)
83
+
84
+ # add to the cache if caching
85
+ if enable_caching
86
+ begin
87
+ @cache.set(cache_key, body, ttl)
88
+ rescue
89
+ end
90
+ end
91
+ end
92
+
93
+ DelSolr::Client::Response.new(body, query_builder, :from_cache => from_cache, :shortcuts => @shortcuts)
94
+ end
95
+
96
+ end
97
+ end
@@ -0,0 +1,13 @@
1
+ module DelSolr
2
+ class Client
3
+ class Configuration
4
+ attr_accessor :server, :port
5
+
6
+ def initialize(server, port)
7
+ @server = server
8
+ @port = port.to_i
9
+ end
10
+
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,35 @@
1
+ #
2
+ # Common extensions that we need to define if they don't already exist...
3
+ #
4
+
5
+ String.class_eval do
6
+ if !''.respond_to?(:blank?)
7
+ def blank?
8
+ self == ''
9
+ end
10
+ end
11
+ end
12
+
13
+ NilClass.class_eval do
14
+ if !nil.respond_to?(:blank?)
15
+ def blank?
16
+ true
17
+ end
18
+ end
19
+ end
20
+
21
+ Hash.class_eval do
22
+ if !{}.respond_to?(:blank?)
23
+ def blank?
24
+ self == {}
25
+ end
26
+ end
27
+ end
28
+
29
+ Fixnum.class_eval do
30
+ if !1.respond_to?(:hours)
31
+ def hours
32
+ self * 60 * 60
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,201 @@
1
+ require 'cgi'
2
+
3
+ module DelSolr
4
+
5
+ class Client
6
+
7
+ class QueryBuilder
8
+
9
+ attr_accessor :query_name, :options
10
+
11
+ # ops can basically be straight solr URL params, but it also supports some other formats
12
+ # of different params to give it more of a "ruby" feel (ie: :filters can be an array, hash, or string,
13
+ # but you can also just specify the fq params directly
14
+ def initialize(query_name, opts = {})
15
+ @query_name = query_name
16
+ @options = opts
17
+ end
18
+
19
+ def request_string
20
+ @request_string ||= build_request_string
21
+ end
22
+
23
+ # returns the query string of the facet query for the given query name (used for resolving counts for given queries)
24
+ def facet_query_by_name(query_name)
25
+ name_to_facet_query[query_name]
26
+ end
27
+
28
+ private
29
+
30
+ def build_request_string()
31
+ raise "query_name must be set" if query_name.blank?
32
+
33
+ opts = self.options.dup
34
+
35
+ # cleanup the nils
36
+ opts.delete_if {|k,v| v.nil?}
37
+
38
+ # resolve "rubyish" names to solr names
39
+ opts[:q] ||= opts[:query]
40
+ opts[:rows] ||= opts[:limit] || 10
41
+ opts[:start] ||= opts[:offset] || 0
42
+ opts[:fl] ||= opts[:fields] || 'id,unique_id,index_type,score'
43
+ opts[:bq] ||= opts[:boost]
44
+ opts[:suggestionCount] ||= opts[:suggestion_count]
45
+ opts[:onlyMorePopular] ||= opts[:only_more_popular]
46
+
47
+ raise ":query or :q must be set" if opts[:q].blank?
48
+
49
+ # clear out the "rubyish" versions, what's left will go straight to solr
50
+ opts.delete(:query)
51
+ opts.delete(:limit)
52
+ opts.delete(:offset)
53
+ opts.delete(:fields)
54
+ opts.delete(:boost)
55
+ opts.delete(:suggestion_count)
56
+ opts.delete(:only_more_popular)
57
+
58
+ # needs to be an array of hashs because it's acceptable to have the same key present > once.
59
+ params = []
60
+
61
+ # remove params as we go so we can just pass whatever is left to solr...
62
+
63
+ params << build_query(:q, opts.delete(:q))
64
+ params << {:wt => 'ruby'}
65
+ params << {:qt => query_name}
66
+ params << {:rows => opts.delete(:rows)}
67
+ params << {:start => opts.delete(:start)}
68
+ params << {:fl => opts.delete(:fl)}
69
+
70
+ filters = opts.delete(:filters)
71
+ params += build_filters(:fq, filters)
72
+
73
+ facets = opts.delete(:facets)
74
+ if facets
75
+ if facets.is_a?(Array)
76
+ params << {:facet => true}
77
+ params += build_facets(facets)
78
+ elsif facets.is_a?(Hash)
79
+ params << {:facet => true}
80
+ params += build_facet(facets)
81
+ elsif facets.is_a?(String)
82
+ params += facets
83
+ else
84
+ raise 'facets must either be a Hash or an Array'
85
+ end
86
+ end
87
+
88
+ # handle friendly highlight name
89
+ if opts.delete(:highlight)
90
+ params << {:hl => 'true'}
91
+ params << {'hl.fl' => opts['hl.fl'] || opts[:fl] }
92
+ end
93
+
94
+ # just pass everything that's left to solr
95
+ opts.each { |k,v| params << {k => v} if !v.nil? }
96
+
97
+ # convert the params (array of hashes)
98
+ param_strings = params.collect do |h|
99
+ if h.is_a?(Hash)
100
+ ha = h.to_a
101
+ "#{ha[0][0]}=#{::CGI::escape(ha[0][1].to_s)}"
102
+ elsif h.is_a?(String)
103
+ h # just return the string
104
+ else
105
+ raise "All params should be a Hash or String"
106
+ end
107
+ end
108
+
109
+ "/solr/select?#{param_strings.join('&')}"
110
+ end
111
+
112
+ # returns the query param
113
+ def build_query(key, queries)
114
+ query_string = ''
115
+ case queries
116
+ when String
117
+ query_string = queries
118
+ when Array
119
+ query_string = queries.join(' ')
120
+ when Hash
121
+ query_string_array = []
122
+ queries.each do |k,v|
123
+ if v.is_a?(Array) # add a filter for each value
124
+ v.each do |val|
125
+ query_string_array << "#{k}:#{val}"
126
+ end
127
+ elsif v.is_a?(Range)
128
+ query_string_array << "#{k}:[#{v.min} TO #{v.max}]"
129
+ else
130
+ query_string_array << "#{k}:#{v}"
131
+ end
132
+ end
133
+ query_string = query_string_array.join(' ')
134
+ end
135
+
136
+ {key => query_string}
137
+ end
138
+
139
+ def build_filters(key, filters)
140
+ params = []
141
+
142
+ # handle "ruby-ish" filters
143
+ case filters
144
+ when String
145
+ params << {key => filters}
146
+ when Array
147
+ filters.each { |f| params << {key => f} }
148
+ when Hash
149
+ filters.each do |k,v|
150
+ if v.is_a?(Array) # add a filter for each value
151
+ v.each do |val|
152
+ params << {key => "#{k}:#{val}"}
153
+ end
154
+ elsif v.is_a?(Range)
155
+ params << {key => "#{k}:[#{v.min} TO #{v.max}]"}
156
+ else
157
+ params << {key => "#{k}:#{v}"}
158
+ end
159
+ end
160
+ end
161
+ params
162
+ end
163
+
164
+ def build_facets(facet_array)
165
+ params = []
166
+ facet_array.each do |facet_hash|
167
+ params += build_facet(facet_hash)
168
+ end
169
+ params
170
+ end
171
+
172
+ def build_facet(facet_hash)
173
+ params = []
174
+ facet_name = facet_hash['name'] || facet_hash[:name]
175
+ facet_hash.each do |k,v|
176
+ # handle some cases specially
177
+ if 'field' == k.to_s
178
+ params << {"facet.field" => v}
179
+ elsif 'query' == k.to_s
180
+ q = build_query("facet.query", v)
181
+ params << q
182
+ if facet_name
183
+ # keep track of names => facet_queries
184
+ name_to_facet_query[facet_name] = q['facet.query']
185
+ end
186
+ elsif ['name', :name].include?(k.to_s)
187
+ # do nothing
188
+ else
189
+ params << {"f.#{facet_hash[:field]}.facet.#{k}" => v}
190
+ end
191
+ end
192
+ params
193
+ end
194
+
195
+ def name_to_facet_query
196
+ @name_to_facet_query ||= {}
197
+ end
198
+
199
+ end
200
+ end
201
+ end
@@ -0,0 +1,171 @@
1
+ module DelSolr
2
+
3
+ class Client
4
+
5
+ class Response
6
+
7
+ attr_reader :raw_response, :query_builder
8
+
9
+ def initialize(solr_response_buffer, query_builder, options = {})
10
+ @query_builder = query_builder
11
+ @from_cache = options[:from_cache]
12
+ begin
13
+ @raw_response = eval(solr_response_buffer)
14
+ rescue
15
+ @raw_response = nil
16
+ end
17
+
18
+ # now define the shortcuts
19
+ options[:shortcuts] ||= [:id, :unique_id, :score]
20
+ options[:shortcuts].each do |shortcut|
21
+ instance_eval %{
22
+ def #{shortcut}s
23
+ @#{shortcut}s ||= docs.collect {|d| d['#{shortcut}'] }
24
+ end
25
+ }
26
+ end
27
+ end
28
+
29
+ # returns the total number of matches
30
+ def total
31
+ @total ||= raw_response['response']['numFound']
32
+ end
33
+
34
+ def blank?
35
+ total < 1
36
+ end
37
+
38
+ alias_method :empty?, :blank?
39
+
40
+ def from_cache?
41
+ @from_cache
42
+ end
43
+
44
+ # returns the offset
45
+ def offset
46
+ @offset ||= raw_response['response']['start']
47
+ end
48
+
49
+ # returns the max score
50
+ def max_score
51
+ @max_score ||= raw_response['response']['maxScore'].to_f
52
+ end
53
+
54
+ # returns an array of all ids for the given search
55
+ def ids
56
+ @ids ||= docs.collect {|d| d['id']}
57
+ end
58
+
59
+ def unique_ids
60
+ @unique_ids ||= docs.collect {|d| d['unique_id']}
61
+ end
62
+
63
+ # returns an array of all the docs
64
+ def docs
65
+ @docs ||= raw_response['response']['docs']
66
+ end
67
+
68
+ # helper for displaying a given field (first tries the highlight, then the stored value)
69
+ def display_for(doc, field)
70
+ highlights_for(doc['unique_id'], field) || doc[field]
71
+ end
72
+
73
+ # returns the highlights for a given id for a given field
74
+ def highlights_for(unique_id, field)
75
+ raw_response['highlighting'] ||= {}
76
+ raw_response['highlighting'][unique_id] ||= {}
77
+ raw_response['highlighting'][unique_id][field]
78
+ end
79
+
80
+ def suggestions
81
+ @suggestions ||= raw_response['suggestions']
82
+ end
83
+
84
+ # returns the query time in ms
85
+ def qtime
86
+ @qtime ||= raw_response['responseHeader']['QTime'].to_i
87
+ end
88
+
89
+ # returns the status code (0 for success)
90
+ def status
91
+ @status ||= raw_response['responseHeader']['status']
92
+ end
93
+
94
+ # returns the params hash
95
+ def params
96
+ @params ||= raw_response['responseHeader']['params']
97
+ end
98
+
99
+ # returns the entire facet hash
100
+ def facets
101
+ @facets ||= raw_response['facet_counts'] || {}
102
+ end
103
+
104
+ # returns the hash of all the facet_fields (ie: {'instock_b' => ['true', 123, 'false', 20]}
105
+ def facet_fields
106
+ @facet_fields ||= facets['facet_fields'] || {}
107
+ end
108
+
109
+ def facet_queries
110
+ @facet_queries ||= facets['facet_queries'] || {}
111
+ end
112
+
113
+ # returns a hash of hashs rather than a hash of arrays (ie: {'instock_b' => {'true' => 123', 'false', => 20} })
114
+ def facet_fields_by_hash
115
+ @facet_fields_by_hash ||= begin
116
+ f = {}
117
+ if facet_fields
118
+ facet_fields.each do |field,value_and_counts|
119
+ f[field] = {}
120
+ value_and_counts.each_with_index do |v, i|
121
+ if i % 2 == 0
122
+ f[field][v] = value_and_counts[i+1]
123
+ end
124
+ end
125
+ end
126
+ end
127
+ f
128
+ end
129
+ end
130
+
131
+ # returns an array of value/counts for a given field (ie: ['true', 123, 'false', 20]
132
+ def facet_field(field)
133
+ facet_fields[field.to_s]
134
+ end
135
+
136
+ # returns the array of field values for the given field in the order they were returned from solr
137
+ def facet_field_values(field)
138
+ facet_field_values ||= {}
139
+ facet_field_values[field.to_s] ||= begin
140
+ a = []
141
+ facet_field(field).each_with_index do |val_or_count, i|
142
+ a << val_or_count if i % 2 == 0 && facet_field(field)[i+1] > 0
143
+ end
144
+ a
145
+ end
146
+ end
147
+
148
+ # returns a hash of value/counts for a given field (ie: {'true' => 123, 'false' => 20}
149
+ def facet_field_by_hash(field)
150
+ facet_fields_by_hash(field.to_s)
151
+ end
152
+
153
+ # returns the count for the given field/value pair
154
+ def facet_field_count(field, value)
155
+ facet_fields_by_hash[field.to_s][value.to_s] if facet_fields_by_hash[field.to_s]
156
+ end
157
+
158
+ # returns the counts for a given facet_query_name
159
+ def facet_query_count_by_name(facet_query_name)
160
+ query_string = query_builder.facet_query_by_name(facet_query_name)
161
+ facet_queries[query_string] if query_string
162
+ end
163
+
164
+ # returns the url send to solr
165
+ def request_url
166
+ query_builder.request_string
167
+ end
168
+
169
+ end
170
+ end
171
+ end
@@ -0,0 +1,9 @@
1
+ module DelSolr
2
+ module VERSION #:nodoc:
3
+ MAJOR = 0
4
+ MINOR = 0
5
+ TINY = 1
6
+
7
+ STRING = [MAJOR, MINOR, TINY].join('.')
8
+ end
9
+ end
@@ -0,0 +1,15 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class ClientTest < Test::Unit::TestCase
4
+
5
+ include Test::Unit::Assertions
6
+
7
+ def test_create
8
+ s = nil
9
+ assert_nothing_raised do
10
+ s = DelSolr::Client.new(:server => 'localhost', :port => 8983)
11
+ end
12
+ assert(s)
13
+ end
14
+
15
+ end
@@ -0,0 +1,2 @@
1
+ require 'test/unit'
2
+ require File.dirname(__FILE__) + '/../lib/delsolr'
@@ -0,0 +1,161 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class QueryBuilderTest < Test::Unit::TestCase
4
+
5
+ include Test::Unit::Assertions
6
+
7
+ def test_001
8
+ qb = nil
9
+
10
+ opts = {}
11
+ opts[:limit] = 13
12
+ opts[:offset] = 3
13
+ opts[:fl] = 'id'
14
+ opts[:query] = 'good book'
15
+
16
+ assert_nothing_raised { qb = DelSolr::Client::QueryBuilder.new('query_name', opts) }
17
+
18
+ assert(qb)
19
+
20
+ p = get_params(qb.request_string)
21
+ assert_equal(p['start'], '3')
22
+ assert_equal(p['rows'], '13')
23
+ assert_equal(p['fl'], 'id')
24
+ assert_equal(p['q'], 'good book')
25
+ end
26
+
27
+ def test_002
28
+ qb = nil
29
+
30
+ opts = {}
31
+ opts[:query] = "blahblah"
32
+ opts[:fields] = 'id,unique_id,score'
33
+
34
+ assert_nothing_raised { qb = DelSolr::Client::QueryBuilder.new('query_name', opts) }
35
+
36
+ assert(qb)
37
+
38
+ p = get_params(qb.request_string)
39
+ assert_equal(p['fl'], 'id,unique_id,score')
40
+ assert_equal(p['q'], 'blahblah')
41
+ end
42
+
43
+ def test_003
44
+ qb = nil
45
+
46
+ opts = {}
47
+ opts[:query] = {:index_type => 'books'}
48
+ opts[:fields] = 'id,unique_id,score'
49
+
50
+ assert_nothing_raised { qb = DelSolr::Client::QueryBuilder.new('query_name', opts) }
51
+
52
+ assert(qb)
53
+
54
+ p = get_params(qb.request_string)
55
+ assert_equal(p['fl'], 'id,unique_id,score')
56
+ assert_equal(p['q'], 'index_type:books')
57
+ end
58
+
59
+ def test_004
60
+ qb = nil
61
+
62
+ opts = {}
63
+ opts[:query] = {:index_type => 'books'}
64
+ opts[:filters] = {:location => 'seattle'}
65
+
66
+ assert_nothing_raised { qb = DelSolr::Client::QueryBuilder.new('query_name', opts) }
67
+
68
+ assert(qb)
69
+
70
+ p = get_params(qb.request_string)
71
+ assert_equal(p['fq'], 'location:seattle')
72
+ assert_equal(p['q'], 'index_type:books')
73
+ end
74
+
75
+ def test_005
76
+ qb = nil
77
+
78
+ opts = {}
79
+ opts[:query] = {:index_type => 'books'}
80
+ opts[:filters] = "location:seattle"
81
+
82
+ assert_nothing_raised { qb = DelSolr::Client::QueryBuilder.new('query_name', opts) }
83
+
84
+ assert(qb)
85
+
86
+ p = get_params(qb.request_string)
87
+
88
+ assert_equal(p['fq'], 'location:seattle')
89
+ assert_equal(p['q'], 'index_type:books')
90
+ end
91
+
92
+ def test_facets
93
+ qb = nil
94
+ opts = {}
95
+ opts[:query] = "games"
96
+ opts[:facets] = [{:field => 'instock_b'}, {:field => 'on_sale_b', :limit => 1}]
97
+
98
+ assert_nothing_raised { qb = DelSolr::Client::QueryBuilder.new('onebox-books', opts) }
99
+
100
+ assert(qb)
101
+
102
+ p = get_params(qb.request_string)
103
+
104
+ assert_equal(p['facet'], 'true')
105
+ assert_equal(p['facet.field'].sort, ['instock_b', 'on_sale_b'].sort)
106
+ assert_equal(p['f.on_sale_b.facet.limit'], '1')
107
+ end
108
+
109
+ def test_facets
110
+ qb = nil
111
+ opts = {}
112
+ opts[:query] = "games"
113
+ opts[:facets] = [{:query => {:city_idm => 19596}, :name => 'seattle'}, {:field => 'language_idm'}]
114
+
115
+ assert_nothing_raised { qb = DelSolr::Client::QueryBuilder.new('onebox-books', opts) }
116
+
117
+ assert(qb)
118
+
119
+ p = get_params(qb.request_string)
120
+
121
+ assert_equal(p['facet'], 'true')
122
+ assert_equal(p['facet.field'], 'language_idm')
123
+ assert_equal(p['facet.query'], 'city_idm:19596')
124
+ end
125
+
126
+ def test_range
127
+ qb = nil
128
+ opts = {}
129
+ opts[:query] = "games"
130
+ opts[:filters] = {:id => (1..3)}
131
+
132
+ assert_nothing_raised { qb = DelSolr::Client::QueryBuilder.new('onebox-books', opts) }
133
+
134
+ assert(qb)
135
+
136
+ p = get_params(qb.request_string)
137
+
138
+ assert_equal('id:[1 TO 3]', p['fq'])
139
+ end
140
+
141
+
142
+ private
143
+
144
+ # given a url returns a hash of the query params (for each duplicate key, it returns an array)
145
+ def get_params(url)
146
+ query = URI.parse(url).query
147
+ query = query.split('&')
148
+ h = {}
149
+ query.each do |p|
150
+ a = p.split('=')
151
+ if h[a[0]]
152
+ h[a[0]] = (Array(h[a[0]]) << CGI::unescape(a[1])) # convert it to an array
153
+ else
154
+ h[a[0]] = CGI::unescape(a[1])
155
+ end
156
+ end
157
+ h
158
+ end
159
+
160
+
161
+ end
@@ -0,0 +1,131 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class ResponseTest < Test::Unit::TestCase
4
+
5
+ include Test::Unit::Assertions
6
+
7
+ @@test_001 = %{
8
+ {
9
+ 'responseHeader'=>{
10
+ 'status'=>0,
11
+ 'QTime'=>151,
12
+ 'params'=>{
13
+ 'wt'=>'ruby',
14
+ 'rows'=>'10',
15
+ 'explainOther'=>'',
16
+ 'start'=>'0',
17
+ 'hl.fl'=>'',
18
+ 'indent'=>'on',
19
+ 'hl'=>'on',
20
+ 'q'=>'index_type:widget',
21
+ 'fl'=>'*,score',
22
+ 'qt'=>'standard',
23
+ 'version'=>'2.2'}},
24
+ 'response'=>{'numFound'=>1522698,'start'=>0,'maxScore'=>1.5583541,'docs'=>[
25
+ {
26
+ 'index_type'=>'widget',
27
+ 'id'=>1,
28
+ 'unique_id'=>'1_widget',
29
+ 'score'=>1.5583541},
30
+ {
31
+ 'index_type'=>'widget',
32
+ 'id'=>3,
33
+ 'unique_id'=>'3_widget',
34
+ 'score'=>1.5583541},
35
+ {
36
+ 'index_type'=>'widget',
37
+ 'id'=>4,
38
+ 'unique_id'=>'4_widget',
39
+ 'score'=>1.5583541},
40
+ {
41
+ 'index_type'=>'widget',
42
+ 'id'=>5,
43
+ 'unique_id'=>'5_widget',
44
+ 'score'=>1.5583541},
45
+ {
46
+ 'index_type'=>'widget',
47
+ 'id'=>7,
48
+ 'unique_id'=>'7_widget',
49
+ 'score'=>1.5583541},
50
+ {
51
+ 'index_type'=>'widget',
52
+ 'id'=>8,
53
+ 'unique_id'=>'8_widget',
54
+ 'score'=>1.5583541},
55
+ {
56
+ 'index_type'=>'widget',
57
+ 'id'=>9,
58
+ 'unique_id'=>'9_widget',
59
+ 'score'=>1.5583541},
60
+ {
61
+ 'index_type'=>'widget',
62
+ 'id'=>10,
63
+ 'unique_id'=>'10_widget',
64
+ 'score'=>1.5583541},
65
+ {
66
+ 'index_type'=>'widget',
67
+ 'id'=>11,
68
+ 'unique_id'=>'11_widget',
69
+ 'score'=>1.5583541},
70
+ {
71
+ 'index_type'=>'widget',
72
+ 'id'=>12,
73
+ 'unique_id'=>'12_widget',
74
+ 'score'=>1.5583541}]
75
+ },
76
+ 'facet_counts'=>{
77
+ 'facet_queries'=>{
78
+ 'city_idm:19596' => 392},
79
+ 'facet_fields'=>{
80
+ 'available_b'=>[
81
+ 'false',1328],
82
+ 'onsale_b'=>[
83
+ 'false',1182,
84
+ 'true',174]}},
85
+ 'highlighting'=>{
86
+ '1_widget'=>{},
87
+ '3_widget'=>{},
88
+ '4_widget'=>{},
89
+ '5_widget'=>{},
90
+ '7_widget'=>{},
91
+ '8_widget'=>{},
92
+ '9_widget'=>{},
93
+ '10_widget'=>{},
94
+ '11_widget'=>{},
95
+ '12_widget'=>{}}}
96
+ }
97
+
98
+ def test_001
99
+ r = nil
100
+ qb = DelSolr::Client::QueryBuilder.new('standard', :query => {:index_type => 'widget'}, :facets => {:query => 'city_idm:19596', :name => 19596} )
101
+ qb.request_string # need to generate this...
102
+ assert_nothing_raised { r = DelSolr::Client::Response.new(@@test_001, qb) }
103
+
104
+ assert_equal(151, r.qtime)
105
+ assert_equal(1.5583541, r.max_score)
106
+ assert_equal(10, r.docs.length)
107
+ assert_equal([1, 3, 4, 5, 7, 8, 9, 10, 11, 12], r.ids)
108
+ assert_equal({
109
+ 'available_b'=>[
110
+ 'false',1328],
111
+ 'onsale_b'=>[
112
+ 'false',1182,
113
+ 'true',174]}, r.facet_fields)
114
+ assert_equal(1182, r.facet_field_count('onsale_b', false))
115
+ assert_equal(174, r.facet_field_count('onsale_b', true))
116
+ assert_equal(1328, r.facet_field_count('available_b', false))
117
+ assert_equal(392, r.facet_query_count_by_name(19596))
118
+ end
119
+
120
+ def test_shortcuts
121
+ r = nil
122
+ qb = DelSolr::Client::QueryBuilder.new('standard', :query => {:index_type => 'widget'}, :facets => {:query => 'city_idm:19596', :name => 19596} )
123
+ qb.request_string # need to generate this...
124
+ assert_nothing_raised { r = DelSolr::Client::Response.new(@@test_001, qb, :shortcuts => [:index_type, :id]) }
125
+
126
+ assert(r.respond_to?(:index_types))
127
+ assert(r.respond_to?(:ids))
128
+ assert(!r.respond_to?(:scores))
129
+ end
130
+
131
+ end
metadata ADDED
@@ -0,0 +1,87 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: delsolr
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Ben VandenBos
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-09-08 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: hoe
17
+ type: :development
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 1.7.0
24
+ version:
25
+ description: Wrapper for querying Lucene Solr
26
+ email:
27
+ - bvandenbos@gmail.com
28
+ executables: []
29
+
30
+ extensions: []
31
+
32
+ extra_rdoc_files:
33
+ - History.txt
34
+ - License.txt
35
+ - Manifest.txt
36
+ - README.txt
37
+ files:
38
+ - History.txt
39
+ - License.txt
40
+ - Manifest.txt
41
+ - README.txt
42
+ - Rakefile
43
+ - lib/delsolr.rb
44
+ - lib/delsolr/configuration.rb
45
+ - lib/delsolr/extensions.rb
46
+ - lib/delsolr/query_builder.rb
47
+ - lib/delsolr/response.rb
48
+ - lib/delsolr/version.rb
49
+ has_rdoc: true
50
+ homepage: http://delsolr.rubyforge.org
51
+ post_install_message: |+
52
+
53
+ For more information on delsolr, see http://delsolr.rubyforge.org
54
+
55
+ NOTE: Change this information in PostInstall.txt
56
+ You can also delete it if you don't want it.
57
+
58
+
59
+ rdoc_options:
60
+ - --main
61
+ - README.txt
62
+ require_paths:
63
+ - lib
64
+ required_ruby_version: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: "0"
69
+ version:
70
+ required_rubygems_version: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: "0"
75
+ version:
76
+ requirements: []
77
+
78
+ rubyforge_project: delsolr
79
+ rubygems_version: 1.2.0
80
+ signing_key:
81
+ specification_version: 2
82
+ summary: Wrapper for querying Lucene Solr
83
+ test_files:
84
+ - test/test_response.rb
85
+ - test/test_query_builder.rb
86
+ - test/test_helper.rb
87
+ - test/test_client.rb