delsolr 0.0.5 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
data/lib/delsolr.rb CHANGED
@@ -22,7 +22,7 @@ module DelSolr
22
22
 
23
23
  class Client
24
24
 
25
- attr_reader :configuration
25
+ attr_reader :configuration, :logger
26
26
 
27
27
  #
28
28
  # [<b><tt>:server</tt></b>]
@@ -39,9 +39,13 @@ module DelSolr
39
39
  #
40
40
  # [<b><tt>:path</tt></b>]
41
41
  # (optional) the path of the solr install (defaults to "/solr")
42
+ #
43
+ # [<b><tt>:logger</tt></b>]
44
+ # (optional) Log4r logger object
42
45
  def initialize(options = {})
43
46
  @configuration = DelSolr::Client::Configuration.new(options[:server], options[:port], options[:timeout], options[:path])
44
47
  @cache = options[:cache]
48
+ @logger = options[:logger]
45
49
  @shortcuts = options[:shortcuts]
46
50
  end
47
51
 
@@ -155,12 +159,19 @@ module DelSolr
155
159
 
156
160
  # if we're caching, first try looking in the cache
157
161
  if enable_caching
162
+ t1 = Time.now
158
163
  body = @cache.get(cache_key) rescue body = nil
159
164
  from_cache = true unless body.blank?
165
+ cache_time = (Time.now - t1).to_i * 1000 # retrieval time from the cache in ms
160
166
  end
161
167
 
162
168
  if body.blank? # cache miss (or wasn't enabled)
163
- header, body = connection.get(configuration.path + query_builder.request_string)
169
+ begin
170
+ header, body = connection.get(configuration.path + query_builder.request_string)
171
+ rescue Timeout::Error
172
+ # If we timed out, just return nil and let the client decide what to do
173
+ return nil
174
+ end
164
175
 
165
176
  # We get UTF-8 from Solr back, make sure the string knows about it
166
177
  # when running on Ruby >= 1.9
@@ -177,7 +188,9 @@ module DelSolr
177
188
  end
178
189
  end
179
190
 
180
- DelSolr::Client::Response.new(body, query_builder, :from_cache => from_cache, :shortcuts => @shortcuts)
191
+ response = DelSolr::Client::Response.new(body, query_builder, :logger => logger, :from_cache => from_cache, :shortcuts => @shortcuts)
192
+ logger.info "#{from_cache ? 'C' : 'S'},#{from_cache ? cache_time : response.qtime},#{response.total},http://#{configuration.server}:#{configuration.port}#{response.request_url}" if logger && response && response.success?
193
+ response
181
194
  end
182
195
 
183
196
  # Adds a document to the buffer to be posted to solr (NOTE: does not perform the actual post)
@@ -37,6 +37,10 @@ module DelSolr
37
37
  #
38
38
  class Document
39
39
 
40
+ def initialize(options={})
41
+ @options = options
42
+ end
43
+
40
44
  # [<b><tt>field_mame</tt></b>]
41
45
  # is the name of the field in your schema.xml
42
46
  # [<b><tt>value</tt></b>]
@@ -50,7 +54,7 @@ module DelSolr
50
54
  end
51
55
 
52
56
  def xml
53
- "<doc>\n" + field_buffer + "</doc>"
57
+ "<doc#{opts2str(@options)}>\n" + field_buffer + "</doc>"
54
58
  end
55
59
 
56
60
  private
@@ -59,14 +63,8 @@ module DelSolr
59
63
  def construct_field_tag(name, value, options={})
60
64
  options[:name] = name.to_s
61
65
  use_cdata = options.delete(:cdata)
62
- opts = []
63
- options.each do |k,v|
64
- opts.push "#{k}=\"#{v}\""
65
- end
66
- opts = opts.join(" ")
67
- opts = " " + opts if opts
68
66
 
69
- return "<field#{opts}>#{use_cdata ? cdata(value) : value}</field>\n"
67
+ return "<field#{opts2str(options)}>#{use_cdata ? cdata(value) : value}</field>\n"
70
68
  end
71
69
 
72
70
  def cdata(str)
@@ -77,6 +75,16 @@ module DelSolr
77
75
  @buffer ||= ""
78
76
  end
79
77
 
78
+ def opts2str(options)
79
+ opts = []
80
+ options.each do |k,v|
81
+ opts.push "#{k}=\"#{v}\""
82
+ end
83
+ opts = opts.join(" ")
84
+ opts = " " + opts unless opts.blank?
85
+ opts
86
+ end
87
+
80
88
  end
81
89
 
82
90
  end
@@ -41,12 +41,13 @@ module DelSolr
41
41
  opts[:q] ||= opts[:query]
42
42
  opts[:rows] ||= opts[:limit] || 10
43
43
  opts[:start] ||= opts[:offset] || 0
44
+ opts[:start] = 0 if opts[:start].to_i < 0
44
45
  opts[:fl] ||= opts[:fields] || FL_DEFAULTS
