fuzzily 0.3.0 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c6b37d6f8b23347b04d0efb6d069c41654def4e5
4
- data.tar.gz: 30c2b6d4362466e5cb63a9ae14496bd92e26ac54
3
+ metadata.gz: 3c1f0be1e0222fd105f14a6021e65e0b92ecf154
4
+ data.tar.gz: a96c50991cf0d5f180960b2f81086dd22038bec7
5
5
  SHA512:
6
- metadata.gz: b65ff29d86fb3822ee426f1f7731d4f254f5cc180c5b4b0cf8f5489d2c492d02341ca3747a349f60291bb035783d617d333a72c55c7e80e794b5198860423348
7
- data.tar.gz: f2993001f8f9189101644ff8ce52d243832e2479f6cc1b567632d6f2d7d2ffe4c6be5cef87a9082eaf62bd50fe482907065dec22a24e561244a8a118e989a4d6
6
+ metadata.gz: 95b1281eba8a4898f740d2b5a2038ba05f8a83ada3766a136c9d468ab8d3091970d9ad26103f7fdb186001d0dbf67f6556d1c99bc53603d6389f901d1bc9a732
7
+ data.tar.gz: 0dedbefc9d1f58f78ee76e7b9777056d83b82f53396b5f4dd3edd647ccd6cf33d998959e10e5f5192b800a51bead6ba06516d0193f1e1254d3d3fe56f83ad08f
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- fuzzily (0.3.0)
4
+ fuzzily (0.3.1)
5
5
  activerecord (>= 2.3.17)
6
6
 
7
7
  GEM
data/README.md CHANGED
@@ -11,7 +11,7 @@
11
11
  > Here aresome photos of **Marrakesh**, Morroco.
12
12
  > Did you mean **Martanesh**, Albania, **Marakkanam**, India, or **Marasheshty**, Romania?
13
13
 
14
- Blurrily finds misspelled, prefix, or partial needles in a haystack of
14
+ Fuzzily 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
 
@@ -45,63 +45,77 @@ You'll need to setup 2 things:
45
45
 
46
46
  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):
47
47
 
48
- class Trigram < ActiveRecord::Base
49
- include Fuzzily::Model
50
- end
48
+ ```ruby
49
+ class Trigram < ActiveRecord::Base
50
+ include Fuzzily::Model
51
+ end
52
+ ```
51
53
 
52
54
  Create a migration for it:
53
55
 
54
- class AddTrigramsModel < ActiveRecord::Migration
55
- extend Fuzzily::Migration
56
- end
56
+ ```ruby
57
+ class AddTrigramsModel < ActiveRecord::Migration
58
+ extend Fuzzily::Migration
59
+ end
60
+ ```
57
61
 
58
- Instrument your model (your searchable fields do not have to be stored, they can be dynamic methods too):
62
+ Instrument your model:
59
63
 
60
- class MyStuff < ActiveRecord::Base
61
- # assuming my_stuffs has a 'name' attribute
62
- fuzzily_searchable :name
63
- end
64
+ ```ruby
65
+ class MyStuff < ActiveRecord::Base
66
+ # assuming my_stuffs has a 'name' attribute
67
+ fuzzily_searchable :name
68
+ end
69
+ ```
64
70
 
65
71
  Index your model (will happen automatically for new/updated records):
66
72
 
67
- MyStuff.bulk_update_fuzzy_name
73
+ ```ruby
74
+ MyStuff.bulk_update_fuzzy_name
75
+ ```
68
76
 
69
77
  Search!
70
78
 
71
- MyStuff.find_by_fuzzy_name('Some Name', :limit => 10)
72
- # => records
79
+ ```ruby
80
+ MyStuff.find_by_fuzzy_name('Some Name', :limit => 10)
81
+ # => records
82
+ ```
73
83
 
74
84
  You can force an update on a specific record with
75
85
 
76
- MyStuff.find(123).update_fuzzy_name!
86
+ ```ruby
87
+ MyStuff.find(123).update_fuzzy_name!
88
+ ```
77
89
 
