thinking-sphinx 2.0.0.rc1 → 2.0.0.rc2

Sign up to get free protection for your applications and to get access to all the features.
data/README.textile CHANGED
@@ -177,3 +177,9 @@ Since I first released this library, there's been quite a few people who have su
177
177
  * James Brooks
178
178
  * Andrew Coldham
179
179
  * Mark Dodwell
180
+ * Frédéric Malamitsas
181
+ * Jon Gubman
182
+ * Michael Schuerig
183
+ * Ben Hutton
184
+ * Alfonso Jiménez
185
+ * Szymon Nowak
data/VERSION CHANGED
@@ -1 +1 @@
1
- 2.0.0.rc1
1
+ 2.0.0.rc2
@@ -27,7 +27,7 @@ Feature: Search and browse models by their defined facets
27
27
  And I should have the facet State
28
28
  And I should have the facet Age
29
29
 
30
- Scenario: Requseting float facets
30
+ Scenario: Requesting float facets
31
31
  Given Sphinx is running
32
32
  And I am searching on alphas
33
33
  When I am requesting facet results
@@ -80,3 +80,11 @@ Feature: Search and browse models by their defined facets
80
80
  And I am searching on posts
81
81
  When I am requesting facet results
82
82
  Then the Comment Ids facet should have 9 keys
83
+
84
+ Scenario: Requesting facets from a subclass
85
+ Given Sphinx is running
86
+ And I am searching on animals
87
+ When I am requesting facet results
88
+ And I want classes included
89
+ Then I should have the facet Class
90
+
@@ -40,3 +40,11 @@ Feature: Sphinx Scopes
40
40
  When I use the with_first_name scope set to "Andrew"
41
41
  And I am retrieving the scoped result count
42
42
  Then I should get a value of 7
43
+
44
+ Scenario: Counts with scopes and additional query terms
45
+ Given Sphinx is running
46
+ And I am searching on people
47
+ When I use the with_first_name scope set to "Andrew"
48
+ And I am retrieving the scoped result count for "Byrne"
49
+ Then I should get a value of 1
50
+
@@ -13,3 +13,7 @@ end
13
13
  When /^I am retrieving the scoped result count$/ do
14
14
  @results = results.search_count
15
15
  end
16
+
17
+ When /^I am retrieving the scoped result count for "([^"]*)"$/ do |query|
18
+ @results = results.search_count query
19
+ end
@@ -1,8 +1,12 @@
1
1
  require 'thinking_sphinx/test'
2
2
 
3
- class Cucumber::ThinkingSphinx::ExternalWorld
4
- def initialize(suppress_delta_output = true)
5
- ::ThinkingSphinx::Test.init
6
- ::ThinkingSphinx::Test.start_with_autostop
3
+ module Cucumber
4
+ module ThinkingSphinx
5
+ class ExternalWorld
6
+ def initialize(suppress_delta_output = true)
7
+ ::ThinkingSphinx::Test.init
8
+ ::ThinkingSphinx::Test.start_with_autostop
9
+ end
10
+ end
7
11
  end
8
12
  end
@@ -27,7 +27,7 @@ require 'thinking_sphinx/adapters/abstract_adapter'
27
27
  require 'thinking_sphinx/adapters/mysql_adapter'
28
28
  require 'thinking_sphinx/adapters/postgresql_adapter'
29
29
 
30
- require 'thinking_sphinx/railtie'
30
+ require 'thinking_sphinx/railtie' if defined?(Rails)
31
31
 
32
32
  module ThinkingSphinx
33
33
  # A ConnectionError will get thrown when a connection to Sphinx can't be
@@ -41,7 +41,7 @@ module ThinkingSphinx
41
41
  end
42
42
 
43
43
  def to_crc32s
44
- (subclasses << self).collect { |klass| klass.to_crc32 }
44
+ (descendants << self).collect { |klass| klass.to_crc32 }
45
45
  end
46
46
 
47
47
  def sphinx_database_adapter
@@ -183,7 +183,7 @@ module ThinkingSphinx
183
183
 
184
184
  def insert_sphinx_index(index)
185
185
  self.sphinx_indexes << index
186
- subclasses.each { |klass| klass.insert_sphinx_index(index) }
186
+ descendants.each { |klass| klass.insert_sphinx_index(index) }
187
187
  end
188
188
 
189
189
  def has_sphinx_indexes?
@@ -308,6 +308,10 @@ module ThinkingSphinx
308
308
  end
309
309
  end
310
310
 
311
+ attr_accessor :excerpts
312
+ attr_accessor :sphinx_attributes
313
+ attr_accessor :matching_fields
314
+
311
315
  def in_index?(suffix)
