fuzzily 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --colour
2
+ --format d
3
+ --backtrace
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in fuzzily.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Julien Letessier
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,98 @@
1
+ # Fuzzily
2
+
3
+ A fast, [trigram](http://en.wikipedia.org/wiki/N-gram)-based, database-backed [fuzzy](http://en.wikipedia.org/wiki/Approximate_string_matching) string search/match engine for Rails.
4
+
5
+ Loosely inspired from an [old blog post](http://unirec.blogspot.co.uk/2007/12/live-fuzzy-search-using-n-grams-in.html).
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ gem 'fuzzily'
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install fuzzily
20
+
21
+ ## Usage
22
+
23
+ You'll need to setup 2 things:
24
+
25
+ - a trigram model (your search index) and its migration
26
+ - the model you want to search for
27
+
28
+ Create and ActiveRecord model in your app (this will be used to store a "fuzzy index" of all the models and fields you will be indexing):
29
+
30
+ class Trigram < ActiveRecord::Base
31
+ include Fuzzily::Model
32
+ end
33
+
34
+ Create a migration for it:
35
+
36
+ class AddTrigramsModel < ActiveRecord::Migration
37
+ extend Fuzzily::Migration
38
+ end
39
+
40
+ Instrument your model (your searchable fields do not have to be stored, they can be dynamic methods too):
41
+
42
+ class MyStuff < ActiveRecord::Base
43
+ # assuming my_stuffs has a 'name' attribute
44
+ fuzzily_searchable :name
45
+ end
46
+
47
+ Index your model (will happen automatically for new/updated records):
48
+
49
+ MyStuff.find_each do |record|
50
+ record.update_fuzzy_name!
51
+ end
52
+
53
+ Search!
54
+
55
+ MyStuff.find_by_fuzzy_name('Some Name', :limit => 10)
56
+ # => records
57
+
58
+
59
+
60
+ ## Indexing more than one field
61
+
62
+ Just list all the field you want to index, or call `fuzzily_searchable` more than once:
63
+
64
+ class MyStuff < ActiveRecord::Base
65
+ fuzzily_searchable :name_fr, :name_en
66
+ fuzzily_searchable :name_de
67
+ end
68
+
69
+
70
+ ## Custom name for the index model
71
+
72
+ If you want or need to name your index model differently (e.g. because you already have a class called `Trigram`):
73
+
74
+ class CustomTrigram < ActiveRecord::Base
75
+ include Fuzzily::Model
76
+ end
77
+
78
+ class AddTrigramsModel < ActiveRecord::Migration
79
+ extend Fuzzily::Migration
80
+ trigrams_table_name = :custom_trigrams
81
+ end
82
+
83
+ class MyStuff < ActiveRecord::Base
84
+ fuzzily_searchable :name, :class_name => 'CustomTrigram'
85
+ end
86
+
87
+
88
+ ## License
89
+
90
+ MIT licence. Quite permissive if you ask me.
91
+
92
+ ## Contributing
93
+
94
+ 1. Fork it
95
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
96
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
97
+ 4. Push to the branch (`git push origin my-new-feature`)
98
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/fuzzily.gemspec ADDED
@@ -0,0 +1,27 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'fuzzily/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "fuzzily"
8
+ gem.version = Fuzzily::VERSION
9
+ gem.authors = ["Julien Letessier"]
10
+ gem.email = ["julien.letessier@gmail.com"]
11
+ gem.description = %q{Fast fuzzy string matching for rails}
12
+ gem.summary = %q{A fast, trigram-based, database-backed fuzzy string search/match engine for Rails.}
13
+ gem.homepage = ""
14
+
15
+ gem.add_runtime_dependency 'activerecord', '~> 2.3'
16
+
17
+ gem.add_development_dependency 'rspec'
18
+ gem.add_development_dependency 'appraisal'
19
+ gem.add_development_dependency 'pry'
20
+ gem.add_development_dependency 'pry-nav'
21
+ gem.add_development_dependency 'sqlite3'
22
+
23
+ gem.files = `git ls-files`.split($/)
24
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
25
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
26
+ gem.require_paths = ["lib"]
27
+ end
@@ -0,0 +1,35 @@
1
+ require 'active_record'
2
+
3
+ module Fuzzily
4
+ module Migration
5
+
6
+ def trigrams_table_name=(custom_name)
7
+ @trigrams_table_name = custom_name
8
+ end
9
+
10
+ def trigrams_table_name
11
+ @trigrams_table_name ||= :trigrams
12
+ end
13
+
14
+ def up
15
+ create_table trigrams_table_name do |t|
16
+ t.string :trigram, :limit => 3
17
+ t.integer :score
18
+ t.integer :owner_id
19
+ t.string :owner_type
20
+ t.string :fuzzy_field
21
+ end
22
+
23
+ add_index trigrams_table_name,
24
+ [:owner_type, :fuzzy_field, :trigram, :owner_id, :score],
25
+ :name => :index_for_match
26
+ add_index trigrams_table_name,
27
+ [:owner_type, :owner_id],
28
+ :name => :index_by_owner
29
+ end
30
+
31
+ def down
32
+ drop_table trigrams_table_name
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,51 @@
1
+ module Fuzzily
2
+ module Model
3
+ # Needs fields: trigram, owner_type, owner_id, score
4
+ # Needs index on [owner_type, trigram] and [owner_type, owner_id]
5
+
6
+ def self.included(by)
7
+ by.ancestors.include?(ActiveRecord::Base) or raise 'Not included in an ActiveRecord subclass'
8
+ by.class_eval do
9
+ return if class_variable_defined?(:@@fuzzily_trigram_model)
10
+
11
+ belongs_to :owner, :polymorphic => true
12
+ validates_presence_of :owner
13
+ validates_uniqueness_of :trigram, :scope => [:owner_type, :owner_id]
14
+ validates_length_of :trigram, :is => 3
15
+ validates_presence_of :score
16
+ validates_presence_of :fuzzy_field
17
+
18
+ named_scope :for_model, lambda { |model| {
19
+ :conditions => { :owner_type => model.kind_of?(Class) ? model.name : model }
20
+ }}
21
+ named_scope :for_field, lambda { |field_name| {
22
+ :conditions => { :fuzzy_field => field_name }
23
+ }}
24
+ named_scope :with_trigram, lambda { |trigrams| {
25
+ :conditions => { :trigram => trigrams }
26
+ }}
27
+
28
+ class_variable_set(:@@fuzzily_trigram_model, true)
29
+ end
30
+
31
+ by.extend(ClassMethods)
32
+ end
33
+
34
+ module ClassMethods
35
+ # options:
36
+ # - model (mandatory)
37
+ # - field (mandatory)
38
+ # - limit (default 10)
39
+ def matches_for(text, options = {})
40
+ options[:limit] ||= 10
41
+ self.
42
+ scoped(:select => 'owner_id, owner_type, SUM(score) AS score').
43
+ scoped(:group => :owner_id).
44
+ scoped(:order => 'score DESC', :limit => options[:limit]).
45
+ with_trigram(text.extend(String).trigrams).
46
+ map(&:owner)
47
+ end
48
+ end
49
+ end
50
+ end
51
+
@@ -0,0 +1,55 @@
1
+ require 'fuzzily/trigram'
2
+
3
+ module Fuzzily
4
+ module Searchable
5
+ # fuzzily_searchable <field> [, <field>...] [, <options>]
6
+ def fuzzily_searchable(*fields)
7
+ options = fields.last.kind_of?(Hash) ? fields.pop : {}
8
+
9
+ fields.each do |field|
10
+ make_field_fuzzily_searchable(field, options)
11
+ end
12
+ end
13
+
14
+ private
15
+
16
+ def make_field_fuzzily_searchable(field, options={})
17
+ class_variable_defined?(:"@@fuzzily_searchable_#{field}") and return
18
+
19
+ trigram_class_name = options.fetch(:class_name, 'Trigram')
20
+ trigram_association = "trigrams_for_#{field}".to_sym
21
+ update_trigrams_method = "update_fuzzy_#{field}!".to_sym
22
+
23
+ has_many trigram_association,
24
+ :class_name => trigram_class_name,
25
+ :as => :owner,
26
+ :conditions => { :fuzzy_field => field.to_s },
27
+ :dependent => :destroy
28
+
29
+ singleton_class.send(:define_method,"find_by_fuzzy_#{field}".to_sym) do |*args|
30
+ case args.size
31
+ when 1 then pattern = args.first ; options = {}
32
+ when 2 then pattern, options = args
33
+ else raise 'Wrong # of arguments'
34
+ end
35
+ Trigram.scoped(options).for_model(self.name).for_field(field).matches(pattern)
36
+ end
37
+
38
+ define_method update_trigrams_method do
39
+ self.send(trigram_association).destroy_all
40
+ self.send(field).extend(String).trigrams.each do |trigram|
41
+ self.send(trigram_association).create!(:score => 1, :trigram => trigram)
42
+ end
43
+ end
44
+
45
+ after_save do |record|
46
+ next unless record.send("#{field}_changed?".to_sym)
47
+ record.send(update_trigrams_method)
48
+ end
49
+
50
+ class_variable_set(:"@@fuzzily_searchable_#{field}", true)
51
+ self
52
+ end
53
+
54
+ end
55
+ end
@@ -0,0 +1,25 @@
1
+ require 'iconv'
2
+
3
+ module Fuzzily
4
+ module String
5
+ def trigrams
6
+ normalized_words.map do |word|
7
+ (0..(word.length - 3)).map { |index| word[index,3] }
8
+ end.flatten.uniq
9
+ end
10
+
11
+ private
12
+
13
+ # Remove accents, downcase, replace spaces and word start with '*',
14
+ # return list of normalized words
15
+ def normalized_words
16
+ self.split(/\s+/).map { |word|
17
+ Iconv.iconv('ascii//translit//ignore', 'utf-8', word).first.downcase.gsub(/\W/,'')
18
+ }.
19
+ delete_if(&:empty?).
20
+ map { |word|
21
+ "**#{word}"
22
+ }
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,3 @@
1
+ module Fuzzily
2
+ VERSION = "0.0.1"
3
+ end
data/lib/fuzzily.rb ADDED
@@ -0,0 +1,7 @@
1
+ require "fuzzily/version"
2
+ require "fuzzily/searchable"
3
+ require "fuzzily/migration"
4
+ require "fuzzily/model"
5
+ require "active_record"
6
+
7
+ ActiveRecord::Base.extend(Fuzzily::Searchable)
@@ -0,0 +1,33 @@
1
+ require 'spec_helper'
2
+
3
+ describe Fuzzily::Migration do
4
+ subject { Class.new(ActiveRecord::Migration).extend(described_class) }
5
+
6
+ it 'is a proper migration' do
7
+ subject.ancestors.should include(ActiveRecord::Migration)
8
+ end
9
+
10
+ it 'applies cleanly' do
11
+ silence_stream(STDOUT) { subject.up }
12
+ end
13
+
14
+ it 'rolls back cleanly' do
15
+ silence_stream(STDOUT) { subject.up ; subject.down }
16
+ end
17
+
18
+ it 'has a customizable table name' do
19
+ subject.trigrams_table_name = :foobars
20
+ silence_stream(STDOUT) { subject.up }
21
+ expect {
22
+ ActiveRecord::Base.connection.execute('INSERT INTO `foobars` (score) VALUES (1)')
23
+ }.to_not raise_error
24
+ end
25
+
26
+ it 'results in a functional model' do
27
+ silence_stream(STDOUT) { subject.up }
28
+ model_class = Class.new(ActiveRecord::Base)
29
+ model_class.table_name = 'trigrams'
30
+ model_class.create(:trigram => 'abc')
31
+ model_class.count.should == 1
32
+ end
33
+ end
@@ -0,0 +1,79 @@
1
+ require 'spec_helper'
2
+
3
+ describe Fuzzily::Model do
4
+ subject do
5
+ Class.new(ActiveRecord::Base).tap do |model|
6
+ model.table_name = :trigrams
7
+ end
8
+ end
9
+
10
+ before(:each) { prepare_trigrams_table }
11
+
12
+ it 'can be included into an ActiveRecord model' do
13
+ subject.send(:include, described_class)
14
+ end
15
+
16
+ it 'can be included twice' do
17
+ subject.send(:include, described_class)
18
+ subject.send(:include, described_class)
19
+ end
20
+
21
+ context '(derived model instance)' do
22
+ before { prepare_owners_table }
23
+ let(:model) { subject.send(:include, described_class) }
24
+
25
+ it 'belongs to an owner' do
26
+ model.new.should respond_to(:owner)
27
+ end
28
+
29
+ describe '.create' do
30
+ it 'can create instances' do
31
+ model.create(:owner => Stuff.create, :score => 1, :trigram => 'abc', :fuzzy_field => :name)
32
+ end
33
+ end
34
+
35
+ describe '.matches_for' do
36
+ before do
37
+ @paris = Stuff.create(:name => 'Paris')
38
+ %w(**p *pa par ari ris).each do |trigram|
39
+ model.create(:owner => @paris, :score => 1, :fuzzy_field => :name, :trigram => trigram)
40
+ end
41
+ end
42
+
43
+ it 'finds matches' do
44
+ model.matches_for('Paris').should == [@paris]
45
+ end
46
+
47
+ it 'finds close matches' do
48
+ model.matches_for('Piriss').should == [@paris]
49
+ end
50
+
51
+ it 'does not confuse fields' do
52
+ model.for_field(:name).matches_for('Paris').should == [@paris]
53
+ model.for_field(:data).matches_for('Paris').should be_empty
54
+ end
55
+
56
+ it 'does not confuse owner types' do
57
+ model.for_model(Stuff).matches_for('Paris').should == [@paris]
58
+ model.for_model(Object).matches_for('Paris').should be_empty
59
+ end
60
+
61
+ context '(with more than one entry)' do
62
+ before do
63
+ @palma = Stuff.create(:name => 'Palma')
64
+ %w(**p *pa pal alm lma).each do |trigram|
65
+ model.create(:owner => @palma, :score => 1, :fuzzy_field => :name, :trigram => trigram)
66
+ end
67
+ end
68
+
69
+ it 'honors the limit option' do
70
+ model.matches_for('Palmyre', :limit => 1).should == [@palma]
71
+ end
72
+
73
+ it 'returns ordered results' do
74
+ model.matches_for('Palmyre').should == [@palma, @paris]
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,72 @@
1
+ require 'spec_helper'
2
+
3
+ describe Fuzzily::Searchable do
4
+ # Prepare ourselves a Trigram repository
5
+ class Trigram < ActiveRecord::Base
6
+ include Fuzzily::Model
7
+ end
8
+
9
+ before(:each) { prepare_trigrams_table }
10
+ before(:each) { prepare_owners_table }
11
+
12
+ subject do
13
+ Stuff.clone.class_eval do
14
+ def self.name ; 'Stuff' ; end
15
+ self
16
+ end
17
+ end
18
+
19
+ describe '.fuzzily_searchable' do
20
+ it 'is available to all of ActiveRecord' do
21
+ subject.should respond_to(:fuzzily_searchable)
22
+ end
23
+
24
+ it 'adds a find_by_fuzzy_<field> method' do
25
+ subject.fuzzily_searchable :name
26
+ subject.should respond_to(:find_by_fuzzy_name)
27
+ end
28
+
29
+ it 'is idempotent' do
30
+ subject.fuzzily_searchable :name
31
+ subject.fuzzily_searchable :name
32
+ subject.should respond_to(:find_by_fuzzy_name)
33
+ end
34
+
35
+ it 'creates the trigrams_for_<field> association' do
36
+ subject.fuzzily_searchable :name
37
+ subject.new.should respond_to(:trigrams_for_name)
38
+ end
39
+ end
40
+
41
+ describe '(callbacks)' do
42
+ it 'generates trigram records on creation' do
43
+ subject.fuzzily_searchable :name
44
+ subject.create(:name => 'Paris')
45
+ subject.last.trigrams_for_name.should_not be_empty
46
+ end
47
+
48
+ it 'generates the correct trigrams' do
49
+ subject.fuzzily_searchable :name
50
+ record = subject.create(:name => 'FOO')
51
+ Trigram.first.trigram.should == '**f'
52
+ Trigram.first.owner_id.should == record.id
53
+ Trigram.first.owner_type.should == 'Stuff'
54
+ end
55
+
56
+ it 'updates all trigram records on save' do
57
+ subject.fuzzily_searchable :name
58
+ subject.create(:name => 'Paris')
59
+ subject.first.update_attribute :name, 'Rome'
60
+ Trigram.all.map(&:trigram).should =~ %w(**r *ro rom ome)
61
+ end
62
+ end
63
+
64
+ describe '#find_by_fuzzy_<field>' do
65
+ it 'works'
66
+ end
67
+
68
+ describe '#update_fuzzy_<field>!' do
69
+ it 'works'
70
+ end
71
+
72
+ end
@@ -0,0 +1,8 @@
1
+ require 'spec_helper'
2
+
3
+ describe Fuzzily::String do
4
+ it 'splits strings into trigrams'
5
+ it 'removes accents'
6
+ it 'removes symbols'
7
+ it 'handles multi word strings'
8
+ end
data/spec/meta_spec.rb ADDED
@@ -0,0 +1,8 @@
1
+ require 'spec_helper'
2
+ # This tests our RSpec setup works
3
+
4
+ describe 'Test suite' do
5
+ it 'has a working ActiveRecord connection' do
6
+ ActiveRecord::Base.connection.execute('SELECT * FROM `sqlite_master`')
7
+ end
8
+ end
@@ -0,0 +1,48 @@
1
+ require 'fuzzily'
2
+ require 'pathname'
3
+ require 'yaml'
4
+
5
+ Database = Pathname.new 'test.sqlite3'
6
+
7
+ # A test model we'll need as a source of trigrams
8
+ class Stuff < ActiveRecord::Base ; end
9
+ class StuffMigration < ActiveRecord::Migration
10
+ def self.up
11
+ create_table :stuffs do |t|
12
+ t.string :name
13
+ t.string :data
14
+ t.timestamps
15
+ end
16
+ end
17
+
18
+ def self.down
19
+ drop_table :stuffs
20
+ end
21
+ end
22
+
23
+ RSpec.configure do |config|
24
+ config.before(:each) do
25
+ # Setup test database
26
+ ActiveRecord::Base.establish_connection(
27
+ :adapter => 'sqlite3',
28
+ :database => Database.to_s
29
+ )
30
+
31
+ def prepare_trigrams_table
32
+ silence_stream(STDOUT) do
33
+ Class.new(ActiveRecord::Migration).extend(Fuzzily::Migration).up
34
+ end
35
+ end
36
+
37
+ def prepare_owners_table
38
+ silence_stream(STDOUT) do
39
+ StuffMigration.up
40
+ end
41
+ end
42
+
43
+ end
44
+
45
+ config.after(:each) do
46
+ Database.delete if Database.exist?
47
+ end
48
+ end
metadata ADDED
@@ -0,0 +1,175 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fuzzily
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Julien Letessier
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2012-10-25 00:00:00 +01:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ requirement: &id001 !ruby/object:Gem::Requirement
23
+ none: false
24
+ requirements:
25
+ - - ~>
26
+ - !ruby/object:Gem::Version
27
+ hash: 5
28
+ segments:
29
+ - 2
30
+ - 3
31
+ version: "2.3"
32
+ prerelease: false
33
+ name: activerecord
34
+ type: :runtime
35
+ version_requirements: *id001
36
+ - !ruby/object:Gem::Dependency
37
+ requirement: &id002 !ruby/object:Gem::Requirement
38
+ none: false
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ hash: 3
43
+ segments:
44
+ - 0
45
+ version: "0"
46
+ prerelease: false
47
+ name: rspec
48
+ type: :development
49
+ version_requirements: *id002
50
+ - !ruby/object:Gem::Dependency
51
+ requirement: &id003 !ruby/object:Gem::Requirement
52
+ none: false
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ hash: 3
57
+ segments:
58
+ - 0
59
+ version: "0"
60
+ prerelease: false
61
+ name: appraisal
62
+ type: :development
63
+ version_requirements: *id003
64
+ - !ruby/object:Gem::Dependency
65
+ requirement: &id004 !ruby/object:Gem::Requirement
66
+ none: false
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ hash: 3
71
+ segments:
72
+ - 0
73
+ version: "0"
74
+ prerelease: false
75
+ name: pry
76
+ type: :development
77
+ version_requirements: *id004
78
+ - !ruby/object:Gem::Dependency
79
+ requirement: &id005 !ruby/object:Gem::Requirement
80
+ none: false
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ hash: 3
85
+ segments:
86
+ - 0
87
+ version: "0"
88
+ prerelease: false
89
+ name: pry-nav
90
+ type: :development
91
+ version_requirements: *id005
92
+ - !ruby/object:Gem::Dependency
93
+ requirement: &id006 !ruby/object:Gem::Requirement
94
+ none: false
95
+ requirements:
96
+ - - ">="
97
+ - !ruby/object:Gem::Version
98
+ hash: 3
99
+ segments:
100
+ - 0
101
+ version: "0"
102
+ prerelease: false
103
+ name: sqlite3
104
+ type: :development
105
+ version_requirements: *id006
106
+ description: Fast fuzzy string matching for rails
107
+ email:
108
+ - julien.letessier@gmail.com
109
+ executables: []
110
+
111
+ extensions: []
112
+
113
+ extra_rdoc_files: []
114
+
115
+ files:
116
+ - .gitignore
117
+ - .rspec
118
+ - Gemfile
119
+ - LICENSE.txt
120
+ - README.md
121
+ - Rakefile
122
+ - fuzzily.gemspec
123
+ - lib/fuzzily.rb
124
+ - lib/fuzzily/migration.rb
125
+ - lib/fuzzily/model.rb
126
+ - lib/fuzzily/searchable.rb
127
+ - lib/fuzzily/trigram.rb
128
+ - lib/fuzzily/version.rb
129
+ - spec/fuzzily/migration_spec.rb
130
+ - spec/fuzzily/model_spec.rb
131
+ - spec/fuzzily/searchable_spec.rb
132
+ - spec/fuzzily/trigram_spec.rb
133
+ - spec/meta_spec.rb
134
+ - spec/spec_helper.rb
135
+ has_rdoc: true
136
+ homepage: ""
137
+ licenses: []
138
+
139
+ post_install_message:
140
+ rdoc_options: []
141
+
142
+ require_paths:
143
+ - lib
144
+ required_ruby_version: !ruby/object:Gem::Requirement
145
+ none: false
146
+ requirements:
147
+ - - ">="
148
+ - !ruby/object:Gem::Version
149
+ hash: 3
150
+ segments:
151
+ - 0
152
+ version: "0"
153
+ required_rubygems_version: !ruby/object:Gem::Requirement
154
+ none: false
155
+ requirements:
156
+ - - ">="
157
+ - !ruby/object:Gem::Version
158
+ hash: 3
159
+ segments:
160
+ - 0
161
+ version: "0"
162
+ requirements: []
163
+
164
+ rubyforge_project:
165
+ rubygems_version: 1.3.9.5
166
+ signing_key:
167
+ specification_version: 3
168
+ summary: A fast, trigram-based, database-backed fuzzy string search/match engine for Rails.
169
+ test_files:
170
+ - spec/fuzzily/migration_spec.rb
171
+ - spec/fuzzily/model_spec.rb
172
+ - spec/fuzzily/searchable_spec.rb
173
+ - spec/fuzzily/trigram_spec.rb
174
+ - spec/meta_spec.rb
175
+ - spec/spec_helper.rb