pg_search 2.1.2 → 2.3.6

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.
Files changed (66) hide show
  1. checksums.yaml +5 -5
  2. data/.codeclimate.yml +1 -0
  3. data/.editorconfig +10 -0
  4. data/.github/dependabot.yml +11 -0
  5. data/.github/workflows/ci.yml +75 -0
  6. data/.jrubyrc +1 -0
  7. data/.rubocop.yml +95 -10
  8. data/.travis.yml +26 -48
  9. data/CHANGELOG.md +179 -112
  10. data/CODE_OF_CONDUCT.md +76 -0
  11. data/CONTRIBUTING.md +5 -3
  12. data/Gemfile +6 -4
  13. data/LICENSE +1 -1
  14. data/README.md +307 -198
  15. data/Rakefile +7 -3
  16. data/lib/pg_search/configuration/association.rb +2 -0
  17. data/lib/pg_search/configuration/column.rb +2 -0
  18. data/lib/pg_search/configuration/foreign_column.rb +2 -0
  19. data/lib/pg_search/configuration.rb +20 -6
  20. data/lib/pg_search/document.rb +7 -5
  21. data/lib/pg_search/features/dmetaphone.rb +7 -7
  22. data/lib/pg_search/features/feature.rb +4 -2
  23. data/lib/pg_search/features/trigram.rb +31 -5
  24. data/lib/pg_search/features/tsearch.rb +18 -14
  25. data/lib/pg_search/features.rb +2 -0
  26. data/lib/pg_search/migration/dmetaphone_generator.rb +3 -1
  27. data/lib/pg_search/migration/generator.rb +4 -2
  28. data/lib/pg_search/migration/multisearch_generator.rb +2 -1
  29. data/lib/pg_search/migration/templates/add_pg_search_dmetaphone_support_functions.rb.erb +6 -6
  30. data/lib/pg_search/migration/templates/create_pg_search_documents.rb.erb +3 -3
  31. data/lib/pg_search/model.rb +57 -0
  32. data/lib/pg_search/multisearch/rebuilder.rb +14 -6
  33. data/lib/pg_search/multisearch.rb +23 -6
  34. data/lib/pg_search/multisearchable.rb +10 -6
  35. data/lib/pg_search/normalizer.rb +2 -0
  36. data/lib/pg_search/railtie.rb +2 -0
  37. data/lib/pg_search/scope_options.rb +26 -49
  38. data/lib/pg_search/tasks.rb +5 -1
  39. data/lib/pg_search/version.rb +3 -1
  40. data/lib/pg_search.rb +17 -55
  41. data/pg_search.gemspec +19 -11
  42. data/spec/.rubocop.yml +2 -2
  43. data/spec/integration/.rubocop.yml +11 -0
  44. data/spec/integration/associations_spec.rb +125 -162
  45. data/spec/integration/deprecation_spec.rb +33 -0
  46. data/spec/integration/pagination_spec.rb +10 -8
  47. data/spec/integration/pg_search_spec.rb +359 -306
  48. data/spec/integration/single_table_inheritance_spec.rb +18 -17
  49. data/spec/lib/pg_search/configuration/association_spec.rb +17 -13
  50. data/spec/lib/pg_search/configuration/column_spec.rb +2 -0
  51. data/spec/lib/pg_search/configuration/foreign_column_spec.rb +6 -4
  52. data/spec/lib/pg_search/features/dmetaphone_spec.rb +6 -4
  53. data/spec/lib/pg_search/features/trigram_spec.rb +51 -20
  54. data/spec/lib/pg_search/features/tsearch_spec.rb +29 -21
  55. data/spec/lib/pg_search/multisearch/rebuilder_spec.rb +151 -85
  56. data/spec/lib/pg_search/multisearch_spec.rb +67 -37
  57. data/spec/lib/pg_search/multisearchable_spec.rb +217 -123
  58. data/spec/lib/pg_search/normalizer_spec.rb +14 -10
  59. data/spec/lib/pg_search_spec.rb +102 -89
  60. data/spec/spec_helper.rb +25 -6
  61. data/spec/support/database.rb +19 -21
  62. data/spec/support/with_model.rb +2 -0
  63. metadata +106 -29
  64. data/.autotest +0 -5
  65. data/.rubocop_todo.yml +0 -163
  66. data/Guardfile +0 -6
data/README.md CHANGED
@@ -1,25 +1,20 @@
1
1
  # [pg_search](http://github.com/Casecommons/pg_search/)
2
2
 