312
316
  self.class.search_for_id self.sphinx_document_id, sphinx_index_name(suffix)
313
317
  end
@@ -12,7 +12,9 @@ module ThinkingSphinx
12
12
  def self.detect(model)
13
13
  case model.connection.class.name
14
14
  when "ActiveRecord::ConnectionAdapters::MysqlAdapter",
15
- "ActiveRecord::ConnectionAdapters::MysqlplusAdapter"
15
+ "ActiveRecord::ConnectionAdapters::MysqlplusAdapter",
16
+ "ActiveRecord::ConnectionAdapters::Mysql2Adapter",
17
+ "ActiveRecord::ConnectionAdapters::NullDBAdapter"
16
18
  ThinkingSphinx::MysqlAdapter.new model
17
19
  when "ActiveRecord::ConnectionAdapters::PostgreSQLAdapter"
18
20
  ThinkingSphinx::PostgreSQLAdapter.new model
@@ -8,8 +8,9 @@ module ThinkingSphinx
8
8
  "class_crc"
9
9
  end
10
10
 
11
- def value(object, attribute_value)
12
- object.class.name
11
+ def value(object, attribute_hash)
12
+ crc = attribute_hash['class_crc']
13
+ ThinkingSphinx::Configuration.instance.models_by_crc[crc]
13
14
  end
14
15
  end
15
16
  end
@@ -243,7 +243,7 @@ module ThinkingSphinx
243
243
  @models_by_crc ||= begin
244
244
  ThinkingSphinx.context.indexed_models.inject({}) do |hash, model|
245
245
  hash[model.constantize.to_crc32] = model
