Skip to content


Subversion checkout URL

You can clone with
Download ZIP
pg_search builds ActiveRecord named scopes that take advantage of PostgreSQL's full text search
Pull request Compare This branch is 7 commits ahead, 530 commits behind master.
Fetching latest commit...
Cannot retrieve the latest commit at this time.
Failed to load latest commit information.




PgSearch builds named scopes that take advantage of PostgreSQL's full text search

Read the blog post introducing PgSearch at


gem install pg_search

Rails 3

In Gemfile

gem 'pg_search'

Rails 2

In environment.rb

config.gem 'pg_search'

In Rakefile

require 'rubygems'
require 'pg_search/tasks'


To add PgSearch to an ActiveRecord model, simply include the PgSearch module.

class Shape < ActiveRecord::Base
  include PgSearch


You can use pg_search_scope to build a search scope. The first parameter is a scope name, and the second parameter is an options hash. The only required option is :against, which tells pg_search_scope which column or columns to search against.

Searching against one column

To search against a column, pass a symbol as the :against option.

class BlogPost < ActiveRecord::Base
  include PgSearch
  pg_search_scope :search_by_title, :against => :title

We now have an ActiveRecord scope named search_by_title on our BlogPost model. It takes one parameter, a search query string.

BlogPost.create!(:title => "Recent Developments in the World of Pastrami")
BlogPost.create!(:title => "Prosciutto and You: A Retrospective")
BlogPost.search_by_title("pastrami") # => [#<BlogPost id: 2, title: "Recent Developments in the World of Pastrami">]

Searching against multiple columns

Just pass an Array if you'd like to search more than one column.

class Person < ActiveRecord::Base
  include PgSearch
  pg_search_scope :search_by_full_name, :against => [:first_name, :last_name]

Now our search query can match either or both of the columns.

person_1 = Person.create!(:first_name => "Grant", :last_name => "Hill")
person_2 = Person.create!(:first_name => "Hugh", :last_name => "Grant")

Person.search_by_full_name("Grant") # => [person_1, person_2]
Person.search_by_full_name("Grant Hill") # => [person_1]

Dynamic search scopes

Just like with Active Record named scopes, you can pass in a Proc object that returns a hash of options. For instance, the following scope takes a parameter that dynamically chooses which column to search against.

Important: The returned hash must include a :query key. Its value does not necessary have to be dynamic. You could choose to hard-code it to a specific value if you wanted.

class Person < ActiveRecord::Base
  include PgSearch
  pg_search_scope :search_by_name, lambda do |name_part, query|
    raise ArgumentError unless [:first, :last].include?(name_part)
      :against => name_part,
      :query => query

person_1 = Person.create!(:first_name => "Grant", :last_name => "Hill")
person_2 = Person.create!(:first_name => "Hugh", :last_name => "Grant")

Person.search_by_name :first, "Grant" # => [person_1]
Person.search_by_name :last, "Grant" # => [person_2]

Searching through associations

You can pass a Hash into the :associated_against option to search columns on other models. The keys are the names of the associations and the value works just like an :against option for the other model.

class Cracker < ActiveRecord::Base
  has_many :cheeses

class Cheese < ActiveRecord::Base