45
46
  opts[:bq] ||= opts[:boost]
46
47
  opts[:suggestionCount] ||= opts[:suggestion_count]
47
48
  opts[:onlyMorePopular] ||= opts[:only_more_popular]
48
49
 
49
- raise ":query or :q must be set" if opts[:q].blank?
50
+ raise ":query or :q must be set" if opts[:q].nil? || opts[:q].empty?
50
51
 
51
52
  # clear out the "rubyish" versions, what's left will go straight to solr
52
53
  opts.delete(:query)
@@ -123,21 +124,36 @@ module DelSolr
123
124
  when Hash
124
125
  query_string_array = []
125
126
  queries.each do |k,v|
126
- if v.is_a?(Array) # add a filter for each value
127
- v.each do |val|
128
- query_string_array << "#{k}:#{val}"
129
- end
130
- elsif v.is_a?(Range)
131
- query_string_array << "#{k}:[#{v.begin} TO #{v.end}]"
132
- else
133
- query_string_array << "#{k}:#{v}"
134
- end
127
+ query_string_array << key_value_pair_string(k, v)
135
128
  end
136
129
  query_string = query_string_array.join(' ')
137
130
  end
138
131
 
139
132
  {key => query_string}
140
133
  end
134
+
135
+ def key_value_pair_string(k, v)
136
+ str = ''
137
+ if v.is_a?(Array) # add a filter for each value
138
+ str_ary = []
139
+ v.each do |val|
140
+ str_ary << key_value_pair_string(k, val)
141
+ end
142
+ str = str_ary.join(' ')
143
+ elsif v.is_a?(Range)
144
+ str = "#{k}:[#{v.begin} TO #{v.end}]"
145
+ elsif v.is_a?(String)
146
+ if v =~ /\s/ && # if it contains a space, we may need to quote it
147
+ !(v =~ /^\[.+ TO .+\]$/) # HACK: if the string is a range query, do not wrap it in quotes
148
+ str = "#{k}:\"#{v}\""
149
+ else
150
+ str = "#{k}:#{v}"
151
+ end
152
+ else
153
+ str = "#{k}:#{v}"
154
+ end
155
+ str
156
+ end
141
157
 
142
158
  def build_filters(key, filters)
143
159
  params = []
@@ -147,18 +163,12 @@ module DelSolr
147
163
  when String
148
164
  params << {key => filters}
149
165
  when Array
150
- filters.each { |f| params << {key => f} }
166
+ filters.each do |f|
167
+ params += build_filters(key, f) # recusively add all the filters in the array
168
+ end
151
169
  when Hash
152
170
  filters.each do |k,v|
153
- if v.is_a?(Array) # add a filter for each value
154
- v.each do |val|
155
- params << {key => "#{k}:#{val}"}
156
- end
157
- elsif v.is_a?(Range)
158
- params << {key => "#{k}:[#{v.begin} TO #{v.end}]"}
159
- else
160
- params << {key => "#{k}:#{v}"}
161
- end
171
+ params << {key => key_value_pair_string(k, v)} unless v.nil?
162
172
  end
163
173
  end
164
174
  params
@@ -9,9 +9,12 @@ module DelSolr
9
9
  def initialize(solr_response_buffer, query_builder, options = {})
10
10
  @query_builder = query_builder
11
11
  @from_cache = options[:from_cache]
12
+ @logger = options[:logger]
12
13
  begin
13
14
  @raw_response = eval(solr_response_buffer)
14
- rescue
15
+ rescue SyntaxError, Exception => e
16
+ @logger.error(solr_response_buffer) if @logger
17
+ @logger.error(e) if @logger
15
18
  @raw_response = nil
16
19
  end
17
20
 
@@ -30,6 +33,11 @@ module DelSolr
30
33
  def raw_response
31
34
  @raw_response
32
35
  end
36
+
37
+ # Did we get some kind of valid response back from solr?
38
+ def success?
39
+ !raw_response.nil?
40
+ end
33
41
 
34
42
  # Returns the total number of matches
35
43
  def total
@@ -38,7 +46,7 @@ module DelSolr
38
46
 
39
47
  # Returns true if there no results
40
48
  def blank?
41
- total.zero?
49
+ raw_response.blank? || total < 1
42
50
  end
43
51
 
44
52
  alias_method :empty?, :blank?
@@ -74,6 +82,21 @@ module DelSolr
74
82
  raw_response['highlighting'][unique_id] ||= {}
75
83
  raw_response['highlighting'][unique_id][field]
76
84
  end
