module MongoSearch class KeywordsExtractor def self.extract(text) if text.blank? [] else text.mb_chars.normalize(:kd).to_s .gsub(/[^\x00-\x7F]/,'') .downcase .split(/[\s\.\-_]+/) end end end module Searchable extend ActiveSupport::Concern included do cattr_accessor :search_fields, :match end module ClassMethods def text_search_in(*args) options = args.pop if args.last.has_key?(:match) self.match = options[:match] rescue NoMethodError ensure self.match ||= :all self.search_fields = args field :_keywords, type: Array index({ _keywords: 1 }, { background: true }) before_save :set_keywords end def search_in(*args) warn('[DEPRECATION `search_in` is deprecated. Please use `text_search_in` instead.') text_search_in(args) end def text_search(query) words = KeywordsExtractor.extract(query).map { |word| /#{word}/ } self.send("#{self.match}_in", _keywords: words) end def search(query) warn("[DEPRECATION] `search` is deprecated. Please use `text_search` instead.") text_search(query) end end private def set_keywords self._keywords = [] search_fields.each do |field| if field.respond_to?(:each) field.each do |name, attribute| extract_keywords_for_association(name, attribute) end elsif respond_to?(field) extract_keywords(send(field)) else extract_keywords(self[field]) end end self._keywords.uniq! end def extract_keywords(text) self._keywords += KeywordsExtractor.extract(text) unless text.nil? end def extract_keywords_for_association(name, attribute) association = send(name) return [] if association.nil? if association.respond_to?(:each) association.each { |doc| extract_keywords(doc[attribute]) } else extract_keywords(association[attribute]) end end end end