Skip to content

Instantly share code, notes, and snippets.

@clemens
Created April 17, 2011 21:27
Show Gist options
  • Save clemens/924493 to your computer and use it in GitHub Desktop.
Save clemens/924493 to your computer and use it in GitHub Desktop.
Faster Sphinx facets
class Product < ActiveRecord::Base
define_index do
# rest of the index ...
has merchant_id
has merchant.company, :as => :merchant_name, :type => :string
has brand_id, :type => :integer
has brand.name, :as => :brand_name, :type => :string
has category_id
has category.name, :as => :category_name, :type => :string
end
class << self
def merchant_facets(*args)
options = args.extract_options!
options.merge!(:group_attribute => 'merchant_id', :label_attribute => 'merchant_name')
facets_2(*(args << options))
end
def brand_facets(*args)
options = args.extract_options!
options.merge!(:group_attribute => 'brand_id', :label_attribute => 'brand_name')
facets_2(*(args << options))
end
def category_facets(*args)
options = args.extract_options!
options.merge!(:group_attribute => 'category_id', :label_attribute => 'category_name')
facets_2(*(args << options))
end
protected
def facets_2(*args)
options = args.extract_options!
group_attribute = options.delete(:group_attribute)
label_attribute = options.delete(:label_attribute)
query = args.first || ''
query = star_query(query, options[:star]) if options[:star]
filters = []
options[:with] ||= {}
options[:with].merge!(:sphinx_deleted => 0, :class_crc => Product.to_crc32)
options[:without] ||= {}
options[:without].merge!(group_attribute => 0)
filters += Array(options[:with]).map { |attribute, values| Riddle::Client::Filter.new(attribute, Array(values)) }
filters += Array(options[:without]).map { |attribute, values| Riddle::Client::Filter.new(attribute, Array(values), true) }
client = ThinkingSphinx::Configuration.instance.client
client.group_by = group_attribute
client.group_function = :attr
client.group_clause = '@count DESC'
client.filters = filters
client.limit = 1_000
client.match_mode = options[:match_mode] if options[:match_mode].present?
# these two should be irrelevant
# client.rank_mode = options[:rank_mode] if options[:rank_mode].present?
# client.sort_mode = options[:sort_mode] if options[:sort_mode].present?
result = client.query(query, 'product')
result[:matches].inject(ActiveSupport::OrderedHash.new) do |facets, match|
k1, k2 = match[:attributes].values_at(group_attribute, label_attribute)
facets[[k1, k2]] = match[:attributes]['@count'] if k1.present? && k2.present?
facets
end
end
# copied and adapted from ThinkingSphinx – wish it was a class method there :)
def star_query(query, star)
token = star.is_a?(Regexp) ? star : /\w+/u
query.gsub(/("#{token}(.*?#{token})?"|(?![!-])#{token})/u) do
pre, proper, post = $`, $&, $'
# E.g. "@foo", "/2", "~3", but not as part of a token
is_operator = pre.match(%r{(\W|^)[@~/]\Z}) ||
pre.match(%r{(\W|^)@\([^\)]*$})
# E.g. "foo bar", with quotes
is_quote = proper.starts_with?('"') && proper.ends_with?('"')
has_star = pre.ends_with?("*") || post.starts_with?("*")
if is_operator || is_quote || has_star
proper
else
"*#{proper}*"
end
end
end
end
end
ThinkingSphinx::Attribute.class_eval do
def type_to_config
{
:multi => :sql_attr_multi,
:datetime => :sql_attr_timestamp,
# :string => :sql_attr_str2ordinal,
:string => :sql_attr_string,
:float => :sql_attr_float,
:boolean => :sql_attr_bool,
:integer => :sql_attr_uint,
:bigint => :sql_attr_bigint
}[type]
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment