thinking-sphinx 2.0.0.rc1 → 2.0.0.rc2

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/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: []