246
- model.constantize.subclasses.each { |subclass|
246
+ model.constantize.descendants.each { |subclass|
247
247
  hash[subclass.to_crc32] = subclass.name
248
248
  }
249
249
  hash
@@ -40,7 +40,7 @@ class ThinkingSphinx::Context
40
40
  private
41
41
 
42
42
  def add_indexed_models
43
- ActiveRecord::Base.subclasses.each do |klass|
43
+ ActiveRecord::Base.descendants.each do |klass|
44
44
  add_indexed_model klass if klass.has_sphinx_indexes?
45
45
  end
46
46
  end
@@ -55,7 +55,7 @@ class ThinkingSphinx::Context
55
55
  model_name = file.gsub(/^#{base}([\w_\/\\]+)\.rb/, '\1')
56
56
 
57
57
  next if model_name.nil?
58
- next if ::ActiveRecord::Base.send(:subclasses).detect { |model|
58
+ next if ::ActiveRecord::Base.send(:descendants).detect { |model|
59
59
  model.name == model_name.camelize
60
60
  }
61
61
 
@@ -44,7 +44,7 @@ module ThinkingSphinx
44
44
  config = ThinkingSphinx::Configuration.instance
45
45
  rotate = ThinkingSphinx.sphinx_running? ? "--rotate" : ""
46
46
 
47
- output = `#{config.bin_path}#{config.indexer_binary_name} --config '#{config.config_file}' #{rotate} #{model.delta_index_names.join(' ')}`
47
+ output = `#{config.bin_path}#{config.indexer_binary_name} --config "#{config.config_file}" #{rotate} #{model.delta_index_names.join(' ')}`
48
48
  puts(output) unless ThinkingSphinx.suppress_delta_output?
49
49
  end
50
50
 
@@ -71,7 +71,12 @@ module ThinkingSphinx
71
71
  @property.is_a?(Field) ? :string : @property.type
72
72
  end
73
73
 
74
- def value(object, attribute_value)
74
+ def float?
75
+ @property.type == :float
76
+ end
77
+
78
+ def value(object, attribute_hash)
79
+ attribute_value = attribute_hash['@groupby']
75
80
  return translate(object, attribute_value) if translate? || float?
76
81
 
77
82
  case @property.type
@@ -117,9 +122,5 @@ module ThinkingSphinx
117
122
  def column
118
123
  @property.columns.first
119
124
  end
120
-
121
- def float?
122
- @property.type == :float
123
- end
124
125
  end
125
126
  end
@@ -44,23 +44,21 @@ module ThinkingSphinx
44
44
  end
45
45
 
46
46
  def populate
47
- facet_names.each do |name|
48
- search_options = facet_search_options.merge(:group_by => name)
49
- add_from_results name, ThinkingSphinx.search(
50
- *(args + [search_options])
51
- )
52
- end
47
+ ThinkingSphinx::Search.bundle_searches(facet_names) { |sphinx, name|
48
+ sphinx.search *(args + [facet_search_options(name)])
49
+ }.each_with_index { |search, index|
50
+ add_from_results facet_names[index], search
51
+ }
53
52
  end
54
53
 
55
- def facet_search_options
56
- config = ThinkingSphinx::Configuration.instance
57
- max = config.configuration.searchd.max_matches || 1000
58
-
54
+ def facet_search_options(facet_name)
59
55
  options.merge(
60
56
  :group_function => :attr,
61
- :limit => max,
62
- :max_matches => max,
63
- :page => 1
57
+ :limit => max_matches,
58
+ :max_matches => max_matches,
59
+ :page => 1,
60
+ :group_by => facet_name,
61
+ :ids_only => !translate?(facet_name)
64
62
  )
65
63
  end
66
64
 
@@ -101,21 +99,34 @@ module ThinkingSphinx
101
99
  }
102
100
  end
103
101
 
104
- def add_from_results(facet, results)
105
- name = ThinkingSphinx::Facet.name_for(facet)
102
+ def translate?(name)
103
+ facet = facet_from_name(name)
104
+ facet.translate? || facet.float?
105
+ end
106
+
107
+ def config
108
+ ThinkingSphinx::Configuration.instance
109
+ end
110
+
111
+ def max_matches
112
+ @max_matches ||= config.configuration.searchd.max_matches || 1000
113
+ end
114
+
115
+ # example: facet = country_facet; name = :country
116
+ def add_from_results(facet, search)
117
+ name = ThinkingSphinx::Facet.name_for(facet)
118
+ facet = facet_from_name(facet)
106
119
 
107
120
  self[name] ||= {}
108
121
 
109
- return if results.empty?
110
-
111
- facet = facet_from_object(results.first, facet) if facet.is_a?(String)
122
+ return if search.empty?
112
123
 
113
- results.each_with_groupby_and_count { |result, group, count|
114
- facet_value = facet.value(result, group)
124
+ search.each_with_match do |result, match|
125
+ facet_value = facet.value(result, match[:attributes])
115
126
 
116
127
  self[name][facet_value] ||= 0
117
- self[name][facet_value] += count
118
- }
128
+ self[name][facet_value] += match[:attributes]["@count"]
129
+ end
119
130
  end
120
131
 
121
132
  def underlying_value(key, value)
@@ -130,7 +141,24 @@ module ThinkingSphinx
130
141
  end
131
142
 
132
143
  def facet_from_object(object, name)
133
- object.sphinx_facets.detect { |facet| facet.attribute_name == name }
144
+ facet = nil
145
+ klass = object.class
146
+
147
+ while klass != ::ActiveRecord::Base && facet.nil?
148
+ facet = klass.sphinx_facets.detect { |facet|
149
+ facet.attribute_name == name
150
+ }
151
+ klass = klass.superclass
152
+ end
153
+
154
+ facet
155
+ end
156
+
157
+ def facet_from_name(name)
158
+ name = ThinkingSphinx::Facet.name_for(name)
159
+ all_facets.detect { |facet|
160
+ facet.name == name
161
+ }
134
162
  end
135
163
  end
136
164
  end
@@ -57,6 +57,22 @@ module ThinkingSphinx
57
57
  ThinkingSphinx.facets *args
58
58
  end
59
59
 
60
+ def self.bundle_searches(enum)
61
+ client = ThinkingSphinx::Configuration.instance.client
62
+
63
+ searches = enum.collect { |item|
64
+ search = yield ThinkingSphinx, item
65
+ search.append_to client
66
+ search
67
+ }
68
+
69
+ client.run.each_with_index.collect { |results, index|
70
+ searches[index].populate_from_queue results
71
+ }
72
+
73
+ searches
74
+ end
75
+
60
76
  def self.matching_fields(fields, bitmask)
61
77
  matches = []
62
78
  bitstring = bitmask.to_s(2).rjust(32, '0').reverse
@@ -111,6 +127,7 @@ module ThinkingSphinx
111
127
  add_scope(method, *args, &block)
112
128
  return self
113
129
  elsif method == :search_count
130
+ merge_search one_class.search(*args)
114
131
  return scoped_count
115
132
  elsif method.to_s[/^each_with_.*/].nil? && !@array.respond_to?(method)
116
133
  super
@@ -186,6 +203,17 @@ module ThinkingSphinx
186
203
  # Compatibility with older versions of will_paginate
187
204
  alias_method :page_count, :total_pages
188
205
 
206
+ # Query time taken
207
+ #
208
+ # @return [Integer]
209
+ #
210
+ def query_time
211
+ populate
212
+ return 0 if @results[:time].nil?
213
+
214
+ @query_time ||= @results[:time]
215
+ end
216
+
189
217
  # The total number of search results available.
190
218
  #
191
219
  # @return [Integer]
@@ -232,6 +260,13 @@ module ThinkingSphinx
232
260
  end
233
261
  end
234
262
 
263
+ def each_with_match(&block)
264
+ populate
265
+ results[:matches].each_with_index do |match, index|
266
+ yield self[index], match
267
+ end
268
+ end
269
+
235
270
  def excerpt_for(string, model = nil)
236
271
  if model.nil? && one_class
237
272
  model ||= one_class
@@ -241,7 +276,7 @@ module ThinkingSphinx
241
276
  client.excerpts(
242
277
  :docs => [string],
243
278
  :words => results[:words].keys.join(' '),
244
- :index => "#{model.source_of_sphinx_index.sphinx_name}_core"
279
+ :index => options[:index] || "#{model.source_of_sphinx_index.sphinx_name}_core"
245
280
  ).first
246
281
  end
247
282
 
@@ -251,6 +286,29 @@ module ThinkingSphinx
251
286
  self
252
287
  end
253
288
 
289
+ def append_to(client)
290
+ prepare client
291
+ client.append_query query, indexes, comment
292
+ client.reset
293
+ end
294
+
295
+ def populate_from_queue(results)
296
+ return if @populated
297
+ @populated = true
298
+ @results = results
299
+
300
+ if options[:ids_only]
301
+ replace @results[:matches].collect { |match|
302
+ match[:attributes]["sphinx_internal_id"]
303
+ }
304
+ else
305
+ replace instances_from_matches
306
+ add_excerpter
307
+ add_sphinx_attributes
308
+ add_matching_fields if client.rank_mode == :fieldmask
309
+ end
310
+ end
311
+
254
312
  private
255
313
 
256
314
  def config
@@ -289,43 +347,32 @@ module ThinkingSphinx
289
347
 
290
348
  def add_excerpter
291
349
  each do |object|
292
- next if object.respond_to?(:excerpts)
293
-
294
- excerpter = ThinkingSphinx::Excerpter.new self, object
295
- block = lambda { excerpter }
350
+ next if object.nil?
296
351
 
297
- object.singleton_class.instance_eval do
298
- define_method(:excerpts, &block)
299
- end
352
+ object.excerpts = ThinkingSphinx::Excerpter.new self, object
300
353
  end
301
354
  end
302
355
 
303
356
  def add_sphinx_attributes
304
357
  each do |object|
305
- next if object.nil? || object.respond_to?(:sphinx_attributes)
358
+ next if object.nil?
306
359
 
307
360
  match = match_hash object
308
361
  next if match.nil?
309
362
 
310
- object.singleton_class.instance_eval do
311
- define_method(:sphinx_attributes) { match[:attributes] }
312
- end
363
+ object.sphinx_attributes = match[:attributes]
313
364
  end
314
365
  end
315
366
 
316
367
  def add_matching_fields
317
368
  each do |object|
318
- next if object.nil? || object.respond_to?(:matching_fields)
369
+ next if object.nil?
319
370
 
320
371
  match = match_hash object
321
372
  next if match.nil?
322
- fields = ThinkingSphinx::Search.matching_fields(
373
+ object.matching_fields = ThinkingSphinx::Search.matching_fields(
323
374
  @results[:fields], match[:weight]
324
375
  )
325
-
326
- object.singleton_class.instance_eval do
327
- define_method(:matching_fields) { fields }
328
- end
329
376
  end
330
377
  end
331
378
 
@@ -352,6 +399,10 @@ module ThinkingSphinx
352
399
  def client
353
400
  client = config.client
354
401
 
402
+ prepare client
403
+ end
404
+
405
+ def prepare(client)
355
406
  index_options = one_class ?
356
407
  one_class.sphinx_indexes.first.local_options : {}
357
408
 
@@ -588,14 +639,6 @@ MSG
588
639
  end
589
640
 
590
641
  # When passed a Time instance, returns the integer timestamp.
591
- #
592
- # If using Rails 2.1+, need to handle timezones to translate them back to
593
- # UTC, as that's what datetimes will be stored as by MySQL.
594
- #
595
- # in_time_zone is a method that was added for the timezone support in
596
- # Rails 2.1, which is why it's used for testing. I'm sure there's better
597
- # ways, but this does the job.
598
- #
599
642
  def filter_value(value)
600
643
  case value
601
644
  when Range
@@ -603,7 +646,7 @@ MSG
603
646
  when Array
604
647
  value.collect { |v| filter_value(v) }.flatten
605
648
  when Time
606
- value.respond_to?(:in_time_zone) ? [value.utc.to_i] : [value.to_i]
649
+ [value.to_i]
607
650
  when NilClass
608
651
  0
609
652
  else
@@ -37,7 +37,7 @@ module ThinkingSphinx
37
37
  end
38
38
 
39
39
  def subclasses_to_s
40
- "'" + (@model.send(:subclasses).collect { |klass|
40
+ "'" + (@model.send(:descendants).collect { |klass|
41
41
  klass.to_crc32.to_s
42
42
  } << @model.to_crc32.to_s).join(",") + "'"
43
43
  end
@@ -106,7 +106,7 @@ describe "ThinkingSphinx::ActiveRecord::Delta" do
106
106
 
107
107
  it "should call indexer for the delta index" do
108
108
  Person.sphinx_indexes.first.delta_object.should_receive(:`).with(
109
- "#{ThinkingSphinx::Configuration.instance.bin_path}indexer --config '#{ThinkingSphinx::Configuration.instance.config_file}' --rotate person_delta"
109
+ "#{ThinkingSphinx::Configuration.instance.bin_path}indexer --config \"#{ThinkingSphinx::Configuration.instance.config_file}\" --rotate person_delta"
110
110
  )
111
111
 
112
112
  @person.send(:index_delta)
@@ -1,28 +1,26 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe ThinkingSphinx::FacetSearch do
4
+ let(:search) { stub('search', :append_to => nil, :empty? => true) }
5
+ let(:config) { ThinkingSphinx::Configuration.instance }
6
+ let(:client) { stub('client', :run => []) }
7
+
8
+ before :each do
9
+ config.stub!(:client => client)
10
+ end
11
+
4
12
  describe 'populate' do
5
- it "should make separate Sphinx queries for each facet" do
6
- ThinkingSphinx.should_receive(:search).with(
7
- hash_including(:group_by => 'city_facet')
8
- ).and_return([])
9
- ThinkingSphinx.should_receive(:search).with(
10
- hash_including(:group_by => 'state_facet')
11
- ).and_return([])
12
- ThinkingSphinx.should_receive(:search).with(
13
- hash_including(:group_by => 'birthday')
14
- ).and_return([])
15
-
16
- ThinkingSphinx::FacetSearch.new(:classes => [Person])
13
+ before :each do
14
+ config.configuration.searchd.max_matches = 10_000
17
15
  end
18
-
16
+
19
17
  it "should request all shared facets in a multi-model request by default" do
20
- ThinkingSphinx.stub!(:search => [])
18
+ ThinkingSphinx.stub!(:search => search)
21
19
  ThinkingSphinx::FacetSearch.new.facet_names.should == ['class_crc']
22
20
  end
23
21
 
24
22
  it "should request all facets in a multi-model request if specified" do
25
- ThinkingSphinx.stub!(:search => [])
23
+ ThinkingSphinx.stub!(:search => search)
26
24
  ThinkingSphinx::FacetSearch.new(
27
25
  :all_facets => true
28
26
  ).facet_names.should == [
@@ -30,80 +28,22 @@ describe ThinkingSphinx::FacetSearch do
30
28
  ]
31
29
  end
32
30
 
33
- describe ':facets option' do
34
- it "should limit facets to the requested set" do
35
- ThinkingSphinx.should_receive(:search).once.and_return([])
36
-
37
- ThinkingSphinx::FacetSearch.new(
38
- :classes => [Person], :facets => :state
39
- )
40
- end
41
- end
42
-
43
- describe "empty result set for attributes" do
44
- before :each do
45
- ThinkingSphinx.stub!(:search => [])
46
- @facets = ThinkingSphinx::FacetSearch.new(
47
- :classes => [Person], :facets => :state
48
- )
49
- end
50
-
51
- it "should add key as attribute" do
52
- @facets.should have_key(:state)
53
- end
54
-
55
- it "should return an empty hash for the facet results" do
56
- @facets[:state].should be_empty
57
- end
58
- end
59
-
60
- describe "non-empty result set" do
61
- before :each do
62
- @person = Person.find(:first)
63
- @people = [@person]
64
- @people.stub!(:each_with_groupby_and_count).
65
- and_yield(@person, @person.city.to_crc32, 1)
66
- ThinkingSphinx.stub!(:search => @people)
67
-
68
- @facets = ThinkingSphinx::FacetSearch.new(
69
- :classes => [Person], :facets => :city
70
- )
71
- end
72
-
73
- it "should return a hash" do
74
- @facets.should be_a_kind_of(Hash)
75
- end
76
-
77
- it "should add key as attribute" do
78
- @facets.keys.should include(:city)
79
- end
80
-
81
- it "should return a hash" do
82
- @facets[:city].should == {@person.city => 1}
83
- end
84
- end
85
-
86
- before :each do
87
- @config = ThinkingSphinx::Configuration.instance
88
- @config.configuration.searchd.max_matches = 10_000
89
- end
90
-
91
31
  it "should use the system-set max_matches for limit on facet calls" do
92
32
  ThinkingSphinx.should_receive(:search) do |options|
93
33
  options[:max_matches].should == 10_000
94
34
  options[:limit].should == 10_000
95
- []
35
+ search
96
36
  end
97
37
 
98
38
  ThinkingSphinx::FacetSearch.new
99
39
  end
100
40
 
101
41
  it "should use the default max-matches if there is no explicit setting" do
102
- @config.configuration.searchd.max_matches = nil
42
+ config.configuration.searchd.max_matches = nil
103
43
  ThinkingSphinx.should_receive(:search) do |options|
104
44
  options[:max_matches].should == 1000
105
45
  options[:limit].should == 1000
106
- []
46
+ search
107
47
  end
108
48
 
109
49
  ThinkingSphinx::FacetSearch.new
@@ -113,7 +53,7 @@ describe ThinkingSphinx::FacetSearch do
113
53
  ThinkingSphinx.should_receive(:search) do |options|
114
54
  options[:max_matches].should == 10_000
115
55
  options[:limit].should == 10_000
116
- []
56
+ search
117
57
  end
118
58
 
119
59
  ThinkingSphinx::FacetSearch.new(
@@ -125,7 +65,7 @@ describe ThinkingSphinx::FacetSearch do
125
65
  it "should not use an explicit :page" do
126
66
  ThinkingSphinx.should_receive(:search) do |options|
127
67
  options[:page].should == 1
128
- []
68
+ search
129
69
  end
130
70
 
131
71
  ThinkingSphinx::FacetSearch.new(:page => 3)
@@ -149,15 +89,69 @@ describe ThinkingSphinx::FacetSearch do
149
89
  }.should raise_error
150
90
  end
151
91
  end
92
+
93
+ describe ':facets option' do
94
+ it "should limit facets to the requested set" do
95
+ ThinkingSphinx.should_receive(:search).once.and_return(search)
96
+
97
+ ThinkingSphinx::FacetSearch.new(
98
+ :classes => [Person], :facets => :state
99
+ )
100
+ end
101
+ end
102
+
103
+ describe "empty result set for attributes" do
104
+ before :each do
105
+ ThinkingSphinx.stub!(:search => search)
106
+ @facets = ThinkingSphinx::FacetSearch.new(
107
+ :classes => [Person], :facets => :state
108
+ )
109
+ end
110
+
111
+ it "should add key as attribute" do
112
+ @facets.should have_key(:state)
113
+ end
114
+
115
+ it "should return an empty hash for the facet results" do
116
+ @facets[:state].should be_empty
117
+ end
118
+ end
119
+
120
+ describe "non-empty result set" do
121
+ before :each do
122
+ @person = Person.find(:first)
123
+ @people = [@person]
124
+ search.stub!(:empty? => false)
125
+ search.stub!(:each_with_match).
126
+ and_yield(@person, {:attributes => {'@groupby' => @person.city.to_crc32, '@count' => 1}})
127
+ ThinkingSphinx::Search.stub!(:bundle_searches => [search])
128
+
129
+ @facets = ThinkingSphinx::FacetSearch.new(
130
+ :classes => [Person], :facets => :city
131
+ )
132
+ end
133
+
134
+ it "should return a hash" do
135
+ @facets.should be_a_kind_of(Hash)
136
+ end
137
+
138
+ it "should add key as attribute" do
139
+ @facets.keys.should include(:city)
140
+ end
141
+
142
+ it "should return a hash" do
143
+ @facets[:city].should == {@person.city => 1}
144
+ end
145
+ end
152
146
  end
153
147
 
154
148
  describe "#for" do
155
149
  before do
156
150
  @person = Person.find(:first)
157
151
  @people = [@person]
158
- @people.stub!(:each_with_groupby_and_count).
159
- and_yield(@person, @person.city.to_crc32, 1)
160
- ThinkingSphinx.stub!(:search => @people)
152
+ search.stub!(:each_with_match).
153
+ and_yield(@person, {:attributes => {'@groupby' => @person.city.to_crc32, '@count' => 1}})
154
+ ThinkingSphinx::Search.stub!(:bundle_searches => [search])
161
155
 
162
156
  @facets = ThinkingSphinx::FacetSearch.new(
163
157
  :classes => [Person], :facets => :city
@@ -291,13 +291,13 @@ describe ThinkingSphinx::Facet do
291
291
  person = Person.find(:first)
292
292
  friendship = Friendship.new(:person => person)
293
293
 
294
- @facet.value(friendship, 1).should == person.first_name
294
+ @facet.value(friendship, {'first_name_facet' => 1}).should == person.first_name
295
295
  end
296
296
 
297
297
  it "should return nil if the association is nil" do
298
298
  friendship = Friendship.new(:person => nil)
299
299
 
300
- @facet.value(friendship, 1).should be_nil
300
+ @facet.value(friendship, {'first_name_facet' => 1}).should be_nil
301
301
  end
302
302
 
303
303
  it "should return multi-level association values" do
@@ -308,7 +308,7 @@ describe ThinkingSphinx::Facet do
308
308
  field = ThinkingSphinx::Field.new(
309
309
  @source, ThinkingSphinx::Index::FauxColumn.new(:person, :tags, :name)
310
310
  )
311
- ThinkingSphinx::Facet.new(field).value(friendship, 'buried'.to_crc32).
311
+ ThinkingSphinx::Facet.new(field).value(friendship, {'name_facet' => 'buried'.to_crc32}).
312
312
  should == 'buried'
313
313
  end
314
314
  end
@@ -326,7 +326,7 @@ describe ThinkingSphinx::Facet do
326
326
  it "should translate using the given model" do
327
327
  alpha = Alpha.new(:cost => 10.5)
328
328
 
329
- @facet.value(alpha, 1093140480).should == 10.5
329
+ @facet.value(alpha, {'cost' => 1093140480}).should == 10.5
330
330
  end
331
331
  end
332
332
  end
@@ -135,6 +135,10 @@ describe ThinkingSphinx::SearchMethods do
135
135
  end
136
136
 
137
137
  describe '.facets' do
138
+ before :each do
139
+ ThinkingSphinx::Search.stub!(:bundle_searches => [])
140
+ end
141
+
138
142
  it "should return a FacetSearch instance" do
139
143
  Alpha.facets.should be_a(ThinkingSphinx::FacetSearch)
140
144
  end
@@ -785,9 +785,10 @@ describe ThinkingSphinx::Search do
785
785
  end
786
786
 
787
787
  it "should set up the excerpter with the instances and search" do
788
- ThinkingSphinx::Excerpter.should_receive(:new).with(@search, @alpha_a)
789
- ThinkingSphinx::Excerpter.should_receive(:new).with(@search, @alpha_b)
790
-
788
+ [@alpha_a, @beta_b, @alpha_b, @beta_a].each do |object|
789
+ ThinkingSphinx::Excerpter.should_receive(:new).with(@search, object)
790
+ end
791
+
791
792
  @search.first
792
793
  end
793
794
  end
@@ -822,11 +823,6 @@ describe ThinkingSphinx::Search do
822
823
  search.first.should respond_to(:matching_fields)
823
824
  end
824
825
 
825
- it "should not add matching_fields method if using a different ranking mode" do
826
- search = ThinkingSphinx::Search.new :rank_mode => :bm25
827
- search.first.should_not respond_to(:matching_fields)
828
- end
829
-
830
826
  it "should not add matching_fields method if object already have one" do
831
827
  search = ThinkingSphinx::Search.new :rank_mode => :fieldmask
832
828
  search.last.matching_fields.should_not be_an(Array)
@@ -1112,6 +1108,15 @@ describe ThinkingSphinx::Search do
1112
1108
  @search.excerpt_for('string')
1113
1109
  end
1114
1110
 
1111
+ it "should respect the provided index option" do
1112
+ @search = ThinkingSphinx::Search.new(:classes => [Alpha], :index => 'foo')
1113
+ @client.should_receive(:excerpts) do |options|
1114
+ options[:index].should == 'foo'
1115
+ end
1116
+
1117
+ @search.excerpt_for('string')
1118
+ end
1119
+
1115
1120
  it "should optionally take a second argument to allow for multi-model searches" do
1116
1121
  @client.should_receive(:excerpts) do |options|
1117
1122
  options[:index].should == 'beta_core'
@@ -28,15 +28,8 @@ Jeweler::Tasks.new do |gem|
28
28
  "spec/**/*_spec.rb"
29
29
  ]
30
30
 
31
- gem.add_dependency 'activerecord', '>= 3.0.0.beta3'
32
- gem.add_dependency 'riddle', '>= 1.0.10'
33
-
34
- gem.add_development_dependency "yard", ">= 0"
35
- gem.add_development_dependency "rspec", ">= 1.2.9"
36
- gem.add_development_dependency "cucumber", ">= 0"
37
- gem.add_development_dependency "will_paginate", "3.0.pre"
38
- gem.add_development_dependency "ginger", "1.2.0"
39
- gem.add_development_dependency "faker", "0.3.1"
31
+ gem.add_dependency 'activerecord', '>= 3.0.0.rc'
32
+ gem.add_dependency 'riddle', '>= 1.0.12'
40
33
 
41
34
  gem.post_install_message = <<-MESSAGE
42
35
  If you're upgrading, you should read this:
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: thinking-sphinx
3
3
  version: !ruby/object:Gem::Version
4
- hash: 977940590
4
+ hash: 977940591
5
5
  prerelease: true
6
6
  segments:
7
7
  - 2
8
8
  - 0
9
9
  - 0
10
- - rc1
11
- version: 2.0.0.rc1
10
+ - rc2
11
+ version: 2.0.0.rc2
12
12
  platform: ruby
13
13
  authors:
14
14
  - Pat Allan
@@ -16,7 +16,7 @@ autorequire:
16
16
  bindir: bin
17
17
  cert_chain: []
18
18
 
19
- date: 2010-07-02 00:00:00 +10:00
19
+ date: 2010-08-23 00:00:00 +08:00
20
20
  default_executable:
21
21
  dependencies:
22
22
  - !ruby/object:Gem::Dependency
@@ -27,13 +27,13 @@ dependencies:
27
27
  requirements:
28
28
  - - ">="
29
29
  - !ruby/object:Gem::Version
30
- hash: -1848230021
30
+ hash: 7712042
31
31
  segments:
32
32
  - 3
33
33
  - 0
34
34
  - 0
35
- - beta3
36
- version: 3.0.0.beta3
35
+ - rc
36
+ version: 3.0.0.rc
37
37
  type: :runtime
38
38
  version_requirements: *id001
39
39
  - !ruby/object:Gem::Dependency
@@ -44,106 +44,14 @@ dependencies:
44
44
  requirements:
45
45
  - - ">="
46
46
  - !ruby/object:Gem::Version
47
- hash: 3
47
+ hash: 15
48
48
  segments:
49
49
  - 1
50
50
  - 0
51
- - 10
52
- version: 1.0.10
51
+ - 12
52
+ version: 1.0.12
53
53
  type: :runtime
54
54
  version_requirements: *id002
55
- - !ruby/object:Gem::Dependency
56
- name: yard
57
- prerelease: false
58
- requirement: &id003 !ruby/object:Gem::Requirement
59
- none: false
60
- requirements:
61
- - - ">="
62
- - !ruby/object:Gem::Version
63
- hash: 3
64
- segments:
65
- - 0
66
- version: "0"
67
- type: :development
68
- version_requirements: *id003
69
- - !ruby/object:Gem::Dependency
70
- name: rspec
71
- prerelease: false
72
- requirement: &id004 !ruby/object:Gem::Requirement
73
- none: false
74
- requirements:
75
- - - ">="
76
- - !ruby/object:Gem::Version
77
- hash: 13
78
- segments:
79
- - 1
80
- - 2
81
- - 9
82
- version: 1.2.9
83
- type: :development
84
- version_requirements: *id004
85
- - !ruby/object:Gem::Dependency
86
- name: cucumber
87
- prerelease: false
88
- requirement: &id005 !ruby/object:Gem::Requirement
89
- none: false
90
- requirements:
91
- - - ">="
92
- - !ruby/object:Gem::Version
93
- hash: 3
94
- segments:
95
- - 0
96
- version: "0"
97
- type: :development
98
- version_requirements: *id005
99
- - !ruby/object:Gem::Dependency
100
- name: will_paginate
101
- prerelease: false
102
- requirement: &id006 !ruby/object:Gem::Requirement
103
- none: false
104
- requirements:
105
- - - "="
106
- - !ruby/object:Gem::Version
107
- hash: 961915916
108
- segments:
109
- - 3
110
- - 0
111
- - pre
112
- version: 3.0.pre
113
- type: :development
114
- version_requirements: *id006
115
- - !ruby/object:Gem::Dependency
116
- name: ginger
117
- prerelease: false
118
- requirement: &id007 !ruby/object:Gem::Requirement
119
- none: false
120
- requirements:
121
- - - "="
122
- - !ruby/object:Gem::Version
123
- hash: 31
124
- segments:
125
- - 1
126
- - 2
127
- - 0
128
- version: 1.2.0
129
- type: :development
130
- version_requirements: *id007
131
- - !ruby/object:Gem::Dependency
132
- name: faker
133
- prerelease: false
134
- requirement: &id008 !ruby/object:Gem::Requirement
135
- none: false
136
- requirements:
137
- - - "="
138
- - !ruby/object:Gem::Version
139
- hash: 17
140
- segments:
141
- - 0
142
- - 3
143
- - 1
144
- version: 0.3.1
145
- type: :development
146
- version_requirements: *id008
147
55
  description: A concise and easy-to-use Ruby library that connects ActiveRecord to the Sphinx search daemon, managing configuration, indexing and searching.
148
56
  email: pat@freelancing-gods.com
149
57
  executables: []