Skip to content

Commit

Permalink
Merge pull request sunspot#177 from wildoats/master
Browse files Browse the repository at this point in the history
Adding support for range facets
  • Loading branch information
alindeman committed Apr 15, 2012
2 parents d2bb4c4 + 67f8f1e commit 54eb2b5
Show file tree
Hide file tree
Showing 8 changed files with 141 additions and 2 deletions.
9 changes: 9 additions & 0 deletions README.md
Expand Up @@ -387,6 +387,15 @@ search.facet(:average_rating).rows.each do |facet|
end
```

#### Range Facets

```ruby
# Posts faceted by range of average ratings
Sunspot.search(Post) do
facet :average_rating, :range => 1..5, :range_interval => 1
end
```

### Ordering

By default, Sunspot orders results by "score": the Solr-determined
Expand Down
22 changes: 22 additions & 0 deletions sunspot/lib/sunspot/dsl/field_query.rb
Expand Up @@ -185,6 +185,19 @@ def group(*field_names, &block)
# semantic meaning is attached to them. The label for +facet+ should be
# a symbol; the label for +row+ can be whatever you'd like.
#
# ==== Range Facets
#
# One can use the Range Faceting feature on any date field or any numeric
# field that supports range queries. This is particularly useful for the
# cases in the past where one might stitch together a series of range
# queries (as facet by query) for things like prices, etc.
#
# For example faceting over average ratings can be done as follows:
#
# Sunspot.search(Post) do
# facet :average_rating, :range => 1..5, :range_interval => 1
# end
#
# ==== Parameters
#
# field_names...<Symbol>:: fields for which to return field facets
Expand Down Expand Up @@ -278,6 +291,15 @@ def facet(*field_names, &block)
end
search_facet = @search.add_date_facet(field, options)
Sunspot::Query::DateFieldFacet.new(field, options)
elsif options[:range]
unless [Sunspot::Type::TimeType, Sunspot::Type::FloatType, Sunspot::Type::IntegerType ].inject(false){|res,type| res || field.type.is_a?(type)}
raise(
ArgumentError,
':range can only be specified for date or numeric fields'
)
end
search_facet = @search.add_range_facet(field, options)
Sunspot::Query::RangeFacet.new(field, options)
else
search_facet = @search.add_field_facet(field, options)
Sunspot::Query::FieldFacet.new(field, options)
Expand Down
2 changes: 1 addition & 1 deletion sunspot/lib/sunspot/query.rb
@@ -1,4 +1,4 @@
%w(filter abstract_field_facet connective boost_query date_field_facet dismax
%w(filter abstract_field_facet connective boost_query date_field_facet range_facet dismax
field_facet highlighting pagination restriction common_query
standard_query more_like_this more_like_this_query geo geofilt bbox query_facet
scope sort sort_composite text_field_boost function_query
Expand Down
14 changes: 14 additions & 0 deletions sunspot/lib/sunspot/query/range_facet.rb
@@ -0,0 +1,14 @@
module Sunspot
module Query
class RangeFacet < AbstractFieldFacet
def to_params
params = super
params[:"facet.range"] = [@field.indexed_name]
params[qualified_param('range.start')] = @field.to_indexed(@options[:range].first)
params[qualified_param('range.end')] = @field.to_indexed(@options[:range].last)
params[qualified_param('range.gap')] = "#{@options[:range_interval] || 10}"
params
end
end
end
end
2 changes: 1 addition & 1 deletion sunspot/lib/sunspot/search.rb
@@ -1,5 +1,5 @@
%w(abstract_search standard_search more_like_this_search query_facet field_facet
date_facet facet_row hit highlight field_group group hit_enumerable).each do |file|
date_facet range_facet facet_row hit highlight field_group group hit_enumerable).each do |file|
require File.join(File.dirname(__FILE__), 'search', file)
end

Expand Down
5 changes: 5 additions & 0 deletions sunspot/lib/sunspot/search/abstract_search.rb
Expand Up @@ -226,6 +226,11 @@ def add_date_facet(field, options) #:nodoc:
add_facet(name, DateFacet.new(field, self, options))
end

def add_range_facet(field, options) #:nodoc:
name = (options[:name] || field.name)
add_facet(name, RangeFacet.new(field, self, options))
end

def highlights_for(doc) #:nodoc:
if @solr_result['highlighting']
@solr_result['highlighting'][doc['id']]
Expand Down
37 changes: 37 additions & 0 deletions sunspot/lib/sunspot/search/range_facet.rb
@@ -0,0 +1,37 @@
module Sunspot
module Search
class RangeFacet
def initialize(field, search, options)
@field, @search, @options = field, search, options
end

def field_name
@field.name
end

def rows
@rows ||=
begin
data = @search.facet_response['facet_ranges'][@field.indexed_name]
gap = (@options[:range_interval] || 10).to_i
rows = []

if data['counts']
Hash[*data['counts']].each_pair do |start_str, count|
start = start_str.to_f
finish = start + gap
rows << FacetRow.new(start..finish, count, self)
end
end

if @options[:sort] == :count
rows.sort! { |lrow, rrow| rrow.count <=> lrow.count }
else
rows.sort! { |lrow, rrow| lrow.value.first <=> rrow.value.first }
end
rows
end
end
end
end
end
52 changes: 52 additions & 0 deletions sunspot/spec/api/query/faceting_examples.rb
Expand Up @@ -262,6 +262,58 @@
end
end

describe 'on range facets' do
before :each do
@range = 2..4
end

it 'does not send range facet parameters if integer range is not specified' do
search do |query|
query.facet :average_rating
end
connection.should_not have_last_search_with(:"facet.range")
end

it 'sets the facet to a range facet if the range is specified' do
search do |query|
query.facet :average_rating, :range => @range
end
connection.should have_last_search_with(:"facet.range" => ['average_rating_ft'])
end

it 'sets the facet start and end' do
search do |query|
query.facet :average_rating, :range => @range
end
connection.should have_last_search_with(
:"f.average_rating_ft.facet.range.start" => '2.0',
:"f.average_rating_ft.facet.range.end" => '4.0'
)
end

it 'defaults the range interval to 10' do
search do |query|
query.facet :average_rating, :range => @range
end
connection.should have_last_search_with(:"f.average_rating_ft.facet.range.gap" => "10")
end

it 'uses custom range interval' do
search do |query|
query.facet :average_rating, :range => @range, :range_interval => 1
end
connection.should have_last_search_with(:"f.average_rating_ft.facet.range.gap" => "1")
end

it 'does not allow date faceting on a non-continuous field' do
lambda do
search do |query|
query.facet :title, :range => @range
end
end.should raise_error(ArgumentError)
end
end

describe 'using queries' do
it 'turns faceting on' do
search do
Expand Down

0 comments on commit 54eb2b5

Please sign in to comment.