lib/fuzzily/searchable.rb in fuzzily-0.2.4 vs lib/fuzzily/searchable.rb in fuzzily-0.3.0

- old
+ new

@@ -1,63 +1,64 @@ require 'fuzzily/trigram' +require 'ostruct' module Fuzzily module Searchable - # fuzzily_searchable <field> [, <field>...] [, <options>] - def fuzzily_searchable(*fields) - options = fields.last.kind_of?(Hash) ? fields.pop : {} - fields.each do |field| - make_field_fuzzily_searchable(field, options) + def self.included(by) + case ActiveRecord::VERSION::MAJOR + when 2 then by.extend Rails2ClassMethods + when 3 then by.extend Rails3ClassMethods + when 4 then by.extend Rails4ClassMethods end end private - def make_field_fuzzily_searchable(field, options={}) - class_variable_defined?(:"@@fuzzily_searchable_#{field}") and return + def _update_fuzzy!(_o) + self.send(_o.trigram_association).delete_all + String.new(self.send(_o.field)).scored_trigrams.each do |trigram, score| + self.send(_o.trigram_association).create!(:score => score, :trigram => trigram, :owner_type => self.class.name) + end + end - trigram_class_name = options.fetch(:class_name, 'Trigram') - trigram_association = "trigrams_for_#{field}".to_sym - update_trigrams_method = "update_fuzzy_#{field}!".to_sym - supports_bulk_inserts = - connection.class.name !~ /sqlite/i || - connection.send(:sqlite_version) >= '3.7.11' + module ClassMethods + # fuzzily_searchable <field> [, <field>...] [, <options>] + def fuzzily_searchable(*fields) + options = fields.last.kind_of?(Hash) ? fields.pop : {} - has_many trigram_association, - :class_name => trigram_class_name, - :as => :owner, - :conditions => { :fuzzy_field => field.to_s }, - :dependent => :destroy, - :autosave => true - - singleton_class.send(:define_method,"find_by_fuzzy_#{field}".to_sym) do |*args| - case args.size - when 1 then pattern = args.first ; options = {} - when 2 then pattern, options = args - else raise 'Wrong # of arguments' + fields.each do |field| + make_field_fuzzily_searchable(field, options) end + end + private + + def _find_by_fuzzy(_o, pattern, options={}) options[:limit] ||= 10 - trigram_class_name.constantize. - scoped(options). + _o.trigram_class_name.constantize. + limit(options[:limit]). for_model(self.name). - for_field(field.to_s). + for_field(_o.field.to_s). matches_for(pattern) end - singleton_class.send(:define_method,"bulk_update_fuzzy_#{field}".to_sym) do - trigram_class = trigram_class_name.constantize + def _bulk_update_fuzzy(_o) + trigram_class = _o.trigram_class_name.constantize - self.scoped(:include => trigram_association).find_in_batches(:batch_size => 100) do |batch| + supports_bulk_inserts = + connection.class.name !~ /sqlite/i || + connection.send(:sqlite_version) >= '3.7.11' + + _with_included_trigrams(_o).find_in_batches(:batch_size => 100) do |batch| inserts = [] batch.each do |record| - data = Fuzzily::String.new(record.send(field)) + data = Fuzzily::String.new(record.send(_o.field)) data.scored_trigrams.each do |trigram, score| - inserts << sanitize_sql_array(['(?,?,?,?,?)', self.name, record.id, field.to_s, score, trigram]) + inserts << sanitize_sql_array(['(?,?,?,?,?)', self.name, record.id, _o.field.to_s, score, trigram]) end end # take care of quoting c = trigram_class.connection @@ -72,11 +73,11 @@ c.quote_column_name('score'), c.quote_column_name('trigram') ] trigram_class.transaction do - batch.each { |record| record.send(trigram_association).delete_all } + batch.each { |record| record.send(_o.trigram_association).delete_all } break if inserts.empty? if supports_bulk_inserts trigram_class.connection.insert(insert_sql + inserts.join(", ")) else @@ -86,22 +87,81 @@ end end end end - define_method update_trigrams_method do - self.send(trigram_association).delete_all - String.new(self.send(field)).scored_trigrams.each do |trigram, score| - self.send(trigram_association).create!(:score => score, :trigram => trigram, :owner_type => self.class.name) + def make_field_fuzzily_searchable(field, options={}) + class_variable_defined?(:"@@fuzzily_searchable_#{field}") and return + + _o = OpenStruct.new( + :field => field, + :trigram_class_name => options.fetch(:class_name, 'Trigram'), + :trigram_association => "trigrams_for_#{field}".to_sym, + :update_trigrams_method => "update_fuzzy_#{field}!".to_sym + ) + + _add_trigram_association(_o) + + singleton_class.send(:define_method,"find_by_fuzzy_#{field}".to_sym) do |*args| + _find_by_fuzzy(_o, *args) end + + singleton_class.send(:define_method,"bulk_update_fuzzy_#{field}".to_sym) do + _bulk_update_fuzzy(_o) + end + + define_method _o.update_trigrams_method do + _update_fuzzy!(_o) + end + + after_save do |record| + next unless record.send("#{field}_changed?".to_sym) + record.send(_o.update_trigrams_method) + end + + class_variable_set(:"@@fuzzily_searchable_#{field}", true) + self end + end - after_save do |record| - next unless record.send("#{field}_changed?".to_sym) - record.send(update_trigrams_method) + module Rails2ClassMethods + include ClassMethods + + private + + def _add_trigram_association(_o) + has_many _o.trigram_association, + :class_name => _o.trigram_class_name, + :as => :owner, + :conditions => { :fuzzy_field => _o.field.to_s }, + :dependent => :destroy, + :autosave => true end - class_variable_set(:"@@fuzzily_searchable_#{field}", true) - self + def _with_included_trigrams(_o) + self.scoped(:include => _o.trigram_association) + end end + + Rails3ClassMethods = Rails2ClassMethods + + module Rails4ClassMethods + include ClassMethods + + private + + def _add_trigram_association(_o) + has_many _o.trigram_association, + lambda { where(:fuzzy_field => _o.field.to_s) }, + :class_name => _o.trigram_class_name, + :as => :owner, + :dependent => :delete_all, + :autosave => true + end + + def _with_included_trigrams(_o) + self.includes(_o.trigram_association) + end + end + end -end +end \ No newline at end of file