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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: cdc3e1fa91903e87b5f1a6dfb0ac0318061e7646
4
- data.tar.gz: 16186ed72bc2e8bbc57c0c7949a6b051756db408
3
+ metadata.gz: cd010cfa10f28bb6d953f4fe09dc9c19f10c6239
4
+ data.tar.gz: c8ece97ff1b44ed62d98272a0df15ff42813dc50
5
5
  SHA512:
6
- metadata.gz: 58ded3351aca03cc89d6c23815845a4f8835b981f68755bae65ba202360e4afb5273e11683d6de1896d21f52f4c81fb921ed6b37c544113aa4bde01cc8c28018
7
- data.tar.gz: 7603d827a286170f0a69a7e77b452ba7919f5e858a0ed0ee2eea196108d25654070417386ccdee7a377d7221c0a06091bd6e4608ea5627697716b5f55988af73
6
+ metadata.gz: 8d1358647c4e451ddd3f837cfd51b9a4bd6c363f075fe8965128c30bfe65d279ef9795ab50282284641cdce0264e381823dfc8e847859a84980c6e4b439ae2cb
7
+ data.tar.gz: b1cf6a6c1efc8e6b3227a3c87b5877c42501d5ca963f96a94d74ae809a39292f78359004cb93f67e73aee49b5ed124439dce8ce12c0ac9f78bc5eeddb5ca6b5a
data/.gitignore CHANGED
@@ -3,6 +3,7 @@ doc
3
3
  *.gem
4
4
  .bundle
5
5
  .idea
6
+ .yardoc
6
7
  tags
7
8
  *~
8
9
  Gemfile.lock
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