3
3
  [![Gem Version](https://img.shields.io/gem/v/pg_search.svg?style=flat)](https://rubygems.org/gems/pg_search)
4
- [![Build Status](https://secure.travis-ci.org/Casecommons/pg_search.svg?branch=master)](https://travis-ci.org/Casecommons/pg_search)
5
- [![Code Climate](https://img.shields.io/codeclimate/github/Casecommons/pg_search.svg?style=flat)](https://codeclimate.com/github/Casecommons/pg_search)
6
- [![Test Coverage](https://codeclimate.com/github/Casecommons/pg_search/badges/coverage.svg)](https://codeclimate.com/github/Casecommons/pg_search/coverage)
7
- [![Dependency Status](https://img.shields.io/gemnasium/Casecommons/pg_search.svg?style=flat)](https://gemnasium.com/Casecommons/pg_search)
8
- [![Inline docs](http://inch-ci.org/github/Casecommons/pg_search.svg?branch=master&style=flat)](http://inch-ci.org/github/Casecommons/pg_search)
4
+ [![Build Status](https://github.com/Casecommons/pg_search/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/Casecommons/pg_search/actions/workflows/ci.yml)
9
5
  [![Join the chat at https://gitter.im/Casecommons/pg_search](https://img.shields.io/badge/gitter-join%20chat-blue.svg)](https://gitter.im/Casecommons/pg_search?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
10
- [![Stories in Ready](https://img.shields.io/waffle/label/Casecommons/pg_search/in%20progress.svg)](https://waffle.io/Casecommons/pg_search)
11
6
 
12
7
  ## DESCRIPTION
13
8
 
14
9
  PgSearch builds named scopes that take advantage of PostgreSQL's full text
15
10
  search.
16
11
 
17
- Read the blog post introducing PgSearch at https://content.pivotal.io/blog/pg-search-how-i-learned-to-stop-worrying-and-love-postgresql-full-text-search
12
+ Read the blog post introducing PgSearch at https://tanzu.vmware.com/content/blog/pg-search-how-i-learned-to-stop-worrying-and-love-postgresql-full-text-search
18
13
 
19
14
  ## REQUIREMENTS
20
15
 
21
- * Ruby 2.1+
22
- * ActiveRecord 4.2+
16
+ * Ruby 2.6+
17
+ * ActiveRecord 5.2+
23
18
  * PostgreSQL 9.2+
24
19
  * [PostgreSQL extensions](https://github.com/Casecommons/pg_search/wiki/Installing-PostgreSQL-Extensions) for certain features
25
20
 
@@ -51,10 +46,51 @@ To add PgSearch to an Active Record model, simply include the PgSearch module.
51
46
 
52
47
  ```ruby
53
48
  class Shape < ActiveRecord::Base
54
- include PgSearch
49
+ include PgSearch::Model
55
50
  end
56
51
  ```
57
52
 
53
+ ### Contents
54
+ * [Multi-search vs. search scopes](#multi-search-vs-search-scopes)
55
+ * [Multi-search](#multi-search)
56
+ * [Setup](#setup)
57
+ * [`multisearchable`](#multisearchable)
58
+ * [More Options ](#more-options)
59
+ * [Multi-search associations](#multi-search-associations)
60
+ * [Searching in the global search index](#searching-in-the-global-search-index)
61
+ * [Chaining method calls onto the results](#chaining-method-calls-onto-the-results)
62
+ * [Configuring multi-search](#configuring-multi-search)
63
+ * [Rebuilding search documents for a given class](#rebuilding-search-documents-for-a-given-class)
64
+ * [Disabling multi-search indexing temporarily](#disabling-multi-search-indexing-temporarily)
65
+ * [`pg_search_scope`](#pg_search_scope)
66
+ * [Searching against one column](#searching-against-one-column)
67
+ * [Searching against multiple columns](#searching-against-multiple-columns)
68
+ * [Dynamic search scopes](#dynamic-search-scopes)
69
+ * [Searching through associations](#searching-through-associations)
70
+ * [Searching using different search features](#searching-using-different-search-features)
71
+ * [`:tsearch` (Full Text Search)](#tsearch-full-text-search)
72
+ * [Weighting](#weighting)
73
+ * [`:prefix` (PostgreSQL 8.4 and newer only)](#prefix-postgresql-84-and-newer-only)
74
+ * [`:negation`](#negation)
75
+ * [`:dictionary`](#dictionary)
76
+ * [`:normalization`](#normalization)
77
+ * [`:any_word`](#any_word)
78
+ * [`:sort_only`](#sort_only)
79
+ * [`:highlight`](#highlight)
80
+ * [`:dmetaphone` (Double Metaphone soundalike search)](#dmetaphone-double-metaphone-soundalike-search)
81
+ * [`:trigram` (Trigram search)](#trigram-trigram-search)
82
+ * [`:threshold`](#threshold)
83
+ * [`:word_similarity`](#word_similarity)
84
+ * [Limiting Fields When Combining Features](#limiting-fields-when-combining-features)
85
+ * [Ignoring accent marks](#ignoring-accent-marks)
86
+ * [Using tsvector columns](#using-tsvector-columns)
87
+ * [Combining multiple tsvectors](#combining-multiple-tsvectors)
88
+ * [Configuring ranking and ordering](#configuring-ranking-and-ordering)
89
+ * [`:ranked_by` (Choosing a ranking algorithm)](#ranked_by-choosing-a-ranking-algorithm)
90
+ * [`:order_within_rank` (Breaking ties)](#order_within_rank-breaking-ties)
91
+ * [`PgSearch#pg_search_rank` (Reading a record's rank as a Float)](#pgsearchpg_search_rank-reading-a-records-rank-as-a-float)
92
+ * [Search rank and chained scopes](#search-rank-and-chained-scopes)
93
+
58
94
  ### Multi-search vs. search scopes
59
95
 
60
96
  pg_search supports two different techniques for searching, multi-search and
@@ -89,13 +125,13 @@ multisearchable in its class definition.
89
125
 
90
126
  ```ruby
91
127
  class EpicPoem < ActiveRecord::Base
92
- include PgSearch
93
- multisearchable :against => [:title, :author]
128
+ include PgSearch::Model
129
+ multisearchable against: [:title, :author]
94
130
  end
95
131
 
96
132
  class Flower < ActiveRecord::Base
97
- include PgSearch
98
- multisearchable :against => :color
133
+ include PgSearch::Model
134
+ multisearchable against: :color
99
135
  end
100
136
  ```
101
137
 
@@ -114,15 +150,15 @@ particular record should be included.
114
150
 
115
151
  ```ruby
116
152
  class Convertible < ActiveRecord::Base
117
- include PgSearch
118
- multisearchable :against => [:make, :model],
119
- :if => :available_in_red?
153
+ include PgSearch::Model
154
+ multisearchable against: [:make, :model],
155
+ if: :available_in_red?
120
156
  end
121
157
 
122
158
  class Jalopy < ActiveRecord::Base
123
- include PgSearch
124
- multisearchable :against => [:make, :model],
125
- :if => lambda { |record| record.model_year > 1970 }
159
+ include PgSearch::Model
160
+ multisearchable against: [:make, :model],
161
+ if: lambda { |record| record.model_year > 1970 }
126
162
  end
127
163
  ```
128
164
 
@@ -134,9 +170,9 @@ timestamp.
134
170
 
135
171
  ```ruby
136
172
  class AntipatternExample
137
- include PgSearch
138
- multisearchable :against => [:contents],
139
- :if => :published?
173
+ include PgSearch::Model
174
+ multisearchable against: [:contents],
175
+ if: :published?
140
176
 
141
177
  def published?
142
178
  published_at < Time.now
@@ -144,8 +180,8 @@ class AntipatternExample
144
180
  end
145
181
 
146
182
  problematic_record = AntipatternExample.create!(
147
- :contents => "Using :if with a timestamp",
148
- :published_at => 10.minutes.from_now
183
+ contents: "Using :if with a timestamp",
184
+ published_at: 10.minutes.from_now
149
185
  )
150
186
 
151
187
  problematic_record.published? # => false
@@ -162,6 +198,51 @@ problematic_record.published? # => true
162
198
  PgSearch.multisearch("timestamp") # => Includes problematic_record
163
199
  ```
164
200
 
201
+ #### More Options
202
+
203
+ **Conditionally update pg_search_documents**
204
+
205
+ You can specify an `:update_if` parameter to conditionally update pg_search documents. For example:
206
+
207
+ ```ruby
208
+ multisearchable(
209
+ against: [:body],
210
+ update_if: :body_changed?
211
+ )
212
+ ```
213
+
214
+ **Specify additional attributes to be saved on the pg_search_documents table**
215
+
216
+ You can specify `:additional_attributes` to be saved within the `pg_search_documents` table. For example, perhaps you are indexing a book model and an article model and wanted to include the author_id.
217
+
218
+ First, we need to add a reference to author to the migration creating our `pg_search_documents` table.
219
+
220
+ ```ruby
221
+ create_table :pg_search_documents do |t|
222
+ t.text :content
223
+ t.references :author, index: true
224
+ t.belongs_to :searchable, polymorphic: true, index: true
225
+ t.timestamps null: false
226
+ end
227
+ ```
228
+
229
+ Then, we can send in this additional attribute in a lambda
230
+
231
+ ```ruby
232
+ multisearchable(
233
+ against: [:title, :body],
234
+ additional_attributes: -> (article) { { author_id: article.author_id } }
235
+ )
236
+ ```
237
+
238
+ This allows much faster searches without joins later on by doing something like:
239
+
240
+ ```ruby
241
+ PgSearch.multisearch(params['search']).where(author_id: 2)
242
+ ```
243
+
244
+ *NOTE: You must currently manually call `record.update_pg_search_document` for the additional attribute to be included in the pg_search_documents table*
245
+
165
246
  #### Multi-search associations
166
247
 
167
248
  Two associations are built automatically. On the original record, there is a
@@ -170,7 +251,7 @@ record, and on the PgSearch::Document record there is a belongs_to :searchable
170
251
  polymorphic association pointing back to the original record.
171
252
 
172
253
  ```ruby
173
- odyssey = EpicPoem.create!(:title => "Odyssey", :author => "Homer")
254
+ odyssey = EpicPoem.create!(title: "Odyssey", author: "Homer")
174
255
  search_document = odyssey.pg_search_document #=> PgSearch::Document instance
175
256
  search_document.searchable #=> #<EpicPoem id: 1, title: "Odyssey", author: "Homer">
176
257
  ```
@@ -181,8 +262,8 @@ To fetch the PgSearch::Document entries for all of the records that match a
181
262
  given query, use PgSearch.multisearch.
182
263
 
183
264
  ```ruby
184
- odyssey = EpicPoem.create!(:title => "Odyssey", :author => "Homer")
185
- rose = Flower.create!(:color => "Red")
265
+ odyssey = EpicPoem.create!(title: "Odyssey", author: "Homer")
266
+ rose = Flower.create!(color: "Red")
186
267
  PgSearch.multisearch("Homer") #=> [#<PgSearch::Document searchable: odyssey>]
187
268
  PgSearch.multisearch("Red") #=> [#<PgSearch::Document searchable: rose>]
188
269
  ```
@@ -196,7 +277,7 @@ receive SQL requests when necessary.
196
277
 
197
278
  ```ruby
198
279
  PgSearch.multisearch("Bertha").limit(10)
199
- PgSearch.multisearch("Juggler").where(:searchable_type => "Occupation")
280
+ PgSearch.multisearch("Juggler").where(searchable_type: "Occupation")
200
281
  PgSearch.multisearch("Alamo").page(3).per(30)
201
282
  PgSearch.multisearch("Diagonal").find_each do |document|
202
283
  puts document.searchable.updated_at
@@ -213,8 +294,8 @@ PgSearch.multisearch_options in an initializer:
213
294
 
214
295
  ```ruby
215
296
  PgSearch.multisearch_options = {
216
- :using => [:tsearch, :trigram],
217
- :ignoring => :accents
297
+ using: [:tsearch, :trigram],
298
+ ignoring: :accents
218
299
  }
219
300
  ```
220
301
 
@@ -235,7 +316,7 @@ To remove all of the documents for a given class, you can simply delete all of
235
316
  the PgSearch::Document records.
236
317
 
237
318
  ```ruby
238
- PgSearch::Document.delete_all(:searchable_type => "Animal")
319
+ PgSearch::Document.delete_by(searchable_type: "Animal")
239
320
  ```
240
321
 
241
322
  To regenerate the documents for a given class, run:
@@ -251,7 +332,14 @@ is your base class. You can prevent ```rebuild``` from deleting your records
251
332
  like so:
252
333
 
253
334
  ```ruby
254
- PgSearch::Multisearch.rebuild(Product, false)
335
+ PgSearch::Multisearch.rebuild(Product, clean_up: false)
336
+ ```
337
+
338
+ ```rebuild``` runs inside a single transaction. To run outside of a transaction,
339
+ you can pass ```transactional: false``` like so:
340
+
341
+ ```ruby
342
+ PgSearch::Multisearch.rebuild(Product, transactional: false)
255
343
  ```
256
344
 
257
345
  Rebuild is also available as a Rake task, for convenience.
@@ -271,7 +359,7 @@ the pg_search_documents table all at once. However, if you call any dynamic
271
359
  methods in :against, the following strategy will be used:
272
360
 
273
361
  ```ruby
274
- PgSearch::Document.delete_all(:searchable_type => "Ingredient")
362
+ PgSearch::Document.delete_all(searchable_type: "Ingredient")
275
363
  Ingredient.find_each { |record| record.update_pg_search_document }
276
364
  ```
277
365
 
@@ -295,11 +383,11 @@ class Movie < ActiveRecord::Base
295
383
 
296
384
  # More sophisticated approach
297
385
  def self.rebuild_pg_search_documents
298
- connection.execute <<-SQL
386
+ connection.execute <<~SQL.squish
299
387
  INSERT INTO pg_search_documents (searchable_type, searchable_id, content, created_at, updated_at)
300
388
  SELECT 'Movie' AS searchable_type,
301
389
  movies.id AS searchable_id,
302
- (movies.name || ' ' || directors.name) AS content,
390
+ CONCAT_WS(' ', movies.name, directors.name) AS content,
303
391
  now() AS created_at,
304
392
  now() AS updated_at
305
393
  FROM movies
@@ -309,6 +397,7 @@ class Movie < ActiveRecord::Base
309
397
  end
310
398
  end
311
399
  ```
400
+ **Note:** If using PostgreSQL before 9.1, replace the `CONCAT_WS()` function call with double-pipe concatenation, eg. `(movies.name || ' ' || directors.name)`. However, now be aware that if *any* of the joined values is NULL then the final `content` value will also be NULL, whereas `CONCAT_WS()` will selectively ignore NULL values.
312
401
 
313
402
  #### Disabling multi-search indexing temporarily
314
403
 
@@ -336,8 +425,8 @@ To search against a column, pass a symbol as the :against option.
336
425
 
337
426
  ```ruby
338
427
  class BlogPost < ActiveRecord::Base
339
- include PgSearch
340
- pg_search_scope :search_by_title, :against => :title
428
+ include PgSearch::Model
429
+ pg_search_scope :search_by_title, against: :title
341
430
  end
342
431
  ```
343
432
 
@@ -345,8 +434,8 @@ We now have an ActiveRecord scope named search_by_title on our BlogPost model.
345
434
  It takes one parameter, a search query string.
346
435
 
347
436
  ```ruby
348
- BlogPost.create!(:title => "Recent Developments in the World of Pastrami")
349
- BlogPost.create!(:title => "Prosciutto and You: A Retrospective")
437
+ BlogPost.create!(title: "Recent Developments in the World of Pastrami")
438
+ BlogPost.create!(title: "Prosciutto and You: A Retrospective")
350
439
  BlogPost.search_by_title("pastrami") # => [#<BlogPost id: 2, title: "Recent Developments in the World of Pastrami">]
351
440
  ```
352
441
 
@@ -356,16 +445,16 @@ Just pass an Array if you'd like to search more than one column.
356
445
 
357
446
  ```ruby
358
447
  class Person < ActiveRecord::Base
359
- include PgSearch
360
- pg_search_scope :search_by_full_name, :against => [:first_name, :last_name]
448
+ include PgSearch::Model
449
+ pg_search_scope :search_by_full_name, against: [:first_name, :last_name]
361
450
  end
362
451
  ```
363
452
 
364
453
  Now our search query can match either or both of the columns.
365
454
 
366
455
  ```ruby
367
- person_1 = Person.create!(:first_name => "Grant", :last_name => "Hill")
368
- person_2 = Person.create!(:first_name => "Hugh", :last_name => "Grant")
456
+ person_1 = Person.create!(first_name: "Grant", last_name: "Hill")
457
+ person_2 = Person.create!(first_name: "Hugh", last_name: "Grant")
369
458
 
370
459
  Person.search_by_full_name("Grant") # => [person_1, person_2]
371
460
  Person.search_by_full_name("Grant Hill") # => [person_1]
@@ -383,18 +472,18 @@ value if you wanted.
383
472
 
384
473
  ```ruby
385
474
  class Person < ActiveRecord::Base
386
- include PgSearch
387
- pg_search_scope :search_by_name, lambda do |name_part, query|
475
+ include PgSearch::Model
476
+ pg_search_scope :search_by_name, lambda { |name_part, query|
388
477
  raise ArgumentError unless [:first, :last].include?(name_part)
389
478
  {
390
- :against => name_part,
391
- :query => query
479
+ against: name_part,
480
+ query: query
392
481
  }
393
- end
482
+ }
394
483
  end
395
484
 
396
- person_1 = Person.create!(:first_name => "Grant", :last_name => "Hill")
397
- person_2 = Person.create!(:first_name => "Hugh", :last_name => "Grant")
485
+ person_1 = Person.create!(first_name: "Grant", last_name: "Hill")
486
+ person_2 = Person.create!(first_name: "Hugh", last_name: "Grant")
398
487
 
399
488
  Person.search_by_name :first, "Grant" # => [person_1]
400
489
  Person.search_by_name :last, "Grant" # => [person_2]
@@ -421,14 +510,14 @@ class Cheese < ActiveRecord::Base
421
510
  end
422
511
 
423
512
  class Salami < ActiveRecord::Base
424
- include PgSearch
513
+ include PgSearch::Model
425
514
 
426
515
  belongs_to :cracker
427
- has_many :cheeses, :through => :cracker
516
+ has_many :cheeses, through: :cracker
428
517
 
429
- pg_search_scope :tasty_search, :associated_against => {
430
- :cheeses => [:kind, :brand],
431
- :cracker => :kind
518
+ pg_search_scope :tasty_search, associated_against: {
519
+ cheeses: [:kind, :brand],
520
+ cracker: :kind
432
521
  }
433
522
  end
434
523
 
@@ -436,13 +525,13 @@ salami_1 = Salami.create!
436
525
  salami_2 = Salami.create!
437
526
  salami_3 = Salami.create!
438
527
 
439
- limburger = Cheese.create!(:kind => "Limburger")
440
- brie = Cheese.create!(:kind => "Brie")
441
- pepper_jack = Cheese.create!(:kind => "Pepper Jack")
528
+ limburger = Cheese.create!(kind: "Limburger")
529
+ brie = Cheese.create!(kind: "Brie")
530
+ pepper_jack = Cheese.create!(kind: "Pepper Jack")
442
531
 
443
- Cracker.create!(:kind => "Black Pepper", :cheeses => [brie], :salami => salami_1)
444
- Cracker.create!(:kind => "Ritz", :cheeses => [limburger, pepper_jack], :salami => salami_2)
445
- Cracker.create!(:kind => "Graham", :cheeses => [limburger], :salami => salami_3)
532
+ Cracker.create!(kind: "Black Pepper", cheeses: [brie], salami: salami_1)
533
+ Cracker.create!(kind: "Ritz", cheeses: [limburger, pepper_jack], salami: salami_2)
534
+ Cracker.create!(kind: "Graham", cheeses: [limburger], salami: salami_3)
446
535
 
447
536
  Salami.tasty_search("pepper") # => [salami_1, salami_2]
448
537
  ```
@@ -456,8 +545,8 @@ search techniques.
456
545
 
457
546
  ```ruby
458
547
  class Beer < ActiveRecord::Base
459
- include PgSearch
460
- pg_search_scope :search_name, :against => :name, :using => [:tsearch, :trigram, :dmetaphone]
548
+ include PgSearch::Model
549
+ pg_search_scope :search_name, against: :name, using: [:tsearch, :trigram, :dmetaphone]
461
550
  end
462
551
  ```
463
552
 
@@ -482,11 +571,11 @@ subtitle, and finally the content.
482
571
 
483
572
  ```ruby
484
573
  class NewsArticle < ActiveRecord::Base
485
- include PgSearch
486
- pg_search_scope :search_full_text, :against => {
487
- :title => 'A',
488
- :subtitle => 'B',
489
- :content => 'C'
574
+ include PgSearch::Model
575
+ pg_search_scope :search_full_text, against: {
576
+ title: 'A',
577
+ subtitle: 'B',
578
+ content: 'C'
490
579
  }
491
580
  end
492
581
  ```
@@ -497,8 +586,8 @@ weight. If you omit the weight, a default will be used.
497
586
 
498
587
  ```ruby
499
588
  class NewsArticle < ActiveRecord::Base
500
- include PgSearch
501
- pg_search_scope :search_full_text, :against => [
589
+ include PgSearch::Model
590
+ pg_search_scope :search_full_text, against: [
502
591
  [:title, 'A'],
503
592
  [:subtitle, 'B'],
504
593
  [:content, 'C']
@@ -506,10 +595,10 @@ class NewsArticle < ActiveRecord::Base
506
595
  end
507
596
 
508
597
  class NewsArticle < ActiveRecord::Base
509
- include PgSearch
510
- pg_search_scope :search_full_text, :against => [
598
+ include PgSearch::Model
599
+ pg_search_scope :search_full_text, against: [
511
600
  [:title, 'A'],
512
- {:subtitle => 'B'},
601
+ {subtitle: 'B'},
513
602
  :content
514
603
  ]
515
604
  end
@@ -524,17 +613,17 @@ shown in the following example.
524
613
 
525
614
  ```ruby
526
615
  class Superhero < ActiveRecord::Base
527
- include PgSearch
616
+ include PgSearch::Model
528
617
  pg_search_scope :whose_name_starts_with,
529
- :against => :name,
530
- :using => {
531
- :tsearch => {:prefix => true}
618
+ against: :name,
619
+ using: {
620
+ tsearch: { prefix: true }
532
621
  }
533
622
  end
534
623
 
535
- batman = Superhero.create :name => 'Batman'
536
- batgirl = Superhero.create :name => 'Batgirl'
537
- robin = Superhero.create :name => 'Robin'
624
+ batman = Superhero.create name: 'Batman'
625
+ batgirl = Superhero.create name: 'Batgirl'
626
+ robin = Superhero.create name: 'Robin'
538
627
 
539
628
  Superhero.whose_name_starts_with("Bat") # => [batman, batgirl]
540
629
  ```
@@ -553,18 +642,18 @@ term that you were trying to exclude.
553
642
 
554
643
  ```ruby
555
644
  class Animal < ActiveRecord::Base
556
- include PgSearch
645
+ include PgSearch::Model
557
646
  pg_search_scope :with_name_matching,
558
- :against => :name,
559
- :using => {
560
- :tsearch => {:negation => true}
647
+ against: :name,
648
+ using: {
649
+ tsearch: {negation: true}
561
650
  }
562
651
  end
563
652
 
564
- one_fish = Animal.create(:name => "one fish")
565
- two_fish = Animal.create(:name => "two fish")
566
- red_fish = Animal.create(:name => "red fish")
567
- blue_fish = Animal.create(:name => "blue fish")
653
+ one_fish = Animal.create(name: "one fish")
654
+ two_fish = Animal.create(name: "two fish")
655
+ red_fish = Animal.create(name: "red fish")
656
+ blue_fish = Animal.create(name: "blue fish")
568
657
 
569
658
  Animal.with_name_matching("fish !red !blue") # => [one_fish, two_fish]
570
659
  ```
@@ -582,22 +671,22 @@ dictionary will be used.
582
671
 
583
672
  ```ruby
584
673
  class BoringTweet < ActiveRecord::Base
585
- include PgSearch
674
+ include PgSearch::Model
586
675
  pg_search_scope :kinda_matching,
587
- :against => :text,
588
- :using => {
589
- :tsearch => {:dictionary => "english"}
676
+ against: :text,
677
+ using: {
678
+ tsearch: {dictionary: "english"}
590
679
  }
591
680
  pg_search_scope :literally_matching,
592
- :against => :text,
593
- :using => {
594
- :tsearch => {:dictionary => "simple"}
681
+ against: :text,
682
+ using: {
683
+ tsearch: {dictionary: "simple"}
595
684
  }
596
685
  end
597
686
 
598
- sleepy = BoringTweet.create! :text => "I snoozed my alarm for fourteen hours today. I bet I can beat that tomorrow! #sleepy"
599
- sleeping = BoringTweet.create! :text => "You know what I like? Sleeping. That's what. #enjoyment"
600
- sleeper = BoringTweet.create! :text => "Have you seen Woody Allen's movie entitled Sleeper? Me neither. #boycott"
687
+ sleepy = BoringTweet.create! text: "I snoozed my alarm for fourteen hours today. I bet I can beat that tomorrow! #sleepy"
688
+ sleeping = BoringTweet.create! text: "You know what I like? Sleeping. That's what. #enjoyment"
689
+ sleeper = BoringTweet.create! text: "Have you seen Woody Allen's movie entitled Sleeper? Me neither. #boycott"
601
690
 
602
691
  BoringTweet.kinda_matching("sleeping") # => [sleepy, sleeping, sleeper]
603
692
  BoringTweet.literally_matching("sleeping") # => [sleeping]
@@ -626,18 +715,18 @@ their numbers together.
626
715
 
627
716
  ```ruby
628
717
  class BigLongDocument < ActiveRecord::Base
629
- include PgSearch
718
+ include PgSearch::Model
630
719
  pg_search_scope :regular_search,
631
- :against => :text
720
+ against: :text
632
721
 
633
722
  pg_search_scope :short_search,
634
- :against => :text,
635
- :using => {
636
- :tsearch => {:normalization => 2}
723
+ against: :text,
724
+ using: {
725
+ tsearch: {normalization: 2}
637
726
  }
638
727
 
639
- long = BigLongDocument.create!(:text => "Four score and twenty years ago")
640
- short = BigLongDocument.create!(:text => "Four score")
728
+ long = BigLongDocument.create!(text: "Four score and twenty years ago")
729
+ short = BigLongDocument.create!(text: "Four score")
641
730
 
642
731
  BigLongDocument.regular_search("four score") #=> [long, short]
643
732
  BigLongDocument.short_search("four score") #=> [short, long]
@@ -650,20 +739,20 @@ models containing any word in the search terms.
650
739
 
651
740
  ```ruby
652
741
  class Number < ActiveRecord::Base
653
- include PgSearch
742
+ include PgSearch::Model
654
743
  pg_search_scope :search_any_word,
655
- :against => :text,
656
- :using => {
657
- :tsearch => {:any_word => true}
744
+ against: :text,
745
+ using: {
746
+ tsearch: {any_word: true}
658
747
  }
659
748
 
660
749
  pg_search_scope :search_all_words,
661
- :against => :text
750
+ against: :text
662
751
  end
663
752
 
664
- one = Number.create! :text => 'one'
665
- two = Number.create! :text => 'two'
666
- three = Number.create! :text => 'three'
753
+ one = Number.create! text: 'one'
754
+ two = Number.create! text: 'two'
755
+ three = Number.create! text: 'three'
667
756
 
668
757
  Number.search_any_word('one two three') # => [one, two, three]
669
758
  Number.search_all_words('one two three') # => []
@@ -676,19 +765,19 @@ but will not include it in the query's WHERE condition.
676
765
 
677
766
  ```ruby
678
767
  class Person < ActiveRecord::Base
679
- include PgSearch
768
+ include PgSearch::Model
680
769
  pg_search_scope :search,
681
- :against => :name,
682
- :using => {
683
- :tsearch => {:any_word => true}
684
- :dmetaphone => {:any_word => true, :sort_only => true}
770
+ against: :name,
771
+ using: {
772
+ tsearch: {any_word: true},
773
+ dmetaphone: {any_word: true, sort_only: true}
685
774
  }
686
775
  end
687
776
 
688
- exact = Person.create!(:name => 'ash hines')
689
- one_exact_one_close = Person.create!(:name => 'ash heinz')
690
- one_exact = Person.create!(:name => 'ash smith')
691
- one_close = Person.create!(:name => 'leigh heinz')
777
+ exact = Person.create!(name: 'ash hines')
778
+ one_exact_one_close = Person.create!(name: 'ash heinz')
779
+ one_exact = Person.create!(name: 'ash smith')
780
+ one_close = Person.create!(name: 'leigh heinz')
692
781
 
693
782
  Person.search('ash hines') # => [exact, one_exact_one_close, one_exact]
694
783
  ```
@@ -701,14 +790,14 @@ Adding .with_pg_search_highlight after the pg_search_scope you can access to
701
790
 
702
791
  ```ruby
703
792
  class Person < ActiveRecord::Base
704
- include PgSearch
793
+ include PgSearch::Model
705
794
  pg_search_scope :search,
706
795
  against: :bio,
707
796
  using: {
708
797
  tsearch: {
709
798
  highlight: {
710
- StartSel: '<start>',
711
- StopSel: '<stop>',
799
+ StartSel: '<b>',
800
+ StopSel: '</b>',
712
801
  MaxWords: 123,
713
802
  MinWords: 456,
714
803
  ShortWord: 4,
@@ -755,16 +844,16 @@ The following example shows how to use :dmetaphone.
755
844
 
756
845
  ```ruby
757
846
  class Word < ActiveRecord::Base
758
- include PgSearch
847
+ include PgSearch::Model
759
848
  pg_search_scope :that_sounds_like,
760
- :against => :spelling,
761
- :using => :dmetaphone
849
+ against: :spelling,
850
+ using: :dmetaphone
762
851
  end
763
852
 
764
- four = Word.create! :spelling => 'four'
765
- far = Word.create! :spelling => 'far'
766
- fur = Word.create! :spelling => 'fur'
767
- five = Word.create! :spelling => 'five'
853
+ four = Word.create! spelling: 'four'
854
+ far = Word.create! spelling: 'far'
855
+ fur = Word.create! spelling: 'fur'
856
+ five = Word.create! spelling: 'five'
768
857
 
769
858
  Word.that_sounds_like("fir") # => [four, far, fur]
770
859
  ```
@@ -786,16 +875,16 @@ feature can be used.
786
875
 
787
876
  ```ruby
788
877
  class Website < ActiveRecord::Base
789
- include PgSearch
878
+ include PgSearch::Model
790
879
  pg_search_scope :kinda_spelled_like,
791
- :against => :name,
792
- :using => :trigram
880
+ against: :name,
881
+ using: :trigram
793
882
  end
794
883
 
795
- yahooo = Website.create! :name => "Yahooo!"
796
- yohoo = Website.create! :name => "Yohoo!"
797
- gogle = Website.create! :name => "Gogle"
798
- facebook = Website.create! :name => "Facebook"
884
+ yahooo = Website.create! name: "Yahooo!"
885
+ yohoo = Website.create! name: "Yohoo!"
886
+ gogle = Website.create! name: "Gogle"
887
+ facebook = Website.create! name: "Facebook"
799
888
 
800
889
  Website.kinda_spelled_like("Yahoo!") # => [yahooo, yohoo]
801
890
  ```
@@ -811,26 +900,26 @@ threshold will force a table scan as the derived query uses the
811
900
 
812
901
  ```ruby
813
902
  class Vegetable < ActiveRecord::Base
814
- include PgSearch
903
+ include PgSearch::Model
815
904
 
816
905
  pg_search_scope :strictly_spelled_like,
817
- :against => :name,
818
- :using => {
819
- :trigram => {
820
- :threshold => 0.5
906
+ against: :name,
907
+ using: {
908
+ trigram: {
909
+ threshold: 0.5
821
910
  }
822
911
  }
823
912
 
824
913
  pg_search_scope :roughly_spelled_like,
825
- :against => :name,
826
- :using => {
827
- :trigram => {
828
- :threshold => 0.1
914
+ against: :name,
915
+ using: {
916
+ trigram: {
917
+ threshold: 0.1
829
918
  }
830
919
  }
831
920
  end
832
921
 
833
- cauliflower = Vegetable.create! :name => "cauliflower"
922
+ cauliflower = Vegetable.create! name: "cauliflower"
834
923
 
835
924
  Vegetable.roughly_spelled_like("couliflower") # => [cauliflower]
836
925
  Vegetable.strictly_spelled_like("couliflower") # => [cauliflower]
@@ -839,6 +928,37 @@ Vegetable.roughly_spelled_like("collyflower") # => [cauliflower]
839
928
  Vegetable.strictly_spelled_like("collyflower") # => []
840
929
  ```
841
930
 
931
+ ##### :word_similarity
932
+
933
+ Allows you to match words in longer strings.
934
+ By default, trigram searches use `%` or `similarity()` as a similarity value.
935
+ Set `word_similarity` to `true` to opt for `<%` and `word_similarity` instead.
936
+ This causes the trigram search to use the similarity of the query term
937
+ and the word with greatest similarity.
938
+
939
+ ```ruby
940
+ class Sentence < ActiveRecord::Base
941
+ include PgSearch::Model
942
+
943
+ pg_search_scope :similarity_like,
944
+ against: :name,
945
+ using: {
946
+ trigram: {
947
+ word_similarity: true
948
+ }
949
+ }
950
+
951
+ pg_search_scope :word_similarity_like,
952
+ against: :name,
953
+ using: [:trigram]
954
+ end
955
+
956
+ sentence = Sentence.create! name: "Those are two words."
957
+
958
+ Sentence.similarity_like("word") # => []
959
+ Sentence.word_similarity_like("word") # => [sentence]
960
+ ```
961
+
842
962
  ### Limiting Fields When Combining Features
843
963
 
844
964
  Sometimes when doing queries combining different features you
@@ -849,14 +969,14 @@ which fields using the 'only' option:
849
969
 
850
970
  ```ruby
851
971
  class Image < ActiveRecord::Base
852
- include PgSearch
972
+ include PgSearch::Model
853
973
 
854
974
  pg_search_scope :combined_search,
855
- :against => [:file_name, :short_description, :long_description]
856
- :using => {
857
- :tsearch => { :dictionary => 'english' },
858
- :trigram => {
859
- :only => [:file_name, :short_description]
975
+ against: [:file_name, :short_description, :long_description]
976
+ using: {
977
+ tsearch: { dictionary: 'english' },
978
+ trigram: {
979
+ only: [:file_name, :short_description]
860
980
  }
861
981
  }
862
982
 
@@ -884,15 +1004,15 @@ must be installed before this feature can be used.
884
1004
 
885
1005
  ```ruby
886
1006
  class SpanishQuestion < ActiveRecord::Base
887
- include PgSearch
1007
+ include PgSearch::Model
888
1008
  pg_search_scope :gringo_search,
889
- :against => :word,
890
- :ignoring => :accents
1009
+ against: :word,
1010
+ ignoring: :accents
891
1011
  end
892
1012
 
893
- what = SpanishQuestion.create(:word => "Qué")
894
- how_many = SpanishQuestion.create(:word => "Cuánto")
895
- how = SpanishQuestion.create(:word => "Cómo")
1013
+ what = SpanishQuestion.create(word: "Qué")
1014
+ how_many = SpanishQuestion.create(word: "Cuánto")
1015
+ how = SpanishQuestion.create(word: "Cómo")
896
1016
 
897
1017
  SpanishQuestion.gringo_search("Que") # => [what]
898
1018
  SpanishQuestion.gringo_search("Cüåñtô") # => [how_many]
@@ -929,8 +1049,8 @@ To use this functionality you'll need to do a few things:
929
1049
 
930
1050
  ```ruby
931
1051
  pg_search_scope :fast_content_search,
932
- :against => :content,
933
- :using => {
1052
+ against: :content,
1053
+ using: {
934
1054
  dmetaphone: {
935
1055
  tsvector_column: 'tsvector_content_dmetaphone'
936
1056
  },
@@ -941,13 +1061,6 @@ To use this functionality you'll need to do a few things:
941
1061
  trigram: {} # trigram does not use tsvectors
942
1062
  }
943
1063
  ```
944
- * You cannot dump a `tsvector` column to `schema.rb`. Instead, you need to switch to using the native PostgreSQL SQL format schema dump.
945
- In your `config/application.rb` you should set
946
-
947
- config.active_record.schema_format = :sql
948
-
949
- Read more about it here: http://guides.rubyonrails.org/migrations.html#types-of-schema-dumps
950
-
951
1064
 
952
1065
  Please note that the :against column is only used when the tsvector_column is
953
1066
  not present for the search type.
@@ -958,26 +1071,26 @@ It's possible to search against more than one tsvector at a time. This could be
958
1071
 
959
1072
  ```ruby
960
1073
  pg_search_scope :search_title,
961
- :against => :title,
962
- :using => {
963
- :tsearch => {
964
- :tsvector_column => "title_tsvector"
1074
+ against: :title,
1075
+ using: {
1076
+ tsearch: {
1077
+ tsvector_column: "title_tsvector"
965
1078
  }
966
1079
  }
967
1080
 
968
1081
  pg_search_scope :search_body,
969
- :against => :body,
970
- :using => {
971
- :tsearch => {
972
- :tsvector_column => "body_tsvector"
1082
+ against: :body,
1083
+ using: {
1084
+ tsearch: {
1085
+ tsvector_column: "body_tsvector"
973
1086
  }
974
1087
  }
975
1088
 
976
1089
  pg_search_scope :search_title_and_body,
977
- :against => [:title, :body],
978
- :using => {
979
- :tsearch => {
980
- :tsvector_column => ["title_tsvector", "body_tsvector"]
1090
+ against: [:title, :body],
1091
+ using: {
1092
+ tsearch: {
1093
+ tsvector_column: ["title_tsvector", "body_tsvector"]
981
1094
  }
982
1095
  }
983
1096
  ```
@@ -992,9 +1105,9 @@ can pass a :ranked_by option to pg_search_scope.
992
1105
 
993
1106
  ```ruby
994
1107
  pg_search_scope :search_by_tsearch_but_rank_by_trigram,
995
- :against => :title,
996
- :using => [:tsearch],
997
- :ranked_by => ":trigram"
1108
+ against: :title,
1109
+ using: [:tsearch],
1110
+ ranked_by: ":trigram"
998
1111
  ```
999
1112
 
1000
1113
  Note that :ranked_by using a String to represent the ranking expression. This
@@ -1004,10 +1117,10 @@ expressions.
1004
1117
 
1005
1118
  ```ruby
1006
1119
  # Weighted ranking to balance multiple approaches
1007
- :ranked_by => ":dmetaphone + (0.25 * :trigram)"
1120
+ ranked_by: ":dmetaphone + (0.25 * :trigram)"
1008
1121
 
1009
1122
  # A more complex example, where books.num_pages is an integer column in the table itself
1010
- :ranked_by => "(books.num_pages * :trigram) + (:tsearch / 2.0)"
1123
+ ranked_by: "(books.num_pages * :trigram) + (:tsearch / 2.0)"
1011
1124
  ```
1012
1125
 
1013
1126
  #### :order_within_rank (Breaking ties)
@@ -1039,8 +1152,8 @@ descending by updated_at, to rank the most recently updated records first.
1039
1152
 
1040
1153
  ```ruby
1041
1154
  pg_search_scope :search_and_break_ties_by_latest_update,
1042
- :against => [:title, :content],
1043
- :order_within_rank => "blog_posts.updated_at DESC"
1155
+ against: [:title, :content],
1156
+ order_within_rank: "blog_posts.updated_at DESC"
1044
1157
  ```
1045
1158
 
1046
1159
  #### PgSearch#pg_search_rank (Reading a record's rank as a Float)
@@ -1077,20 +1190,16 @@ shirt_brands = ShirtBrand.search_by_name("Penguin")
1077
1190
 
1078
1191
  PgSearch would not have been possible without inspiration from texticle (now renamed
1079
1192
  [textacular](https://github.com/textacular/textacular)). Thanks to [Aaron
1080
- Patterson](http://tenderlovemaking.com/) for the original version!
1193
+ Patterson](http://tenderlovemaking.com/) for the original version and to Casebook PBC (https://www.casebook.net) for gifting the community with it!
1081
1194
 
1082
1195
  ## CONTRIBUTIONS AND FEEDBACK
1083
1196
 
1084
- Welcomed! Feel free to join and contribute to our [public Pivotal Tracker
1085
- project](https://www.pivotaltracker.com/projects/228645) where we manage new
1086
- feature ideas and bugs.
1197
+ Please read our [CONTRIBUTING guide](https://github.com/Casecommons/pg_search/blob/master/CONTRIBUTING.md).
1087
1198
 
1088
1199
  We also have a [Google Group](http://groups.google.com/group/casecommons-dev)
1089
- for discussing pg_search and other Case Commons open source projects.
1090
-
1091
- Please read our [CONTRIBUTING guide](https://github.com/Casecommons/pg_search/blob/master/CONTRIBUTING.md).
1200
+ for discussing pg_search and other Casebook PBC open source projects.
1092
1201
 
1093
1202
  ## LICENSE
1094
1203
 
1095
- Copyright © 2010–2017 [Case Commons, Inc](http://casecommons.org).
1204
+ Copyright © 2010–2021 [Casebook PBC](http://www.casebook.net).
1096
1205
  Licensed under the MIT license, see [LICENSE](/LICENSE) file.