78
90
  ## Indexing more than one field
79
91
 
80
92
  Just list all the field you want to index, or call `fuzzily_searchable` more than once:
81
93
 
82
- class MyStuff < ActiveRecord::Base
83
- fuzzily_searchable :name_fr, :name_en
84
- fuzzily_searchable :name_de
85
- end
86
-
94
+ ```ruby
95
+ class MyStuff < ActiveRecord::Base
96
+ fuzzily_searchable :name_fr, :name_en
97
+ fuzzily_searchable :name_de
98
+ end
99
+ ```
87
100
 
88
101
  ## Custom name for the index model
89
102
 
90
103
  If you want or need to name your index model differently (e.g. because you already have a class called `Trigram`):
91
104
 
92
- class CustomTrigram < ActiveRecord::Base
93
- include Fuzzily::Model
94
- end
95
-
96
- class AddTrigramsModel < ActiveRecord::Migration
97
- extend Fuzzily::Migration
98
- trigrams_table_name = :custom_trigrams
99
- end
105
+ ```ruby
106
+ class CustomTrigram < ActiveRecord::Base
107
+ include Fuzzily::Model
108
+ end
100
109
 
101
- class MyStuff < ActiveRecord::Base
102
- fuzzily_searchable :name, :class_name => 'CustomTrigram'
103
- end
110
+ class AddTrigramsModel < ActiveRecord::Migration
111
+ extend Fuzzily::Migration
112
+ trigrams_table_name = :custom_trigrams
113
+ end
104
114
 
115
+ class MyStuff < ActiveRecord::Base
116
+ fuzzily_searchable :name, :class_name => 'CustomTrigram'
117
+ end
118
+ ```
105
119
 
106
120
  ## Speeding things up
107
121
 
@@ -117,6 +131,38 @@ MySQL and pgSQL.
117
131
  This is not the default in the gem as ActiveRecord does not suport `ENUM`
118
132
  columns in any version.
119
133
 
134
+ ## UUID's
135
+
136
+ When using Rails 4 with UUID's, you will need to change the `owner_id` column type to `UUID`.
137
+
138
+ ```ruby
139
+ class AddTrigramsModel < ActiveRecord::Migration
140
+ extend Fuzzily::Migration
141
+ trigrams_owner_id_column_type = :uuid
142
+ end
143
+ ```
144
+
145
+ ## Searching virtual attributes
146
+
147
+ Your searchable fields do not have to be stored, they can be dynamic methods
148
+ too. Just remember to add a virtual change method as well.
149
+ For instance, if you model has `first_name` and `last_name` attributes, and you
150
+ want to index a compound `name` dynamic attribute:
151
+
152
+ ```ruby
153
+ class Employee < ActiveRecord::Base
154
+ fuzzily_searchable :name
155
+ def name
156
+ "#{first_name} #{last_name}"
157
+ end
158
+
159
+ def name_changed?
160
+ first_name_changed? || last_name_changed?
161
+ end
162
+ end
163
+ ```
164
+
165
+
120
166
 
121
167
  ## License
122
168
 
@@ -133,5 +179,5 @@ Copyright (c) 2013 HouseTrip Ltd.
133
179
  5. Create a new Pull Request
134
180
 
135
181
 
136
- Thanks to @bclennox, @fdegiuli, @nickbender, @Shanison for pointing out
182
+ Thanks to @bclennox, @fdegiuli, @nickbender, @Shanison, @rickbutton for pointing out
137
183
  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.3.0)
4
+ fuzzily (0.3.1)
5
5
  activerecord (>= 2.3.17)
6
6
 
7
7
  GEM
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: /Users/mezis/Dropbox/Development/fuzzily
3
3
  specs:
4
- fuzzily (0.3.0)
4
+ fuzzily (0.3.1)
5
5
  activerecord (>= 2.3.17)
6
6
 
7
7
  GEM
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: /Users/mezis/Dropbox/Development/fuzzily
3
3
  specs:
