pg_search 0.7.3 → 0.7.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +4 -4
- data/CHANGELOG.md +5 -0
- data/README.md +376 -307
- data/lib/pg_search/multisearch/rebuilder.rb +18 -12
- data/lib/pg_search/multisearchable.rb +4 -1
- data/lib/pg_search/version.rb +1 -1
- data/spec/integration/pg_search_spec.rb +43 -0
- data/spec/lib/pg_search/multisearch/rebuilder_spec.rb +59 -3
- data/spec/spec_helper.rb +9 -9
- metadata +23 -23
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 60b69d6e0600c725eba8d5f820eb05439982824a
|
4
|
+
data.tar.gz: d1f90c57f0d2db953516b2edf99127b0326be044
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6157156f723baccc67229b771852de1adf1d21ffa5bc41e4a593908453c7af7125e1ae79ba46a0fa91de593e01c3b3117858e284c2e70b0cb9c7b4dd48184a27
|
7
|
+
data.tar.gz: 60a6c495d334f7af8dc4f98f9c20637de6058c218b1e35d98e1d298d923fa4bf6199ec456b8c6a5c1ab357d6c44e4b40348e16a99b92a03063dd8931fc4fa4d4
|
data/.travis.yml
CHANGED
@@ -3,24 +3,24 @@ language: ruby
|
|
3
3
|
rvm:
|
4
4
|
- 1.9.3
|
5
5
|
- 2.0.0
|
6
|
-
- 2.1.
|
6
|
+
- 2.1.1
|
7
7
|
- jruby-19mode
|
8
8
|
|
9
9
|
env:
|
10
10
|
- ACTIVE_RECORD_BRANCH="master"
|
11
|
+
- ACTIVE_RECORD_BRANCH="4-1-stable"
|
11
12
|
- ACTIVE_RECORD_BRANCH="4-0-stable"
|
12
|
-
- ACTIVE_RECORD_VERSION="~> 4.1.0
|
13
|
+
- ACTIVE_RECORD_VERSION="~> 4.1.0"
|
13
14
|
- ACTIVE_RECORD_VERSION="~> 4.0.0"
|
14
15
|
- ACTIVE_RECORD_VERSION="~> 3.2.0"
|
15
16
|
- ACTIVE_RECORD_VERSION="~> 3.1.0"
|
16
17
|
|
17
18
|
matrix:
|
18
19
|
allow_failures:
|
19
|
-
- rvm: 2.1.0
|
20
20
|
- rvm: jruby-19mode
|
21
21
|
- env: ACTIVE_RECORD_BRANCH="master"
|
22
|
+
- env: ACTIVE_RECORD_BRANCH="4-1-stable"
|
22
23
|
- env: ACTIVE_RECORD_BRANCH="4-0-stable"
|
23
|
-
- env: ACTIVE_RECORD_VERSION="~> 4.1.0.beta1"
|
24
24
|
|
25
25
|
before_script:
|
26
26
|
- "psql -c 'create database pg_search_test;' -U postgres >/dev/null"
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,10 @@
|
|
1
1
|
# pg_search changelog
|
2
2
|
|
3
|
+
## 0.7.4
|
4
|
+
|
5
|
+
* Fix which STI class name is used for searchable_type for PgSearch::Document. (Ewan McDougall)
|
6
|
+
* Add support for non-standard primary keys. (Matt Beedle)
|
7
|
+
|
3
8
|
## 0.7.3
|
4
9
|
|
5
10
|
* Allow simultaneously searching using `:associated_against` and `:tsvector_column` (Adam Becker)
|
data/README.md
CHANGED
@@ -84,9 +84,11 @@ https://github.com/Casecommons/pg_search/tree/0.6-stable
|
|
84
84
|
|
85
85
|
To add PgSearch to an Active Record model, simply include the PgSearch module.
|
86
86
|
|
87
|
-
|
88
|
-
|
89
|
-
|
87
|
+
```ruby
|
88
|
+
class Shape < ActiveRecord::Base
|
89
|
+
include PgSearch
|
90
|
+
end
|
91
|
+
```
|
90
92
|
|
91
93
|
### Multi-search vs. search scopes
|
92
94
|
|
@@ -118,15 +120,17 @@ pg_search_documents database table.
|
|
118
120
|
To add a model to the global search index for your application, call
|
119
121
|
multisearchable in its class definition.
|
120
122
|
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
123
|
+
```ruby
|
124
|
+
class EpicPoem < ActiveRecord::Base
|
125
|
+
include PgSearch
|
126
|
+
multisearchable :against => [:title, :author]
|
127
|
+
end
|
125
128
|
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
129
|
+
class Flower < ActiveRecord::Base
|
130
|
+
include PgSearch
|
131
|
+
multisearchable :against => :color
|
132
|
+
end
|
133
|
+
```
|
130
134
|
|
131
135
|
If this model already has existing records, you will need to reindex this
|
132
136
|
model to get existing records into the pg_search_documents table. See the
|
@@ -141,17 +145,19 @@ text.
|
|
141
145
|
You can also pass a Proc or method name to call to determine whether or not a
|
142
146
|
particular record should be included.
|
143
147
|
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
148
|
+
```ruby
|
149
|
+
class Convertible < ActiveRecord::Base
|
150
|
+
include PgSearch
|
151
|
+
multisearchable :against => [:make, :model],
|
152
|
+
:if => :available_in_red?
|
153
|
+
end
|
149
154
|
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
+
class Jalopy < ActiveRecord::Base
|
156
|
+
include PgSearch
|
157
|
+
multisearchable :against => [:make, :model],
|
158
|
+
:if => lambda { |record| record.model_year > 1970 }
|
159
|
+
end
|
160
|
+
```
|
155
161
|
|
156
162
|
Note that the Proc or method name is called in an after_save hook. This means
|
157
163
|
that you should be careful when using Time or other objects. In the following
|
@@ -159,33 +165,35 @@ example, if the record was last saved before the published_at timestamp, it
|
|
159
165
|
won't get listed in global search at all until it is touched again after the
|
160
166
|
timestamp.
|
161
167
|
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
168
|
+
```ruby
|
169
|
+
class AntipatternExample
|
170
|
+
include PgSearch
|
171
|
+
multisearchable :against => [:contents],
|
172
|
+
:if => :published?
|
166
173
|
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
174
|
+
def published?
|
175
|
+
published_at < Time.now
|
176
|
+
end
|
177
|
+
end
|
171
178
|
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
179
|
+
problematic_record = AntipatternExample.create!(
|
180
|
+
:contents => "Using :if with a timestamp",
|
181
|
+
:published_at => 10.minutes.from_now
|
182
|
+
)
|
176
183
|
|
177
|
-
|
178
|
-
|
184
|
+
problematic_record.published? # => false
|
185
|
+
PgSearch.multisearch("timestamp") # => No results
|
179
186
|
|
180
|
-
|
187
|
+
sleep 20.minutes
|
181
188
|
|
182
|
-
|
183
|
-
|
189
|
+
problematic_record.published? # => true
|
190
|
+
PgSearch.multisearch("timestamp") # => No results
|
184
191
|
|
185
|
-
|
192
|
+
problematic_record.save!
|
186
193
|
|
187
|
-
|
188
|
-
|
194
|
+
problematic_record.published? # => true
|
195
|
+
PgSearch.multisearch("timestamp") # => Includes problematic_record
|
196
|
+
```
|
189
197
|
|
190
198
|
#### Multi-search associations
|
191
199
|
|
@@ -194,19 +202,23 @@ has_one :pg_search_document association pointing to the PgSearch::Document
|
|
194
202
|
record, and on the PgSearch::Document record there is a belongs_to :searchable
|
195
203
|
polymorphic association pointing back to the original record.
|
196
204
|
|
197
|
-
|
198
|
-
|
199
|
-
|
205
|
+
```ruby
|
206
|
+
odyssey = EpicPoem.create!(:title => "Odyssey", :author => "Homer")
|
207
|
+
search_document = odyssey.pg_search_document #=> PgSearch::Document instance
|
208
|
+
search_document.searchable #=> #<EpicPoem id: 1, title: "Odyssey", author: "Homer">
|
209
|
+
```
|
200
210
|
|
201
211
|
#### Searching in the global search index
|
202
212
|
|
203
213
|
To fetch the PgSearch::Document entries for all of the records that match a
|
204
214
|
given query, use PgSearch.multisearch.
|
205
215
|
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
216
|
+
```ruby
|
217
|
+
odyssey = EpicPoem.create!(:title => "Odyssey", :author => "Homer")
|
218
|
+
rose = Flower.create!(:color => "Red")
|
219
|
+
PgSearch.multisearch("Homer") #=> [#<PgSearch::Document searchable: odyssey>]
|
220
|
+
PgSearch.multisearch("Red") #=> [#<PgSearch::Document searchable: rose>]
|
221
|
+
```
|
210
222
|
|
211
223
|
#### Chaining method calls onto the results
|
212
224
|
|
@@ -215,12 +227,14 @@ so you can chain scope calls to the end. This works with gems like Kaminari
|
|
215
227
|
that add scope methods. Just like with regular scopes, the database will only
|
216
228
|
receive SQL requests when necessary.
|
217
229
|
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
230
|
+
```ruby
|
231
|
+
PgSearch.multisearch("Bertha").limit(10)
|
232
|
+
PgSearch.multisearch("Juggler").where(:searchable_type => "Occupation")
|
233
|
+
PgSearch.multisearch("Alamo").page(3).per_page(30)
|
234
|
+
PgSearch.multisearch("Diagonal").find_each do |document|
|
235
|
+
puts document.searchable.updated_at
|
236
|
+
end
|
237
|
+
```
|
224
238
|
|
225
239
|
#### Configuring multi-search
|
226
240
|
|
@@ -228,10 +242,12 @@ PgSearch.multisearch can be configured using the same options as
|
|
228
242
|
`pg_search_scope` (explained in more detail below). Just set the
|
229
243
|
PgSearch.multisearch_options in an initializer:
|
230
244
|
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
245
|
+
```ruby
|
246
|
+
PgSearch.multisearch_options = {
|
247
|
+
:using => [:tsearch, :trigram],
|
248
|
+
:ignoring => :accents
|
249
|
+
}
|
250
|
+
```
|
235
251
|
|
236
252
|
#### Rebuilding search documents for a given class
|
237
253
|
|
@@ -249,11 +265,15 @@ directly modify the database.
|
|
249
265
|
To remove all of the documents for a given class, you can simply delete all of
|
250
266
|
the PgSearch::Document records.
|
251
267
|
|
252
|
-
|
268
|
+
```ruby
|
269
|
+
PgSearch::Document.delete_all(:searchable_type => "Animal")
|
270
|
+
```
|
253
271
|
|
254
272
|
To regenerate the documents for a given class, run:
|
255
273
|
|
256
|
-
|
274
|
+
```ruby
|
275
|
+
PgSearch::Multisearch.rebuild(Product)
|
276
|
+
```
|
257
277
|
|
258
278
|
This is also available as a Rake task, for convenience.
|
259
279
|
|
@@ -271,41 +291,45 @@ Active Record attributes, an efficient single SQL statement is run to update
|
|
271
291
|
the pg_search_documents table all at once. However, if you call any dynamic
|
272
292
|
methods in :against, the following strategy will be used:
|
273
293
|
|
274
|
-
|
275
|
-
|
294
|
+
```ruby
|
295
|
+
PgSearch::Document.delete_all(:searchable_type => "Ingredient")
|
296
|
+
Ingredient.find_each { |record| record.update_pg_search_document }
|
297
|
+
```
|
276
298
|
|
277
299
|
You can also provide a custom implementation for rebuilding the documents by
|
278
300
|
adding a class method called `rebuild_pg_search_documents` to your model.
|
279
301
|
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
302
|
+
```ruby
|
303
|
+
class Movie < ActiveRecord::Base
|
304
|
+
belongs_to :director
|
305
|
+
|
306
|
+
def director_name
|
307
|
+
director.name
|
308
|
+
end
|
309
|
+
|
310
|
+
multisearchable against: [:name, :director_name]
|
311
|
+
|
312
|
+
# Naive approach
|
313
|
+
def self.rebuild_pg_search_documents
|
314
|
+
find_each { |record| record.update_pg_search_document }
|
315
|
+
end
|
316
|
+
|
317
|
+
# More sophisticated approach
|
318
|
+
def self.rebuild_pg_search_documents
|
319
|
+
connection.execute <<-SQL
|
320
|
+
INSERT INTO pg_search_documents (searchable_type, searchable_id, content, created_at, updated_at)
|
321
|
+
SELECT 'Movie' AS searchable_type,
|
322
|
+
movies.id AS searchable_id,
|
323
|
+
(movies.name || ' ' || directors.name) AS content,
|
324
|
+
now() AS created_at,
|
325
|
+
now() AS updated_at
|
326
|
+
FROM movies
|
327
|
+
LEFT JOIN directors
|
328
|
+
ON directors.id = movies.director_id
|
329
|
+
SQL
|
330
|
+
end
|
331
|
+
end
|
332
|
+
```
|
309
333
|
|
310
334
|
#### Disabling multi-search indexing temporarily
|
311
335
|
|
@@ -314,9 +338,11 @@ records from an external source, you might want to speed things up by turning
|
|
314
338
|
off indexing temporarily. You could then use one of the techniques above to
|
315
339
|
rebuild the search documents off-line.
|
316
340
|
|
317
|
-
|
318
|
-
|
319
|
-
|
341
|
+
```ruby
|
342
|
+
PgSearch.disable_multisearch do
|
343
|
+
Movie.import_from_xml_file(File.open("movies.xml"))
|
344
|
+
end
|
345
|
+
```
|
320
346
|
|
321
347
|
### pg_search_scope
|
322
348
|
|
@@ -329,34 +355,42 @@ search against.
|
|
329
355
|
|
330
356
|
To search against a column, pass a symbol as the :against option.
|
331
357
|
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
358
|
+
```ruby
|
359
|
+
class BlogPost < ActiveRecord::Base
|
360
|
+
include PgSearch
|
361
|
+
pg_search_scope :search_by_title, :against => :title
|
362
|
+
end
|
363
|
+
```
|
336
364
|
|
337
365
|
We now have an ActiveRecord scope named search_by_title on our BlogPost model.
|
338
366
|
It takes one parameter, a search query string.
|
339
367
|
|
340
|
-
|
341
|
-
|
342
|
-
|
368
|
+
```ruby
|
369
|
+
BlogPost.create!(:title => "Recent Developments in the World of Pastrami")
|
370
|
+
BlogPost.create!(:title => "Prosciutto and You: A Retrospective")
|
371
|
+
BlogPost.search_by_title("pastrami") # => [#<BlogPost id: 2, title: "Recent Developments in the World of Pastrami">]
|
372
|
+
```
|
343
373
|
|
344
374
|
#### Searching against multiple columns
|
345
375
|
|
346
376
|
Just pass an Array if you'd like to search more than one column.
|
347
377
|
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
378
|
+
```ruby
|
379
|
+
class Person < ActiveRecord::Base
|
380
|
+
include PgSearch
|
381
|
+
pg_search_scope :search_by_full_name, :against => [:first_name, :last_name]
|
382
|
+
end
|
383
|
+
```
|
352
384
|
|
353
385
|
Now our search query can match either or both of the columns.
|
354
386
|
|
355
|
-
|
356
|
-
|
387
|
+
```ruby
|
388
|
+
person_1 = Person.create!(:first_name => "Grant", :last_name => "Hill")
|
389
|
+
person_2 = Person.create!(:first_name => "Hugh", :last_name => "Grant")
|
357
390
|
|
358
|
-
|
359
|
-
|
391
|
+
Person.search_by_full_name("Grant") # => [person_1, person_2]
|
392
|
+
Person.search_by_full_name("Grant Hill") # => [person_1]
|
393
|
+
```
|
360
394
|
|
361
395
|
#### Dynamic search scopes
|
362
396
|
|
@@ -368,22 +402,24 @@ Important: The returned hash must include a :query key. Its value does not
|
|
368
402
|
necessary have to be dynamic. You could choose to hard-code it to a specific
|
369
403
|
value if you wanted.
|
370
404
|
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
405
|
+
```ruby
|
406
|
+
class Person < ActiveRecord::Base
|
407
|
+
include PgSearch
|
408
|
+
pg_search_scope :search_by_name, lambda do |name_part, query|
|
409
|
+
raise ArgumentError unless [:first, :last].include?(name_part)
|
410
|
+
{
|
411
|
+
:against => name_part,
|
412
|
+
:query => query
|
413
|
+
}
|
414
|
+
end
|
415
|
+
end
|
381
416
|
|
382
|
-
|
383
|
-
|
417
|
+
person_1 = Person.create!(:first_name => "Grant", :last_name => "Hill")
|
418
|
+
person_2 = Person.create!(:first_name => "Hugh", :last_name => "Grant")
|
384
419
|
|
385
|
-
|
386
|
-
|
420
|
+
Person.search_by_name :first, "Grant" # => [person_1]
|
421
|
+
Person.search_by_name :last, "Grant" # => [person_2]
|
422
|
+
```
|
387
423
|
|
388
424
|
#### Searching through associations
|
389
425
|
|
@@ -406,38 +442,40 @@ works just like an :against option for the other model. Right now, searching
|
|
406
442
|
deeper than one association away is not supported. You can work around this by
|
407
443
|
setting up a series of :through associations to point all the way through.
|
408
444
|
|
409
|
-
|
410
|
-
|
411
|
-
|
445
|
+
```ruby
|
446
|
+
class Cracker < ActiveRecord::Base
|
447
|
+
has_many :cheeses
|
448
|
+
end
|
412
449
|
|
413
|
-
|
414
|
-
|
450
|
+
class Cheese < ActiveRecord::Base
|
451
|
+
end
|
415
452
|
|
416
|
-
|
417
|
-
|
453
|
+
class Salami < ActiveRecord::Base
|
454
|
+
include PgSearch
|
418
455
|
|
419
|
-
|
420
|
-
|
456
|
+
belongs_to :cracker
|
457
|
+
has_many :cheeses, :through => :cracker
|
421
458
|
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
459
|
+
pg_search_scope :tasty_search, :associated_against => {
|
460
|
+
:cheeses => [:kind, :brand],
|
461
|
+
:cracker => :kind
|
462
|
+
}
|
463
|
+
end
|
427
464
|
|
428
|
-
|
429
|
-
|
430
|
-
|
465
|
+
salami_1 = Salami.create!
|
466
|
+
salami_2 = Salami.create!
|
467
|
+
salami_3 = Salami.create!
|
431
468
|
|
432
|
-
|
433
|
-
|
434
|
-
|
469
|
+
limburger = Cheese.create!(:kind => "Limburger")
|
470
|
+
brie = Cheese.create!(:kind => "Brie")
|
471
|
+
pepper_jack = Cheese.create!(:kind => "Pepper Jack")
|
435
472
|
|
436
|
-
|
437
|
-
|
438
|
-
|
473
|
+
Cracker.create!(:kind => "Black Pepper", :cheeses => [brie], :salami => salami_1)
|
474
|
+
Cracker.create!(:kind => "Ritz", :cheeses => [limburger, pepper_jack], :salami => salami_2)
|
475
|
+
Cracker.create!(:kind => "Graham", :cheeses => [limburger], :salami => salami_3)
|
439
476
|
|
440
|
-
|
477
|
+
Salami.tasty_search("pepper") # => [salami_1, salami_2]
|
478
|
+
```
|
441
479
|
|
442
480
|
### Searching using different search features
|
443
481
|
|
@@ -446,10 +484,12 @@ search](http://www.postgresql.org/docs/current/static/textsearch-intro.html).
|
|
446
484
|
If you pass the :using option to pg_search_scope, you can choose alternative
|
447
485
|
search techniques.
|
448
486
|
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
487
|
+
```ruby
|
488
|
+
class Beer < ActiveRecord::Base
|
489
|
+
include PgSearch
|
490
|
+
pg_search_scope :search_name, :against => :name, :using => [:tsearch, :trigram, :dmetaphone]
|
491
|
+
end
|
492
|
+
```
|
453
493
|
|
454
494
|
The currently implemented features are
|
455
495
|
|
@@ -471,36 +511,40 @@ with earlier letters are weighted higher than those with later letters. So, in
|
|
471
511
|
the following example, the title is the most important, followed by the
|
472
512
|
subtitle, and finally the content.
|
473
513
|
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
514
|
+
```ruby
|
515
|
+
class NewsArticle < ActiveRecord::Base
|
516
|
+
include PgSearch
|
517
|
+
pg_search_scope :search_full_text, :against => {
|
518
|
+
:title => 'A',
|
519
|
+
:subtitle => 'B',
|
520
|
+
:content => 'C'
|
521
|
+
}
|
522
|
+
end
|
523
|
+
```
|
482
524
|
|
483
525
|
You can also pass the weights in as an array of arrays, or any other structure
|
484
526
|
that responds to #each and yields either a single symbol or a symbol and a
|
485
527
|
weight. If you omit the weight, a default will be used.
|
486
528
|
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
529
|
+
```ruby
|
530
|
+
class NewsArticle < ActiveRecord::Base
|
531
|
+
include PgSearch
|
532
|
+
pg_search_scope :search_full_text, :against => [
|
533
|
+
[:title, 'A'],
|
534
|
+
[:subtitle, 'B'],
|
535
|
+
[:content, 'C']
|
536
|
+
]
|
537
|
+
end
|
538
|
+
|
539
|
+
class NewsArticle < ActiveRecord::Base
|
540
|
+
include PgSearch
|
541
|
+
pg_search_scope :search_full_text, :against => [
|
542
|
+
[:title, 'A'],
|
543
|
+
{:subtitle => 'B'},
|
544
|
+
:content
|
545
|
+
]
|
546
|
+
end
|
547
|
+
```
|
504
548
|
|
505
549
|
##### :prefix (PostgreSQL 8.4 and newer only)
|
506
550
|
|
@@ -509,20 +553,22 @@ to search for partial words, however, you can set :prefix to true. Since this
|
|
509
553
|
is a :tsearch-specific option, you should pass it to :tsearch directly, as
|
510
554
|
shown in the following example.
|
511
555
|
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
556
|
+
```ruby
|
557
|
+
class Superhero < ActiveRecord::Base
|
558
|
+
include PgSearch
|
559
|
+
pg_search_scope :whose_name_starts_with,
|
560
|
+
:against => :name,
|
561
|
+
:using => {
|
562
|
+
:tsearch => {:prefix => true}
|
563
|
+
}
|
564
|
+
end
|
520
565
|
|
521
|
-
|
522
|
-
|
523
|
-
|
566
|
+
batman = Superhero.create :name => 'Batman'
|
567
|
+
batgirl = Superhero.create :name => 'Batgirl'
|
568
|
+
robin = Superhero.create :name => 'Robin'
|
524
569
|
|
525
|
-
|
570
|
+
Superhero.whose_name_starts_with("Bat") # => [batman, batgirl]
|
571
|
+
```
|
526
572
|
|
527
573
|
##### :dictionary
|
528
574
|
|
@@ -535,26 +581,28 @@ you don't want stemming, you should pick the "simple" dictionary which does
|
|
535
581
|
not do any stemming. If you don't specify a dictionary, the "simple"
|
536
582
|
dictionary will be used.
|
537
583
|
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
584
|
+
```ruby
|
585
|
+
class BoringTweet < ActiveRecord::Base
|
586
|
+
include PgSearch
|
587
|
+
pg_search_scope :kinda_matching,
|
588
|
+
:against => :text,
|
589
|
+
:using => {
|
590
|
+
:tsearch => {:dictionary => "english"}
|
591
|
+
}
|
592
|
+
pg_search_scope :literally_matching,
|
593
|
+
:against => :text,
|
594
|
+
:using => {
|
595
|
+
:tsearch => {:dictionary => "simple"}
|
596
|
+
}
|
597
|
+
end
|
598
|
+
|
599
|
+
sleepy = BoringTweet.create! :text => "I snoozed my alarm for fourteen hours today. I bet I can beat that tomorrow! #sleepy"
|
600
|
+
sleeping = BoringTweet.create! :text => "You know what I like? Sleeping. That's what. #enjoyment"
|
601
|
+
sleeper = BoringTweet.create! :text => "Have you seen Woody Allen's movie entitled Sleeper? Me neither. #boycott"
|
602
|
+
|
603
|
+
BoringTweet.kinda_matching("sleeping") # => [sleepy, sleeping, sleeper]
|
604
|
+
BoringTweet.literally_matching("sleeping") # => [sleeping]
|
605
|
+
```
|
558
606
|
|
559
607
|
##### :normalization
|
560
608
|
|
@@ -577,46 +625,50 @@ This integer is a bitmask, so if you want to combine algorithms, you can add
|
|
577
625
|
their numbers together.
|
578
626
|
(e.g. to use algorithms 1, 8, and 32, you would pass 1 + 8 + 32 = 41)
|
579
627
|
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
628
|
+
```ruby
|
629
|
+
class BigLongDocument < ActiveRecord::Base
|
630
|
+
include PgSearch
|
631
|
+
pg_search_scope :regular_search,
|
632
|
+
:against => :text
|
584
633
|
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
634
|
+
pg_search_scope :short_search,
|
635
|
+
:against => :text,
|
636
|
+
:using => {
|
637
|
+
:tsearch => {:normalization => 2}
|
638
|
+
}
|
590
639
|
|
591
|
-
|
592
|
-
|
640
|
+
long = BigLongDocument.create!(:text => "Four score and twenty years ago")
|
641
|
+
short = BigLongDocument.create!(:text => "Four score")
|
593
642
|
|
594
|
-
|
595
|
-
|
643
|
+
BigLongDocument.regular_search("four score") #=> [long, short]
|
644
|
+
BigLongDocument.short_search("four score") #=> [short, long]
|
645
|
+
```
|
596
646
|
|
597
647
|
##### :any_word
|
598
648
|
|
599
649
|
Setting this attribute to true will perform a search which will return all
|
600
650
|
models containing any word in the search terms.
|
601
651
|
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
652
|
+
```ruby
|
653
|
+
class Number < ActiveRecord::Base
|
654
|
+
include PgSearch
|
655
|
+
pg_search_scope :search_any_word,
|
656
|
+
:against => :text,
|
657
|
+
:using => {
|
658
|
+
:tsearch => {:any_word => true}
|
659
|
+
}
|
609
660
|
|
610
|
-
|
611
|
-
|
612
|
-
|
661
|
+
pg_search_scope :search_all_words,
|
662
|
+
:against => :text
|
663
|
+
end
|
613
664
|
|
614
|
-
|
615
|
-
|
616
|
-
|
665
|
+
one = Number.create! :text => 'one'
|
666
|
+
two = Number.create! :text => 'two'
|
667
|
+
three = Number.create! :text => 'three'
|
617
668
|
|
618
|
-
|
619
|
-
|
669
|
+
Number.search_any_word('one two three') # => [one, two, three]
|
670
|
+
Number.search_all_words('one two three') # => []
|
671
|
+
```
|
620
672
|
|
621
673
|
#### :dmetaphone (Double Metaphone soundalike search)
|
622
674
|
|
@@ -637,19 +689,21 @@ generate and run a migration for this, run:
|
|
637
689
|
|
638
690
|
The following example shows how to use :dmetaphone.
|
639
691
|
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
|
644
|
-
|
645
|
-
|
692
|
+
```ruby
|
693
|
+
class Word < ActiveRecord::Base
|
694
|
+
include PgSearch
|
695
|
+
pg_search_scope :that_sounds_like,
|
696
|
+
:against => :spelling,
|
697
|
+
:using => :dmetaphone
|
698
|
+
end
|
646
699
|
|
647
|
-
|
648
|
-
|
649
|
-
|
650
|
-
|
700
|
+
four = Word.create! :spelling => 'four'
|
701
|
+
far = Word.create! :spelling => 'far'
|
702
|
+
fur = Word.create! :spelling => 'fur'
|
703
|
+
five = Word.create! :spelling => 'five'
|
651
704
|
|
652
|
-
|
705
|
+
Word.that_sounds_like("fir") # => [four, far, fur]
|
706
|
+
```
|
653
707
|
|
654
708
|
#### :trigram (Trigram search)
|
655
709
|
|
@@ -666,19 +720,21 @@ Trigram support is currently available as part of the [pg_trgm contrib
|
|
666
720
|
package](http://www.postgresql.org/docs/current/static/pgtrgm.html) that must
|
667
721
|
be installed before this feature can be used.
|
668
722
|
|
669
|
-
|
670
|
-
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
|
723
|
+
```ruby
|
724
|
+
class Website < ActiveRecord::Base
|
725
|
+
include PgSearch
|
726
|
+
pg_search_scope :kinda_spelled_like,
|
727
|
+
:against => :name,
|
728
|
+
:using => :trigram
|
729
|
+
end
|
675
730
|
|
676
|
-
|
677
|
-
|
678
|
-
|
679
|
-
|
731
|
+
yahooo = Website.create! :name => "Yahooo!"
|
732
|
+
yohoo = Website.create! :name => "Yohoo!"
|
733
|
+
gogle = Website.create! :name => "Gogle"
|
734
|
+
facebook = Website.create! :name => "Facebook"
|
680
735
|
|
681
|
-
|
736
|
+
Website.kinda_spelled_like("Yahoo!") # => [yahooo, yohoo]
|
737
|
+
```
|
682
738
|
|
683
739
|
##### :threshold
|
684
740
|
|
@@ -687,34 +743,35 @@ using pg_trgm's calculations. You may specify a custom threshold if you prefer.
|
|
687
743
|
Higher numbers match more strictly, and thus return fewer results. Lower numbers
|
688
744
|
match more permissively, letting in more results.
|
689
745
|
|
690
|
-
|
691
|
-
|
746
|
+
```ruby
|
747
|
+
class Vegetable < ActiveRecord::Base
|
748
|
+
include PgSearch
|
692
749
|
|
693
|
-
|
694
|
-
|
695
|
-
|
696
|
-
|
697
|
-
|
698
|
-
|
699
|
-
|
700
|
-
|
701
|
-
pg_search_scope :roughly_spelled_like,
|
702
|
-
:against => :name,
|
703
|
-
:using => {
|
704
|
-
:trigram => {
|
705
|
-
:threshold => 0.1
|
706
|
-
}
|
707
|
-
}
|
708
|
-
end
|
750
|
+
pg_search_scope :strictly_spelled_like,
|
751
|
+
:against => :name,
|
752
|
+
:using => {
|
753
|
+
:trigram => {
|
754
|
+
:threshold => 0.5
|
755
|
+
}
|
756
|
+
}
|
709
757
|
|
710
|
-
|
758
|
+
pg_search_scope :roughly_spelled_like,
|
759
|
+
:against => :name,
|
760
|
+
:using => {
|
761
|
+
:trigram => {
|
762
|
+
:threshold => 0.1
|
763
|
+
}
|
764
|
+
}
|
765
|
+
end
|
711
766
|
|
712
|
-
|
713
|
-
Vegetable.strictly_spelled_like("couliflower") # => [cauliflower]
|
767
|
+
cauliflower = Vegetable.create! :name => "cauliflower"
|
714
768
|
|
715
|
-
|
716
|
-
|
769
|
+
Vegetable.roughly_spelled_like("couliflower") # => [cauliflower]
|
770
|
+
Vegetable.strictly_spelled_like("couliflower") # => [cauliflower]
|
717
771
|
|
772
|
+
Vegetable.roughly_spelled_like("collyflower") # => [cauliflower]
|
773
|
+
Vegetable.strictly_spelled_like("collyflower") # => []
|
774
|
+
```
|
718
775
|
|
719
776
|
### Ignoring accent marks (PostgreSQL 9.0 and newer only)
|
720
777
|
|
@@ -727,19 +784,21 @@ Ignoring accents uses the [unaccent contrib
|
|
727
784
|
package](http://www.postgresql.org/docs/current/static/unaccent.html) that
|
728
785
|
must be installed before this feature can be used.
|
729
786
|
|
730
|
-
|
731
|
-
|
732
|
-
|
733
|
-
|
734
|
-
|
735
|
-
|
787
|
+
```ruby
|
788
|
+
class SpanishQuestion < ActiveRecord::Base
|
789
|
+
include PgSearch
|
790
|
+
pg_search_scope :gringo_search,
|
791
|
+
:against => :word,
|
792
|
+
:ignoring => :accents
|
793
|
+
end
|
736
794
|
|
737
|
-
|
738
|
-
|
739
|
-
|
795
|
+
what = SpanishQuestion.create(:word => "Qué")
|
796
|
+
how_many = SpanishQuestion.create(:word => "Cuánto")
|
797
|
+
how = SpanishQuestion.create(:word => "Cómo")
|
740
798
|
|
741
|
-
|
742
|
-
|
799
|
+
SpanishQuestion.gringo_search("Que") # => [what]
|
800
|
+
SpanishQuestion.gringo_search("Cüåñtô") # => [how_many]
|
801
|
+
```
|
743
802
|
|
744
803
|
Advanced users may wish to add indexes for the expressions that pg_search
|
745
804
|
generates. Unfortunately, the unaccent function supplied by this contrib
|
@@ -747,7 +806,9 @@ package is not indexable (as of PostgreSQL 9.1). Thus, you may want to write
|
|
747
806
|
your own wrapper function and use it instead. This can be configured by
|
748
807
|
calling the following code, perhaps in an initializer.
|
749
808
|
|
750
|
-
|
809
|
+
```ruby
|
810
|
+
PgSearch.unaccent_function = "my_unaccent"
|
811
|
+
```
|
751
812
|
|
752
813
|
### Using tsvector columns
|
753
814
|
|
@@ -800,21 +861,25 @@ By default, pg_search ranks results based on the :tsearch similarity between
|
|
800
861
|
the searchable text and the query. To use a different ranking algorithm, you
|
801
862
|
can pass a :ranked_by option to pg_search_scope.
|
802
863
|
|
803
|
-
|
804
|
-
|
805
|
-
|
806
|
-
|
864
|
+
```ruby
|
865
|
+
pg_search_scope :search_by_tsearch_but_rank_by_trigram,
|
866
|
+
:against => :title,
|
867
|
+
:using => [:tsearch],
|
868
|
+
:ranked_by => ":trigram"
|
869
|
+
```
|
807
870
|
|
808
871
|
Note that :ranked_by using a String to represent the ranking expression. This
|
809
872
|
allows for more complex possibilities. Strings like ":tsearch", ":trigram",
|
810
873
|
and ":dmetaphone" are automatically expanded into the appropriate SQL
|
811
874
|
expressions.
|
812
875
|
|
813
|
-
|
814
|
-
|
876
|
+
```ruby
|
877
|
+
# Weighted ranking to balance multiple approaches
|
878
|
+
:ranked_by => ":dmetaphone + (0.25 * :trigram)"
|
815
879
|
|
816
|
-
|
817
|
-
|
880
|
+
# A more complex example, where books.num_pages is an integer column in the table itself
|
881
|
+
:ranked_by => "(books.num_pages * :trigram) + (:tsearch / 2.0)"
|
882
|
+
```
|
818
883
|
|
819
884
|
#### :order_within_rank (Breaking ties)
|
820
885
|
|
@@ -843,9 +908,11 @@ want old records to outrank new records. By passing an :order_within_rank, you
|
|
843
908
|
can specify an alternate tiebreaker expression. A common example would be
|
844
909
|
descending by updated_at, to rank the most recently updated records first.
|
845
910
|
|
846
|
-
|
847
|
-
|
848
|
-
|
911
|
+
```ruby
|
912
|
+
pg_search_scope :search_and_break_ties_by_latest_update,
|
913
|
+
:against => [:title, :content],
|
914
|
+
:order_within_rank => "blog_posts.updated_at DESC"
|
915
|
+
````
|
849
916
|
|
850
917
|
#### PgSearch#pg_search_rank (Reading a record's rank as a Float)
|
851
918
|
|
@@ -854,9 +921,11 @@ can be helpful for debugging why one record outranks another. You could also
|
|
854
921
|
use it to show some sort of relevancy value to end users of an application.
|
855
922
|
Just call .pg_search_rank on a record returned by a pg_search_scope.
|
856
923
|
|
857
|
-
|
858
|
-
|
859
|
-
|
924
|
+
```ruby
|
925
|
+
shirt_brands = ShirtBrand.search_by_name("Penguin")
|
926
|
+
shirt_brands[0].pg_search_rank #=> 0.0759909
|
927
|
+
shirt_brands[1].pg_search_rank #=> 0.0607927
|
928
|
+
```
|
860
929
|
|
861
930
|
## ATTRIBUTIONS
|
862
931
|
|