fuzzily 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/.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