sunspot 1.1.0 → 1.2.0

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.
Files changed (66) hide show
  1. data/Gemfile +10 -0
  2. data/Gemfile.lock +32 -0
  3. data/History.txt +24 -0
  4. data/README.rdoc +18 -5
  5. data/lib/sunspot.rb +40 -0
  6. data/lib/sunspot/dsl.rb +2 -2
  7. data/lib/sunspot/dsl/field_query.rb +2 -2
  8. data/lib/sunspot/dsl/fields.rb +0 -10
  9. data/lib/sunspot/dsl/restriction.rb +4 -4
  10. data/lib/sunspot/dsl/restriction_with_near.rb +121 -0
  11. data/lib/sunspot/dsl/scope.rb +55 -67
  12. data/lib/sunspot/dsl/standard_query.rb +11 -15
  13. data/lib/sunspot/field.rb +30 -29
  14. data/lib/sunspot/field_factory.rb +0 -18
  15. data/lib/sunspot/installer/solrconfig_updater.rb +0 -30
  16. data/lib/sunspot/query.rb +4 -3
  17. data/lib/sunspot/query/common_query.rb +2 -2
  18. data/lib/sunspot/query/composite_fulltext.rb +7 -2
  19. data/lib/sunspot/query/connective.rb +21 -6
  20. data/lib/sunspot/query/dismax.rb +1 -0
  21. data/lib/sunspot/query/geo.rb +53 -0
  22. data/lib/sunspot/query/more_like_this.rb +1 -0
  23. data/lib/sunspot/query/restriction.rb +5 -5
  24. data/lib/sunspot/query/standard_query.rb +0 -4
  25. data/lib/sunspot/search/abstract_search.rb +1 -7
  26. data/lib/sunspot/search/hit.rb +10 -10
  27. data/lib/sunspot/search/query_facet.rb +8 -3
  28. data/lib/sunspot/session.rb +10 -2
  29. data/lib/sunspot/session_proxy.rb +16 -0
  30. data/lib/sunspot/session_proxy/master_slave_session_proxy.rb +1 -1
  31. data/lib/sunspot/session_proxy/sharding_session_proxy.rb +7 -0
  32. data/lib/sunspot/session_proxy/silent_fail_session_proxy.rb +42 -0
  33. data/lib/sunspot/session_proxy/thread_local_session_proxy.rb +1 -1
  34. data/lib/sunspot/setup.rb +1 -17
  35. data/lib/sunspot/type.rb +38 -6
  36. data/lib/sunspot/util.rb +21 -31
  37. data/lib/sunspot/version.rb +1 -1
  38. data/solr/solr/conf/solrconfig.xml +0 -4
  39. data/spec/api/binding_spec.rb +12 -0
  40. data/spec/api/indexer/attributes_spec.rb +22 -22
  41. data/spec/api/query/connectives_examples.rb +14 -1
  42. data/spec/api/query/fulltext_examples.rb +3 -3
  43. data/spec/api/query/geo_examples.rb +69 -0
  44. data/spec/api/query/scope_examples.rb +32 -13
  45. data/spec/api/query/standard_spec.rb +1 -1
  46. data/spec/api/search/faceting_spec.rb +5 -1
  47. data/spec/api/search/hits_spec.rb +14 -12
  48. data/spec/api/session_proxy/class_sharding_session_proxy_spec.rb +1 -1
  49. data/spec/api/session_proxy/sharding_session_proxy_spec.rb +1 -1
  50. data/spec/api/session_proxy/silent_fail_session_proxy_spec.rb +24 -0
  51. data/spec/api/session_spec.rb +22 -0
  52. data/spec/integration/local_search_spec.rb +42 -69
  53. data/spec/integration/scoped_search_spec.rb +30 -0
  54. data/spec/mocks/connection.rb +6 -2
  55. data/spec/mocks/photo.rb +0 -1
  56. data/spec/mocks/post.rb +11 -2
  57. data/spec/mocks/user.rb +6 -1
  58. data/spec/spec_helper.rb +2 -12
  59. metadata +209 -177
  60. data/lib/sunspot/query/local.rb +0 -26
  61. data/solr/solr/lib/lucene-spatial-2.9.1.jar +0 -0
  62. data/solr/solr/lib/solr-spatial-light-0.0.6.jar +0 -0
  63. data/spec/api/query/local_examples.rb +0 -38
  64. data/tasks/gemspec.rake +0 -33
  65. data/tasks/rcov.rake +0 -28
  66. data/tasks/spec.rake +0 -24
