r1107@monsoon: jk | 2006-09-09 19:03:52 +0200
[acts_as_ferret.git] / lib / instance_methods.rb
blob8ff4b1bd89bb4eed2ef1fbe35d799f93ce161ed1
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               fields_for_ferret.each_pair do |field, config|
42                 next if config[:store] == :no || config[:highlight] == :no
43                 options[:field] = field
44                 highlights << i.highlight(query, doc_num, options)
45               end
46             end
47           end
48           return highlights.compact.flatten[0..options[:num_excerpts]-1]
49         end
50         
51         # re-eneable ferret indexing after a call to #disable_ferret
52         def ferret_enable; @ferret_disabled = nil end
53        
54         # returns true if ferret indexing is enabled
55         def ferret_enabled?; @ferret_disabled.nil? end
57         # Disable Ferret for a specified amount of time. ::once will disable
58         # Ferret for the next call to #save (this is the default), ::always will 
59         # do so for all subsequent calls.
60         # To manually trigger reindexing of a record, you can call #ferret_update 
61         # directly. 
62         #
63         # When given a block, this will be executed without any ferret indexing of 
64         # this object taking place. The optional argument in this case can be used 
65         # to indicate if the object should be indexed after executing the block
66         # (::index_when_finished). Automatic Ferret indexing of this object will be 
67         # turned on after the block has been executed.
68         def disable_ferret(option = :once)
69           if block_given?
70             @ferret_disabled = :always
71             yield
72             ferret_enable
73             ferret_update if option == :index_when_finished
74           elsif [:once, :always].include?(option)
75             @ferret_disabled = option
76           else
77             raise ArgumentError.new("Invalid Argument #{option}")
78           end
79         end
81         # add to index
82         def ferret_create
83           if ferret_enabled?
84             logger.debug "ferret_create/update: #{self.class.name} : #{self.id}"
85             self.class.ferret_index << self.to_doc
86           else
87             ferret_enable if @ferret_disabled == :once
88           end
89           @ferret_enabled = true
90           true # signal success to AR
91         end
92         alias :ferret_update :ferret_create
93         
95         # remove from index
96         def ferret_destroy
97           logger.debug "ferret_destroy: #{self.class.name} : #{self.id}"
98           begin
99             self.class.ferret_index.query_delete(query_for_self)
100           rescue
101             logger.warn("Could not find indexed value for this object: #{$!}")
102           end
103           true # signal success to AR
104         end
105         
106         # convert instance to ferret document
107         def to_doc
108           logger.debug "creating doc for class: #{self.class.name}, id: #{self.id}"
109           # Churn through the complete Active Record and add it to the Ferret document
110           doc = Ferret::Document.new
111           # store the id of each item
112           doc[:id] = self.id
114           # store the class name if configured to do so
115           if configuration[:store_class_name]
116             doc[:class_name] = self.class.name
117           end
118           # iterate through the fields and add them to the document
119           #if fields_for_ferret
120             # have user defined fields
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         protected
136         # build a ferret query matching only this record
137         def query_for_self
138           query = Ferret::Search::TermQuery.new(:id, self.id.to_s)
139           if self.class.configuration[:single_index]
140             bq = Ferret::Search::BooleanQuery.new
141             bq.add_query(query, :must)
142             bq.add_query(Ferret::Search::TermQuery.new(:class_name, self.class.name), :must)
143             return bq
144           end
145           return query
146         end
148       end
150     end
151   end