This repository has been archived by the owner on Sep 27, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 63
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
First working version, specs not complete
- Loading branch information
Showing
20 changed files
with
482 additions
and
130 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
--colour | ||
--format d | ||
--backtrace |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,6 @@ | ||
require "bundler/gem_tasks" | ||
require 'rspec/core/rake_task' | ||
|
||
RSpec::Core::RakeTask.new(:spec) | ||
|
||
task :default => :spec |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,10 +8,18 @@ Gem::Specification.new do |gem| | |
gem.version = Fuzzily::VERSION | ||
gem.authors = ["Julien Letessier"] | ||
gem.email = ["[email protected]"] | ||
gem.description = %q{TODO: Write a gem description} | ||
gem.summary = %q{TODO: Write a gem summary} | ||
gem.description = %q{Fast fuzzy string matching for rails} | ||
gem.summary = %q{A fast, trigram-based, database-backed fuzzy string search/match engine for Rails.} | ||
gem.homepage = "" | ||
|
||
gem.add_runtime_dependency 'activerecord', '~> 2.3' | ||
|
||
gem.add_development_dependency 'rspec' | ||
gem.add_development_dependency 'appraisal' | ||
gem.add_development_dependency 'pry' | ||
gem.add_development_dependency 'pry-nav' | ||
gem.add_development_dependency 'sqlite3' | ||
|
||
gem.files = `git ls-files`.split($/) | ||
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) } | ||
gem.test_files = gem.files.grep(%r{^(test|spec|features)/}) | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,7 @@ | ||
require "fuzzily/version" | ||
require "fuzzily/searchable" | ||
require "fuzzily/migration" | ||
require "fuzzily/model" | ||
require "active_record" | ||
|
||
module Fuzzily | ||
# Your code goes here... | ||
end | ||
ActiveRecord::Base.extend(Fuzzily::Searchable) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
require 'active_record' | ||
|
||
module Fuzzily | ||
module Migration | ||
|
||
def trigrams_table_name=(custom_name) | ||
@trigrams_table_name = custom_name | ||
end | ||
|
||
def trigrams_table_name | ||
@trigrams_table_name ||= :trigrams | ||
end | ||
|
||
def up | ||
create_table trigrams_table_name do |t| | ||
t.string :trigram, :limit => 3 | ||
t.integer :score | ||
t.integer :owner_id | ||
t.string :owner_type | ||
t.string :fuzzy_field | ||
end | ||
|
||
add_index trigrams_table_name, | ||
[:owner_type, :fuzzy_field, :trigram, :owner_id, :score], | ||
:name => :index_for_match | ||
add_index trigrams_table_name, | ||
[:owner_type, :owner_id], | ||
:name => :index_by_owner | ||
end | ||
|
||
def down | ||
drop_table trigrams_table_name | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
module Fuzzily | ||
module Model | ||
# Needs fields: trigram, owner_type, owner_id, score | ||
# Needs index on [owner_type, trigram] and [owner_type, owner_id] | ||
|
||
def self.included(by) | ||
by.ancestors.include?(ActiveRecord::Base) or raise 'Not included in an ActiveRecord subclass' | ||
by.class_eval do | ||
return if class_variable_defined?(:@@fuzzily_trigram_model) | ||
|
||
belongs_to :owner, :polymorphic => true | ||
validates_presence_of :owner | ||
validates_uniqueness_of :trigram, :scope => [:owner_type, :owner_id] | ||
validates_length_of :trigram, :is => 3 | ||
validates_presence_of :score | ||
validates_presence_of :fuzzy_field | ||
|
||
named_scope :for_model, lambda { |model| { | ||
:conditions => { :owner_type => model.kind_of?(Class) ? model.name : model } | ||
}} | ||
named_scope :for_field, lambda { |field_name| { | ||
:conditions => { :fuzzy_field => field_name } | ||
}} | ||
named_scope :with_trigram, lambda { |trigrams| { | ||
:conditions => { :trigram => trigrams } | ||
}} | ||
|
||
class_variable_set(:@@fuzzily_trigram_model, true) | ||
end | ||
|
||
by.extend(ClassMethods) | ||
end | ||
|
||
module ClassMethods | ||
# options: | ||
# - model (mandatory) | ||
# - field (mandatory) | ||
# - limit (default 10) | ||
def matches_for(text, options = {}) | ||
options[:limit] ||= 10 | ||
self. | ||
scoped(:select => 'owner_id, owner_type, SUM(score) AS score'). | ||
scoped(:group => :owner_id). | ||
scoped(:order => 'score DESC', :limit => options[:limit]). | ||
with_trigram(text.extend(String).trigrams). | ||
map(&:owner) | ||
end | ||
end | ||
end | ||
end | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
require 'fuzzily/trigram' | ||
|
||
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) | ||
end | ||
end | ||
|
||
private | ||
|
||
def make_field_fuzzily_searchable(field, options={}) | ||
class_variable_defined?(:"@@fuzzily_searchable_#{field}") and return | ||
|
||
trigram_class_name = options.fetch(:class_name, 'Trigram') | ||
trigram_association = "trigrams_for_#{field}".to_sym | ||
update_trigrams_method = "update_fuzzy_#{field}!".to_sym | ||
|
||
has_many trigram_association, | ||
:class_name => trigram_class_name, | ||
:as => :owner, | ||
:conditions => { :fuzzy_field => field.to_s }, | ||
:dependent => :destroy | ||
|
||
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' | ||
end | ||
Trigram.scoped(options).for_model(self.name).for_field(field).matches(pattern) | ||
end | ||
|
||
define_method update_trigrams_method do | ||
self.send(trigram_association).destroy_all | ||
self.send(field).extend(String).trigrams.each do |trigram| | ||
self.send(trigram_association).create!(:score => 1, :trigram => trigram) | ||
end | ||
end | ||
|
||
after_save do |record| | ||
next unless record.send("#{field}_changed?".to_sym) | ||
self.send(update_trigrams_method) | ||
end | ||
|
||
class_variable_set(:"@@fuzzily_searchable_#{field}", true) | ||
self | ||
end | ||
|
||
end | ||
end |
Oops, something went wrong.