Chrononaut-no_fuzz 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +1 -0
- data/MIT-LICENSE +20 -0
- data/Manifest +21 -0
- data/README.markdown +34 -0
- data/Rakefile +37 -0
- data/generators/no_fuzz/USAGE +5 -0
- data/generators/no_fuzz/no_fuzz_generator.rb +33 -0
- data/generators/no_fuzz/templates/migration.rb +15 -0
- data/generators/no_fuzz/templates/model.rb +2 -0
- data/init.rb +2 -0
- data/install.rb +1 -0
- data/lib/no_fuzz.rb +70 -0
- data/no_fuzz.gemspec +35 -0
- data/rails/init.rb +1 -0
- data/tasks/no_fuzz_tasks.rake +4 -0
- data/test/database.yml +21 -0
- data/test/no_fuzz_test.rb +18 -0
- data/test/schema.rb +11 -0
- data/test/test.sqlite3 +0 -0
- data/test/test_helper.rb +34 -0
- data/uninstall.rb +1 -0
- metadata +91 -0
data/CHANGELOG
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
v0.0.1 Test release
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 [name of plugin creator]
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Manifest
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
Rakefile
|
2
|
+
README.markdown
|
3
|
+
tasks/no_fuzz_tasks.rake
|
4
|
+
uninstall.rb
|
5
|
+
init.rb
|
6
|
+
generators/no_fuzz/no_fuzz_generator.rb
|
7
|
+
generators/no_fuzz/templates/model.rb
|
8
|
+
generators/no_fuzz/templates/migration.rb
|
9
|
+
generators/no_fuzz/USAGE
|
10
|
+
rails/init.rb
|
11
|
+
CHANGELOG
|
12
|
+
lib/no_fuzz.rb
|
13
|
+
MIT-LICENSE
|
14
|
+
no_fuzz.gemspec
|
15
|
+
install.rb
|
16
|
+
test/no_fuzz_test.rb
|
17
|
+
test/test_helper.rb
|
18
|
+
test/database.yml
|
19
|
+
test/schema.rb
|
20
|
+
test/test.sqlite3
|
21
|
+
Manifest
|
data/README.markdown
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# NoFuzz
|
2
|
+
|
3
|
+
Simple as can be fuzzy search. Works with any database supported by Active
|
4
|
+
Record. No dependencies.
|
5
|
+
|
6
|
+
Based on code and ideas in Steven Ruttenberg's nice blog entry "Live fuzzy
|
7
|
+
search using n-grams in Rails" [1].
|
8
|
+
|
9
|
+
Note that this family of fuzzy search techniques work best on dictionary-type
|
10
|
+
lookups, i.e not for large amounts of text.
|
11
|
+
|
12
|
+
kristian's acts_as_fuzzy_search [2] is a similar plugin, but it targets DataMapper.
|
13
|
+
|
14
|
+
1: http://unirec.blogspot.com/2007/12/live-fuzzy-search-using-n-grams-in.html
|
15
|
+
|
16
|
+
2: http://github.com/mkristian/kristians_rails_plugins/tree/master/act_as_fuzzy_search
|
17
|
+
|
18
|
+
# Basic Usage
|
19
|
+
|
20
|
+
Add the following code in the model you'd like to index:
|
21
|
+
|
22
|
+
include NoFuzz
|
23
|
+
fuzzy :field
|
24
|
+
|
25
|
+
Where field is the field used for the indexing data (you can use multiple fields
|
26
|
+
if you want).
|
27
|
+
|
28
|
+
Populate the index by running 'Model.populate_trigram_index'. Then, you can
|
29
|
+
search fuzzily with the fuzzy_find method:
|
30
|
+
|
31
|
+
Model.fuzzy_find("query")
|
32
|
+
Model.fuzzy_find("query", 10) # find maximum 10 rows
|
33
|
+
|
34
|
+
Copyright (c) 2009 Bjørn Arild Mæland, released under the MIT license
|
data/Rakefile
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require 'rubygems'
|
3
|
+
require 'rake'
|
4
|
+
require 'rake/testtask'
|
5
|
+
require 'rake/rdoctask'
|
6
|
+
#require 'spec/rake/spectask'
|
7
|
+
require 'echoe'
|
8
|
+
|
9
|
+
desc 'Default: run unit tests.'
|
10
|
+
task :default => :test
|
11
|
+
|
12
|
+
Echoe.new('no_fuzz') do |p|
|
13
|
+
p.author = "Bjørn Arild Mæland"
|
14
|
+
p.email = "bjorn.maeland@gmail.com"
|
15
|
+
p.summary = "No Fuzz"
|
16
|
+
p.url = "http://www.github.com/Chrononaut/no_fuzz/"
|
17
|
+
p.ignore_pattern = FileList[".gitignore"]
|
18
|
+
p.include_rakefile = true
|
19
|
+
end
|
20
|
+
|
21
|
+
desc 'Test the no_fuzz plugin.'
|
22
|
+
Rake::TestTask.new(:test) do |t|
|
23
|
+
t.libs << 'lib'
|
24
|
+
t.libs << 'test'
|
25
|
+
t.pattern = 'test/**/*_test.rb'
|
26
|
+
t.verbose = true
|
27
|
+
end
|
28
|
+
|
29
|
+
DOCUMENTED_FILES = FileList['README.markdown', 'MIT-LICENSE', 'lib/**/*.rb']
|
30
|
+
|
31
|
+
desc 'Generate documentation for the no_fuzz plugin.'
|
32
|
+
Rake::RDocTask.new do |rdoc|
|
33
|
+
rdoc.rdoc_dir = 'doc'
|
34
|
+
rdoc.title = "HostConnect"
|
35
|
+
rdoc.options << '--line-numbers' << '--inline-source' << '--main' << 'README.markdown'
|
36
|
+
rdoc.rdoc_files.include(*DOCUMENTED_FILES)
|
37
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
class NoFuzzGenerator < Rails::Generator::NamedBase
|
2
|
+
def manifest
|
3
|
+
record do |m|
|
4
|
+
m.template "model.rb", "app/models/#{class_name.underscore.downcase}_trigram.rb", {
|
5
|
+
:assigns => local_assigns
|
6
|
+
}
|
7
|
+
|
8
|
+
m.migration_template "migration.rb", "db/migrate", {
|
9
|
+
:assigns => local_assigns,
|
10
|
+
:migration_file_name => local_assigns[:migration_class_name].underscore.downcase
|
11
|
+
}
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
def local_custom_name
|
17
|
+
class_name.underscore.downcase
|
18
|
+
end
|
19
|
+
|
20
|
+
def gracefully_pluralize(str)
|
21
|
+
str.pluralize! if ActiveRecord::Base.pluralize_table_names
|
22
|
+
str
|
23
|
+
end
|
24
|
+
|
25
|
+
def local_assigns
|
26
|
+
returning(assigns = {}) do
|
27
|
+
assigns[:class_name] = local_custom_name.classify
|
28
|
+
assigns[:migration_class_name] = "CreateTrigramsTableFor#{assigns[:class_name]}"
|
29
|
+
assigns[:table_name] = gracefully_pluralize(local_custom_name + "_trigram")
|
30
|
+
assigns[:foreign_key] = (class_name.underscore.downcase + "_id")
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class <%= migration_name -%> < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
create_table :<%= table_name -%>, :force => true do |t|
|
4
|
+
t.integer :<%= foreign_key -%>, :null => false
|
5
|
+
t.string :tg, :length => 3, :null => false # trigrams
|
6
|
+
t.integer :score, :default => 1, :null => false
|
7
|
+
end
|
8
|
+
add_index :<%= table_name -%>, :tg
|
9
|
+
add_index :<%= table_name -%>, :<%= foreign_key %>
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.down
|
13
|
+
drop_table :<%= table_name %>
|
14
|
+
end
|
15
|
+
end
|
data/init.rb
ADDED
data/install.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
# Install hook code here
|
data/lib/no_fuzz.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
# TODO:
|
2
|
+
# - scores
|
3
|
+
# I.e fuzzy {:first_name => 1, :last_name => 2}, last_name gives double score
|
4
|
+
# Currently everything gets scored with 1
|
5
|
+
# - normalization
|
6
|
+
# - weighting of fuzzy_find results
|
7
|
+
|
8
|
+
module NoFuzz
|
9
|
+
def self.included(model)
|
10
|
+
model.extend ClassMethods
|
11
|
+
end
|
12
|
+
|
13
|
+
module ClassMethods
|
14
|
+
def self.extended(model)
|
15
|
+
@@model = model
|
16
|
+
end
|
17
|
+
|
18
|
+
def fuzzy(*fields)
|
19
|
+
# put the parameters as instance variable of the model
|
20
|
+
@@model.instance_variable_set(:@fuzzy_fields, fields)
|
21
|
+
@@model.instance_variable_set(:@fuzzy_ref_id, "#{@@model}_id".downcase)
|
22
|
+
@@model.instance_variable_set(:@fuzzy_trigram_model, "#{@@model}Trigram".constantize)
|
23
|
+
end
|
24
|
+
|
25
|
+
def populate_trigram_index
|
26
|
+
clear_trigram_index
|
27
|
+
|
28
|
+
fuzzy_ref_id = self.instance_variable_get(:@fuzzy_ref_id)
|
29
|
+
trigram_model = self.instance_variable_get(:@fuzzy_trigram_model)
|
30
|
+
fields = self.instance_variable_get(:@fuzzy_fields)
|
31
|
+
|
32
|
+
fields.each do |f|
|
33
|
+
self.all.each do |i|
|
34
|
+
word = ' ' + i.send(f)
|
35
|
+
(0..word.length-3).each do |idx|
|
36
|
+
tg = word[idx,3]
|
37
|
+
trigram_model.create(:tg => tg, fuzzy_ref_id => i.id)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
true
|
42
|
+
end
|
43
|
+
|
44
|
+
def clear_trigram_index
|
45
|
+
self.instance_variable_get(:@fuzzy_trigram_model).delete_all
|
46
|
+
end
|
47
|
+
|
48
|
+
def fuzzy_find(word, limit = 0)
|
49
|
+
fuzzy_ref_id = self.instance_variable_get(:@fuzzy_ref_id)
|
50
|
+
trigram_model = self.instance_variable_get(:@fuzzy_trigram_model)
|
51
|
+
fields = self.instance_variable_get(:@fuzzy_fields)
|
52
|
+
|
53
|
+
word = ' ' + word + ' '
|
54
|
+
trigrams = (0..word.length-3).collect { |idx| word[idx,3] }
|
55
|
+
|
56
|
+
# ordered hash of package_id => score pairs
|
57
|
+
trigram_groups = trigram_model.sum(:score, :conditions => [ "tg IN (?)", trigrams],
|
58
|
+
:group => fuzzy_ref_id.to_s)
|
59
|
+
|
60
|
+
count = 0
|
61
|
+
@res = []
|
62
|
+
trigram_groups.sort_by {|a| -a[1]}.each do |group|
|
63
|
+
@res << self.find(group[0])
|
64
|
+
count += 1
|
65
|
+
break if count == limit
|
66
|
+
end
|
67
|
+
@res
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
data/no_fuzz.gemspec
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{no_fuzz}
|
5
|
+
s.version = "0.0.1"
|
6
|
+
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
|
8
|
+
s.authors = ["Bj\303\270rn Arild M\303\246land"]
|
9
|
+
s.date = %q{2009-03-30}
|
10
|
+
s.description = %q{No Fuzz}
|
11
|
+
s.email = %q{bjorn.maeland@gmail.com}
|
12
|
+
s.extra_rdoc_files = ["README.markdown", "tasks/no_fuzz_tasks.rake", "CHANGELOG", "lib/no_fuzz.rb"]
|
13
|
+
s.files = ["Rakefile", "README.markdown", "tasks/no_fuzz_tasks.rake", "uninstall.rb", "init.rb", "generators/no_fuzz/no_fuzz_generator.rb", "generators/no_fuzz/templates/model.rb", "generators/no_fuzz/templates/migration.rb", "generators/no_fuzz/USAGE", "rails/init.rb", "CHANGELOG", "lib/no_fuzz.rb", "MIT-LICENSE", "install.rb", "test/no_fuzz_test.rb", "test/test_helper.rb", "test/database.yml", "test/schema.rb", "test/test.sqlite3", "Manifest", "no_fuzz.gemspec"]
|
14
|
+
s.has_rdoc = true
|
15
|
+
s.homepage = %q{http://www.github.com/Chrononaut/no_fuzz/}
|
16
|
+
s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "No_fuzz", "--main", "README.markdown"]
|
17
|
+
s.require_paths = ["lib"]
|
18
|
+
s.rubyforge_project = %q{no_fuzz}
|
19
|
+
s.rubygems_version = %q{1.3.1}
|
20
|
+
s.summary = %q{No Fuzz}
|
21
|
+
s.test_files = ["test/no_fuzz_test.rb", "test/test_helper.rb"]
|
22
|
+
|
23
|
+
if s.respond_to? :specification_version then
|
24
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
25
|
+
s.specification_version = 2
|
26
|
+
|
27
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
28
|
+
s.add_development_dependency(%q<echoe>, [">= 0"])
|
29
|
+
else
|
30
|
+
s.add_dependency(%q<echoe>, [">= 0"])
|
31
|
+
end
|
32
|
+
else
|
33
|
+
s.add_dependency(%q<echoe>, [">= 0"])
|
34
|
+
end
|
35
|
+
end
|
data/rails/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'no_fuzz'
|
data/test/database.yml
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
sqlite:
|
2
|
+
:adapter: sqlite
|
3
|
+
:dbfile: vendor/plugins/no_fuzz/test/test.sqlite
|
4
|
+
|
5
|
+
sqlite3:
|
6
|
+
:adapter: sqlite3
|
7
|
+
:dbfile: test/test.sqlite3
|
8
|
+
|
9
|
+
postgresql:
|
10
|
+
:adapter: postgresql
|
11
|
+
:username: postgres
|
12
|
+
:password: postgres
|
13
|
+
:database: no_fuzz_plugin_test
|
14
|
+
:min_messages: ERROR
|
15
|
+
|
16
|
+
mysql:
|
17
|
+
:adapter: mysql
|
18
|
+
:host: localhost
|
19
|
+
:username: root
|
20
|
+
:password: password
|
21
|
+
:database: no_fuzz_plugin_test
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper.rb'
|
2
|
+
|
3
|
+
class NoFuzzTest < ActiveSupport::TestCase
|
4
|
+
|
5
|
+
load_schema
|
6
|
+
|
7
|
+
class Package < ActiveRecord::Base
|
8
|
+
end
|
9
|
+
|
10
|
+
class PackageTrigram < ActiveRecord::Base
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_schema_has_loaded_correctly
|
14
|
+
assert_equal [], Package.all
|
15
|
+
assert_equal [], PackageTrigram.all
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
data/test/schema.rb
ADDED
data/test/test.sqlite3
ADDED
Binary file
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'rubygems'
|
3
|
+
require 'active_record'
|
4
|
+
require 'active_support'
|
5
|
+
require 'active_support/test_case'
|
6
|
+
|
7
|
+
def load_schema
|
8
|
+
config = YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml'))
|
9
|
+
ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + "/debug.log")
|
10
|
+
|
11
|
+
db_adapter = ENV['DB']
|
12
|
+
|
13
|
+
# no db passed, try one of these fine config-free DBs before bombing.
|
14
|
+
db_adapter ||=
|
15
|
+
begin
|
16
|
+
require 'rubygems'
|
17
|
+
require 'sqlite'
|
18
|
+
'sqlite'
|
19
|
+
rescue MissingSourceFile
|
20
|
+
begin
|
21
|
+
require 'sqlite3'
|
22
|
+
'sqlite3'
|
23
|
+
rescue MissingSourceFile
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
if db_adapter.nil?
|
28
|
+
raise "No DB Adapter selected. Pass the DB= option to pick one, or install Sqlite or Sqlite3."
|
29
|
+
end
|
30
|
+
|
31
|
+
ActiveRecord::Base.establish_connection(config[db_adapter])
|
32
|
+
#load(File.dirname(__FILE__) + "/schema.rb")
|
33
|
+
require File.dirname(__FILE__) + '/../rails/init.rb'
|
34
|
+
end
|
data/uninstall.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
# Uninstall hook code here
|
metadata
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: Chrononaut-no_fuzz
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- "Bj\xC3\xB8rn Arild M\xC3\xA6land"
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-03-30 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: echoe
|
17
|
+
type: :development
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
version:
|
25
|
+
description: No Fuzz
|
26
|
+
email: bjorn.maeland@gmail.com
|
27
|
+
executables: []
|
28
|
+
|
29
|
+
extensions: []
|
30
|
+
|
31
|
+
extra_rdoc_files:
|
32
|
+
- README.markdown
|
33
|
+
- tasks/no_fuzz_tasks.rake
|
34
|
+
- CHANGELOG
|
35
|
+
- lib/no_fuzz.rb
|
36
|
+
files:
|
37
|
+
- Rakefile
|
38
|
+
- README.markdown
|
39
|
+
- tasks/no_fuzz_tasks.rake
|
40
|
+
- uninstall.rb
|
41
|
+
- init.rb
|
42
|
+
- generators/no_fuzz/no_fuzz_generator.rb
|
43
|
+
- generators/no_fuzz/templates/model.rb
|
44
|
+
- generators/no_fuzz/templates/migration.rb
|
45
|
+
- generators/no_fuzz/USAGE
|
46
|
+
- rails/init.rb
|
47
|
+
- CHANGELOG
|
48
|
+
- lib/no_fuzz.rb
|
49
|
+
- MIT-LICENSE
|
50
|
+
- install.rb
|
51
|
+
- test/no_fuzz_test.rb
|
52
|
+
- test/test_helper.rb
|
53
|
+
- test/database.yml
|
54
|
+
- test/schema.rb
|
55
|
+
- test/test.sqlite3
|
56
|
+
- Manifest
|
57
|
+
- no_fuzz.gemspec
|
58
|
+
has_rdoc: true
|
59
|
+
homepage: http://www.github.com/Chrononaut/no_fuzz/
|
60
|
+
post_install_message:
|
61
|
+
rdoc_options:
|
62
|
+
- --line-numbers
|
63
|
+
- --inline-source
|
64
|
+
- --title
|
65
|
+
- No_fuzz
|
66
|
+
- --main
|
67
|
+
- README.markdown
|
68
|
+
require_paths:
|
69
|
+
- lib
|
70
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: "0"
|
75
|
+
version:
|
76
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
77
|
+
requirements:
|
78
|
+
- - ">="
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: "1.2"
|
81
|
+
version:
|
82
|
+
requirements: []
|
83
|
+
|
84
|
+
rubyforge_project: no_fuzz
|
85
|
+
rubygems_version: 1.2.0
|
86
|
+
signing_key:
|
87
|
+
specification_version: 2
|
88
|
+
summary: No Fuzz
|
89
|
+
test_files:
|
90
|
+
- test/no_fuzz_test.rb
|
91
|
+
- test/test_helper.rb
|