@@ -81,7 +81,7 @@ module Sunspot
81
81
  begin
82
82
  hits = if solr_response && solr_response['docs']
83
83
  solr_response['docs'].map do |doc|
84
- Hit.new(doc, highlights_for(doc), distance_for(doc), self)
84
+ Hit.new(doc, highlights_for(doc), self)
85
85
  end
86
86
  end
87
87
  maybe_will_paginate(hits || [])
@@ -268,12 +268,6 @@ module Sunspot
268
268
  end
269
269
  end
270
270
 
271
- def distance_for(doc)
272
- if @solr_result['distances']
273
- @solr_result['distances'][doc['id']]
274
- end
275
- end
276
-
277
271
  def verified_hits
278
272
  @verified_hits ||= maybe_will_paginate(hits.select { |hit| hit.instance })
279
273
  end
@@ -3,8 +3,8 @@ module Sunspot
3
3
  #
4
4
  # Hit objects represent the raw information returned by Solr for a single
5
5
  # document. As well as the primary key and class name, hit objects give
6
- # access to stored field values, keyword relevance score, and geographical
7
- # distance (for geographical search).
6
+ # access to stored field values, keyword relevance score, and keyword
7
+ # highlighting.
8
8
  #
9
9
  class Hit
10
10
  SPECIAL_KEYS = Set.new(%w(id type score)) #:nodoc:
@@ -23,17 +23,12 @@ module Sunspot
23
23
  #
24
24
  attr_reader :score
25
25
  #
26
- # For geographical searches, this is the distance between the search
27
- # centerpoint and the document's location. Otherwise, it's nil.
28
- #
29
- attr_reader :distance
30
26
 
31
27
  attr_writer :result #:nodoc:
32
28
 
33
- def initialize(raw_hit, highlights, distance, search) #:nodoc:
29
+ def initialize(raw_hit, highlights, search) #:nodoc:
34
30
  @class_name, @primary_key = *raw_hit['id'].match(/([^ ]+) (.+)/)[1..2]
35
31
  @score = raw_hit['score']
36
- @distance = distance
37
32
  @search = search
38
33
  @stored_values = raw_hit
39
34
  @stored_cache = {}
@@ -104,7 +99,7 @@ module Sunspot
104
99
  private
105
100
 
106
101
  def setup
107
- @setup ||= Sunspot::Setup.for(@class_name)
102
+ @setup ||= Sunspot::Setup.for(Util.full_const_get(@class_name))
108
103
  end
109
104
 
110
105
  def highlights_cache
@@ -126,7 +121,12 @@ module Sunspot
126
121
  def stored_value(field_name, dynamic_field_name)
127
122
  setup.stored_fields(field_name, dynamic_field_name).each do |field|
128
123
  if value = @stored_values[field.indexed_name]
129
- return field.cast(value)
124
+ case value
125
+ when Array
126
+ return value.map { |item| field.cast(item) }
127
+ else
128
+ return field.cast(value)
129
+ end
130
130
  end
131
131
  end
132
132
  nil
@@ -38,7 +38,7 @@ module Sunspot
38
38
  private
39
39
 
40
40
  def sort_rows!(rows)
41
- case @options[:sort] || (:count if @options[:limit])
41
+ case @options[:sort] || (:count if limit)
42
42
  when :count
43
43
  rows.sort! { |lrow, rrow| rrow.count <=> lrow.count }
44
44
  when :index
@@ -52,11 +52,16 @@ module Sunspot
52
52
  end
53
53
  end
54
54
  end
55
- if @options[:limit]
56
- rows.replace(rows.first(@options[:limit]))
55
+ if limit
56
+ rows.replace(rows.first(limit))
57
57
  end
58
58
  rows
59
59
  end
60
+
61
+ def limit
62
+ return @limit if defined?(@limit)
63
+ @limit = (@options[:limit].to_i if @options[:limit].to_i > 0)
64
+ end
60
65
  end
61
66
  end
62
67
  end
@@ -107,6 +107,14 @@ module Sunspot
107
107
  connection.commit
108
108
  end
109
109
 
110
+ #
111
+ # See Sunspot.optimize
112
+ #
113
+ def optimize
114
+ @adds = @deletes = 0
115
+ connection.optimize
116
+ end
117
+
110
118
  #
111
119
  # See Sunspot.remove
112
120
  #
