|
Subject: ANN: acts_as_ferret Newsgroups: gmane.comp.lang.ruby.rails Date: 2005-12-02 18:22:48 GMT (2 years, 44 weeks, 2 days and 10 minutes ago) Hi all This week I have worked with Rails and Ferret to test Ferrets (and Lucenes) capabilities. I decided to make a mixin for ActiveRecord as it seemed the simplest possible solution and I ended up making this into a plugin. For more info on Ferret see: http://ferret.davebalmain.com/trac/ The plugin is functional but could easily be refined. Anyway I want to share it with you. Regard it as a basic solution. Most of the ideas and code is taken from these sources Howtos and help on Ferret with Rails: # http://wiki.rubyonrails.com/rails/pages/HowToIntegrateFerretWithRails # http://article.gmane.org/gmane.comp.lang.ruby.rails/26859 # http://ferret.davebalmain.com/trac # http://aslakhellesoy.com/articles/2005/11/18/using-ferret-with-activerecord # http://rubyforge.org/pipermail/ferret-talk/2005-November/000014.html Howtos on creating plugins: # http://wiki.rubyonrails.com/rails/pages/HowToWriteAnActsAsFoxPlugin # http://www.jamis.jamisbuck.org/articles/2005/10/11/plugging-into-rails # http://lesscode.org/2005/10/27/rails-simplest-plugin-manager/ # http://wiki.rubyonrails.com/rails/pages/HowTosPlugins The result is the acts_as_ferret Mixin for ActivcRecord. Use it as follows: In any model.rb add acts_as_ferret class Foo < ActiveRecord::Base acts_as_ferret end All CRUD operations will be performed on both ActiveRecord (as usual) and a ferret index for further searching. The following method is available in your controllers: ActiveRecord::find_by_contents(query) # Query is a string representing you query The plugin follows the usual plugin structure and consists of 2 files: {RAILS_ROOT}/vendor/plugins/acts_as_ferret/init.rb {RAILS_ROOT}/vendor/plugins/acts_as_ferret/lib/acts_as_ferret.rb The Ferret DB is stored in: {RAILS_ROOT}/db/index.db Here follows the code: # CODE for init.rb require 'acts_as_ferret' # END init.rb # CODE for acts_as_ferret.rb require 'active_record' require 'ferret' module FerretMixin #(was: Foo) module Acts #:nodoc: module ARFerret #:nodoc: def self.append_features(base) super base.extend(MacroMethods) end # declare the class level helper methods # which will load the relevant instance methods defined below when invoked module MacroMethods def acts_as_ferret extend FerretMixin::Acts::ARFerret::ClassMethods class_eval do include FerretMixin::Acts::ARFerret::ClassMethods after_create :ferret_create after_update :ferret_update after_destroy :ferret_destroy end end end module ClassMethods include Ferret INDEX_DIR = "#{RAILS_ROOT}/db/index.db" def self.reloadable?; false end # Finds instances by file contents. def find_by_contents(query, options = {}) index_searcher ||= Search::IndexSearcher.new(INDEX_DIR) query_parser ||= QueryParser.new(index_searcher.reader.get_field_names.to_a) query = query_parser.parse(query) result = [] index_searcher.search_each(query) do |doc, score| id = index_searcher.reader.get_document(doc)["id"] res = self.find(id) result << res if res end index_searcher.close() result end # private def ferret_create index ||= Index::Index.new(:key => :id, :path => INDEX_DIR, :create_if_missing => true, :default_field => "*") index << self.to_doc index.optimize() index.close() end def ferret_update #code to update index index ||= Index::Index.new(:key => :id, :path => INDEX_DIR, :create_if_missing => true, :default_field => "*") index.delete(self.id.to_s) index << self.to_doc index.optimize index.close() end def ferret_destroy # code to delete from index index ||= Index::Index.new(:key => :id, :path => INDEX_DIR, :create_if_missing => true, :default_field => "*") index_writer.delete(self.id.to_s) index_writer.optimize() index_writer.close() end def to_doc # Churn through the complete Active Record and add it to the Ferret document doc = Ferret::Document::Document.new self.attributes.each_pair do |key,val| doc << Ferret::Document::Field.new(key, val.to_s, Ferret::Document::Field::Store::YES, Ferret::Document::Field::Index::TOKENIZED) end doc end end end end end # reopen ActiveRecord and include all the above to make # them available to all our models if they want it ActiveRecord::Base.class_eval do include FerretMixin::Acts::ARFerret end # END acts_as_ferret.rb |
|
|