Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

Already on GitHub? Sign in to your account

Add support for multitype search and eager loading #131

Closed
wants to merge 3 commits into
from
@@ -40,24 +40,32 @@ def results
else
return [] if hits.empty?
- type = @response['hits']['hits'].first['_type']
- raise NoMethodError, "You have tried to eager load the model instances, " +
- "but Tire cannot find the model class because " +
- "document has no _type property." unless type
-
- begin
- klass = type.camelize.constantize
- rescue NameError => e
- raise NameError, "You have tried to eager load the model instances, but " +
- "Tire cannot find the model class '#{type.camelize}' " +
- "based on _type '#{type}'.", e.backtrace
+ load_options = options[:load] === true ? {} : options[:load]
+
+ # Collect all ids of one type to perform one database query per type
+ records_by_type = {}
+ hits.group_by { |hit| hit['_type'] }.each do |type, hits|
+ raise NoMethodError, "You have tried to eager load the model instances, " +
+ "but Tire cannot find the model class because " +
+ "document has no _type property." unless type
+ ids = hits.map{ |hit| hit['_id'] }
+ begin
+ model = type.camelize.constantize
+ rescue NameError => e
+ raise NameError, "Cannot find the model class '#{type.camelize}' " +
+ "based on _type '#{type}' during eager loading.", e.backtrace
+ end
+ records = model.where(model.primary_key => ids).all(load_options)
+ records_by_type[type] = records.index_by(&:id)
end
+ # Preserve original order from search results
+ records = hits.map { |hit| records_by_type[hit['_type']][hit['_id'].to_i] }
+ records.compact!
- ids = @response['hits']['hits'].map { |h| h['_id'] }
- records = @options[:load] === true ? klass.find(ids) : klass.find(ids, @options[:load])
+ # Remove missing records from the total
+ @total -= hits.size - records.size
- # Reorder records to preserve order from search results
- ids.map { |id| records.detect { |record| record.id.to_s == id.to_s } }
+ records
end
end
end
View
@@ -9,9 +9,9 @@ class Search
def initialize(indices=nil, options = {}, &block)
@indices = Array(indices)
@options = options
- @type = @options[:type]
+ @type = Array(@options[:type])
- @url = Configuration.url+['/', @indices.join(','), @type, '_search'].compact.join('/').squeeze('/')
+ @url = Configuration.url+['/', @indices.join(','), @type.join(','), '_search'].compact.join('/').squeeze('/')
block.arity < 1 ? instance_eval(&block) : block.call(self) if block_given?
end
@@ -217,23 +217,32 @@ class ResultsCollectionTest < Test::Unit::TestCase
end
should "load the records via model find method from database" do
- ActiveRecordArticle.expects(:find).with([1,2,3]).
+ mock_relation = Object.new
+ ActiveRecordArticle.expects(:where).with('id' => [1, 2, 3]).
+ returns(mock_relation)
+ mock_relation.expects(:all).with({}).
returns([ Results::Item.new(:id => 3),
Results::Item.new(:id => 1),
Results::Item.new(:id => 2) ])
Results::Collection.new(@response, :load => true).results
end
should "pass the :load option Hash to model find metod" do
- ActiveRecordArticle.expects(:find).with([1,2,3], :include => 'comments').
+ mock_relation = Object.new
+ ActiveRecordArticle.expects(:where).with('id' => [1, 2, 3]).
+ returns(mock_relation)
+ mock_relation.expects(:all).with({ :include => 'comments' }).
returns([ Results::Item.new(:id => 3),
Results::Item.new(:id => 1),
Results::Item.new(:id => 2) ])
Results::Collection.new(@response, :load => { :include => 'comments' }).results
end
should "preserve the order of records returned from search" do
- ActiveRecordArticle.expects(:find).with([1,2,3]).
+ mock_relation = Object.new
+ ActiveRecordArticle.expects(:where).with('id' => [1, 2, 3]).
+ returns(mock_relation)
+ mock_relation.expects(:all).with({}).
returns([ Results::Item.new(:id => 3),
Results::Item.new(:id => 1),
Results::Item.new(:id => 2) ])