pg_search 0.7.3 → 0.7.4
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 +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
|
|