class Salami < ActiveRecord::Base
  include PgSearch

  belongs_to :cracker
  has_many :cheeses, :through => :cracker

  pg_search_scope :tasty_search, :associated_against => {
    :cheeses => [:kind, :brand],
    :cracker => :kind

salami_1 = Salami.create!
salami_2 = Salami.create!
salami_3 = Salami.create!

limburger = Cheese.create!(:kind => "Limburger")
brie = Cheese.create!(:kind => "Brie")
pepper_jack = Cheese.create!(:kind => "Pepper Jack")

Cracker.create!(:kind => "Black Pepper", :cheeses => [brie], :salami => salami_1)
Cracker.create!(:kind => "Ritz", :cheeses => [limburger, pepper_jack], :salami => salami_2)
Cracker.create!(:kind => "Graham", :cheeses => [limburger], :salami => salami_3)

Salami.tasty_search("pepper") # => [salami_1, salami_2]

Searching using different search features

By default, pg_search_scope uses the built-in PostgreSQL text search. If you pass the :features option to pg_search_scope, you can choose alternative search techniques.

class Beer < ActiveRecord::Base
  include PgSearch
  pg_search_scope :against => :name, :features => [:tsearch, :trigram, :dmetaphone]

The currently implemented features are

  • :tsearch - Full text search (built-in with 8.3 and later, available as a contrib package for some earlier versions)

  • :trigram - Trigram search, which requires the trigram contrib package

  • :dmetaphone - Double Metaphone search, which requires the fuzzystrmatch contrib package

:tsearch (Full Text Search)

PostgreSQL's built-in full text search supports weighting, prefix searches, and stemming in multiple languages.


Each searchable column can be given a weight of “A”, “B”, “C”, or “D”. Columns with earlier letters are weighted higher than those with later letters. So, in the following example, the title is the most important, followed by the subtitle, and finally the content.

class NewsArticle < ActiveRecord::Base
  include PgSearch
  pg_search_scope :against => {
    :title => 'A',
    :subtitle => 'B',
    :content => 'C'

You can also pass the weights in as an array of arrays, or any other structure that responds to #each and yields either a single symbol or a symbol and a weight. If you omit the weight, a default will be used.

class NewsArticle < ActiveRecord::Base
  include PgSearch
  pg_search_scope :against => [
    [:title, 'A'],
    [:subtitle, 'B'],
    [:content, 'C']

class NewsArticle < ActiveRecord::Base
  include PgSearch
  pg_search_scope :against => [
    [:title, 'A'],
    {:subtitle => 'B'},

PostgreSQL's full text search matches on whole words by default. If you want to search for partial words, however, you can set :prefix to true. Since this is a :tsearch-specific option, you should pass it to :tsearch directly, as shown in the following example.

class Superhero < ActiveRecord::Base
  include PgSearch
  pg_search_scope :whose_name_starts_with,
                  :against => :name,
                  :using => {
                    :tsearch => {:prefix => true}

batman = Superhero.create :name => 'Batman'
batgirl = Superhero.create :name => 'Batgirl'
robin = Superhero.create :name => 'Robin'

Superhero.whose_name_starts_with("Bat") # => [batman, batgirl]

PostgreSQL full text search also support multiple dictionaries for stemming. You can learn more about how dictionaries work by reading the PostgreSQL documention. If you use one of the language dictionaries, such as “english”, then variants of words (e.g. “jumping” and “jumped”) will match each other. If you don't want stemming, you should pick the “simple” dictionary which does not do any stemming. If you don't specify a dictionary, the “simple” dictionary will be used.

class BoringTweet < ActiveRecord::Base
  include PgSearch
  pg_search_scope :kinda_matching,
                  :against => :text,
                  :using => {
                    :tsearch => {:dictionary => "english"}
  pg_search_scope :literally_matching,
                  :against => :text,
                  :using => {
                    :tsearch => {:dictionary => "simple"}

sleepy = BoringTweet.create! :text => "I snoozed my alarm for fourteen hours today. I bet I can beat that tomorrow! #sleepy"
sleeping = BoringTweet.create! :text => "You know what I like? Sleeping. That's what. #enjoyment"
sleeper = BoringTweet.create! :text => "Have you seen Woody Allen's movie entitled Sleeper? Me neither. #boycott"

BoringTweet.kinda_matching("sleeping") # => [sleepy, sleeping, sleeper]
BoringTweet.literally_matching("sleeping") # => [sleeping]

:dmetaphone (Double Metaphone soundalike search)

Double Metaphone is an algorithm for matching words that sound alike even if they are spelled very differently. For example, “Geoff” and “Jeff” sound identical and thus match. Currently, this is not a true double-metaphone, as only the first metaphone is used for searching.

Double Metaphone support is currently available as part of the fuzzystrmatch contrib package that must be installed before this feature can be used. In addition to the contrib package, you must install a utility function into your database. To generate a migration for this, add the following line to your Rakefile:

include "pg_search/tasks"

and then run:

$ rake pg_search:migration:dmetaphone

The following example shows how to use :dmetaphone.

class Word < ActiveRecord::Base
  include PgSearch
  pg_search_scope :that_sounds_like,
                  :against => :spelling,
                  :using => :dmetaphone

four = Word.create! :spelling => 'four'
far = Word.create! :spelling => 'far'
fur = Word.create! :spelling => 'fur'
five = Word.create! :spelling => 'five'

Word.that_sounds_like("fir") # => [four, far, fur]

:trigram (Trigram search)

Trigram search works by counting how many three-letter substrings (or “trigrams”) match between the query and the text. For example, the string “Lorem ipsum” can be split into the following trigrams:

[" Lo", "Lor", "ore", "rem", "em ", "m i", " ip", "ips", "psu", "sum", "um ", "m  "]

Trigram search has some ability to work even with typos and misspellings in the query or text.

Trigram support is currently available as part of the pg_trgm contrib package that must be installed before this feature can be used.

class Website < ActiveRecord::Base
  include PgSearch
  pg_search_scope :kinda_spelled_like,
                  :against => :name,
                  :using => :trigram

yahooo = Website.create! :name => "Yahooo!"
yohoo = Website.create! :name => "Yohoo!"
gogle = Website.create! :name => "Gogle"
facebook = Website.create! :name => "Facebook"

Website.kinda_spelled_like("Yahoo!") # => [yahooo, yohoo]

Ignoring accent marks

Most of the time you will want to ignore accent marks when searching. This makes it possible to find words like “piñata” when searching with the query “pinata”. If you set a pg_search_scope to ignore accents, it will ignore accents in both the searchable text and the query terms.

Ignoring accents uses the unaccent contrib package that must be installed before this feature can be used.

class SpanishQuestion < ActiveRecord::Base
  include PgSearch
  pg_search_scope :gringo_search,
                  :against => :word,
                  :ignoring => :accents

what = SpanishQuestion.create(:word => "Qué")
how_many = SpanishQuestion.create(:word => "Cuánto")
how = SpanishQuestion.create(:word => "Cómo")

SpanishQuestion.gringo_search("Que") # => [what]
SpanishQuestion.gringo_search("Cüåñtô") # => [how_many]

Using tsvector columns

PostgreSQL allows you the ability to search against a column with type tsvector instead of using an expression; this speeds up searching dramatically as it offloads creation of the tsvector that the tsquery is evaluated against.

To use this functionality you'll need to do a few things:

  • Create a column of type tsvector that you'd like to search against. If you want to search using multiple search methods, for example tsearch and dmetaphone, you'll need a column for each.

  • Create a trigger function that will update the column(s) using the expression appropriate for that type of search. See:

  • Should you have any pre-existing data in the table, update the newly-created tsvector columns with the expression that your trigger function uses.

  • Add the option to pg_search_scope, e.g:

    pg_search_scope :fast_content_search,

    :against => :content,
    :using => {
      dmetaphone: {
        tsvector_column: 'tsvector_content_dmetaphone'
      tsearch: {
        dictionary: 'english',
        tsvector_column: 'tsvector_content_tsearch'
      trigram: {} # trigram does not use tsvectors

Please note that the :against column is only used when the tsvector_column is not present for the search type.


  • ActiveRecord 2 or 3

  • Postgresql

  • Postgresql contrib modules for certain features


PgSearch would not have been possible without inspiration from texticle. Thanks to Aaron Patterson!


Welcomed! Feel free to join and contribute to our public Pivotal Tracker project where we manage new feature ideas and bugs.

We also have a Google Group for discussing pg_search and other Case Commons open source projects.



Something went wrong with that request. Please try again.