pg_search 0.7.0 → 0.7.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/CHANGELOG.md +187 -0
- data/CONTRIBUTING.md +17 -0
- data/Gemfile +1 -1
- data/README.md +844 -0
- data/Rakefile +0 -11
- data/lib/pg_search/features/trigram.rb +8 -3
- data/lib/pg_search/version.rb +1 -1
- data/pg_search.gemspec +1 -3
- data/spec/integration/associations_spec.rb +42 -0
- data/spec/integration/pagination_spec.rb +6 -3
- data/spec/integration/pg_search_spec.rb +21 -2
- data/spec/lib/pg_search/document_spec.rb +2 -2
- data/spec/lib/pg_search/features/dmetaphone_spec.rb +2 -2
- data/spec/lib/pg_search/features/trigram_spec.rb +36 -17
- data/spec/lib/pg_search/features/tsearch_spec.rb +2 -2
- data/spec/lib/pg_search/multisearch/rebuilder_spec.rb +18 -2
- data/spec/lib/pg_search/normalizer_spec.rb +7 -7
- data/spec/spec_helper.rb +7 -9
- metadata +7 -34
- data/CHANGELOG.rdoc +0 -178
- data/README.rdoc +0 -669
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cd010cfa10f28bb6d953f4fe09dc9c19f10c6239
|
4
|
+
data.tar.gz: c8ece97ff1b44ed62d98272a0df15ff42813dc50
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8d1358647c4e451ddd3f837cfd51b9a4bd6c363f075fe8965128c30bfe65d279ef9795ab50282284641cdce0264e381823dfc8e847859a84980c6e4b439ae2cb
|
7
|
+
data.tar.gz: b1cf6a6c1efc8e6b3227a3c87b5877c42501d5ca963f96a94d74ae809a39292f78359004cb93f67e73aee49b5ed124439dce8ce12c0ac9f78bc5eeddb5ca6b5a
|
data/.gitignore
CHANGED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,187 @@
|
|
1
|
+
# pg_search changelog
|
2
|
+
|
3
|
+
## 0.7.1
|
4
|
+
|
5
|
+
* Fix issue with {:using => :trigram, :ignoring => :accents} that generated
|
6
|
+
bad SQL. (Steven Harman)
|
7
|
+
|
8
|
+
## 0.7.0
|
9
|
+
|
10
|
+
* Start requiring Ruby 1.9.2 or later
|
11
|
+
|
12
|
+
## 0.6.4
|
13
|
+
|
14
|
+
* Fix issue with using more than two features in the same scope
|
15
|
+
|
16
|
+
## 0.6.3
|
17
|
+
|
18
|
+
* Fix issues and deprecations for Active Record 4.0.0.rc1
|
19
|
+
|
20
|
+
## 0.6.2
|
21
|
+
|
22
|
+
* Add workaround for issue with how ActiveRecord relations handle Arel OR
|
23
|
+
nodes.
|
24
|
+
|
25
|
+
## 0.6.1
|
26
|
+
|
27
|
+
* Fix issue with Arel::InfixOperation that prevented #count from working,
|
28
|
+
breaking pagination.
|
29
|
+
|
30
|
+
## 0.6.0
|
31
|
+
|
32
|
+
* Drop support for Active Record 3.0.
|
33
|
+
* Address warnings in Ruby 2.0.
|
34
|
+
* Remove all usages of sanitize_sql_array for future Rails 4 compatibility.
|
35
|
+
* Start using Arel internally to build SQL strings (not yet complete).
|
36
|
+
* Disable eager loading, fixes issue #14.
|
37
|
+
* Support named schemas in pg_search:multisearch:rebuild. (Victor Olteanu)
|
38
|
+
|
39
|
+
|
40
|
+
## 0.5.7
|
41
|
+
|
42
|
+
* Fix issue with eager loading now that the Scope class has been removed.
|
43
|
+
(Piotr Murach)
|
44
|
+
|
45
|
+
|
46
|
+
## 0.5.6
|
47
|
+
|
48
|
+
* PgSearch#multisearchable accepts :if and :unless for conditional inclusion
|
49
|
+
in search documents table. (Francois Harbec)
|
50
|
+
* Stop using array_to_string() in SQL since it is not indexable.
|
51
|
+
|
52
|
+
|
53
|
+
## 0.5.5
|
54
|
+
|
55
|
+
* Fix bug with single table inheritance.
|
56
|
+
* Allow option for specifying an alternate function for unaccent().
|
57
|
+
|
58
|
+
|
59
|
+
## 0.5.4
|
60
|
+
|
61
|
+
* Fix bug in associated_against join clause when search scope is chained
|
62
|
+
after other scopes.
|
63
|
+
* Fix autoloading of PgSearch::VERSION constant.
|
64
|
+
|
65
|
+
|
66
|
+
## 0.5.3
|
67
|
+
|
68
|
+
* Prevent multiple attempts to create pg_search_document within a single
|
69
|
+
transaction. (JT Archie & Trace Wax)
|
70
|
+
|
71
|
+
|
72
|
+
## 0.5.2
|
73
|
+
|
74
|
+
* Don't save twice if pg_search_document is missing on update.
|
75
|
+
|
76
|
+
|
77
|
+
## 0.5.1
|
78
|
+
|
79
|
+
* Add ability to override multisearch rebuild SQL.
|
80
|
+
|
81
|
+
|
82
|
+
## 0.5
|
83
|
+
|
84
|
+
* Convert migration rake tasks into generators.
|
85
|
+
* Use rake task arguments for multisearch rebuild instead of environment
|
86
|
+
variable.
|
87
|
+
* Always cast columns to text.
|
88
|
+
|
89
|
+
|
90
|
+
## 0.4.2
|
91
|
+
|
92
|
+
* Fill in timestamps correctly when rebuilding multisearch documents.
|
93
|
+
(Barton McGuire)
|
94
|
+
* Fix various issues with rebuilding multisearch documents. (Eugen Neagoe)
|
95
|
+
* Fix syntax error in pg_search_dmetaphone() migration. (Casey Foster)
|
96
|
+
* Rename PgSearch#rank to PgSearch#pg_search_rank and always return a Float.
|
97
|
+
* Fix issue with :associated_against and non-text columns.
|
98
|
+
|
99
|
+
|
100
|
+
## 0.4.1
|
101
|
+
|
102
|
+
* Fix Active Record 3.2 deprecation warnings. (Steven Harman)
|
103
|
+
|
104
|
+
* Fix issue with undefined logger when PgSearch::Document.search is already
|
105
|
+
defined.
|
106
|
+
|
107
|
+
|
108
|
+
## 0.4
|
109
|
+
|
110
|
+
* Add ability to search again tsvector columns. (Kris Hicks)
|
111
|
+
|
112
|
+
|
113
|
+
## 0.3.4
|
114
|
+
|
115
|
+
* Fix issue with {:using => {:tsearch => {:prefix => true}}} and hyphens.
|
116
|
+
* Get tests running against PostgreSQL 9.1 by using CREATE EXTENSION
|
117
|
+
|
118
|
+
|
119
|
+
## 0.3.3
|
120
|
+
|
121
|
+
* Backport array_agg() aggregate function to PostgreSQL 8.3 and earlier.
|
122
|
+
This fixes :associated_against searches.
|
123
|
+
* Backport unnest() function to PostgreSQL 8.3 and earlier. This fixes
|
124
|
+
{:using => :dmetaphone} searches.
|
125
|
+
* Disable {:using => {:tsearch => {:prefix => true}}} in PostgreSQL 8.3 and
|
126
|
+
earlier.
|
127
|
+
|
128
|
+
|
129
|
+
## 0.3.2
|
130
|
+
|
131
|
+
* Fix :prefix search in PostgreSQL 8.x
|
132
|
+
* Disable {:ignoring => :accents} in PostgreSQL 8.x
|
133
|
+
|
134
|
+
|
135
|
+
## 0.3.1
|
136
|
+
|
137
|
+
* Fix syntax error in generated dmetaphone migration. (Max De Marzi)
|
138
|
+
|
139
|
+
|
140
|
+
## 0.3
|
141
|
+
|
142
|
+
* Drop Active Record 2.0 support.
|
143
|
+
* Add PgSearch.multisearch for cross-model searching.
|
144
|
+
* Fix PostgreSQL warnings about truncated identifiers
|
145
|
+
* Support specifying a method of rank normalisation when using tsearch.
|
146
|
+
(Arthur Gunn)
|
147
|
+
* Add :any_word option to :tsearch which uses OR between query terms instead
|
148
|
+
of AND. (Fernando Espinosa)
|
149
|
+
|
150
|
+
## 0.2.2
|
151
|
+
|
152
|
+
* Fix a compatibility issue between Ruby 1.8.7 and 1.9.3 when using Rails 2
|
153
|
+
(James Badger)
|
154
|
+
|
155
|
+
## 0.2.1
|
156
|
+
|
157
|
+
* Backport support for searching against tsvector columns (Kris Hicks)
|
158
|
+
|
159
|
+
## 0.2
|
160
|
+
|
161
|
+
* Set dictionary to :simple by default for :tsearch. Before it was unset,
|
162
|
+
which would fall back to PostgreSQL's default dictionary, usually
|
163
|
+
"english".
|
164
|
+
* Fix a bug with search strings containing a colon ":"
|
165
|
+
* Improve performance of :associated_against by only doing one INNER JOIN
|
166
|
+
per association
|
167
|
+
|
168
|
+
## 0.1.1
|
169
|
+
|
170
|
+
* Fix a bug with dmetaphone searches containing " w " (which dmetaphone maps
|
171
|
+
to an empty string)
|
172
|
+
|
173
|
+
## 0.1
|
174
|
+
|
175
|
+
* Change API to {:ignoring => :accents} from {:normalizing => :diacritics}
|
176
|
+
* Improve documentation
|
177
|
+
* Fix bug where :associated_against would not work without an :against
|
178
|
+
present
|
179
|
+
|
180
|
+
## 0.0.2
|
181
|
+
|
182
|
+
* Fix gem ownership.
|
183
|
+
|
184
|
+
#
|
185
|
+
## 0.0.1
|
186
|
+
|
187
|
+
* Initial release.
|
data/CONTRIBUTING.md
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# Contributing to pg_search
|
2
|
+
|
3
|
+
First off, if you experience a bug, we welcome you to report it. Please provide a minimal test case showing the code that you ran, its output, and what you expected the output to be instead. If you are able to fix the bug and make a pull request, we are much more likely to get it resolved quickly, but don't feel bad to just report an issue if you don't know how to fix it.
|
4
|
+
|
5
|
+
pg_search supports Ruby 1.9.2 and higher, Active Record 3.1 and higher, and PostgreSQL 8.0 and higher. It can be hard to test against all of those versions, but please do your best to avoid features that are only available in newer versions. For example, don't use Ruby 2.0's `prepend` keyword.
|
6
|
+
|
7
|
+
If you have a substantial feature to add, you might want to discuss it first on the [mailing list](https://groups.google.com/forum/#!forum/casecommons-dev). We might have thought hard about it already, and can sometimes help with tips and tricks.
|
8
|
+
|
9
|
+
When in doubt, go ahead and make a pull request. If something needs tweaking or rethinking, we will do our best to respond and make that clear.
|
10
|
+
|
11
|
+
Don't be discouraged if the maintainers ask you to change your code. We are always appreciative when people work hard to modify our code, but we also have a lot of opinions about coding style and object design.
|
12
|
+
|
13
|
+
Run the tests by running rake. It will update all gems to their latest version. This is by design, because we want to be proactive about compatibility with other libraries. To test against a specific version of Active Record, you can set the `ACTIVE_RECORD_VERSION` environment variable.
|
14
|
+
|
15
|
+
$ ACTIVE_RECORD_VERSION=3.1 rake
|
16
|
+
|
17
|
+
Last, but not least, have fun! pg_search is a labor of love.
|
data/Gemfile
CHANGED
@@ -3,7 +3,7 @@ source "http://rubygems.org"
|
|
3
3
|
gemspec
|
4
4
|
|
5
5
|
gem 'pg', :platform => :ruby
|
6
|
-
gem "activerecord-jdbcpostgresql-adapter", :platform => :jruby
|
6
|
+
gem "activerecord-jdbcpostgresql-adapter", ">= 1.3.1", :platform => :jruby
|
7
7
|
|
8
8
|
gem "activerecord", ENV["ACTIVE_RECORD_VERSION"] if ENV["ACTIVE_RECORD_VERSION"]
|
9
9
|
gem "activerecord", :github => "rails", :branch => ENV["ACTIVE_RECORD_BRANCH"] if ENV["ACTIVE_RECORD_BRANCH"]
|
data/README.md
ADDED
@@ -0,0 +1,844 @@
|
|
1
|
+
# pg_search
|
2
|
+
|
3
|
+
http://github.com/Casecommons/pg_search/
|
4
|
+
|
5
|
+
[<img src="https://secure.travis-ci.org/Casecommons/pg_search.png?branch=master"
|
6
|
+
alt="Build Status" />](http://travis-ci.org/Casecommons/pg_search)
|
7
|
+
[<img src="https://gemnasium.com/Casecommons/pg_search.png" alt="Dependency Status"
|
8
|
+
/>](https://gemnasium.com/Casecommons/pg_search)
|
9
|
+
[<img src="https://codeclimate.com/github/Casecommons/pg_search.png"
|
10
|
+
/>](https://codeclimate.com/github/Casecommons/pg_search)
|
11
|
+
[<img src="https://coveralls.io/repos/Casecommons/pg_search/badge.png?branch=master"
|
12
|
+
alt="Coverage Status" />](https://coveralls.io/r/Casecommons/pg_search)
|
13
|
+
[<img src="https://badge.fury.io/rb/pg_search.png" alt="Gem Version"
|
14
|
+
/>](http://badge.fury.io/rb/pg_search)
|
15
|
+
|
16
|
+
## DESCRIPTION
|
17
|
+
|
18
|
+
PgSearch builds named scopes that take advantage of PostgreSQL's full text
|
19
|
+
search.
|
20
|
+
|
21
|
+
Read the blog post introducing PgSearch at http://pivotallabs.com/pg-search/
|
22
|
+
|
23
|
+
## REQUIREMENTS
|
24
|
+
|
25
|
+
* Ruby 1.9.2 or later
|
26
|
+
* Active Record 3.1 or later
|
27
|
+
* PostgreSQL
|
28
|
+
* [PostgreSQL contrib packages for certain
|
29
|
+
features](https://github.com/Casecommons/pg_search/wiki/Installing-Postgres-Contrib-Modules)
|
30
|
+
|
31
|
+
|
32
|
+
## INSTALL
|
33
|
+
|
34
|
+
gem install pg_search
|
35
|
+
|
36
|
+
### Rails 3.1 or later, Ruby 1.9.2 or later
|
37
|
+
|
38
|
+
In Gemfile
|
39
|
+
|
40
|
+
gem 'pg_search'
|
41
|
+
|
42
|
+
### Rails 3.0
|
43
|
+
|
44
|
+
The newest versions of PgSearch no longer support Rails 3.0. However, the 0.5
|
45
|
+
series still works. It's not actively maintained, but submissions are welcome
|
46
|
+
for backports and bugfixes.
|
47
|
+
|
48
|
+
gem 'pg_search', "~> 0.5.7"
|
49
|
+
|
50
|
+
The 0.5 branch lives at
|
51
|
+
https://github.com/Casecommons/pg_search/tree/0.5-stable
|
52
|
+
|
53
|
+
### Rails 2
|
54
|
+
|
55
|
+
The newest versions of PgSearch no longer support Rails 2. However, the 0.2
|
56
|
+
series still works. It's not actively maintained, but submissions are welcome
|
57
|
+
for backports and bugfixes.
|
58
|
+
|
59
|
+
gem 'pg_search', "~> 0.2.0"
|
60
|
+
|
61
|
+
The 0.2 branch lives at
|
62
|
+
https://github.com/Casecommons/pg_search/tree/0.2-stable
|
63
|
+
|
64
|
+
### Other Active Record projects
|
65
|
+
|
66
|
+
In addition to installing and requiring the gem, you may want to include the
|
67
|
+
PgSearch rake tasks in your Rakefile. This isn't necessary for Rails projects,
|
68
|
+
which gain the Rake tasks via a Railtie.
|
69
|
+
|
70
|
+
load "pg_search/tasks.rb"
|
71
|
+
|
72
|
+
### Ruby 1.8.7 or earlier
|
73
|
+
|
74
|
+
The newest versions of PgSearch no longer support Ruby 1.8.7. However, the 0.6
|
75
|
+
series still works. It's not actively maintained, but submissions are welcome
|
76
|
+
for backports and bugfixes.
|
77
|
+
|
78
|
+
gem 'pg_search', "~> 0.6.4"
|
79
|
+
|
80
|
+
The 0.6 branch lives at
|
81
|
+
https://github.com/Casecommons/pg_search/tree/0.6-stable
|
82
|
+
|
83
|
+
## USAGE
|
84
|
+
|
85
|
+
To add PgSearch to an Active Record model, simply include the PgSearch module.
|
86
|
+
|
87
|
+
class Shape < ActiveRecord::Base
|
88
|
+
include PgSearch
|
89
|
+
end
|
90
|
+
|
91
|
+
### Multi-search vs. search scopes
|
92
|
+
|
93
|
+
pg_search supports two different techniques for searching, multi-search and
|
94
|
+
search scopes.
|
95
|
+
|
96
|
+
The first technique is multi-search, in which records of many different Active
|
97
|
+
Record classes can be mixed together into one global search index across your
|
98
|
+
entire application. Most sites that want to support a generic search page will
|
99
|
+
want to use this feature.
|
100
|
+
|
101
|
+
The other technique is search scopes, which allow you to do more advanced
|
102
|
+
searching against only one Active Record class. This is more useful for
|
103
|
+
building things like autocompleters or filtering a list of items in a faceted
|
104
|
+
search.
|
105
|
+
|
106
|
+
### Multi-search
|
107
|
+
|
108
|
+
#### Setup
|
109
|
+
|
110
|
+
Before using multi-search, you must generate and run a migration to create the
|
111
|
+
pg_search_documents database table.
|
112
|
+
|
113
|
+
$ rails g pg_search:migration:multisearch
|
114
|
+
$ rake db:migrate
|
115
|
+
|
116
|
+
#### multisearchable
|
117
|
+
|
118
|
+
To add a model to the global search index for your application, call
|
119
|
+
multisearchable in its class definition.
|
120
|
+
|
121
|
+
class EpicPoem < ActiveRecord::Base
|
122
|
+
include PgSearch
|
123
|
+
multisearchable :against => [:title, :author]
|
124
|
+
end
|
125
|
+
|
126
|
+
class Flower < ActiveRecord::Base
|
127
|
+
include PgSearch
|
128
|
+
multisearchable :against => :color
|
129
|
+
end
|
130
|
+
|
131
|
+
If this model already has existing records, you will need to reindex this
|
132
|
+
model to get existing records into the pg_search_documents table. See the
|
133
|
+
rebuild task below.
|
134
|
+
|
135
|
+
Whenever a record is created, updated, or destroyed, an Active Record callback
|
136
|
+
will fire, leading to the creation of a corresponding PgSearch::Document
|
137
|
+
record in the pg_search_documents table. The :against option can be one or
|
138
|
+
several methods which will be called on the record to generate its search
|
139
|
+
text.
|
140
|
+
|
141
|
+
You can also pass a Proc or method name to call to determine whether or not a
|
142
|
+
particular record should be included.
|
143
|
+
|
144
|
+
class Convertible < ActiveRecord::Base
|
145
|
+
include PgSearch
|
146
|
+
multisearchable :against => [:make, :model],
|
147
|
+
:if => :available_in_red?
|
148
|
+
end
|
149
|
+
|
150
|
+
class Jalopy < ActiveRecord::Base
|
151
|
+
include PgSearch
|
152
|
+
multisearchable :against => [:make, :model],
|
153
|
+
:if => lambda { |record| record.model_year > 1970 }
|
154
|
+
end
|
155
|
+
|
156
|
+
Note that the Proc or method name is called in an after_save hook. This means
|
157
|
+
that you should be careful when using Time or other objects. In the following
|
158
|
+
example, if the record was last saved before the published_at timestamp, it
|
159
|
+
won't get listed in global search at all until it is touched again after the
|
160
|
+
timestamp.
|
161
|
+
|
162
|
+
class AntipatternExample
|
163
|
+
include PgSearch
|
164
|
+
multisearchable :against => [:contents],
|
165
|
+
:if => :published?
|
166
|
+
|
167
|
+
def published?
|
168
|
+
published_at < Time.now
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
problematic_record = AntipatternExample.create!(
|
173
|
+
:contents => "Using :if with a timestamp",
|
174
|
+
:published_at => 10.minutes.from_now
|
175
|
+
)
|
176
|
+
|
177
|
+
problematic_record.published? # => false
|
178
|
+
PgSearch.multisearch("timestamp") # => No results
|
179
|
+
|
180
|
+
sleep 20.minutes
|
181
|
+
|
182
|
+
problematic_record.published? # => true
|
183
|
+
PgSearch.multisearch("timestamp") # => No results
|
184
|
+
|
185
|
+
problematic_record.save!
|
186
|
+
|
187
|
+
problematic_record.published? # => true
|
188
|
+
PgSearch.multisearch("timestamp") # => Includes problematic_record
|
189
|
+
|
190
|
+
#### Multi-search associations
|
191
|
+
|
192
|
+
Two associations are built automatically. On the original record, there is a
|
193
|
+
has_one :pg_search_document association pointing to the PgSearch::Document
|
194
|
+
record, and on the PgSearch::Document record there is a belongs_to :searchable
|
195
|
+
polymorphic association pointing back to the original record.
|
196
|
+
|
197
|
+
odyssey = EpicPoem.create!(:title => "Odyssey", :author => "Homer")
|
198
|
+
search_document = odyssey.pg_search_document #=> PgSearch::Document instance
|
199
|
+
search_document.searchable #=> #<EpicPoem id: 1, title: "Odyssey", author: "Homer">
|
200
|
+
|
201
|
+
#### Searching in the global search index
|
202
|
+
|
203
|
+
To fetch the PgSearch::Document entries for all of the records that match a
|
204
|
+
given query, use PgSearch.multisearch.
|
205
|
+
|
206
|
+
odyssey = EpicPoem.create!(:title => "Odyssey", :author => "Homer")
|
207
|
+
rose = Flower.create!(:color => "Red")
|
208
|
+
PgSearch.multisearch("Homer") #=> [#<PgSearch::Document searchable: odyssey>]
|
209
|
+
PgSearch.multisearch("Red") #=> [#<PgSearch::Document searchable: rose>]
|
210
|
+
|
211
|
+
#### Chaining method calls onto the results
|
212
|
+
|
213
|
+
PgSearch.multisearch returns an ActiveRecord::Relation, just like scopes do,
|
214
|
+
so you can chain scope calls to the end. This works with gems like Kaminari
|
215
|
+
that add scope methods. Just like with regular scopes, the database will only
|
216
|
+
receive SQL requests when necessary.
|
217
|
+
|
218
|
+
PgSearch.multisearch("Bertha").limit(10)
|
219
|
+
PgSearch.multisearch("Juggler").where(:searchable_type => "Occupation")
|
220
|
+
PgSearch.multisearch("Alamo").page(3).per_page(30)
|
221
|
+
PgSearch.multisearch("Diagonal").find_each do |document|
|
222
|
+
puts document.searchable.updated_at
|
223
|
+
end
|
224
|
+
|
225
|
+
#### Configuring multi-search
|
226
|
+
|
227
|
+
PgSearch.multisearch can be configured using the same options as
|
228
|
+
`pg_search_scope` (explained in more detail below). Just set the
|
229
|
+
PgSearch.multisearch_options in an initializer:
|
230
|
+
|
231
|
+
PgSearch.multisearch_options = {
|
232
|
+
:using => [:tsearch, :trigram],
|
233
|
+
:ignoring => :accents
|
234
|
+
}
|
235
|
+
|
236
|
+
#### Rebuilding search documents for a given class
|
237
|
+
|
238
|
+
If you change the :against option on a class, add multisearchable to a class
|
239
|
+
that already has records in the database, or remove multisearchable from a
|
240
|
+
class in order to remove it from the index, you will find that the
|
241
|
+
pg_search_documents table could become out-of-sync with the actual records in
|
242
|
+
your other tables.
|
243
|
+
|
244
|
+
The index can also become out-of-sync if you ever modify records in a way that
|
245
|
+
does not trigger Active Record callbacks. For instance, the #update_attribute
|
246
|
+
instance method and the .update_all class method both skip callbacks and
|
247
|
+
directly modify the database.
|
248
|
+
|
249
|
+
To remove all of the documents for a given class, you can simply delete all of
|
250
|
+
the PgSearch::Document records.
|
251
|
+
|
252
|
+
PgSearch::Document.delete_all(:searchable_type => "Animal")
|
253
|
+
|
254
|
+
To regenerate the documents for a given class, run:
|
255
|
+
|
256
|
+
PgSearch::Multisearch.rebuild(Product)
|
257
|
+
|
258
|
+
This is also available as a Rake task, for convenience.
|
259
|
+
|
260
|
+
$ rake pg_search:multisearch:rebuild[BlogPost]
|
261
|
+
|
262
|
+
A second optional argument can be passed to specify the PostgreSQL schema
|
263
|
+
search path to use, for multi-tenant databases that have multiple
|
264
|
+
pg_search_documents tables. The following will set the schema search path to
|
265
|
+
"my_schema" before reindexing.
|
266
|
+
|
267
|
+
$ rake pg_search:multisearch:rebuild[BlogPost,my_schema]
|
268
|
+
|
269
|
+
For models that are multisearchable :against methods that directly map to
|
270
|
+
Active Record attributes, an efficient single SQL statement is run to update
|
271
|
+
the pg_search_documents table all at once. However, if you call any dynamic
|
272
|
+
methods in :against, the following strategy will be used:
|
273
|
+
|
274
|
+
PgSearch::Document.delete_all(:searchable_type => "Ingredient")
|
275
|
+
Ingredient.find_each { |record| record.update_pg_search_document }
|
276
|
+
|
277
|
+
You can also provide a custom implementation for rebuilding the documents by
|
278
|
+
adding a class method called `rebuild_pg_search_documents` to your model.
|
279
|
+
|
280
|
+
class Movie < ActiveRecord::Base
|
281
|
+
belongs_to :director
|
282
|
+
|
283
|
+
def director_name
|
284
|
+
director.name
|
285
|
+
end
|
286
|
+
|
287
|
+
multisearchable against: [:name, :director_name]
|
288
|
+
|
289
|
+
# Naive approach
|
290
|
+
def self.rebuild_pg_search_documents
|
291
|
+
find_each { |record| record.update_pg_search_document }
|
292
|
+
end
|
293
|
+
|
294
|
+
# More sophisticated approach
|
295
|
+
def self.rebuild_pg_search_documents
|
296
|
+
connection.execute <<-SQL
|
297
|
+
INSERT INTO pg_search_documents (searchable_type, searchable_id, content, created_at, updated_at)
|
298
|
+
SELECT 'Movie' AS searchable_type,
|
299
|
+
movies.id AS searchable_id,
|
300
|
+
(movies.name || ' ' || directors.name) AS content,
|
301
|
+
now() AS created_at,
|
302
|
+
now() AS updated_at
|
303
|
+
FROM movies
|
304
|
+
LEFT JOIN directors
|
305
|
+
ON directors.id = movies.director_id
|
306
|
+
SQL
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
310
|
+
#### Disabling multi-search indexing temporarily
|
311
|
+
|
312
|
+
If you have a large bulk operation to perform, such as importing a lot of
|
313
|
+
records from an external source, you might want to speed things up by turning
|
314
|
+
off indexing temporarily. You could then use one of the techniques above to
|
315
|
+
rebuild the search documents off-line.
|
316
|
+
|
317
|
+
PgSearch.disable_multisearch do
|
318
|
+
Movie.import_from_xml_file(File.open("movies.xml"))
|
319
|
+
end
|
320
|
+
|
321
|
+
### pg_search_scope
|
322
|
+
|
323
|
+
You can use pg_search_scope to build a search scope. The first parameter is a
|
324
|
+
scope name, and the second parameter is an options hash. The only required
|
325
|
+
option is :against, which tells pg_search_scope which column or columns to
|
326
|
+
search against.
|
327
|
+
|
328
|
+
#### Searching against one column
|
329
|
+
|
330
|
+
To search against a column, pass a symbol as the :against option.
|
331
|
+
|
332
|
+
class BlogPost < ActiveRecord::Base
|
333
|
+
include PgSearch
|
334
|
+
pg_search_scope :search_by_title, :against => :title
|
335
|
+
end
|
336
|
+
|
337
|
+
We now have an ActiveRecord scope named search_by_title on our BlogPost model.
|
338
|
+
It takes one parameter, a search query string.
|
339
|
+
|
340
|
+
BlogPost.create!(:title => "Recent Developments in the World of Pastrami")
|
341
|
+
BlogPost.create!(:title => "Prosciutto and You: A Retrospective")
|
342
|
+
BlogPost.search_by_title("pastrami") # => [#<BlogPost id: 2, title: "Recent Developments in the World of Pastrami">]
|
343
|
+
|
344
|
+
#### Searching against multiple columns
|
345
|
+
|
346
|
+
Just pass an Array if you'd like to search more than one column.
|
347
|
+
|
348
|
+
class Person < ActiveRecord::Base
|
349
|
+
include PgSearch
|
350
|
+
pg_search_scope :search_by_full_name, :against => [:first_name, :last_name]
|
351
|
+
end
|
352
|
+
|
353
|
+
Now our search query can match either or both of the columns.
|
354
|
+
|
355
|
+
person_1 = Person.create!(:first_name => "Grant", :last_name => "Hill")
|
356
|
+
person_2 = Person.create!(:first_name => "Hugh", :last_name => "Grant")
|
357
|
+
|
358
|
+
Person.search_by_full_name("Grant") # => [person_1, person_2]
|
359
|
+
Person.search_by_full_name("Grant Hill") # => [person_1]
|
360
|
+
|
361
|
+
#### Dynamic search scopes
|
362
|
+
|
363
|
+
Just like with Active Record named scopes, you can pass in a Proc object that
|
364
|
+
returns a hash of options. For instance, the following scope takes a parameter
|
365
|
+
that dynamically chooses which column to search against.
|
366
|
+
|
367
|
+
Important: The returned hash must include a :query key. Its value does not
|
368
|
+
necessary have to be dynamic. You could choose to hard-code it to a specific
|
369
|
+
value if you wanted.
|
370
|
+
|
371
|
+
class Person < ActiveRecord::Base
|
372
|
+
include PgSearch
|
373
|
+
pg_search_scope :search_by_name, lambda do |name_part, query|
|
374
|
+
raise ArgumentError unless [:first, :last].include?(name_part)
|
375
|
+
{
|
376
|
+
:against => name_part,
|
377
|
+
:query => query
|
378
|
+
}
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
person_1 = Person.create!(:first_name => "Grant", :last_name => "Hill")
|
383
|
+
person_2 = Person.create!(:first_name => "Hugh", :last_name => "Grant")
|
384
|
+
|
385
|
+
Person.search_by_name :first, "Grant" # => [person_1]
|
386
|
+
Person.search_by_name :last, "Grant" # => [person_2]
|
387
|
+
|
388
|
+
#### Searching through associations
|
389
|
+
|
390
|
+
It is possible to search columns on associated models. Note that if you do
|
391
|
+
this, it will be impossible to speed up searches with database indexes.
|
392
|
+
However, it is supported as a quick way to try out cross-model searching.
|
393
|
+
|
394
|
+
In PostgreSQL 8.3 and earlier, you must install a utility function into your
|
395
|
+
database. To generate and run a migration for this, run:
|
396
|
+
|
397
|
+
$ rails g pg_search:migration:associated_against
|
398
|
+
$ rake db:migrate
|
399
|
+
|
400
|
+
This migration is safe to run against newer versions of PostgreSQL as well. It
|
401
|
+
will essentially do nothing.
|
402
|
+
|
403
|
+
You can pass a Hash into the :associated_against option to set up searching
|
404
|
+
through associations. The keys are the names of the associations and the value
|
405
|
+
works just like an :against option for the other model. Right now, searching
|
406
|
+
deeper than one association away is not supported. You can work around this by
|
407
|
+
setting up a series of :through associations to point all the way through.
|
408
|
+
|
409
|
+
class Cracker < ActiveRecord::Base
|
410
|
+
has_many :cheeses
|
411
|
+
end
|
412
|
+
|
413
|
+
class Cheese < ActiveRecord::Base
|
414
|
+
end
|
415
|
+
|
416
|
+
class Salami < ActiveRecord::Base
|
417
|
+
include PgSearch
|
418
|
+
|
419
|
+
belongs_to :cracker
|
420
|
+
has_many :cheeses, :through => :cracker
|
421
|
+
|
422
|
+
pg_search_scope :tasty_search, :associated_against => {
|
423
|
+
:cheeses => [:kind, :brand],
|
424
|
+
:cracker => :kind
|
425
|
+
}
|
426
|
+
end
|
427
|
+
|
428
|
+
salami_1 = Salami.create!
|
429
|
+
salami_2 = Salami.create!
|
430
|
+
salami_3 = Salami.create!
|
431
|
+
|
432
|
+
limburger = Cheese.create!(:kind => "Limburger")
|
433
|
+
brie = Cheese.create!(:kind => "Brie")
|
434
|
+
pepper_jack = Cheese.create!(:kind => "Pepper Jack")
|
435
|
+
|
436
|
+
Cracker.create!(:kind => "Black Pepper", :cheeses => [brie], :salami => salami_1)
|
437
|
+
Cracker.create!(:kind => "Ritz", :cheeses => [limburger, pepper_jack], :salami => salami_2)
|
438
|
+
Cracker.create!(:kind => "Graham", :cheeses => [limburger], :salami => salami_3)
|
439
|
+
|
440
|
+
Salami.tasty_search("pepper") # => [salami_1, salami_2]
|
441
|
+
|
442
|
+
### Searching using different search features
|
443
|
+
|
444
|
+
By default, pg_search_scope uses the built-in [PostgreSQL text
|
445
|
+
search](http://www.postgresql.org/docs/current/static/textsearch-intro.html).
|
446
|
+
If you pass the :using option to pg_search_scope, you can choose alternative
|
447
|
+
search techniques.
|
448
|
+
|
449
|
+
class Beer < ActiveRecord::Base
|
450
|
+
include PgSearch
|
451
|
+
pg_search_scope :search_name, :against => :name, :using => [:tsearch, :trigram, :dmetaphone]
|
452
|
+
end
|
453
|
+
|
454
|
+
The currently implemented features are
|
455
|
+
|
456
|
+
* :tsearch - [Full text search](http://www.postgresql.org/docs/current/static/textsearch-intro.html)
|
457
|
+
(built-in with 8.3 and later, available as a contrib package for some earlier versions)
|
458
|
+
* :trigram - [Trigram search](http://www.postgresql.org/docs/current/static/pgtrgm.html), which
|
459
|
+
requires the trigram contrib package
|
460
|
+
* :dmetaphone - [Double Metaphone search](http://www.postgresql.org/docs/9.0/static/fuzzystrmatch.html#AEN124771), which requires the fuzzystrmatch contrib package
|
461
|
+
|
462
|
+
|
463
|
+
#### :tsearch (Full Text Search)
|
464
|
+
|
465
|
+
PostgreSQL's built-in full text search supports weighting, prefix searches,
|
466
|
+
and stemming in multiple languages.
|
467
|
+
|
468
|
+
##### Weighting
|
469
|
+
Each searchable column can be given a weight of "A", "B", "C", or "D". Columns
|
470
|
+
with earlier letters are weighted higher than those with later letters. So, in
|
471
|
+
the following example, the title is the most important, followed by the
|
472
|
+
subtitle, and finally the content.
|
473
|
+
|
474
|
+
class NewsArticle < ActiveRecord::Base
|
475
|
+
include PgSearch
|
476
|
+
pg_search_scope :search_full_text, :against => {
|
477
|
+
:title => 'A',
|
478
|
+
:subtitle => 'B',
|
479
|
+
:content => 'C'
|
480
|
+
}
|
481
|
+
end
|
482
|
+
|
483
|
+
You can also pass the weights in as an array of arrays, or any other structure
|
484
|
+
that responds to #each and yields either a single symbol or a symbol and a
|
485
|
+
weight. If you omit the weight, a default will be used.
|
486
|
+
|
487
|
+
class NewsArticle < ActiveRecord::Base
|
488
|
+
include PgSearch
|
489
|
+
pg_search_scope :search_full_text, :against => [
|
490
|
+
[:title, 'A'],
|
491
|
+
[:subtitle, 'B'],
|
492
|
+
[:content, 'C']
|
493
|
+
]
|
494
|
+
end
|
495
|
+
|
496
|
+
class NewsArticle < ActiveRecord::Base
|
497
|
+
include PgSearch
|
498
|
+
pg_search_scope :search_full_text, :against => [
|
499
|
+
[:title, 'A'],
|
500
|
+
{:subtitle => 'B'},
|
501
|
+
:content
|
502
|
+
]
|
503
|
+
end
|
504
|
+
|
505
|
+
##### :prefix (PostgreSQL 8.4 and newer only)
|
506
|
+
|
507
|
+
PostgreSQL's full text search matches on whole words by default. If you want
|
508
|
+
to search for partial words, however, you can set :prefix to true. Since this
|
509
|
+
is a :tsearch-specific option, you should pass it to :tsearch directly, as
|
510
|
+
shown in the following example.
|
511
|
+
|
512
|
+
class Superhero < ActiveRecord::Base
|
513
|
+
include PgSearch
|
514
|
+
pg_search_scope :whose_name_starts_with,
|
515
|
+
:against => :name,
|
516
|
+
:using => {
|
517
|
+
:tsearch => {:prefix => true}
|
518
|
+
}
|
519
|
+
end
|
520
|
+
|
521
|
+
batman = Superhero.create :name => 'Batman'
|
522
|
+
batgirl = Superhero.create :name => 'Batgirl'
|
523
|
+
robin = Superhero.create :name => 'Robin'
|
524
|
+
|
525
|
+
Superhero.whose_name_starts_with("Bat") # => [batman, batgirl]
|
526
|
+
|
527
|
+
##### :dictionary
|
528
|
+
|
529
|
+
PostgreSQL full text search also support multiple dictionaries for stemming.
|
530
|
+
You can learn more about how dictionaries work by reading the [PostgreSQL
|
531
|
+
documention](http://www.postgresql.org/docs/current/static/textsearch-dictionaries.html).
|
532
|
+
If you use one of the language dictionaries, such as "english",
|
533
|
+
then variants of words (e.g. "jumping" and "jumped") will match each other. If
|
534
|
+
you don't want stemming, you should pick the "simple" dictionary which does
|
535
|
+
not do any stemming. If you don't specify a dictionary, the "simple"
|
536
|
+
dictionary will be used.
|
537
|
+
|
538
|
+
class BoringTweet < ActiveRecord::Base
|
539
|
+
include PgSearch
|
540
|
+
pg_search_scope :kinda_matching,
|
541
|
+
:against => :text,
|
542
|
+
:using => {
|
543
|
+
:tsearch => {:dictionary => "english"}
|
544
|
+
}
|
545
|
+
pg_search_scope :literally_matching,
|
546
|
+
:against => :text,
|
547
|
+
:using => {
|
548
|
+
:tsearch => {:dictionary => "simple"}
|
549
|
+
}
|
550
|
+
end
|
551
|
+
|
552
|
+
sleepy = BoringTweet.create! :text => "I snoozed my alarm for fourteen hours today. I bet I can beat that tomorrow! #sleepy"
|
553
|
+
sleeping = BoringTweet.create! :text => "You know what I like? Sleeping. That's what. #enjoyment"
|
554
|
+
sleeper = BoringTweet.create! :text => "Have you seen Woody Allen's movie entitled Sleeper? Me neither. #boycott"
|
555
|
+
|
556
|
+
BoringTweet.kinda_matching("sleeping") # => [sleepy, sleeping, sleeper]
|
557
|
+
BoringTweet.literally_matching("sleeping") # => [sleeping]
|
558
|
+
|
559
|
+
##### :normalization
|
560
|
+
|
561
|
+
PostgreSQL supports multiple algorithms for ranking results against queries.
|
562
|
+
For instance, you might want to consider overall document size or the distance
|
563
|
+
between multiple search terms in the original text. This option takes an
|
564
|
+
integer, which is passed directly to PostgreSQL. According to the latest
|
565
|
+
[PostgreSQL documentation](http://www.postgresql.org/docs/current/static/textsearch-controls.html),
|
566
|
+
the supported algorithms are:
|
567
|
+
|
568
|
+
0 (the default) ignores the document length
|
569
|
+
1 divides the rank by 1 + the logarithm of the document length
|
570
|
+
2 divides the rank by the document length
|
571
|
+
4 divides the rank by the mean harmonic distance between extents
|
572
|
+
8 divides the rank by the number of unique words in document
|
573
|
+
16 divides the rank by 1 + the logarithm of the number of unique words in document
|
574
|
+
32 divides the rank by itself + 1
|
575
|
+
|
576
|
+
This integer is a bitmask, so if you want to combine algorithms, you can add
|
577
|
+
their numbers together.
|
578
|
+
(e.g. to use algorithms 1, 8, and 32, you would pass 1 + 8 + 32 = 41)
|
579
|
+
|
580
|
+
class BigLongDocument < ActiveRecord::Base
|
581
|
+
include PgSearch
|
582
|
+
pg_search_scope :regular_search,
|
583
|
+
:against => :text
|
584
|
+
|
585
|
+
pg_search_scope :short_search,
|
586
|
+
:against => :text,
|
587
|
+
:using => {
|
588
|
+
:tsearch => {:normalization => 2}
|
589
|
+
}
|
590
|
+
|
591
|
+
long = BigLongDocument.create!(:text => "Four score and twenty years ago")
|
592
|
+
short = BigLongDocument.create!(:text => "Four score")
|
593
|
+
|
594
|
+
BigLongDocument.regular_search("four score") #=> [long, short]
|
595
|
+
BigLongDocument.short_search("four score") #=> [short, long]
|
596
|
+
|
597
|
+
##### :any_word
|
598
|
+
|
599
|
+
Setting this attribute to true will perform a search which will return all
|
600
|
+
models containing any word in the search terms.
|
601
|
+
|
602
|
+
class Number < ActiveRecord::Base
|
603
|
+
include PgSearch
|
604
|
+
pg_search_scope :search_any_word,
|
605
|
+
:against => :text,
|
606
|
+
:using => {
|
607
|
+
:tsearch => {:any_word => true}
|
608
|
+
}
|
609
|
+
|
610
|
+
pg_search_scope :search_all_words,
|
611
|
+
:against => :text
|
612
|
+
end
|
613
|
+
|
614
|
+
one = Number.create! :text => 'one'
|
615
|
+
two = Number.create! :text => 'two'
|
616
|
+
three = Number.create! :text => 'three'
|
617
|
+
|
618
|
+
Number.search_any_word('one two three') # => [one, two, three]
|
619
|
+
Number.search_all_words('one two three') # => []
|
620
|
+
|
621
|
+
#### :dmetaphone (Double Metaphone soundalike search)
|
622
|
+
|
623
|
+
[Double Metaphone](http://en.wikipedia.org/wiki/Double_Metaphone) is an
|
624
|
+
algorithm for matching words that sound alike even if they are spelled very
|
625
|
+
differently. For example, "Geoff" and "Jeff" sound identical and thus match.
|
626
|
+
Currently, this is not a true double-metaphone, as only the first metaphone is
|
627
|
+
used for searching.
|
628
|
+
|
629
|
+
Double Metaphone support is currently available as part of the [fuzzystrmatch
|
630
|
+
contrib package](http://www.postgresql.org/docs/current/static/fuzzystrmatch.html)
|
631
|
+
that must be installed before this feature can be used. In addition to the
|
632
|
+
contrib package, you must install a utility function into your database. To
|
633
|
+
generate and run a migration for this, run:
|
634
|
+
|
635
|
+
$ rails g pg_search:migration:dmetaphone
|
636
|
+
$ rake db:migrate
|
637
|
+
|
638
|
+
The following example shows how to use :dmetaphone.
|
639
|
+
|
640
|
+
class Word < ActiveRecord::Base
|
641
|
+
include PgSearch
|
642
|
+
pg_search_scope :that_sounds_like,
|
643
|
+
:against => :spelling,
|
644
|
+
:using => :dmetaphone
|
645
|
+
end
|
646
|
+
|
647
|
+
four = Word.create! :spelling => 'four'
|
648
|
+
far = Word.create! :spelling => 'far'
|
649
|
+
fur = Word.create! :spelling => 'fur'
|
650
|
+
five = Word.create! :spelling => 'five'
|
651
|
+
|
652
|
+
Word.that_sounds_like("fir") # => [four, far, fur]
|
653
|
+
|
654
|
+
#### :trigram (Trigram search)
|
655
|
+
|
656
|
+
Trigram search works by counting how many three-letter substrings (or
|
657
|
+
"trigrams") match between the query and the text. For example, the string
|
658
|
+
"Lorem ipsum" can be split into the following trigrams:
|
659
|
+
|
660
|
+
[" Lo", "Lor", "ore", "rem", "em ", "m i", " ip", "ips", "psu", "sum", "um ", "m "]
|
661
|
+
|
662
|
+
Trigram search has some ability to work even with typos and misspellings in
|
663
|
+
the query or text.
|
664
|
+
|
665
|
+
Trigram support is currently available as part of the [pg_trgm contrib
|
666
|
+
package](http://www.postgresql.org/docs/current/static/pgtrgm.html) that must
|
667
|
+
be installed before this feature can be used.
|
668
|
+
|
669
|
+
class Website < ActiveRecord::Base
|
670
|
+
include PgSearch
|
671
|
+
pg_search_scope :kinda_spelled_like,
|
672
|
+
:against => :name,
|
673
|
+
:using => :trigram
|
674
|
+
end
|
675
|
+
|
676
|
+
yahooo = Website.create! :name => "Yahooo!"
|
677
|
+
yohoo = Website.create! :name => "Yohoo!"
|
678
|
+
gogle = Website.create! :name => "Gogle"
|
679
|
+
facebook = Website.create! :name => "Facebook"
|
680
|
+
|
681
|
+
Website.kinda_spelled_like("Yahoo!") # => [yahooo, yohoo]
|
682
|
+
|
683
|
+
### Ignoring accent marks (PostgreSQL 9.0 and newer only)
|
684
|
+
|
685
|
+
Most of the time you will want to ignore accent marks when searching. This
|
686
|
+
makes it possible to find words like "piñata" when searching with the query
|
687
|
+
"pinata". If you set a pg_search_scope to ignore accents, it will ignore
|
688
|
+
accents in both the searchable text and the query terms.
|
689
|
+
|
690
|
+
Ignoring accents uses the [unaccent contrib
|
691
|
+
package](http://www.postgresql.org/docs/current/static/unaccent.html) that
|
692
|
+
must be installed before this feature can be used.
|
693
|
+
|
694
|
+
class SpanishQuestion < ActiveRecord::Base
|
695
|
+
include PgSearch
|
696
|
+
pg_search_scope :gringo_search,
|
697
|
+
:against => :word,
|
698
|
+
:ignoring => :accents
|
699
|
+
end
|
700
|
+
|
701
|
+
what = SpanishQuestion.create(:word => "Qué")
|
702
|
+
how_many = SpanishQuestion.create(:word => "Cuánto")
|
703
|
+
how = SpanishQuestion.create(:word => "Cómo")
|
704
|
+
|
705
|
+
SpanishQuestion.gringo_search("Que") # => [what]
|
706
|
+
SpanishQuestion.gringo_search("Cüåñtô") # => [how_many]
|
707
|
+
|
708
|
+
Advanced users may wish to add indexes for the expressions that pg_search
|
709
|
+
generates. Unfortunately, the unaccent function supplied by this contrib
|
710
|
+
package is not indexable (as of PostgreSQL 9.1). Thus, you may want to write
|
711
|
+
your own wrapper function and use it instead. This can be configured by
|
712
|
+
calling the following code, perhaps in an initializer.
|
713
|
+
|
714
|
+
PgSearch.unaccent_function = "my_unaccent"
|
715
|
+
|
716
|
+
### Using tsvector columns
|
717
|
+
|
718
|
+
PostgreSQL allows you the ability to search against a column with type
|
719
|
+
tsvector instead of using an expression; this speeds up searching dramatically
|
720
|
+
as it offloads creation of the tsvector that the tsquery is evaluated against.
|
721
|
+
|
722
|
+
To use this functionality you'll need to do a few things:
|
723
|
+
|
724
|
+
* Create a column of type tsvector that you'd like to search against. If you
|
725
|
+
want to search using multiple search methods, for example tsearch and
|
726
|
+
dmetaphone, you'll need a column for each.
|
727
|
+
* Create a trigger function that will update the column(s) using the
|
728
|
+
expression appropriate for that type of search. See:
|
729
|
+
[the PostgreSQL documentation for text search triggers](http://www.postgresql.org/docs/current/static/textsearch-features.html#TEXTSEARCH-UPDATE-TRIGGERS)
|
730
|
+
* Should you have any pre-existing data in the table, update the
|
731
|
+
newly-created tsvector columns with the expression that your trigger
|
732
|
+
function uses.
|
733
|
+
* Add the option to pg_search_scope, e.g:
|
734
|
+
|
735
|
+
pg_search_scope :fast_content_search,
|
736
|
+
:against => :content,
|
737
|
+
:using => {
|
738
|
+
dmetaphone: {
|
739
|
+
tsvector_column: 'tsvector_content_dmetaphone'
|
740
|
+
},
|
741
|
+
tsearch: {
|
742
|
+
dictionary: 'english',
|
743
|
+
tsvector_column: 'tsvector_content_tsearch'
|
744
|
+
}
|
745
|
+
trigram: {} # trigram does not use tsvectors
|
746
|
+
}
|
747
|
+
|
748
|
+
* You cannot dump a `tsvector` column to `schema.rb`. Instead, you need to switch to using the native PostgreSQL SQL format schema dump.
|
749
|
+
In your `config/application.rb` you should set
|
750
|
+
|
751
|
+
config.active_record.schema_format = :sql
|
752
|
+
|
753
|
+
Read more about it here: http://guides.rubyonrails.org/migrations.html#types-of-schema-dumps
|
754
|
+
|
755
|
+
|
756
|
+
Please note that the :against column is only used when the tsvector_column is
|
757
|
+
not present for the search type.
|
758
|
+
|
759
|
+
### Configuring ranking and ordering
|
760
|
+
|
761
|
+
#### :ranked_by (Choosing a ranking algorithm)
|
762
|
+
|
763
|
+
By default, pg_search ranks results based on the :tsearch similarity between
|
764
|
+
the searchable text and the query. To use a different ranking algorithm, you
|
765
|
+
can pass a :ranked_by option to pg_search_scope.
|
766
|
+
|
767
|
+
pg_search_scope :search_by_tsearch_but_rank_by_trigram,
|
768
|
+
:against => :title,
|
769
|
+
:using => [:tsearch],
|
770
|
+
:ranked_by => ":trigram"
|
771
|
+
|
772
|
+
Note that :ranked_by using a String to represent the ranking expression. This
|
773
|
+
allows for more complex possibilities. Strings like ":tsearch", ":trigram",
|
774
|
+
and ":dmetaphone" are automatically expanded into the appropriate SQL
|
775
|
+
expressions.
|
776
|
+
|
777
|
+
# Weighted ranking to balance multiple approaches
|
778
|
+
:ranked_by => ":dmetaphone + (0.25 * :trigram)"
|
779
|
+
|
780
|
+
# A more complex example, where books.num_pages is an integer column in the table itself
|
781
|
+
:ranked_by => "(books.num_pages * :trigram) + (:tsearch / 2.0)"
|
782
|
+
|
783
|
+
#### :order_within_rank (Breaking ties)
|
784
|
+
|
785
|
+
PostgreSQL does not guarantee a consistent order when multiple records have
|
786
|
+
the same value in the ORDER BY clause. This can cause trouble with pagination.
|
787
|
+
Imagine a case where 12 records all have the same ranking value. If you use a
|
788
|
+
pagination library such as [kaminari](https://github.com/amatsuda/kaminari) or
|
789
|
+
[will_paginate](https://github.com/mislav/will_paginate) to return results in
|
790
|
+
pages of 10, then you would expect to see 10 of the records on page 1, and the
|
791
|
+
remaining 2 records at the top of the next page, ahead of lower-ranked
|
792
|
+
results.
|
793
|
+
|
794
|
+
But since there is no consistent ordering, PostgreSQL might choose to
|
795
|
+
rearrange the order of those 12 records between different SQL statements. You
|
796
|
+
might end up getting some of the same records from page 1 on page 2 as well,
|
797
|
+
and likewise there may be records that don't show up at all.
|
798
|
+
|
799
|
+
pg_search fixes this problem by adding a second expression to the ORDER BY
|
800
|
+
clause, after the :ranked_by expression explained above. By default, the
|
801
|
+
tiebreaker order is ascending by id.
|
802
|
+
|
803
|
+
ORDER BY [complicated :ranked_by expression...], id ASC
|
804
|
+
|
805
|
+
This might not be desirable for your application, especially if you do not
|
806
|
+
want old records to outrank new records. By passing an :order_within_rank, you
|
807
|
+
can specify an alternate tiebreaker expression. A common example would be
|
808
|
+
descending by updated_at, to rank the most recently updated records first.
|
809
|
+
|
810
|
+
pg_search_scope :search_and_break_ties_by_latest_update,
|
811
|
+
:against => [:title, :content],
|
812
|
+
:order_within_rank => "blog_posts.updated_at DESC"
|
813
|
+
|
814
|
+
#### PgSearch#pg_search_rank (Reading a record's rank as a Float)
|
815
|
+
|
816
|
+
It may be useful or interesting to see the rank of a particular record. This
|
817
|
+
can be helpful for debugging why one record outranks another. You could also
|
818
|
+
use it to show some sort of relevancy value to end users of an application.
|
819
|
+
Just call .pg_search_rank on a record returned by a pg_search_scope.
|
820
|
+
|
821
|
+
shirt_brands = ShirtBrand.search_by_name("Penguin")
|
822
|
+
shirt_brands[0].pg_search_rank #=> 0.0759909
|
823
|
+
shirt_brands[1].pg_search_rank #=> 0.0607927
|
824
|
+
|
825
|
+
## ATTRIBUTIONS
|
826
|
+
|
827
|
+
PgSearch would not have been possible without inspiration from texticle (now renamed
|
828
|
+
[textacular](https://github.com/textacular/textacular)). Thanks to [Aaron
|
829
|
+
Patterson](http://tenderlovemaking.com/) for the original version!
|
830
|
+
|
831
|
+
## CONTRIBUTIONS AND FEEDBACK
|
832
|
+
|
833
|
+
Welcomed! Feel free to join and contribute to our [public Pivotal Tracker
|
834
|
+
project](https://www.pivotaltracker.com/projects/228645) where we manage new
|
835
|
+
feature ideas and bugs.
|
836
|
+
|
837
|
+
We also have a [Google Group](http://groups.google.com/group/casecommons-dev)
|
838
|
+
for discussing pg_search and other Case Commons open source projects.
|
839
|
+
|
840
|
+
Please read our [CONTRIBUTING guide](https://github.com/Casecommons/pg_search/blob/master/CONTRIBUTING.md).
|
841
|
+
|
842
|
+
## LICENSE
|
843
|
+
|
844
|
+
MIT
|