Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

cast_all to avoid N+1 queries for reference facets

  • Loading branch information...
commit ca27c23aba9e067872f8f4c9560880bf59426531 1 parent 4e85eab
@nbraem nbraem authored
View
6 sunspot/lib/sunspot/field.rb
@@ -58,6 +58,12 @@ def cast(value)
@type.cast(value)
end
+ # Batch version of cast.
+ #
+ def cast_all(values)
+ @type.cast_all(values)
+ end
+
#
# Whether this field accepts multiple values.
#
View
11 sunspot/lib/sunspot/search/date_facet.rb
@@ -14,14 +14,17 @@ def rows
begin
data = @search.facet_response['facet_dates'][@field.indexed_name]
gap = (@options[:time_interval] || 86400).to_i
- rows = []
+ rows, values, counts = [], [], []
data.each_pair do |value, count|
if value =~ /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/
- start_time = @field.cast(value)
- end_time = start_time + gap
- rows << FacetRow.new(start_time..end_time, count, self)
+ values << value
+ counts << count
end
end
+ @field.cast_all(values).zip(counts) do |start_time, count|
+ end_time = start_time + gap
+ rows << FacetRow.new(start_time..end_time, count, self)
+ end
if @options[:sort] == :count
rows.sort! { |lrow, rrow| rrow.count <=> lrow.count }
else
View
7 sunspot/lib/sunspot/search/field_facet.rb
@@ -38,8 +38,13 @@ def rows(options = {})
has_query_facets = !rows.empty?
if @search.facet_response['facet_fields']
if data = @search.facet_response['facet_fields'][key]
+ values, counts = [], []
data.each_slice(2) do |value, count|
- row = FacetRow.new(@field.cast(value), count, self)
+ values << value
+ counts << count
+ end
+ @field.cast_all(values).zip(counts).each do |value, count|
+ row = FacetRow.new(value, count, self)
rows << row
end
end
View
16 sunspot/lib/sunspot/type.rb
@@ -23,6 +23,9 @@ module Sunspot
# Convert a Solr string representation of a value into the appropriate
# Ruby type.
#
+ # Optionally a type can override +cast_all+, which is a batch version of
+ # +cast+.
+ #
module Type
class <<self
def register(sunspot_type, *classes)
@@ -81,6 +84,10 @@ def to_literal(object)
"#{self.class.name} cannot be used as a Solr literal"
)
end
+
+ def cast_all(values)
+ values.map { |value| cast(value) }
+ end
end
#
@@ -378,6 +385,15 @@ def cast(index_id) #:nodoc:
class_name, id = Sunspot::Adapters::InstanceAdapter.class_name_id_from(index_id)
Adapters::DataAccessor.create(Sunspot::Util.full_const_get(class_name)).load(id)
end
+
+ def cast_all(index_ids) #:nodoc:
+ return [] if index_ids.empty?
+
+ class_names_ids = index_ids.map { |index_id| Sunspot::Adapters::InstanceAdapter.class_name_id_from(index_id) }
+ class_names, ids = class_names_ids[0].zip(*class_names_ids[1..-1]) # unzip
+ raise ArgumentError("cast_all can only cast references of same type") if class_names.uniq.length > 1
+ Adapters::DataAccessor.create(Sunspot::Util.full_const_get(class_names[0])).load_all(ids)
+ end
end
class ClassType < AbstractType
View
13 sunspot/spec/api/search/faceting_spec.rb
@@ -366,4 +366,17 @@ def facet_values_from_options(options = {})
end
facet_values(result, :blog).should include(blog1, blog2)
end
+
+ it 'loads facet rows in batch' do
+ blog1, blog2 = Blog.new(:id => 1), Blog.new(:id => 2)
+ stub_facet(:blog_s, "Blog 1" => 1, "Blog 2" => 2)
+ result = session.search Post do
+ facet :blog
+ end
+ mock_adapter = MockAdapter::DataAccessor.new(Blog)
+ Sunspot::Adapters::DataAccessor.should_receive(:create).once.with(Blog).and_return(mock_adapter)
+ mock_adapter.should_receive(:load_all).once.with(["2", "1"]).and_return([blog2, blog1])
+ mock_adapter.should_not_receive(:load)
+ facet_values(result, :blog).should include(blog1, blog2)
+ end
end
Please sign in to comment.
Something went wrong with that request. Please try again.