85
+
86
+ def suggestions
87
+ @suggestions ||= raw_response['spellcheck']['suggestions'] if raw_response && raw_response['spellcheck']
88
+ end
89
+
90
+ # solr is super-weird about the way it returns suggestions,
91
+ # hence this strangeness:
92
+ # 'spellcheck'=>{'suggestions'=>['fishh',{'numFound'=>1,'startOffset'=>0,'endOffset'=>4,'suggestion'=>['fish']},'collation','fish']}
93
+ def collation
94
+ @collation ||= begin
95
+ collation = nil
96
+ suggestions.in_groups_of(2) {|k,v| collation = v if k == 'collation'} if suggestions
97
+ collation
98
+ end
99
+ end
77
100
 
78
101
  # Returns the query time in ms
79
102
  def qtime
@@ -125,7 +148,7 @@ module DelSolr
125
148
 
126
149
  # Returns an array of value/counts for a given field (ie: ['true', 123, 'false', 20]
127
150
  def facet_field(field)
128
- facet_fields[field.to_s]
151
+ facet_fields[field.to_s] || []
129
152
  end
130
153
 
131
154
  # Returns the array of field values for the given field in the order they were returned from solr
@@ -142,7 +165,7 @@ module DelSolr
142
165
 
143
166
  # Returns a hash of value/counts for a given field (ie: {'true' => 123, 'false' => 20}
144
167
  def facet_field_by_hash(field)
145
- facet_fields_by_hash(field.to_s)
168
+ facet_fields_by_hash[field.to_s]
146
169
  end
147
170
 
148
171
  # Returns the count for the given field/value pair
data/test/test_client.rb CHANGED
@@ -1,4 +1,4 @@
1
- require File.dirname(__FILE__) + '/test_helper'
1
+ require File.expand_path(File.dirname(__FILE__)) + '/test_helper'
2
2
  require 'rubygems'
3
3
  gem 'mocha', '>=0.9.0'
4
4
  require 'mocha'
data/test/test_helper.rb CHANGED
@@ -1,2 +1,2 @@
1
1
  require 'test/unit'
2
- require File.dirname(__FILE__) + '/../lib/delsolr'
2
+ require File.expand_path(File.dirname(__FILE__)) + '/../lib/delsolr'
@@ -1,4 +1,4 @@
1
- require File.dirname(__FILE__) + '/test_helper'
1
+ require File.expand_path(File.dirname(__FILE__)) + '/test_helper'
2
2
 
3
3
  class QueryBuilderTest < Test::Unit::TestCase
4
4
 
@@ -1,4 +1,4 @@
1
- require File.dirname(__FILE__) + '/test_helper'
1
+ require File.expand_path(File.dirname(__FILE__)) + '/test_helper'
2
2
 
3
3
  class ResponseTest < Test::Unit::TestCase
4
4
 
metadata CHANGED
@@ -1,7 +1,12 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: delsolr
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.5
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 0
8
+ - 6
9
+ version: 0.0.6
5
10
  platform: ruby
6
11
  authors:
7
12
  - Ben VandenBos
@@ -14,14 +19,19 @@ default_executable:
14
19
  dependencies:
15
20
  - !ruby/object:Gem::Dependency
16
21
  name: mocha
17
- type: :development
18
- version_requirement:
19
- version_requirements: !ruby/object:Gem::Requirement
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
20
25
  requirements:
21
26
  - - ">="
22
27
  - !ruby/object:Gem::Version
28
+ segments:
29
+ - 0
30
+ - 9
31
+ - 0
23
32
  version: 0.9.0
24
- version:
33
+ type: :development
34
+ version_requirements: *id001
25
35
  description: Ruby wrapper for Lucene Solr
26
36
  email:
27
37
  executables: []
@@ -39,6 +49,10 @@ files:
39
49
  - lib/delsolr/query_builder.rb
40
50
  - lib/delsolr/response.rb
41
51
  - lib/delsolr/document.rb
52
+ - test/test_client.rb
53
+ - test/test_helper.rb
54
+ - test/test_query_builder.rb
55
+ - test/test_response.rb
42
56
  has_rdoc: true
43
57
  homepage: http://github.com/avvo/delsolr
44
58
  licenses: []
@@ -49,21 +63,25 @@ rdoc_options: []
49
63
  require_paths:
50
64
  - lib
51
65
  required_ruby_version: !ruby/object:Gem::Requirement
66
+ none: false
52
67
  requirements:
53
68
  - - ">="
54
69
  - !ruby/object:Gem::Version
70
+ segments:
71
+ - 0
55
72
  version: "0"
56
- version:
57
73
  required_rubygems_version: !ruby/object:Gem::Requirement
74
+ none: false
58
75
  requirements:
59
76
  - - ">="
60
77
  - !ruby/object:Gem::Version
78
+ segments:
79
+ - 0
61
80
  version: "0"
62
- version:
63
81
  requirements: []
64
82
 
65
83
  rubyforge_project:
66
- rubygems_version: 1.3.5
84
+ rubygems_version: 1.3.7
67
85
  signing_key:
68
86
  specification_version: 3
69
87
  summary: DelSolr is a light weight ruby wrapper for solr. It's intention is to expose the full power of solr queries while keeping the interface as ruby-esque as possible.