4
- fuzzily (0.3.0)
4
+ fuzzily (0.3.1)
5
5
  activerecord (>= 2.3.17)
6
6
 
7
7
  GEM
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: /Users/mezis/Dropbox/Development/fuzzily
3
3
  specs:
4
- fuzzily (0.3.0)
4
+ fuzzily (0.3.1)
5
5
  activerecord (>= 2.3.17)
6
6
 
7
7
  GEM
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: /Users/mezis/Dropbox/Development/fuzzily
3
3
  specs:
4
- fuzzily (0.3.0)
4
+ fuzzily (0.3.1)
5
5
  activerecord (>= 2.3.17)
6
6
 
7
7
  GEM
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: /Users/mezis/Dropbox/Development/fuzzily
3
3
  specs:
4
- fuzzily (0.3.0)
4
+ fuzzily (0.3.1)
5
5
  activerecord (>= 2.3.17)
6
6
 
7
7
  GEM
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: /Users/mezis/Dropbox/Development/fuzzily
3
3
  specs:
4
- fuzzily (0.3.0)
4
+ fuzzily (0.3.1)
5
5
  activerecord (>= 2.3.17)
6
6
 
7
7
  GEM
@@ -12,11 +12,19 @@ module Fuzzily
12
12
  @trigrams_table_name ||= :trigrams
13
13
  end
14
14
 
15
+ def trigrams_owner_id_column_type=(custom_type)
16
+ @trigrams_owner_id_column_type = custom_type
17
+ end
18
+
19
+ def trigrams_owner_id_column_type
20
+ @trigrams_owner_id_column_type ||= :integer
21
+ end
22
+
15
23
  def up
16
24
  create_table trigrams_table_name do |t|
17
25
  t.string :trigram, :limit => 3
18
26
  t.integer :score, :limit => 2
19
- t.integer :owner_id
27
+ t.send trigrams_owner_id_column_type, :owner_id
20
28
  t.string :owner_type
21
29
  t.string :fuzzy_field
22
30
  end
@@ -35,8 +35,7 @@ module Fuzzily
35
35
  scoped(:select => 'owner_id, owner_type, count(*) AS matches, MAX(score) AS score').
36
36
  scoped(:group => 'owner_id, owner_type').
37
37
  scoped(:order => 'matches DESC, score ASC').
38
- with_trigram(trigrams).
39
- map(&:owner)
38
+ with_trigram(trigrams)
40
39
  end
41
40
 
42
41
  def _add_fuzzy_scopes
@@ -59,8 +58,7 @@ module Fuzzily
59
58
  select('owner_id, owner_type, count(*) AS matches, MAX(score) AS score').
60
59
  group('owner_id, owner_type').
61
60
  order('matches DESC, score ASC').
62
- with_trigram(trigrams).
63
- map(&:owner)
61
+ with_trigram(trigrams)
64
62
  end
65
63
 
66
64
  def _add_fuzzy_scopes
@@ -36,12 +36,23 @@ module Fuzzily
36
36
 
37
37
  def _find_by_fuzzy(_o, pattern, options={})
38
38
  options[:limit] ||= 10
39
+ options[:offset] ||= 0
39
40
 
40
- _o.trigram_class_name.constantize.
41
+ trigrams = _o.trigram_class_name.constantize.
41
42
  limit(options[:limit]).
43
+ offset(options[:offset]).
42
44
  for_model(self.name).
43
45
  for_field(_o.field.to_s).
44
46
  matches_for(pattern)
47
+ records = _load_for_ids(trigrams.map(&:owner_id))
48
+ # order records as per trigram query (no portable way to do this in SQL)
49
+ trigrams.map { |t| records[t.owner_id] }
50
+ end
51
+
52
+ def _load_for_ids(ids)
53
+ {}.tap do |result|
54
+ find(ids).each { |_r| result[_r.id] = _r }
55
+ end
45
56
  end
46
57
 
47
58
  def _bulk_update_fuzzy(_o)
@@ -123,9 +134,7 @@ module Fuzzily
123
134
  end