@@ -115,9 +123,9 @@ module Sunspot
115
123
  types = objects
116
124
  conjunction = Query::Connective::Conjunction.new
117
125
  if types.length == 1
118
- conjunction.add_restriction(TypeField.instance, Query::Restriction::EqualTo, types.first)
126
+ conjunction.add_positive_restriction(TypeField.instance, Query::Restriction::EqualTo, types.first)
119
127
  else
120
- conjunction.add_restriction(TypeField.instance, Query::Restriction::AnyOf, types)
128
+ conjunction.add_positive_restriction(TypeField.instance, Query::Restriction::AnyOf, types)
121
129
  end
122
130
  dsl = DSL::Scope.new(conjunction, setup_for_types(types))
123
131
  Util.instance_eval_or_call(dsl, &block)
@@ -27,6 +27,14 @@ module Sunspot
27
27
  module SessionProxy
28
28
  NotSupportedError = Class.new(StandardError)
29
29
 
30
+ autoload(
31
+ :AbstractSessionProxy,
32
+ File.join(
33
+ File.dirname(__FILE__),
34
+ 'session_proxy',
35
+ 'abstract_session_proxy'
36
+ )
37
+ )
30
38
  autoload(
31
39
  :ThreadLocalSessionProxy,
32
40
  File.join(
@@ -67,5 +75,13 @@ module Sunspot
67
75
  'id_sharding_session_proxy'
68
76
  )
69
77
  )
78
+ autoload(
79
+ :SilentFailSessionProxy,
80
+ File.join(
81
+ File.dirname(__FILE__),
82
+ 'session_proxy',
83
+ 'silent_fail_session_proxy'
84
+ )
85
+ )
70
86
  end
71
87
  end
@@ -18,7 +18,7 @@ module Sunspot
18
18
  attr_reader :slave_session
19
19
 
20
20
  delegate :batch, :commit, :commit_if_delete_dirty, :commit_if_dirty,
21
- :config, :delete_dirty?, :dirty?, :index, :index!, :remove,
21
+ :config, :delete_dirty?, :dirty?, :index, :index!, :optimize, :remove,
22
22
  :remove!, :remove_all, :remove_all!, :remove_by_id,
23
23
  :remove_by_id!, :to => :master_session
24
24
  delegate :new_search, :search, :new_more_like_this, :more_like_this, :to => :slave_session
@@ -119,6 +119,13 @@ module Sunspot
119
119
  all_sessions.each { |session| session.commit }
120
120
  end
121
121
 
122
+ #
123
+ # Optimize all shards. See Sunspot.optimize
124
+ #
125
+ def optimize
126
+ all_sessions.each { |session| session.optimize }
127
+ end
128
+
122
129
  #
123
130
  # Commit all dirty sessions. Only dirty sessions will be committed.
124
131
  #
