Skip to content
Browse files

Refined the facet code, with thanks to Dan Pickett's fork, and added …

…drilling down within a facet set
  • Loading branch information...
1 parent c3cd9a3 commit 529d43e3d36f772b6aec6da9252ad11125d7fdce @pat pat committed Jan 17, 2009
View
1 README
@@ -103,3 +103,4 @@ Since I first released this library, there's been quite a few people who have su
- Lachie Cox
- Lourens Naude
- Tom Davies
+- Dan Pickett
View
1 README.textile
@@ -103,3 +103,4 @@ Since I first released this library, there's been quite a few people who have su
* Lachie Cox
* Lourens Naude
* Tom Davies
+* Dan Pickett
View
23 features/facets.feature
@@ -1,7 +1,26 @@
Feature: Search and browse models by their defined facets
- Scenario: Test
+ Scenario: Requesting facets
Given Sphinx is running
And I am searching on developers
When I am requesting facet results
- Then I should see facet results
+ Then I should have valid facet results
+ And I should have 4 facets
+ And I should have the facet State
+ And I should have the facet Country
+ And I should have the facet Age
+ And I should have the facet City
+
+ Scenario: Requesting facet results
+ Given Sphinx is running
+ And I am searching on developers
+ When I am requesting facet results
+ And I drill down where Country is Australia
+ Then I should get 11 results
+
+ Scenario: Requesting facet results by multiple facets
+ Given Sphinx is running
+ And I am searching on developers
+ When I am requesting facet results
+ And I drill down where Country is Australia and Age is 30
+ Then I should get 4 results
View
26 features/step_definitions/facet_steps.rb
@@ -2,7 +2,29 @@
@method = :facets
end
-Then "I should see facet results" do
+When /^I drill down where (\w+) is (\w+)$/ do |facet, value|
+ @results = results.for(facet.downcase.to_sym => value)
+end
+
+When /^I drill down where (\w+) is (\w+) and (\w+) is (\w+)$/ do |facet_one, value_one, facet_two, value_two|
+ value_one = value_one.to_i unless value_one[/^\d+$/].nil?
+ value_two = value_two.to_i unless value_two[/^\d+$/].nil?
+
+ @results = results.for(
+ facet_one.downcase.to_sym => value_one,
+ facet_two.downcase.to_sym => value_two
+ )
+end
+
+Then "I should have valid facet results" do
results.should be_kind_of(Hash)
results.values.each { |value| value.should be_kind_of(Hash) }
-end
+end
+
+Then /^I should have (\d+) facets?$/ do |count|
+ results.keys.length.should == count.to_i
+end
+
+Then /^I should have the facet (\w+)$/ do |name|
+ results[name.downcase.to_sym].should be_kind_of(Hash)
+end
View
1 features/support/models/developer.rb
@@ -3,5 +3,6 @@ class Developer < ActiveRecord::Base
indexes country, :facet => true
indexes state, :facet => true
has age, :facet => true
+ facet city
end
end
View
1 lib/thinking_sphinx.rb
@@ -13,6 +13,7 @@
require 'thinking_sphinx/collection'
require 'thinking_sphinx/configuration'
require 'thinking_sphinx/facet'
+require 'thinking_sphinx/facet_collection'
require 'thinking_sphinx/field'
require 'thinking_sphinx/index'
require 'thinking_sphinx/rails_additions'
View
2 lib/thinking_sphinx/attribute.rb
@@ -152,7 +152,7 @@ def type
def to_facet
return nil unless @faceted
- ThinkingSphinx::Facet.new(unique_name, @columns, self)
+ ThinkingSphinx::Facet.new(self)
end
private
View
10 lib/thinking_sphinx/collection.rb
@@ -130,5 +130,13 @@ def each_with_weighting(&block)
yield self[index], match[:weight]
end
end
+
+ def inject_with_groupby_and_count(initial = nil, &block)
+ index = -1
+ results[:matches].inject(initial) do |memo, match|
+ index += 1
+ yield memo, self[index], match[:attributes]["@groupby"], match[:attributes]["@count"]
+ end
+ end
end
-end
+end
View
27 lib/thinking_sphinx/facet.rb
@@ -1,11 +1,17 @@
module ThinkingSphinx
class Facet
- attr_reader :name, :column, :reference
+ attr_reader :reference
- def initialize(name, columns, reference)
- @name = name
- @columns = columns
+ def initialize(reference)
@reference = reference
+
+ if reference.columns.length != 1
+ raise "Can't translate Facets on multiple-column field or attribute"
+ end
+ end
+
+ def name
+ reference.unique_name
end
def attribute_name
@@ -32,18 +38,21 @@ def value(object, attribute_value)
end
end
+ def to_s
+ name
+ end
+
private
def translate(object, attribute_value)
- if @columns.length > 1
- raise "Can't translate Facets on multiple-column field or attribute"
- end
-
- column = @columns.first
column.__stack.each { |method|
object = object.send(method)
}
object.send(column.__name)
end
+
+ def column
+ @reference.columns.first
+ end
end
end
View
44 lib/thinking_sphinx/facet_collection.rb
@@ -0,0 +1,44 @@
+module ThinkingSphinx
+ class FacetCollection < Hash
+ attr_accessor :arguments
+
+ def initialize(arguments)
+ @arguments = arguments.clone
+ @attribute_values = {}
+ @facets = []
+ end
+
+ def add_from_results(facet, results)
+ self[facet.name] = {}
+ @attribute_values[facet.name] = {}
+ @facets << facet
+
+ results.each_with_groupby_and_count { |result, group, count|
+ facet_value = facet.value(result, group)
+
+ self[facet.name][facet_value] = count
+ @attribute_values[facet.name][facet_value] = group
+ }
+ end
+
+ def for(hash = {})
+ arguments = @arguments.clone
+ options = arguments.extract_options!
+ options[:with] ||= {}
+
+ hash.each do |key, value|
+ attrib = facet_for_key(key).attribute_name
+ options[:with][attrib] = @attribute_values[key][value]
+ end
+
+ arguments << options
+ ThinkingSphinx::Search.search *arguments
+ end
+
+ private
+
+ def facet_for_key(key)
+ @facets.detect { |facet| facet.name == key }
+ end
+ end
+end
View
2 lib/thinking_sphinx/field.rb
@@ -115,7 +115,7 @@ def unique_name
def to_facet
return nil unless @faceted
- ThinkingSphinx::Facet.new(unique_name, @columns, self)
+ ThinkingSphinx::Facet.new(self)
end
private
View
9 lib/thinking_sphinx/index/builder.rb
@@ -147,6 +147,15 @@ def has(*args)
end
alias_method :attribute, :has
+ def facet(*args)
+ options = args.extract_options!
+ options[:facet] = true
+
+ args.each do |columns|
+ attributes << Attribute.new(FauxColumn.coerce(columns), options)
+ end
+ end
+
# Use this method to add some manual SQL conditions for your index
# request. You can pass in as many strings as you like, they'll get
# joined together with ANDs later on.
View
15 lib/thinking_sphinx/search.rb
@@ -21,7 +21,7 @@ def search_for_ids(*args)
options = args.extract_options!
page = options[:page] ? options[:page].to_i : 1
-
+
ThinkingSphinx::Collection.ids_from_results(results, page, client.limit, options)
end
@@ -353,18 +353,13 @@ def search_for_id(*args)
end
def facets(*args)
- options = args.extract_options!.merge! :group_function => :attr
+ hash = ThinkingSphinx::FacetCollection.new args
+ options = args.extract_options!.clone.merge! :group_function => :attr
- options[:class].sphinx_facets.inject({}) do |hash, facet|
- facet_result = {}
+ options[:class].sphinx_facets.inject(hash) do |hash, facet|
options[:group_by] = facet.attribute_name
- results = search *(args + [options])
- results.each_with_groupby_and_count do |result, group, count|
- facet_result[facet.value(result, group)] = count
- end
- hash[facet.name] = facet_result
-
+ hash.add_from_results facet, search(*(args + [options]))
hash
end
end

0 comments on commit 529d43e

Please sign in to comment.
Something went wrong with that request. Please try again.