delsolr 0.0.1

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.
@@ -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