Permalink
Browse files

Allow searches to span multiple types.

* Only uses one database query per type is used for eager loading.
* The time complexity of preserving the search result order
  in eager loading is now linear rather than quadratic.
  • Loading branch information...
1 parent 37bf00a commit a3a696a815d1daeb9f757391d0cbc2e8661d30a9 @dylanahsmith committed Oct 21, 2011
Showing with 36 additions and 20 deletions.
  1. +34 −18 lib/tire/results/collection.rb
  2. +2 −2 lib/tire/search.rb
@@ -40,30 +40,46 @@ 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
+ ranked_results = hits.map do |hit|
+ type = hit['_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
+ [type, hit['_id']]
end
- ids = @response['hits']['hits'].map { |h| h['_id'] }
+ # Collect all ids of one type to perform one database query per type
+ ids_by_type = {}
+ ranked_results.each do |type, id|
+ ids_by_type[type] ||= []
+ ids_by_type[type] <<= id
+ end
+ objects_by_type = {}
+ ids_by_type.each do |type, ids|
+ begin
+ model = 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
+ end
- load_options = options[:load] === true ? {} : options[:load]
- # Tolerate missing records by not giving find the array of ids
- records = klass.where(klass.primary_key.to_sym => ids).find(:all, load_options)
+ objects_by_id = {}
+ load_options = options[:load] === true ? {} : options[:load]
+ # Tolerate missing records
+ model.where(model.primary_key.to_sym => ids).find(:all, load_options).each do |result|
+ objects_by_id[result.id.to_s] = result
+ end
+ objects_by_type[type] = objects_by_id
+ end
+ # Preserve original order from search results
+ ranked_results.map! { |type, id| objects_by_type[type][id.to_s] }
+ ranked_results.compact!
# Remove missing records from the total
- @total -= ids.size - records.size
+ @total -= hits.size - ranked_results.size
- # Reorder records to preserve order from search results
- ids.map { |id| records.detect { |record| record.id.to_s == id.to_s } }
+ ranked_results
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

0 comments on commit a3a696a

Please sign in to comment.