124
135
  end
125
136
 
126
- module Rails2ClassMethods
127
- include ClassMethods
128
-
137
+ module Rails2Rails3ClassMethods
129
138
  private
130
139
 
131
140
  def _add_trigram_association(_o)
@@ -142,7 +151,23 @@ module Fuzzily
142
151
  end
143
152
  end
144
153
 
145
- Rails3ClassMethods = Rails2ClassMethods
154
+ module Rails2ClassMethods
155
+ include ClassMethods
156
+ include Rails2Rails3ClassMethods
157
+
158
+ def self.extended(base)
159
+ base.class_eval do
160
+ named_scope :offset, lambda { |*args| { :offset => args.first } }
161
+ end
162
+ end
163
+ end
164
+
165
+ module Rails3ClassMethods
166
+ include ClassMethods
167
+ include Rails2Rails3ClassMethods
168
+ end
169
+
170
+
146
171
 
147
172
  module Rails4ClassMethods
148
173
  include ClassMethods
@@ -164,4 +189,4 @@ module Fuzzily
164
189
  end
165
190
 
166
191
  end
167
- end
192
+ end
@@ -1,3 +1,3 @@
1
1
  module Fuzzily
2
- VERSION = "0.3.0"
2
+ VERSION = "0.3.1"
3
3
  end
@@ -43,21 +43,21 @@ describe Fuzzily::Model do
43
43
  end
44
44
 
45
45
  it 'finds matches' do
46
- model.matches_for('Paris').should == [@paris]
46
+ model.matches_for('Paris').map(&:owner).should == [@paris]
47
47
  end
48
48
 
49
49
  it 'finds close matches' do
50
- model.matches_for('Piriss').should == [@paris]
50
+ model.matches_for('Piriss').map(&:owner).should == [@paris]
51
51
  end
52
52
 
53
53
  it 'does not confuse fields' do
54
- model.for_field(:name).matches_for('Paris').should == [@paris]
55
- model.for_field(:data).matches_for('Paris').should be_empty
54
+ model.for_field(:name).matches_for('Paris').map(&:owner).should == [@paris]
55
+ model.for_field(:data).matches_for('Paris').map(&:owner).should be_empty
56
56
  end
57
57
 
58
58
  it 'does not confuse owner types' do
59
- model.for_model(Stuff).matches_for('Paris').should == [@paris]
60
- model.for_model(Object).matches_for('Paris').should be_empty
59
+ model.for_model(Stuff).matches_for('Paris').map(&:owner).should == [@paris]
60
+ model.for_model(Object).matches_for('Paris').map(&:owner).should be_empty
61
61
  end
62
62
 
63
63
  context '(with more than one entry)' do
@@ -69,7 +69,7 @@ describe Fuzzily::Model do
69
69
  end
70
70
 
71
71
  it 'returns ordered results' do
72
- model.matches_for('Palmyre').should == [@palma, @paris]
72
+ model.matches_for('Palmyre').map(&:owner).should == [@palma, @paris]
73
73
  end
74
74
  end
75
75
  end
@@ -135,12 +135,18 @@ describe Fuzzily::Searchable do
135
135
  subject.find_by_fuzzy_name('Lon').should == [@london, @lo]
136
136
  end
137
137
 
138
- it 'honours limie option' do
138
+ it 'honours limit option' do
139
139
  subject.fuzzily_searchable :name
140
140
  3.times { subject.create!(:name => 'Paris') }
141
141
  subject.find_by_fuzzy_name('Paris', :limit => 2).length.should == 2
142
142
  end
143
+
144
+ it 'honours offset option' do
145
+ subject.fuzzily_searchable :name
146
+ 3.times { subject.create!(:name => 'Paris') }
147
+ subject.find_by_fuzzy_name('Paris', :offset => 2).length.should == 1
148
+ end
143
149
  end
144
150
  end
145
151
 
146
- end
152
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fuzzily
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Julien Letessier
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-08-31 00:00:00.000000000 Z
11
+ date: 2013-12-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord