delsolr 0.0.5 → 0.0.6

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