@@ -0,0 +1,42 @@
1
+ require File.join(File.dirname(__FILE__), 'abstract_session_proxy')
2
+
3
+ module Sunspot
4
+ module SessionProxy
5
+ class SilentFailSessionProxy < AbstractSessionProxy
6
+
7
+ attr_reader :search_session
8
+
9
+ delegate :new_search, :search, :config,
10
+ :new_more_like_this, :more_like_this,
11
+ :delete_dirty, :delete_dirty?,
12
+ :to => :search_session
13
+
14
+ def initialize(search_session = Sunspot.session)
15
+ @search_session = search_session
16
+ end
17
+
18
+ def rescued_exception(method, e)
19
+ $stderr.puts("Exception in #{method}: #{e.message}")
20
+ end
21
+
22
+ SUPPORTED_METHODS = [
23
+ :batch, :commit, :commit_if_dirty, :commit_if_delete_dirty, :dirty?,
24
+ :index!, :index, :optimize, :remove!, :remove, :remove_all!, :remove_all,
25
+ :remove_by_id!, :remove_by_id
26
+ ]
27
+
28
+ SUPPORTED_METHODS.each do |method|
29
+ module_eval(<<-RUBY)
30
+ def #{method}(*args, &block)
31
+ begin
32
+ search_session.#{method}(*args, &block)
33
+ rescue => e
34
+ self.rescued_exception(:#{method}, e)
35
+ end
36
+ end
37
+ RUBY
38
+ end
39
+
40
+ end
41
+ end
42
+ end
@@ -17,7 +17,7 @@ module Sunspot
17
17
  attr_reader :config
18
18
  @@next_id = 0
19
19
 
20
- delegate :batch, :commit, :commit_if_delete_dirty, :commit_if_dirty, :delete_dirty?, :dirty?, :index, :index!, :new_search, :remove, :remove!, :remove_all, :remove_all!, :remove_by_id, :remove_by_id!, :search, :more_like_this, :new_more_like_this, :to => :session
20
+ delegate :batch, :commit, :commit_if_delete_dirty, :commit_if_dirty, :delete_dirty?, :dirty?, :index, :index!, :new_search, :optimize, :remove, :remove!, :remove_all, :remove_all!, :remove_by_id, :remove_by_id!, :search, :more_like_this, :new_more_like_this, :to => :session
21
21
 
22
22
  #
23
23
  # Optionally pass an existing Sunspot::Configuration object. If none is
data/lib/sunspot/setup.rb CHANGED
@@ -77,15 +77,6 @@ module Sunspot
77
77
  end
78
78
  end
79
79
 
80
- #
81
- # The coordinates field factory is used for populating the coordinate fields
82
- # of documents during index, but does not actually generate fields (since
83
- # the field names used in search are static).
84
- #
85
- def set_coordinates_field(name = nil, &block)
86
- @coordinates_field_factory = FieldFactory::Coordinates.new(name, &block)
87
- end
88
-
89
80
  #
90
81
  # Add a document boost to documents at index time. Document boost can be
91
82
  # static (the same for all documents of this class), or extracted on a per-
@@ -234,7 +225,6 @@ module Sunspot
234
225
  def all_field_factories
235
226
  all_field_factories = []
236
227
  all_field_factories.concat(field_factories).concat(text_field_factories).concat(dynamic_field_factories)
237
- all_field_factories << @coordinates_field_factory if @coordinates_field_factory
238
228
  all_field_factories
239
229
  end
240
230
 
@@ -319,13 +309,7 @@ module Sunspot
319
309
  # Setup instance associated with the given class or its nearest ancestor
320
310
  #
321
311
  def for(clazz) #:nodoc:
322
- class_name =
323
- if clazz.respond_to?(:name)
324
- clazz.name
325
- else
326
- clazz
327
- end
328
- setups[class_name.to_sym] || self.for(clazz.superclass) if clazz
312
+ setups[clazz.name.to_sym] || self.for(clazz.superclass) if clazz
329
313
  end
330
314
 
331
315
  protected
data/lib/sunspot/type.rb CHANGED
@@ -1,3 +1,10 @@
1
+ require 'singleton'
2
+ begin
3
+ require 'geohash'
4
+ rescue LoadError => e
5
+ require 'pr_geohash'
6
+ end
7
+
1
8
  module Sunspot
2
9
  #
3
10
  # This module contains singleton objects that represent the types that can be
@@ -58,12 +65,7 @@ module Sunspot
58
65
  end
59
66
 
60
67
  class AbstractType #:nodoc:
61
- class <<self
62
- def instance
63
- @instance ||= new
64
- end
65
- private :new
66
- end
68
+ include Singleton
67
69
 
68
70
  def accepts_dynamic?
69
71
  true
@@ -317,11 +319,41 @@ module Sunspot
317
319
  true
318
320
  when 'false'
319
321
  false
322
+ when true, false
323
+ string
320
324
  end
321
325
  end
322
326
  end
323
327
  register BooleanType, TrueClass, FalseClass
324
328
 
329
+ #
330
+ # The Location type encodes geographical coordinates as a GeoHash.
331
+ # The data for this type must respond to the `lat` and `lng` methods; you
332
+ # can use Sunspot::Util::Coordinates as a wrapper if your source data does
333
+ # not follow this API.
334
+ #
335
+ # Location fields are most usefully searched using the
336
+ # Sunspot::DSL::RestrictionWithType#near method; see that method for more
337
+ # information on geographical search.
338
+ #
339
+ # ==== Example
340
+ #
341
+ # Sunspot.setup(Post) do
342
+ # location :coordinates do
343
+ # Sunspot::Util::Coordinates.new(coordinates[0], coordinates[1])
344
+ # end
345
+ # end
346
+ #
347
+ class LocationType < AbstractType
348
+ def indexed_name(name)
349
+ "#{name}_s"
350
+ end
351
+
352
+ def to_indexed(value)
353
+ GeoHash.encode(value.lat.to_f, value.lng.to_f, 12)
354
+ end
355
+ end
356
+
325
357
  class ClassType < AbstractType
326
358
  def indexed_name(name) #:nodoc:
327
359
  'class_name'
data/lib/sunspot/util.rb CHANGED
@@ -108,6 +108,20 @@ module Sunspot
108
108
  end
109
109
  end
110
110
 
111
+ #
112
+ # When generating boosts, Solr requires that the values be in standard
113
+ # (not scientific) notation. We would like to ensure a minimum number of
114
+ # significant digits (i.e., digits that are not prefix zeros) for small
115
+ # float values.
116
+ #
117
+ def format_float(f, digits)
118
+ if f < 1
119
+ sprintf('%.*f', digits - Math.log10(f), f)
120
+ else
121
+ f.to_s
122
+ end
123
+ end
124
+
111
125
  #
112
126
  # Perform a deep merge of hashes, returning the result as a new hash.
113
127
  # See #deep_merge_into for rules used to merge the hashes
@@ -182,35 +196,7 @@ module Sunspot
182
196
  end
183
197
  end
184
198
 
185
- class Coordinates #:nodoc:
186
- def initialize(coords)
187
- @coords = coords
188
- end
189
-
190
- def lat
191
- if @coords.respond_to?(:first)
192
- @coords.first
193
- elsif @coords.respond_to?(:lat)
194
- @coords.lat
195
- else
196
- @coords.latitude
197
- end.to_f
198
- end
199
-
200
- def lng
201
- if @coords.respond_to?(:last)
202
- @coords.last
203
- elsif @coords.respond_to?(:lng)
204
- @coords.lng
205
- elsif @coords.respond_to?(:lon)
206
- @coords.lon
207
- elsif @coords.respond_to?(:long)
208
- @coords.long
209
- elsif @coords.respond_to?(:longitude)
210
- @coords.longitude
211
- end.to_f
212
- end
213
- end
199
+ Coordinates = Struct.new(:lat, :lng)
214
200
 
215
201
  class ContextBoundDelegate
216
202
  class <<self
@@ -237,12 +223,16 @@ module Sunspot
237
223
  @__receiver__, @__calling_context__ = receiver, calling_context
238
224
  end
239
225
 
226
+ def id
227
+ @__calling_context__.__send__(:id)
228
+ end
229
+
240
230
  def method_missing(method, *args, &block)
241
231
  begin
242
- @__receiver__.send(method.to_sym, *args, &block)
232
+ @__receiver__.__send__(method.to_sym, *args, &block)
243
233
  rescue ::NoMethodError => e
244
234
  begin
245
- @__calling_context__.send(method.to_sym, *args, &block)
235
+ @__calling_context__.__send__(method.to_sym, *args, &block)
246
236
  rescue ::NoMethodError
247
237
  raise(e)
248
238
  end
@@ -1,3 +1,3 @@
1
1
  module Sunspot
2
- VERSION = '1.1.0'
2
+ VERSION = '1.2.0'
3
3
  end
@@ -442,9 +442,6 @@
442
442
  <str name="version">2.1</str>
443
443
  -->
444
444
  </lst>
445
- <arr name="last-components">
446
- <str>spatial</str>
447
- </arr>
448
445
  </requestHandler>
449
446
  <!-- Please refer to http://wiki.apache.org/solr/SolrReplication for details on configuring replication -->
450
447
  <!-- remove the <lst name="master"> section if this is just a slave -->
@@ -928,7 +925,6 @@
928
925
  <healthcheck type="file">server-enabled</healthcheck>
929
926
  -->
930
927
  </admin>
931
- <searchComponent name="spatial" class="me.outofti.solrspatiallight.SpatialQueryComponent"/>
932
928
  <requestHandler class="solr.MoreLikeThisHandler" name="/mlt">
933
929
  <lst name="defaults">
934
930
  <str name="mlt.mintf">1</str>
@@ -9,6 +9,14 @@ describe "DSL bindings" do
9
9
  value.should == 'value'
10
10
  end
11
11
 
12
+ it 'should give access to calling context\'s id method in search DSL' do
13
+ value = nil
14
+ session.search(Post) do
15
+ value = id
16
+ end
17
+ value.should == 16
18
+ end
19
+
12
20
  it 'should give access to calling context\'s methods in nested DSL block' do
13
21
  value = nil
14
22
  session.search(Post) do
@@ -35,4 +43,8 @@ describe "DSL bindings" do
35
43
  def test_method
36
44
  'value'
37
45
  end
46
+
47
+ def id
48
+ 16
49
+ end
38
50
  end