fixing #181
[acts_as_ferret.git] / lib / shared_index_class_methods.rb
bloba97e2fd1a30cc50cb8bc1168bfb04cde2dd05b45
1 module ActsAsFerret
2   
3   # class methods for classes using acts_as_ferret :single_index => true
4   module SharedIndexClassMethods
6     def find_id_by_contents(q, options = {}, &block)
7       # add class name scoping to query if necessary
8       unless options[:models] == :all # search needs to be restricted by one or more class names
9         options[:models] ||= [] 
10         # add this class to the list of given models
11         options[:models] << self unless options[:models].include?(self)
12         # keep original query 
13         original_query = q
14         
15         if original_query.is_a? String
16           model_query = options[:models].map(&:name).join '|'
17           q += %{ +class_name:"#{model_query}"}
18         else
19           q = Ferret::Search::BooleanQuery.new
20           q.add_query(original_query, :must)
21           model_query = Ferret::Search::BooleanQuery.new
22           options[:models].each do |model|
23             model_query.add_query(Ferret::Search::TermQuery.new(:class_name, model.name), :should)
24           end
25           q.add_query(model_query, :must)
26         end
27       end
28       options.delete :models
29       
30       super(q, options, &block)
31     end
33     # Overrides the standard find_by_contents for searching a shared index.
34     #
35     # please note that records from different models will be fetched in
36     # separate sql calls, so any sql order_by clause given with 
37     # find_options[:order] will be ignored.
38     def find_by_contents(q, options = {}, find_options = {})
39       if order = find_options.delete(:order)
40         logger.warn "using a shared index, so ignoring order_by clause #{order}"
41       end
42       total_hits, result = find_records_lazy_or_not q, options, find_options
43       # sort so results have the same order they had when originally retrieved
44       # from ferret
45       return SearchResults.new(result, total_hits)
46     end
48     protected
50     def ar_find_by_contents(q, options = {}, find_options = {})
51       total_hits, id_arrays = collect_results(q, options)
52       result = retrieve_records(id_arrays, find_options)
53       result.sort! { |a, b| id_arrays[a.class.name][a.id.to_s].first <=> id_arrays[b.class.name][b.id.to_s].first }
54       [ total_hits, result ]
55     end
57     def collect_results(q, options = {})
58       id_arrays = {}
59       # get object ids for index hits
60       rank = 0
61       total_hits = find_id_by_contents(q, options) do |model, id, score, data|
62         id_arrays[model] ||= {}
63         # store result rank and score
64         id_arrays[model][id] = [ rank += 1, score ]
65       end
66       [ total_hits, id_arrays ]
67     end
69     
70     # determine all field names in the shared index
71     # TODO unused
72 #    def single_index_field_names(models)
73 #      @single_index_field_names ||= (
74 #          searcher = Ferret::Search::Searcher.new(class_index_dir)
75 #          if searcher.reader.respond_to?(:get_field_names)
76 #            (searcher.reader.send(:get_field_names) - ['id', 'class_name']).to_a
77 #          else
78 #            puts <<-END
79 #unable to retrieve field names for class #{self.name}, please 
80 #consider naming all indexed fields in your call to acts_as_ferret!
81 #            END
82 #            models.map { |m| m.content_columns.map { |col| col.name } }.flatten
83 #          end
84 #      )
86 #    end
88   end
89 end