fuzzily 0.3.0 → 0.3.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.
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