fuzzily 0.2.4 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +8 -2
- data/Appraisals +4 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +17 -16
- data/README.md +5 -2
- data/gemfiles/rails23.gemfile.lock +3 -3
- data/gemfiles/rails30.gemfile.lock +1 -1
- data/gemfiles/rails31.gemfile.lock +1 -1
- data/gemfiles/rails32.gemfile.lock +1 -1
- data/gemfiles/rails32_mysql.gemfile.lock +1 -1
- data/gemfiles/rails32_pg.gemfile.lock +1 -1
- data/gemfiles/rails40.gemfile +7 -0
- data/gemfiles/rails40.gemfile.lock +89 -0
- data/lib/fuzzily.rb +1 -1
- data/lib/fuzzily/model.rb +54 -21
- data/lib/fuzzily/searchable.rb +107 -47
- data/lib/fuzzily/version.rb +1 -1
- data/spec/fuzzily/searchable_spec.rb +27 -16
- data/spec/spec_helper.rb +1 -1
- metadata +3 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c6b37d6f8b23347b04d0efb6d069c41654def4e5
|
4
|
+
data.tar.gz: 30c2b6d4362466e5cb63a9ae14496bd92e26ac54
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b65ff29d86fb3822ee426f1f7731d4f254f5cc180c5b4b0cf8f5489d2c492d02341ca3747a349f60291bb035783d617d333a72c55c7e80e794b5198860423348
|
7
|
+
data.tar.gz: f2993001f8f9189101644ff8ce52d243832e2479f6cc1b567632d6f2d7d2ffe4c6be5cef87a9082eaf62bd50fe482907065dec22a24e561244a8a118e989a4d6
|
data/.travis.yml
CHANGED
@@ -10,14 +10,20 @@ gemfile:
|
|
10
10
|
- gemfiles/rails32.gemfile
|
11
11
|
- gemfiles/rails32_pg.gemfile
|
12
12
|
- gemfiles/rails32_mysql.gemfile
|
13
|
+
- gemfiles/rails40.gemfile
|
13
14
|
before_install: bundle install
|
14
15
|
bundler_args:
|
15
16
|
matrix:
|
16
17
|
exclude:
|
17
18
|
- rvm: 2.0.0
|
18
19
|
gemfile: gemfiles/rails23.gemfile
|
19
|
-
|
20
|
+
- rvm: 1.8.7
|
21
|
+
gemfile: gemfiles/rails40.gemfile
|
22
|
+
- rvm: 1.9.2
|
23
|
+
gemfile: gemfiles/rails40.gemfile
|
20
24
|
before_script:
|
21
25
|
- psql -c 'create database fuzzily_test;' -U postgres
|
22
26
|
- mysql -e 'create database fuzzily_test;'
|
23
|
-
env:
|
27
|
+
env:
|
28
|
+
global:
|
29
|
+
- TRAVIS=TRUE
|
data/Appraisals
CHANGED
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
fuzzily (0.
|
4
|
+
fuzzily (0.3.0)
|
5
5
|
activerecord (>= 2.3.17)
|
6
6
|
|
7
7
|
GEM
|
8
|
-
remote:
|
8
|
+
remote: https://rubygems.org/
|
9
9
|
specs:
|
10
10
|
activemodel (3.2.14)
|
11
11
|
activesupport (= 3.2.14)
|
@@ -33,12 +33,12 @@ GEM
|
|
33
33
|
thor
|
34
34
|
diff-lcs (1.2.4)
|
35
35
|
i18n (0.6.5)
|
36
|
-
method_source (0.8.
|
37
|
-
mime-types (1.
|
36
|
+
method_source (0.8.2)
|
37
|
+
mime-types (1.25)
|
38
38
|
multi_json (1.7.9)
|
39
|
-
mysql2 (0.3.
|
40
|
-
pg (0.
|
41
|
-
pry (0.9.12.
|
39
|
+
mysql2 (0.3.13)
|
40
|
+
pg (0.16.0)
|
41
|
+
pry (0.9.12.2)
|
42
42
|
coderay (~> 1.0.5)
|
43
43
|
method_source (~> 0.8)
|
44
44
|
slop (~> 3.4)
|
@@ -47,20 +47,20 @@ GEM
|
|
47
47
|
rake (10.1.0)
|
48
48
|
rest-client (1.6.7)
|
49
49
|
mime-types (>= 1.16)
|
50
|
-
rspec (2.
|
51
|
-
rspec-core (~> 2.
|
52
|
-
rspec-expectations (~> 2.
|
53
|
-
rspec-mocks (~> 2.
|
54
|
-
rspec-core (2.
|
55
|
-
rspec-expectations (2.
|
50
|
+
rspec (2.14.1)
|
51
|
+
rspec-core (~> 2.14.0)
|
52
|
+
rspec-expectations (~> 2.14.0)
|
53
|
+
rspec-mocks (~> 2.14.0)
|
54
|
+
rspec-core (2.14.5)
|
55
|
+
rspec-expectations (2.14.2)
|
56
56
|
diff-lcs (>= 1.1.3, < 2.0)
|
57
|
-
rspec-mocks (2.
|
57
|
+
rspec-mocks (2.14.3)
|
58
58
|
simplecov (0.7.1)
|
59
59
|
multi_json (~> 1.0)
|
60
60
|
simplecov-html (~> 0.7.1)
|
61
61
|
simplecov-html (0.7.1)
|
62
|
-
slop (3.4.
|
63
|
-
sqlite3 (1.3.
|
62
|
+
slop (3.4.6)
|
63
|
+
sqlite3 (1.3.8)
|
64
64
|
thor (0.18.1)
|
65
65
|
tzinfo (0.3.37)
|
66
66
|
|
@@ -68,6 +68,7 @@ PLATFORMS
|
|
68
68
|
ruby
|
69
69
|
|
70
70
|
DEPENDENCIES
|
71
|
+
activerecord (~> 3.2.1)
|
71
72
|
appraisal
|
72
73
|
coveralls
|
73
74
|
fuzzily!
|
data/README.md
CHANGED
@@ -15,7 +15,7 @@ Blurrily finds misspelled, prefix, or partial needles in a haystack of
|
|
15
15
|
strings. It's 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.
|
16
16
|
Loosely inspired from an [old blog post](http://unirec.blogspot.co.uk/2007/12/live-fuzzy-search-using-n-grams-in.html).
|
17
17
|
|
18
|
-
Tested with ActiveRecord (2.3, 3.0, 3.1, 3.2) on various Rubies (1.8.7, 1.9.2, 1.9.3, 2.0.0) and the most common adapters (SQLite3, MySQL, and PostgreSQL).
|
18
|
+
Tested with ActiveRecord (2.3, 3.0, 3.1, 3.2, 4.0) on various Rubies (1.8.7, 1.9.2, 1.9.3, 2.0.0) and the most common adapters (SQLite3, MySQL, and PostgreSQL).
|
19
19
|
|
20
20
|
If your dateset is big, if you need yet more speed, or do not use ActiveRecord,
|
21
21
|
check out [blurrily](http://github.com/mezis/blurrily), another gem (backed with a C extension)
|
@@ -71,7 +71,9 @@ Search!
|
|
71
71
|
MyStuff.find_by_fuzzy_name('Some Name', :limit => 10)
|
72
72
|
# => records
|
73
73
|
|
74
|
+
You can force an update on a specific record with
|
74
75
|
|
76
|
+
MyStuff.find(123).update_fuzzy_name!
|
75
77
|
|
76
78
|
## Indexing more than one field
|
77
79
|
|
@@ -131,4 +133,5 @@ Copyright (c) 2013 HouseTrip Ltd.
|
|
131
133
|
5. Create a new Pull Request
|
132
134
|
|
133
135
|
|
134
|
-
Thanks to @bclennox
|
136
|
+
Thanks to @bclennox, @fdegiuli, @nickbender, @Shanison for pointing out
|
137
|
+
and/or helping on various issues.
|
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: /Users/mezis/Dropbox/Development/fuzzily
|
3
3
|
specs:
|
4
|
-
fuzzily (0.
|
4
|
+
fuzzily (0.3.0)
|
5
5
|
activerecord (>= 2.3.17)
|
6
6
|
|
7
7
|
GEM
|
@@ -48,8 +48,8 @@ GEM
|
|
48
48
|
multi_json (~> 1.0)
|
49
49
|
simplecov-html (~> 0.7.1)
|
50
50
|
simplecov-html (0.7.1)
|
51
|
-
slop (3.4.
|
52
|
-
sqlite3 (1.3.
|
51
|
+
slop (3.4.6)
|
52
|
+
sqlite3 (1.3.8)
|
53
53
|
thor (0.18.1)
|
54
54
|
|
55
55
|
PLATFORMS
|
@@ -0,0 +1,89 @@
|
|
1
|
+
PATH
|
2
|
+
remote: /Users/mezis/Dropbox/Development/fuzzily
|
3
|
+
specs:
|
4
|
+
fuzzily (0.3.0)
|
5
|
+
activerecord (>= 2.3.17)
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
activemodel (4.0.0)
|
11
|
+
activesupport (= 4.0.0)
|
12
|
+
builder (~> 3.1.0)
|
13
|
+
activerecord (4.0.0)
|
14
|
+
activemodel (= 4.0.0)
|
15
|
+
activerecord-deprecated_finders (~> 1.0.2)
|
16
|
+
activesupport (= 4.0.0)
|
17
|
+
arel (~> 4.0.0)
|
18
|
+
activerecord-deprecated_finders (1.0.3)
|
19
|
+
activesupport (4.0.0)
|
20
|
+
i18n (~> 0.6, >= 0.6.4)
|
21
|
+
minitest (~> 4.2)
|
22
|
+
multi_json (~> 1.3)
|
23
|
+
thread_safe (~> 0.1)
|
24
|
+
tzinfo (~> 0.3.37)
|
25
|
+
appraisal (0.5.2)
|
26
|
+
bundler
|
27
|
+
rake
|
28
|
+
arel (4.0.0)
|
29
|
+
atomic (1.1.13)
|
30
|
+
builder (3.1.4)
|
31
|
+
coderay (1.0.9)
|
32
|
+
colorize (0.5.8)
|
33
|
+
coveralls (0.6.7)
|
34
|
+
colorize
|
35
|
+
multi_json (~> 1.3)
|
36
|
+
rest-client
|
37
|
+
simplecov (>= 0.7)
|
38
|
+
thor
|
39
|
+
diff-lcs (1.2.4)
|
40
|
+
i18n (0.6.5)
|
41
|
+
method_source (0.8.2)
|
42
|
+
mime-types (1.25)
|
43
|
+
minitest (4.7.5)
|
44
|
+
multi_json (1.7.9)
|
45
|
+
mysql2 (0.3.13)
|
46
|
+
pg (0.16.0)
|
47
|
+
pry (0.9.12.2)
|
48
|
+
coderay (~> 1.0.5)
|
49
|
+
method_source (~> 0.8)
|
50
|
+
slop (~> 3.4)
|
51
|
+
pry-nav (0.2.3)
|
52
|
+
pry (~> 0.9.10)
|
53
|
+
rake (10.1.0)
|
54
|
+
rest-client (1.6.7)
|
55
|
+
mime-types (>= 1.16)
|
56
|
+
rspec (2.14.1)
|
57
|
+
rspec-core (~> 2.14.0)
|
58
|
+
rspec-expectations (~> 2.14.0)
|
59
|
+
rspec-mocks (~> 2.14.0)
|
60
|
+
rspec-core (2.14.5)
|
61
|
+
rspec-expectations (2.14.2)
|
62
|
+
diff-lcs (>= 1.1.3, < 2.0)
|
63
|
+
rspec-mocks (2.14.3)
|
64
|
+
simplecov (0.7.1)
|
65
|
+
multi_json (~> 1.0)
|
66
|
+
simplecov-html (~> 0.7.1)
|
67
|
+
simplecov-html (0.7.1)
|
68
|
+
slop (3.4.6)
|
69
|
+
sqlite3 (1.3.8)
|
70
|
+
thor (0.18.1)
|
71
|
+
thread_safe (0.1.2)
|
72
|
+
atomic
|
73
|
+
tzinfo (0.3.37)
|
74
|
+
|
75
|
+
PLATFORMS
|
76
|
+
ruby
|
77
|
+
|
78
|
+
DEPENDENCIES
|
79
|
+
activerecord (~> 4.0.0)
|
80
|
+
appraisal
|
81
|
+
coveralls
|
82
|
+
fuzzily!
|
83
|
+
mysql2
|
84
|
+
pg
|
85
|
+
pry
|
86
|
+
pry-nav
|
87
|
+
rake
|
88
|
+
rspec
|
89
|
+
sqlite3
|
data/lib/fuzzily.rb
CHANGED
data/lib/fuzzily/model.rb
CHANGED
@@ -5,8 +5,7 @@ module Fuzzily
|
|
5
5
|
|
6
6
|
def self.included(by)
|
7
7
|
by.ancestors.include?(ActiveRecord::Base) or raise 'Not included in an ActiveRecord subclass'
|
8
|
-
|
9
|
-
scope_method = ActiveRecord::VERSION::MAJOR == 2 ? :named_scope : :scope
|
8
|
+
by.extend(ClassMethods)
|
10
9
|
|
11
10
|
by.class_eval do
|
12
11
|
return if class_variable_defined?(:@@fuzzily_trigram_model)
|
@@ -18,31 +17,65 @@ module Fuzzily
|
|
18
17
|
validates_presence_of :score
|
19
18
|
validates_presence_of :fuzzy_field
|
20
19
|
|
21
|
-
|
22
|
-
:conditions => { :owner_type => model.kind_of?(Class) ? model.name : model }
|
23
|
-
}}
|
24
|
-
send scope_method, :for_field, lambda { |field_name| {
|
25
|
-
:conditions => { :fuzzy_field => field_name }
|
26
|
-
}}
|
27
|
-
send scope_method, :with_trigram, lambda { |trigrams| {
|
28
|
-
:conditions => { :trigram => trigrams }
|
29
|
-
}}
|
30
|
-
|
20
|
+
_add_fuzzy_scopes
|
31
21
|
class_variable_set(:@@fuzzily_trigram_model, true)
|
32
22
|
end
|
33
|
-
|
34
|
-
by.extend(ClassMethods)
|
35
23
|
end
|
36
24
|
|
37
25
|
module ClassMethods
|
38
26
|
def matches_for(text)
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
27
|
+
_matches_for_trigrams Fuzzily::String.new(text).trigrams
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
module Rails2
|
33
|
+
def _matches_for_trigrams(trigrams)
|
34
|
+
self.
|
35
|
+
scoped(:select => 'owner_id, owner_type, count(*) AS matches, MAX(score) AS score').
|
36
|
+
scoped(:group => 'owner_id, owner_type').
|
37
|
+
scoped(:order => 'matches DESC, score ASC').
|
38
|
+
with_trigram(trigrams).
|
39
|
+
map(&:owner)
|
40
|
+
end
|
41
|
+
|
42
|
+
def _add_fuzzy_scopes
|
43
|
+
named_scope :for_model, lambda { |model| {
|
44
|
+
:conditions => { :owner_type => model.kind_of?(Class) ? model.name : model }
|
45
|
+
}}
|
46
|
+
named_scope :for_field, lambda { |field_name| {
|
47
|
+
:conditions => { :fuzzy_field => field_name }
|
48
|
+
}}
|
49
|
+
named_scope :with_trigram, lambda { |trigrams| {
|
50
|
+
:conditions => { :trigram => trigrams }
|
51
|
+
}}
|
52
|
+
named_scope :limit, lambda { |count| { :limit => count }}
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
module Rails3
|
57
|
+
def _matches_for_trigrams(trigrams)
|
58
|
+
self.
|
59
|
+
select('owner_id, owner_type, count(*) AS matches, MAX(score) AS score').
|
60
|
+
group('owner_id, owner_type').
|
61
|
+
order('matches DESC, score ASC').
|
62
|
+
with_trigram(trigrams).
|
63
|
+
map(&:owner)
|
64
|
+
end
|
65
|
+
|
66
|
+
def _add_fuzzy_scopes
|
67
|
+
scope :for_model, lambda { |model|
|
68
|
+
where(:owner_type => model.kind_of?(Class) ? model.name : model)
|
69
|
+
}
|
70
|
+
scope :for_field, lambda { |field_name| where(:fuzzy_field => field_name) }
|
71
|
+
scope :with_trigram, lambda { |trigrams| where(:trigram => trigrams) }
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
if ActiveRecord::VERSION::MAJOR == 2
|
76
|
+
include Rails2
|
77
|
+
else
|
78
|
+
include Rails3
|
46
79
|
end
|
47
80
|
end
|
48
81
|
end
|
data/lib/fuzzily/searchable.rb
CHANGED
@@ -1,61 +1,62 @@
|
|
1
1
|
require 'fuzzily/trigram'
|
2
|
+
require 'ostruct'
|
2
3
|
|
3
4
|
module Fuzzily
|
4
5
|
module Searchable
|
5
|
-
# fuzzily_searchable <field> [, <field>...] [, <options>]
|
6
|
-
def fuzzily_searchable(*fields)
|
7
|
-
options = fields.last.kind_of?(Hash) ? fields.pop : {}
|
8
6
|
|
9
|
-
|
10
|
-
|
7
|
+
def self.included(by)
|
8
|
+
case ActiveRecord::VERSION::MAJOR
|
9
|
+
when 2 then by.extend Rails2ClassMethods
|
10
|
+
when 3 then by.extend Rails3ClassMethods
|
11
|
+
when 4 then by.extend Rails4ClassMethods
|
11
12
|
end
|
12
13
|
end
|
13
14
|
|
14
15
|
private
|
15
16
|
|
16
|
-
def
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
:dependent => :destroy,
|
32
|
-
:autosave => true
|
33
|
-
|
34
|
-
singleton_class.send(:define_method,"find_by_fuzzy_#{field}".to_sym) do |*args|
|
35
|
-
case args.size
|
36
|
-
when 1 then pattern = args.first ; options = {}
|
37
|
-
when 2 then pattern, options = args
|
38
|
-
else raise 'Wrong # of arguments'
|
17
|
+
def _update_fuzzy!(_o)
|
18
|
+
self.send(_o.trigram_association).delete_all
|
19
|
+
String.new(self.send(_o.field)).scored_trigrams.each do |trigram, score|
|
20
|
+
self.send(_o.trigram_association).create!(:score => score, :trigram => trigram, :owner_type => self.class.name)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
module ClassMethods
|
26
|
+
# fuzzily_searchable <field> [, <field>...] [, <options>]
|
27
|
+
def fuzzily_searchable(*fields)
|
28
|
+
options = fields.last.kind_of?(Hash) ? fields.pop : {}
|
29
|
+
|
30
|
+
fields.each do |field|
|
31
|
+
make_field_fuzzily_searchable(field, options)
|
39
32
|
end
|
33
|
+
end
|
40
34
|
|
35
|
+
private
|
36
|
+
|
37
|
+
def _find_by_fuzzy(_o, pattern, options={})
|
41
38
|
options[:limit] ||= 10
|
42
39
|
|
43
|
-
trigram_class_name.constantize.
|
44
|
-
|
40
|
+
_o.trigram_class_name.constantize.
|
41
|
+
limit(options[:limit]).
|
45
42
|
for_model(self.name).
|
46
|
-
for_field(field.to_s).
|
43
|
+
for_field(_o.field.to_s).
|
47
44
|
matches_for(pattern)
|
48
45
|
end
|
49
46
|
|
50
|
-
|
51
|
-
trigram_class = trigram_class_name.constantize
|
47
|
+
def _bulk_update_fuzzy(_o)
|
48
|
+
trigram_class = _o.trigram_class_name.constantize
|
49
|
+
|
50
|
+
supports_bulk_inserts =
|
51
|
+
connection.class.name !~ /sqlite/i ||
|
52
|
+
connection.send(:sqlite_version) >= '3.7.11'
|
52
53
|
|
53
|
-
|
54
|
+
_with_included_trigrams(_o).find_in_batches(:batch_size => 100) do |batch|
|
54
55
|
inserts = []
|
55
56
|
batch.each do |record|
|
56
|
-
data = Fuzzily::String.new(record.send(field))
|
57
|
+
data = Fuzzily::String.new(record.send(_o.field))
|
57
58
|
data.scored_trigrams.each do |trigram, score|
|
58
|
-
inserts << sanitize_sql_array(['(?,?,?,?,?)', self.name, record.id, field.to_s, score, trigram])
|
59
|
+
inserts << sanitize_sql_array(['(?,?,?,?,?)', self.name, record.id, _o.field.to_s, score, trigram])
|
59
60
|
end
|
60
61
|
end
|
61
62
|
|
@@ -74,7 +75,7 @@ module Fuzzily
|
|
74
75
|
]
|
75
76
|
|
76
77
|
trigram_class.transaction do
|
77
|
-
batch.each { |record| record.send(trigram_association).delete_all }
|
78
|
+
batch.each { |record| record.send(_o.trigram_association).delete_all }
|
78
79
|
break if inserts.empty?
|
79
80
|
|
80
81
|
if supports_bulk_inserts
|
@@ -88,20 +89,79 @@ module Fuzzily
|
|
88
89
|
end
|
89
90
|
end
|
90
91
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
92
|
+
def make_field_fuzzily_searchable(field, options={})
|
93
|
+
class_variable_defined?(:"@@fuzzily_searchable_#{field}") and return
|
94
|
+
|
95
|
+
_o = OpenStruct.new(
|
96
|
+
:field => field,
|
97
|
+
:trigram_class_name => options.fetch(:class_name, 'Trigram'),
|
98
|
+
:trigram_association => "trigrams_for_#{field}".to_sym,
|
99
|
+
:update_trigrams_method => "update_fuzzy_#{field}!".to_sym
|
100
|
+
)
|
101
|
+
|
102
|
+
_add_trigram_association(_o)
|
103
|
+
|
104
|
+
singleton_class.send(:define_method,"find_by_fuzzy_#{field}".to_sym) do |*args|
|
105
|
+
_find_by_fuzzy(_o, *args)
|
106
|
+
end
|
107
|
+
|
108
|
+
singleton_class.send(:define_method,"bulk_update_fuzzy_#{field}".to_sym) do
|
109
|
+
_bulk_update_fuzzy(_o)
|
110
|
+
end
|
111
|
+
|
112
|
+
define_method _o.update_trigrams_method do
|
113
|
+
_update_fuzzy!(_o)
|
95
114
|
end
|
115
|
+
|
116
|
+
after_save do |record|
|
117
|
+
next unless record.send("#{field}_changed?".to_sym)
|
118
|
+
record.send(_o.update_trigrams_method)
|
119
|
+
end
|
120
|
+
|
121
|
+
class_variable_set(:"@@fuzzily_searchable_#{field}", true)
|
122
|
+
self
|
96
123
|
end
|
124
|
+
end
|
125
|
+
|
126
|
+
module Rails2ClassMethods
|
127
|
+
include ClassMethods
|
97
128
|
|
98
|
-
|
99
|
-
|
100
|
-
|
129
|
+
private
|
130
|
+
|
131
|
+
def _add_trigram_association(_o)
|
132
|
+
has_many _o.trigram_association,
|
133
|
+
:class_name => _o.trigram_class_name,
|
134
|
+
:as => :owner,
|
135
|
+
:conditions => { :fuzzy_field => _o.field.to_s },
|
136
|
+
:dependent => :destroy,
|
137
|
+
:autosave => true
|
101
138
|
end
|
102
139
|
|
103
|
-
|
104
|
-
|
140
|
+
def _with_included_trigrams(_o)
|
141
|
+
self.scoped(:include => _o.trigram_association)
|
142
|
+
end
|
105
143
|
end
|
144
|
+
|
145
|
+
Rails3ClassMethods = Rails2ClassMethods
|
146
|
+
|
147
|
+
module Rails4ClassMethods
|
148
|
+
include ClassMethods
|
149
|
+
|
150
|
+
private
|
151
|
+
|
152
|
+
def _add_trigram_association(_o)
|
153
|
+
has_many _o.trigram_association,
|
154
|
+
lambda { where(:fuzzy_field => _o.field.to_s) },
|
155
|
+
:class_name => _o.trigram_class_name,
|
156
|
+
:as => :owner,
|
157
|
+
:dependent => :delete_all,
|
158
|
+
:autosave => true
|
159
|
+
end
|
160
|
+
|
161
|
+
def _with_included_trigrams(_o)
|
162
|
+
self.includes(_o.trigram_association)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
106
166
|
end
|
107
|
-
end
|
167
|
+
end
|
data/lib/fuzzily/version.rb
CHANGED
@@ -43,22 +43,27 @@ describe Fuzzily::Searchable do
|
|
43
43
|
before { subject.fuzzily_searchable :name }
|
44
44
|
|
45
45
|
it 'generates trigram records on creation' do
|
46
|
-
subject.create(:name => 'Paris')
|
46
|
+
subject.create!(:name => 'Paris')
|
47
47
|
subject.last.trigrams_for_name.should_not be_empty
|
48
48
|
end
|
49
49
|
|
50
50
|
it 'generates the correct trigrams' do
|
51
|
-
record = subject.create(:name => 'FOO')
|
51
|
+
record = subject.create!(:name => 'FOO')
|
52
52
|
Trigram.first.trigram.should == '**f'
|
53
53
|
Trigram.first.owner_id.should == record.id
|
54
54
|
Trigram.first.owner_type.should == 'Stuff'
|
55
55
|
end
|
56
56
|
|
57
57
|
it 'updates all trigram records on save' do
|
58
|
-
subject.create(:name => 'Paris')
|
58
|
+
subject.create!(:name => 'Paris')
|
59
59
|
subject.first.update_attribute :name, 'Rome'
|
60
60
|
Trigram.all.map(&:trigram).should =~ %w(**r *ro rom ome me*)
|
61
61
|
end
|
62
|
+
|
63
|
+
it 'deletes all trigrams on destroy' do
|
64
|
+
subject.create!(:name => 'Paris').destroy
|
65
|
+
Trigram.all.should be_empty
|
66
|
+
end
|
62
67
|
end
|
63
68
|
|
64
69
|
describe '#update_fuzzy_<field>!' do
|
@@ -67,14 +72,14 @@ describe Fuzzily::Searchable do
|
|
67
72
|
end
|
68
73
|
|
69
74
|
it 're-creates trigrams' do
|
70
|
-
subject.create(:name => 'Paris')
|
75
|
+
subject.create!(:name => 'Paris')
|
71
76
|
old_ids = Trigram.all.map(&:id)
|
72
77
|
subject.last.update_fuzzy_name!
|
73
78
|
(old_ids & Trigram.all.map(&:id)).should be_empty
|
74
79
|
end
|
75
80
|
|
76
81
|
it 'ignores nil values' do
|
77
|
-
subject.create(:name => nil)
|
82
|
+
subject.create!(:name => nil)
|
78
83
|
subject.last.update_fuzzy_name!
|
79
84
|
Trigram.all.should be_empty
|
80
85
|
end
|
@@ -84,14 +89,14 @@ describe Fuzzily::Searchable do
|
|
84
89
|
before { subject.fuzzily_searchable :name }
|
85
90
|
|
86
91
|
it 'creates all trigrams' do
|
87
|
-
subject.create(:name => 'Paris')
|
92
|
+
subject.create!(:name => 'Paris')
|
88
93
|
Trigram.delete_all
|
89
94
|
subject.bulk_update_fuzzy_name
|
90
95
|
Trigram.all.should_not be_empty
|
91
96
|
end
|
92
97
|
|
93
98
|
it 'ignores nil values' do
|
94
|
-
subject.create(:name => nil)
|
99
|
+
subject.create!(:name => nil)
|
95
100
|
Trigram.delete_all
|
96
101
|
subject.bulk_update_fuzzy_name
|
97
102
|
Trigram.all.should be_empty
|
@@ -102,9 +107,9 @@ describe Fuzzily::Searchable do
|
|
102
107
|
describe '#find_by_fuzzy_<field>' do
|
103
108
|
it 'returns records' do
|
104
109
|
subject.fuzzily_searchable :name
|
105
|
-
@paris = subject.create(:name => 'Paris')
|
106
|
-
@palma = subject.create(:name => 'Palma de Majorca')
|
107
|
-
@palmyre = subject.create(:name => 'La Palmyre')
|
110
|
+
@paris = subject.create!(:name => 'Paris')
|
111
|
+
@palma = subject.create!(:name => 'Palma de Majorca')
|
112
|
+
@palmyre = subject.create!(:name => 'La Palmyre')
|
108
113
|
|
109
114
|
subject.find_by_fuzzy_name('Piris').should_not be_empty
|
110
115
|
subject.find_by_fuzzy_name('Piris').should =~ [@paris, @palma]
|
@@ -113,10 +118,10 @@ describe Fuzzily::Searchable do
|
|
113
118
|
|
114
119
|
it 'favours exact matches' do
|
115
120
|
subject.fuzzily_searchable :name
|
116
|
-
@new_york = subject.create(:name => 'New York')
|
117
|
-
@yorkshire = subject.create(:name => 'Yorkshire')
|
118
|
-
@york = subject.create(:name => 'York')
|
119
|
-
@yorkisthan = subject.create(:name => 'Yorkisthan')
|
121
|
+
@new_york = subject.create!(:name => 'New York')
|
122
|
+
@yorkshire = subject.create!(:name => 'Yorkshire')
|
123
|
+
@york = subject.create!(:name => 'York')
|
124
|
+
@yorkisthan = subject.create!(:name => 'Yorkisthan')
|
120
125
|
|
121
126
|
subject.find_by_fuzzy_name('York').should == [@york, @new_york, @yorkshire, @yorkisthan]
|
122
127
|
subject.find_by_fuzzy_name('Yorkshire').should == [@yorkshire, @york, @yorkisthan, @new_york]
|
@@ -124,11 +129,17 @@ describe Fuzzily::Searchable do
|
|
124
129
|
|
125
130
|
it 'does not favour short words' do
|
126
131
|
subject.fuzzily_searchable :name
|
127
|
-
@lo = subject.create(:name => 'Lo') # **l *lo lo*
|
128
|
-
@london = subject.create(:name => 'London') # **l *lo lon ond ndo don on*
|
132
|
+
@lo = subject.create!(:name => 'Lo') # **l *lo lo*
|
133
|
+
@london = subject.create!(:name => 'London') # **l *lo lon ond ndo don on*
|
129
134
|
# **l *lo lon
|
130
135
|
subject.find_by_fuzzy_name('Lon').should == [@london, @lo]
|
131
136
|
end
|
137
|
+
|
138
|
+
it 'honours limie option' do
|
139
|
+
subject.fuzzily_searchable :name
|
140
|
+
3.times { subject.create!(:name => 'Paris') }
|
141
|
+
subject.find_by_fuzzy_name('Paris', :limit => 2).length.should == 2
|
142
|
+
end
|
132
143
|
end
|
133
144
|
end
|
134
145
|
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fuzzily
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Julien Letessier
|
@@ -180,6 +180,8 @@ files:
|
|
180
180
|
- gemfiles/rails32_mysql.gemfile.lock
|
181
181
|
- gemfiles/rails32_pg.gemfile
|
182
182
|
- gemfiles/rails32_pg.gemfile.lock
|
183
|
+
- gemfiles/rails40.gemfile
|
184
|
+
- gemfiles/rails40.gemfile.lock
|
183
185
|
- lib/fuzzily.rb
|
184
186
|
- lib/fuzzily/migration.rb
|
185
187
|
- lib/fuzzily/model.rb
|