r2680@monsoon: jk | 2006-11-15 20:06:03 +0100
[acts_as_ferret.git] / lib / instance_methods.rb
blob25840cd3caaa2bb020086d9710c17afead8adb24
1 module FerretMixin
2   module Acts #:nodoc:
3     module ARFerret #:nodoc:
5       module InstanceMethods
6         include MoreLikeThis
8           # Returns an array of strings with the matches highlighted. The +query+ can
9           # either a query String or a Ferret::Search::Query object.
10           # 
11           # === Options
12           #
13           # field::            field to take the content from. This field has 
14           #                    to have it's content stored in the index 
15           #                    (:store => :yes in your call to aaf). If not
16           #                    given, all stored fields are searched, and the
17           #                    highlighted content found in all of them is returned.
18           #                    set :highlight => :no in the field options to
19           #                    avoid highlighting of contents from a :stored field.
20           # excerpt_length::   Default: 150. Length of excerpt to show. Highlighted
21           #                    terms will be in the centre of the excerpt.
22           # num_excerpts::     Default: 2. Number of excerpts to return.
23           # pre_tag::          Default: "<em>". Tag to place to the left of the
24           #                    match.  
25           # post_tag::         Default: "</em>". This tag should close the
26           #                    +:pre_tag+.
27           # ellipsis::         Default: "...". This is the string that is appended
28           #                    at the beginning and end of excerpts (unless the
29           #                    excerpt hits the start or end of the field. You'll
30           #                    probably want to change this so a Unicode elipsis
31           #                    character.
32         def highlight(query, options = {})
33           options = { :num_excerpts => 2, :pre_tag => '<em>', :post_tag => '</em>' }.update(options)
34           i = self.class.ferret_index
35           highlights = []
36           i.synchronize do
37             doc_num = self.document_number
38             if options[:field]
39               highlights << i.highlight(query, doc_num, options)
40             else
41               query = i.process_query(query) # process only once
42               fields_for_ferret.each_pair do |field, config|
43                 next if config[:store] == :no || config[:highlight] == :no
44                 options[:field] = field
45                 highlights << i.highlight(query, doc_num, options)
46               end
47             end
48           end
49           return highlights.compact.flatten[0..options[:num_excerpts]-1]
50         end
51         
52         # re-eneable ferret indexing after a call to #disable_ferret
53         def ferret_enable; @ferret_disabled = nil end
54        
55         # returns true if ferret indexing is enabled
56         def ferret_enabled?; @ferret_disabled.nil? end
58         # Disable Ferret for a specified amount of time. ::once will disable
59         # Ferret for the next call to #save (this is the default), ::always will 
60         # do so for all subsequent calls.
61         # To manually trigger reindexing of a record, you can call #ferret_update 
62         # directly. 
63         #
64         # When given a block, this will be executed without any ferret indexing of 
65         # this object taking place. The optional argument in this case can be used 
66         # to indicate if the object should be indexed after executing the block
67         # (::index_when_finished). Automatic Ferret indexing of this object will be 
68         # turned on after the block has been executed.
69         def disable_ferret(option = :once)
70           if block_given?
71             @ferret_disabled = :always
72             yield
73             ferret_enable
74             ferret_update if option == :index_when_finished
75           elsif [:once, :always].include?(option)
76             @ferret_disabled = option
77           else
78             raise ArgumentError.new("Invalid Argument #{option}")
79           end
80         end
82         # add to index
83         def ferret_create
84           if ferret_enabled?
85             logger.debug "ferret_create/update: #{self.class.name} : #{self.id}"
86             self.class.ferret_index << self.to_doc
87           else
88             ferret_enable if @ferret_disabled == :once
89           end
90           @ferret_enabled = true
91           true # signal success to AR
92         end
93         alias :ferret_update :ferret_create
94         
96         # remove from index
97         def ferret_destroy
98           logger.debug "ferret_destroy: #{self.class.name} : #{self.id}"
99           begin
100             self.class.ferret_index.query_delete(query_for_self)
101           rescue
102             logger.warn("Could not find indexed value for this object: #{$!}")
103           end
104           true # signal success to AR
105         end
106         
107         # convert instance to ferret document
108         def to_doc
109           logger.debug "creating doc for class: #{self.class.name}, id: #{self.id}"
110           # Churn through the complete Active Record and add it to the Ferret document
111           doc = Ferret::Document.new
112           # store the id of each item
113           doc[:id] = self.id
115           # store the class name if configured to do so
116           if configuration[:store_class_name]
117             doc[:class_name] = self.class.name
118           end
119           
120           # iterate through the fields and add them to the document
121           fields_for_ferret.each_pair do |field, config|
122             doc[field] = self.send("#{field}_to_ferret") unless config[:ignore]
123           end
124           return doc
125         end
127         # returns the ferret document number this record has.
128         def document_number
129           hits = self.class.ferret_index.search(query_for_self)
130           return hits.hits.first.doc if hits.total_hits == 1
131           raise "cannot determine document number from primary key: #{self}"
132         end
134         # holds the score this record had when it was found via
135         # acts_as_ferret
136         attr_accessor :ferret_score
137           
138         protected
140         # build a ferret query matching only this record
141         def query_for_self
142           query = Ferret::Search::TermQuery.new(:id, self.id.to_s)
143           if self.class.configuration[:single_index]
144             bq = Ferret::Search::BooleanQuery.new
145             bq.add_query(query, :must)
146             bq.add_query(Ferret::Search::TermQuery.new(:class_name, self.class.name), :must)
147             return bq
148           end
149           return query
150         end
152       end
154     end
155   end