{"id":13427918,"url":"https://github.com/Casecommons/pg_search","last_synced_at":"2025-03-16T00:32:32.924Z","repository":{"id":1310947,"uuid":"1254797","full_name":"Casecommons/pg_search","owner":"Casecommons","description":"pg_search builds ActiveRecord named scopes that take advantage of PostgreSQL’s full text search ","archived":false,"fork":false,"pushed_at":"2025-02-04T17:19:08.000Z","size":1233,"stargazers_count":1408,"open_issues_count":154,"forks_count":372,"subscribers_count":7,"default_branch":"master","last_synced_at":"2025-03-11T16:11:11.594Z","etag":null,"topics":["activerecord","metaphone","multi-search","pg-search","postgresql","ruby","search","trigrams","tsearch"],"latest_commit_sha":null,"homepage":"http://www.casebook.net","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":"sunng87/node-geohash","license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Casecommons.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2011-01-14T15:26:06.000Z","updated_at":"2025-03-11T08:49:42.000Z","dependencies_parsed_at":"2024-05-01T13:19:59.119Z","dependency_job_id":"ff178fdd-86c6-47bf-b638-540b0c8553fc","html_url":"https://github.com/Casecommons/pg_search","commit_stats":{"total_commits":999,"total_committers":118,"mean_commits":8.466101694915254,"dds":0.3323323323323323,"last_synced_commit":"5c5011b1bc8fdd8995a7e09a7215bb3aa940a138"},"previous_names":[],"tags_count":65,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Casecommons%2Fpg_search","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Casecommons%2Fpg_search/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Casecommons%2Fpg_search/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Casecommons%2Fpg_search/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Casecommons","download_url":"https://codeload.github.com/Casecommons/pg_search/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243809865,"owners_count":20351403,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["activerecord","metaphone","multi-search","pg-search","postgresql","ruby","search","trigrams","tsearch"],"created_at":"2024-07-31T01:00:42.511Z","updated_at":"2025-03-16T00:32:32.915Z","avatar_url":"https://github.com/Casecommons.png","language":"Ruby","readme":"# [pg_search](http://github.com/Casecommons/pg_search/)\n\n[![Gem Version](https://img.shields.io/gem/v/pg_search.svg?style=flat)](https://rubygems.org/gems/pg_search)\n[![Build Status](https://github.com/Casecommons/pg_search/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/Casecommons/pg_search/actions/workflows/ci.yml)\n[![Join the chat at https://gitter.im/Casecommons/pg_search](https://img.shields.io/badge/gitter-join%20chat-blue.svg)](https://gitter.im/Casecommons/pg_search?utm_source=badge\u0026utm_medium=badge\u0026utm_campaign=pr-badge\u0026utm_content=badge)\n\n## DESCRIPTION\n\nPgSearch builds named scopes that take advantage of PostgreSQL's full text\nsearch.\n\nRead the blog post introducing PgSearch at https://tanzu.vmware.com/content/blog/pg-search-how-i-learned-to-stop-worrying-and-love-postgresql-full-text-search\n\n## REQUIREMENTS\n\n*   Ruby 3.1+\n*   Active Record 7.0+\n*   PostgreSQL 9.2+\n*   [PostgreSQL extensions](https://github.com/Casecommons/pg_search/wiki/Installing-PostgreSQL-Extensions) for certain features\n\n## INSTALL\n\n```\n$ gem install pg_search\n```\n\nor add this line to your Gemfile:\n\n```ruby\ngem 'pg_search'\n```\n\n### Non-Rails projects\n\nIn addition to installing and requiring the gem, you may want to include the\nPgSearch rake tasks in your Rakefile. This isn't necessary for Rails projects,\nwhich gain the Rake tasks via a Railtie.\n\n```ruby\nload \"pg_search/tasks.rb\"\n```\n\n## USAGE\n\nTo add PgSearch to an Active Record model, simply include the PgSearch module.\n\n```ruby\nclass Shape \u003c ActiveRecord::Base\n  include PgSearch::Model\nend\n```\n\n### Contents\n* [Multi-search vs. search scopes](#multi-search-vs-search-scopes)\n* [Multi-search](#multi-search)\n  * [Setup](#setup)\n  * [`multisearchable`](#multisearchable)\n  * [More Options ](#more-options)\n  * [Multi-search associations](#multi-search-associations)\n  * [Searching in the global search index](#searching-in-the-global-search-index)\n  * [Chaining method calls onto the results](#chaining-method-calls-onto-the-results)\n  * [Configuring multi-search](#configuring-multi-search)\n  * [Rebuilding search documents for a given class](#rebuilding-search-documents-for-a-given-class)\n  * [Disabling multi-search indexing temporarily](#disabling-multi-search-indexing-temporarily)\n* [`pg_search_scope`](#pg_search_scope)\n  * [Searching against one column](#searching-against-one-column)\n  * [Searching against multiple columns](#searching-against-multiple-columns)\n  * [Dynamic search scopes](#dynamic-search-scopes)\n  * [Searching through associations](#searching-through-associations)\n* [Searching using different search features](#searching-using-different-search-features)\n  * [`:tsearch` (Full Text Search)](#tsearch-full-text-search)\n    * [Weighting](#weighting)\n    * [`:prefix` (PostgreSQL 8.4 and newer only)](#prefix-postgresql-84-and-newer-only)\n    * [`:negation`](#negation)\n    * [`:dictionary`](#dictionary)\n    * [`:normalization`](#normalization)\n    * [`:any_word`](#any_word)\n    * [`:sort_only`](#sort_only)\n    * [`:highlight`](#highlight)\n  * [`:dmetaphone` (Double Metaphone soundalike search)](#dmetaphone-double-metaphone-soundalike-search)\n  * [`:trigram` (Trigram search)](#trigram-trigram-search)\n    * [`:threshold`](#threshold)\n    * [`:word_similarity`](#word_similarity)\n* [Limiting Fields When Combining Features](#limiting-fields-when-combining-features)\n* [Ignoring accent marks](#ignoring-accent-marks)\n* [Using tsvector columns](#using-tsvector-columns)\n  * [Combining multiple tsvectors](#combining-multiple-tsvectors)\n* [Configuring ranking and ordering](#configuring-ranking-and-ordering)\n  * [`:ranked_by` (Choosing a ranking algorithm)](#ranked_by-choosing-a-ranking-algorithm)\n  * [`:order_within_rank` (Breaking ties)](#order_within_rank-breaking-ties)\n  * [`PgSearch#pg_search_rank` (Reading a record's rank as a Float)](#pgsearchpg_search_rank-reading-a-records-rank-as-a-float)\n  * [Search rank and chained scopes](#search-rank-and-chained-scopes)\n\n### Multi-search vs. search scopes\n\npg_search supports two different techniques for searching, multi-search and\nsearch scopes.\n\nThe first technique is multi-search, in which records of many different Active\nRecord classes can be mixed together into one global search index across your\nentire application. Most sites that want to support a generic search page will\nwant to use this feature.\n\nThe other technique is search scopes, which allow you to do more advanced\nsearching against only one Active Record class. This is more useful for\nbuilding things like autocompleters or filtering a list of items in a faceted\nsearch.\n\n### Multi-search\n\n#### Setup\n\nBefore using multi-search, you must generate and run a migration to create the\npg_search_documents database table.\n\n```bash\n$ rails g pg_search:migration:multisearch\n$ rake db:migrate\n```\n\n#### multisearchable\n\nTo add a model to the global search index for your application, call\nmultisearchable in its class definition.\n\n```ruby\nclass EpicPoem \u003c ActiveRecord::Base\n  include PgSearch::Model\n  multisearchable against: [:title, :author]\nend\n\nclass Flower \u003c ActiveRecord::Base\n  include PgSearch::Model\n  multisearchable against: :color\nend\n```\n\nIf this model already has existing records, you will need to reindex this\nmodel to get existing records into the pg_search_documents table. See the\nrebuild task below.\n\nWhenever a record is created, updated, or destroyed, an Active Record callback\nwill fire, leading to the creation of a corresponding PgSearch::Document\nrecord in the pg_search_documents table. The :against option can be one or\nseveral methods which will be called on the record to generate its search\ntext.\n\nYou can also pass a Proc or method name to call to determine whether or not a\nparticular record should be included.\n\n```ruby\nclass Convertible \u003c ActiveRecord::Base\n  include PgSearch::Model\n  multisearchable against: [:make, :model],\n                  if: :available_in_red?\nend\n\nclass Jalopy \u003c ActiveRecord::Base\n  include PgSearch::Model\n  multisearchable against: [:make, :model],\n                  if: lambda { |record| record.model_year \u003e 1970 }\nend\n```\n\nNote that the Proc or method name is called in an after_save hook. This means\nthat you should be careful when using Time or other objects. In the following\nexample, if the record was last saved before the published_at timestamp, it\nwon't get listed in global search at all until it is touched again after the\ntimestamp.\n\n```ruby\nclass AntipatternExample\n  include PgSearch::Model\n  multisearchable against: [:contents],\n                  if: :published?\n\n  def published?\n    published_at \u003c Time.now\n  end\nend\n\nproblematic_record = AntipatternExample.create!(\n  contents: \"Using :if with a timestamp\",\n  published_at: 10.minutes.from_now\n)\n\nproblematic_record.published?     # =\u003e false\nPgSearch.multisearch(\"timestamp\") # =\u003e No results\n\nsleep 20.minutes\n\nproblematic_record.published?     # =\u003e true\nPgSearch.multisearch(\"timestamp\") # =\u003e No results\n\nproblematic_record.save!\n\nproblematic_record.published?     # =\u003e true\nPgSearch.multisearch(\"timestamp\") # =\u003e Includes problematic_record\n```\n\n#### More Options\n\n**Conditionally update pg_search_documents**\n\nYou can also use the `:update_if` option to pass a Proc or method name to call\nto determine whether or not a particular record should be updated.\n\nNote that the Proc or method name is called in an `after_save` hook, so if you\nare relying on ActiveRecord dirty flags use `*_previously_changed?`.\n\n```ruby\nclass Message \u003c ActiveRecord::Base\n  include PgSearch::Model\n  multisearchable against: [:body],\n                  update_if: :body_previously_changed?\nend\n```\n\n**Specify additional attributes to be saved on the pg_search_documents table**\n\nYou can specify `:additional_attributes` to be saved within the `pg_search_documents` table. For example, perhaps you are indexing a book model and an article model and wanted to include the author_id.\n\nFirst, we need to add a reference to author to the migration creating our `pg_search_documents` table.\n\n```ruby\n  create_table :pg_search_documents do |t|\n    t.text :content\n    t.references :author, index: true\n    t.belongs_to :searchable, polymorphic: true, index: true\n    t.timestamps null: false\n  end\n```\n\nThen, we can send in this additional attribute in a lambda\n\n```ruby\n  multisearchable(\n    against: [:title, :body],\n    additional_attributes: -\u003e (article) { { author_id: article.author_id } }\n  )\n```\n\nThis allows much faster searches without joins later on by doing something like:\n\n```ruby\nPgSearch.multisearch(params['search']).where(author_id: 2)\n```\n\n*NOTE: You must currently manually call `record.update_pg_search_document` for\nthe additional attribute to be included in the pg_search_documents table*\n\n#### Multi-search associations\n\nTwo associations are built automatically. On the original record, there is a\nhas_one :pg_search_document association pointing to the PgSearch::Document\nrecord, and on the PgSearch::Document record there is a belongs_to :searchable\npolymorphic association pointing back to the original record.\n\n```ruby\nodyssey = EpicPoem.create!(title: \"Odyssey\", author: \"Homer\")\nsearch_document = odyssey.pg_search_document #=\u003e PgSearch::Document instance\nsearch_document.searchable #=\u003e #\u003cEpicPoem id: 1, title: \"Odyssey\", author: \"Homer\"\u003e\n```\n\n#### Searching in the global search index\n\nTo fetch the PgSearch::Document entries for all of the records that match a\ngiven query, use PgSearch.multisearch.\n\n```ruby\nodyssey = EpicPoem.create!(title: \"Odyssey\", author: \"Homer\")\nrose = Flower.create!(color: \"Red\")\nPgSearch.multisearch(\"Homer\") #=\u003e [#\u003cPgSearch::Document searchable: odyssey\u003e]\nPgSearch.multisearch(\"Red\") #=\u003e [#\u003cPgSearch::Document searchable: rose\u003e]\n```\n\n#### Chaining method calls onto the results\n\nPgSearch.multisearch returns an ActiveRecord::Relation, just like scopes do,\nso you can chain scope calls to the end. This works with gems like Kaminari\nthat add scope methods. Just like with regular scopes, the database will only\nreceive SQL requests when necessary.\n\n```ruby\nPgSearch.multisearch(\"Bertha\").limit(10)\nPgSearch.multisearch(\"Juggler\").where(searchable_type: \"Occupation\")\nPgSearch.multisearch(\"Alamo\").page(3).per(30)\nPgSearch.multisearch(\"Diagonal\").find_each do |document|\n  puts document.searchable.updated_at\nend\nPgSearch.multisearch(\"Moro\").reorder(\"\").group(:searchable_type).count(:all)\nPgSearch.multisearch(\"Square\").includes(:searchable)\n```\n\n#### Configuring multi-search\n\nPgSearch.multisearch can be configured using the same options as\n`pg_search_scope` (explained in more detail below). Just set the\nPgSearch.multisearch_options in an initializer:\n\n```ruby\nPgSearch.multisearch_options = {\n  using: [:tsearch, :trigram],\n  ignoring: :accents\n}\n```\n\n#### Rebuilding search documents for a given class\n\nIf you change the :against option on a class, add multisearchable to a class\nthat already has records in the database, or remove multisearchable from a\nclass in order to remove it from the index, you will find that the\npg_search_documents table could become out-of-sync with the actual records in\nyour other tables.\n\nThe index can also become out-of-sync if you ever modify records in a way that\ndoes not trigger Active Record callbacks. For instance, the #update_attribute\ninstance method and the .update_all class method both skip callbacks and\ndirectly modify the database.\n\nTo remove all of the documents for a given class, you can simply delete all of\nthe PgSearch::Document records.\n\n```ruby\nPgSearch::Document.delete_by(searchable_type: \"Animal\")\n```\n\nTo regenerate the documents for a given class, run:\n\n```ruby\nPgSearch::Multisearch.rebuild(Product)\n```\n\nThe ```rebuild``` method will delete all the documents for the given class\nbefore regenerating them. In some situations this may not be desirable,\nsuch as when you're using single-table inheritance and ```searchable_type```\nis your base class. You can prevent ```rebuild``` from deleting your records\nlike so:\n\n```ruby\nPgSearch::Multisearch.rebuild(Product, clean_up: false)\n```\n\n```rebuild``` runs inside a single transaction. To run outside of a transaction,\nyou can pass ```transactional: false``` like so:\n\n```ruby\nPgSearch::Multisearch.rebuild(Product, transactional: false)\n```\n\nRebuild is also available as a Rake task, for convenience.\n\n    $ rake pg_search:multisearch:rebuild[BlogPost]\n\nA second optional argument can be passed to specify the PostgreSQL schema\nsearch path to use, for multi-tenant databases that have multiple\npg_search_documents tables. The following will set the schema search path to\n\"my_schema\" before reindexing.\n\n    $ rake pg_search:multisearch:rebuild[BlogPost,my_schema]\n\nFor models that are multisearchable `:against` methods that directly map to\nActive Record attributes, an efficient single SQL statement is run to update\nthe `pg_search_documents` table all at once. However, if you call any dynamic\nmethods in `:against` then `update_pg_search_document` will be called on the\nindividual records being indexed in batches.\n\nYou can also provide a custom implementation for rebuilding the documents by\nadding a class method called `rebuild_pg_search_documents` to your model.\n\n```ruby\nclass Movie \u003c ActiveRecord::Base\n  belongs_to :director\n\n  def director_name\n    director.name\n  end\n\n  multisearchable against: [:name, :director_name]\n\n  # Naive approach\n  def self.rebuild_pg_search_documents\n    find_each { |record| record.update_pg_search_document }\n  end\n\n  # More sophisticated approach\n  def self.rebuild_pg_search_documents\n    connection.execute \u003c\u003c~SQL.squish\n     INSERT INTO pg_search_documents (searchable_type, searchable_id, content, created_at, updated_at)\n       SELECT 'Movie' AS searchable_type,\n              movies.id AS searchable_id,\n              CONCAT_WS(' ', movies.name, directors.name) AS content,\n              now() AS created_at,\n              now() AS updated_at\n       FROM movies\n       LEFT JOIN directors\n         ON directors.id = movies.director_id\n    SQL\n  end\nend\n```\n**Note:** If using PostgreSQL before 9.1, replace the `CONCAT_WS()` function call with double-pipe concatenation, eg. `(movies.name || ' ' || directors.name)`. However, now be aware that if *any* of the joined values is NULL then the final `content` value will also be NULL, whereas `CONCAT_WS()` will selectively ignore NULL values.\n\n#### Disabling multi-search indexing temporarily\n\nIf you have a large bulk operation to perform, such as importing a lot of\nrecords from an external source, you might want to speed things up by turning\noff indexing temporarily. You could then use one of the techniques above to\nrebuild the search documents off-line.\n\n```ruby\nPgSearch.disable_multisearch do\n  Movie.import_from_xml_file(File.open(\"movies.xml\"))\nend\n```\n\n### pg_search_scope\n\nYou can use pg_search_scope to build a search scope. The first parameter is a\nscope name, and the second parameter is an options hash. The only required\noption is :against, which tells pg_search_scope which column or columns to\nsearch against.\n\n#### Searching against one column\n\nTo search against a column, pass a symbol as the :against option.\n\n```ruby\nclass BlogPost \u003c ActiveRecord::Base\n  include PgSearch::Model\n  pg_search_scope :search_by_title, against: :title\nend\n```\n\nWe now have an ActiveRecord scope named search_by_title on our BlogPost model.\nIt takes one parameter, a search query string.\n\n```ruby\nBlogPost.create!(title: \"Recent Developments in the World of Pastrami\")\nBlogPost.create!(title: \"Prosciutto and You: A Retrospective\")\nBlogPost.search_by_title(\"pastrami\") # =\u003e [#\u003cBlogPost id: 2, title: \"Recent Developments in the World of Pastrami\"\u003e]\n```\n\n#### Searching against multiple columns\n\nJust pass an Array if you'd like to search more than one column.\n\n```ruby\nclass Person \u003c ActiveRecord::Base\n  include PgSearch::Model\n  pg_search_scope :search_by_full_name, against: [:first_name, :last_name]\nend\n```\n\nNow our search query can match either or both of the columns.\n\n```ruby\nperson_1 = Person.create!(first_name: \"Grant\", last_name: \"Hill\")\nperson_2 = Person.create!(first_name: \"Hugh\", last_name: \"Grant\")\n\nPerson.search_by_full_name(\"Grant\") # =\u003e [person_1, person_2]\nPerson.search_by_full_name(\"Grant Hill\") # =\u003e [person_1]\n```\n\n#### Dynamic search scopes\n\nJust like with Active Record named scopes, you can pass in a Proc object that\nreturns a hash of options. For instance, the following scope takes a parameter\nthat dynamically chooses which column to search against.\n\nImportant: The returned hash must include a :query key. Its value does not\nnecessary have to be dynamic. You could choose to hard-code it to a specific\nvalue if you wanted.\n\n```ruby\nclass Person \u003c ActiveRecord::Base\n  include PgSearch::Model\n  pg_search_scope :search_by_name, lambda { |name_part, query|\n    raise ArgumentError unless [:first, :last].include?(name_part)\n    {\n      against: name_part,\n      query: query\n    }\n  }\nend\n\nperson_1 = Person.create!(first_name: \"Grant\", last_name: \"Hill\")\nperson_2 = Person.create!(first_name: \"Hugh\", last_name: \"Grant\")\n\nPerson.search_by_name :first, \"Grant\" # =\u003e [person_1]\nPerson.search_by_name :last, \"Grant\" # =\u003e [person_2]\n```\n\n#### Searching through associations\n\nIt is possible to search columns on associated models. Note that if you do\nthis, it will be impossible to speed up searches with database indexes.\nHowever, it is supported as a quick way to try out cross-model searching.\n\nYou can pass a Hash into the :associated_against option to set up searching\nthrough associations. The keys are the names of the associations and the value\nworks just like an :against option for the other model. Right now, searching\ndeeper than one association away is not supported. You can work around this by\nsetting up a series of :through associations to point all the way through.\n\n```ruby\nclass Cracker \u003c ActiveRecord::Base\n  has_many :cheeses\nend\n\nclass Cheese \u003c ActiveRecord::Base\nend\n\nclass Salami \u003c ActiveRecord::Base\n  include PgSearch::Model\n\n  belongs_to :cracker\n  has_many :cheeses, through: :cracker\n\n  pg_search_scope :tasty_search, associated_against: {\n    cheeses: [:kind, :brand],\n    cracker: :kind\n  }\nend\n\nsalami_1 = Salami.create!\nsalami_2 = Salami.create!\nsalami_3 = Salami.create!\n\nlimburger = Cheese.create!(kind: \"Limburger\")\nbrie = Cheese.create!(kind: \"Brie\")\npepper_jack = Cheese.create!(kind: \"Pepper Jack\")\n\nCracker.create!(kind: \"Black Pepper\", cheeses: [brie], salami: salami_1)\nCracker.create!(kind: \"Ritz\", cheeses: [limburger, pepper_jack], salami: salami_2)\nCracker.create!(kind: \"Graham\", cheeses: [limburger], salami: salami_3)\n\nSalami.tasty_search(\"pepper\") # =\u003e [salami_1, salami_2]\n```\n\n### Searching using different search features\n\nBy default, pg_search_scope uses the built-in [PostgreSQL text\nsearch](http://www.postgresql.org/docs/current/static/textsearch-intro.html).\nIf you pass the :using option to pg_search_scope, you can choose alternative\nsearch techniques.\n\n```ruby\nclass Beer \u003c ActiveRecord::Base\n  include PgSearch::Model\n  pg_search_scope :search_name, against: :name, using: [:tsearch, :trigram, :dmetaphone]\nend\n```\n\nHere's an example if you pass multiple :using options with additional configurations.\n\n```ruby\nclass Beer \u003c ActiveRecord::Base\n  include PgSearch::Model\n  pg_search_scope :search_name,\n  against: :name,\n  using: {\n      :trigram =\u003e {},\n      :dmetaphone =\u003e {},\n      :tsearch =\u003e { :prefix =\u003e true }\n  }\nend\n```\n\nThe currently implemented features are\n\n*   :tsearch - [Full text search](http://www.postgresql.org/docs/current/static/textsearch-intro.html), which is built-in to PostgreSQL\n*   :trigram - [Trigram search](http://www.postgresql.org/docs/current/static/pgtrgm.html), which\n    requires the trigram extension\n*   :dmetaphone - [Double Metaphone search](http://www.postgresql.org/docs/current/static/fuzzystrmatch.html#AEN177521), which requires the fuzzystrmatch extension\n\n\n#### :tsearch (Full Text Search)\n\nPostgreSQL's built-in full text search supports weighting, prefix searches,\nand stemming in multiple languages.\n\n##### Weighting\nEach searchable column can be given a weight of \"A\", \"B\", \"C\", or \"D\". Columns\nwith earlier letters are weighted higher than those with later letters. So, in\nthe following example, the title is the most important, followed by the\nsubtitle, and finally the content.\n\n```ruby\nclass NewsArticle \u003c ActiveRecord::Base\n  include PgSearch::Model\n  pg_search_scope :search_full_text, against: {\n    title: 'A',\n    subtitle: 'B',\n    content: 'C'\n  }\nend\n```\n\nYou can also pass the weights in as an array of arrays, or any other structure\nthat responds to #each and yields either a single symbol or a symbol and a\nweight. If you omit the weight, a default will be used.\n\n```ruby\nclass NewsArticle \u003c ActiveRecord::Base\n  include PgSearch::Model\n  pg_search_scope :search_full_text, against: [\n    [:title, 'A'],\n    [:subtitle, 'B'],\n    [:content, 'C']\n  ]\nend\n\nclass NewsArticle \u003c ActiveRecord::Base\n  include PgSearch::Model\n  pg_search_scope :search_full_text, against: [\n    [:title, 'A'],\n    {subtitle: 'B'},\n    :content\n  ]\nend\n```\n\n##### :prefix (PostgreSQL 8.4 and newer only)\n\nPostgreSQL's full text search matches on whole words by default. If you want\nto search for partial words, however, you can set :prefix to true. Since this\nis a :tsearch-specific option, you should pass it to :tsearch directly, as\nshown in the following example.\n\n```ruby\nclass Superhero \u003c ActiveRecord::Base\n  include PgSearch::Model\n  pg_search_scope :whose_name_starts_with,\n                  against: :name,\n                  using: {\n                    tsearch: { prefix: true }\n                  }\nend\n\nbatman = Superhero.create name: 'Batman'\nbatgirl = Superhero.create name: 'Batgirl'\nrobin = Superhero.create name: 'Robin'\n\nSuperhero.whose_name_starts_with(\"Bat\") # =\u003e [batman, batgirl]\n```\n##### :negation\n\nPostgreSQL's full text search matches all search terms by default. If you want\nto exclude certain words, you can set :negation to true. Then any term that begins with\nan exclamation point `!` will be excluded from the results. Since this\nis a :tsearch-specific option, you should pass it to :tsearch directly, as\nshown in the following example.\n\nNote that combining this with other search features can have unexpected results. For\nexample, :trigram searches don't have a concept of excluded terms, and thus if you\nuse both :tsearch and :trigram in tandem, you may still find results that contain the\nterm that you were trying to exclude.\n\n```ruby\nclass Animal \u003c ActiveRecord::Base\n  include PgSearch::Model\n  pg_search_scope :with_name_matching,\n                  against: :name,\n                  using: {\n                    tsearch: {negation: true}\n                  }\nend\n\none_fish = Animal.create(name: \"one fish\")\ntwo_fish = Animal.create(name: \"two fish\")\nred_fish = Animal.create(name: \"red fish\")\nblue_fish = Animal.create(name: \"blue fish\")\n\nAnimal.with_name_matching(\"fish !red !blue\") # =\u003e [one_fish, two_fish]\n```\n\n##### :dictionary\n\nPostgreSQL full text search also support multiple dictionaries for stemming.\nYou can learn more about how dictionaries work by reading the [PostgreSQL\ndocumention](http://www.postgresql.org/docs/current/static/textsearch-dictionaries.html).\nIf you use one of the language dictionaries, such as \"english\",\nthen variants of words (e.g. \"jumping\" and \"jumped\") will match each other. If\nyou don't want stemming, you should pick the \"simple\" dictionary which does\nnot do any stemming. If you don't specify a dictionary, the \"simple\"\ndictionary will be used.\n\n```ruby\nclass BoringTweet \u003c ActiveRecord::Base\n  include PgSearch::Model\n  pg_search_scope :kinda_matching,\n                  against: :text,\n                  using: {\n                    tsearch: {dictionary: \"english\"}\n                  }\n  pg_search_scope :literally_matching,\n                  against: :text,\n                  using: {\n                    tsearch: {dictionary: \"simple\"}\n                  }\nend\n\nsleep = BoringTweet.create! text: \"I snoozed my alarm for fourteen hours today. I bet I can beat that tomorrow! #sleep\"\nsleeping = BoringTweet.create! text: \"You know what I like? Sleeping. That's what. #enjoyment\"\nsleeps = BoringTweet.create! text: \"In the jungle, the mighty jungle, the lion sleeps #tonight\"\n\nBoringTweet.kinda_matching(\"sleeping\") # =\u003e [sleep, sleeping, sleeps]\nBoringTweet.literally_matching(\"sleeps\") # =\u003e [sleeps]\n```\n\n##### :normalization\n\nPostgreSQL supports multiple algorithms for ranking results against queries.\nFor instance, you might want to consider overall document size or the distance\nbetween multiple search terms in the original text. This option takes an\ninteger, which is passed directly to PostgreSQL. According to the latest\n[PostgreSQL documentation](http://www.postgresql.org/docs/current/static/textsearch-controls.html),\nthe supported algorithms are:\n\n    0 (the default) ignores the document length\n    1 divides the rank by 1 + the logarithm of the document length\n    2 divides the rank by the document length\n    4 divides the rank by the mean harmonic distance between extents\n    8 divides the rank by the number of unique words in document\n    16 divides the rank by 1 + the logarithm of the number of unique words in document\n    32 divides the rank by itself + 1\n\nThis integer is a bitmask, so if you want to combine algorithms, you can add\ntheir numbers together.\n(e.g. to use algorithms 1, 8, and 32, you would pass 1 + 8 + 32 = 41)\n\n```ruby\nclass BigLongDocument \u003c ActiveRecord::Base\n  include PgSearch::Model\n  pg_search_scope :regular_search,\n                  against: :text\n\n  pg_search_scope :short_search,\n                  against: :text,\n                  using: {\n                    tsearch: {normalization: 2}\n                  }\n\nlong = BigLongDocument.create!(text: \"Four score and twenty years ago\")\nshort = BigLongDocument.create!(text: \"Four score\")\n\nBigLongDocument.regular_search(\"four score\") #=\u003e [long, short]\nBigLongDocument.short_search(\"four score\") #=\u003e [short, long]\n```\n\n##### :any_word\n\nSetting this attribute to true will perform a search which will return all\nmodels containing any word in the search terms.\n\n```ruby\nclass Number \u003c ActiveRecord::Base\n  include PgSearch::Model\n  pg_search_scope :search_any_word,\n                  against: :text,\n                  using: {\n                    tsearch: {any_word: true}\n                  }\n\n  pg_search_scope :search_all_words,\n                  against: :text\nend\n\none = Number.create! text: 'one'\ntwo = Number.create! text: 'two'\nthree = Number.create! text: 'three'\n\nNumber.search_any_word('one two three') # =\u003e [one, two, three]\nNumber.search_all_words('one two three') # =\u003e []\n```\n\n##### :sort_only\n\nSetting this attribute to true will make this feature available for sorting,\nbut will not include it in the query's WHERE condition.\n\n```ruby\nclass Person \u003c ActiveRecord::Base\n  include PgSearch::Model\n  pg_search_scope :search,\n                  against: :name,\n                  using: {\n                    tsearch: {any_word: true},\n                    dmetaphone: {any_word: true, sort_only: true}\n                  }\nend\n\nexact = Person.create!(name: 'ash hines')\none_exact_one_close = Person.create!(name: 'ash heinz')\none_exact = Person.create!(name: 'ash smith')\none_close = Person.create!(name: 'leigh heinz')\n\nPerson.search('ash hines') # =\u003e [exact, one_exact_one_close, one_exact]\n```\n\n##### :highlight\n\nAdding .with_pg_search_highlight after the pg_search_scope you can access to\n`pg_highlight` attribute for each object.\n\n\n```ruby\nclass Person \u003c ActiveRecord::Base\n  include PgSearch::Model\n  pg_search_scope :search,\n                  against: :bio,\n                  using: {\n                    tsearch: {\n                      highlight: {\n                        StartSel: '\u003cb\u003e',\n                        StopSel: '\u003c/b\u003e',\n                        MinWords: 123,\n                        MaxWords: 456,\n                        ShortWord: 4,\n                        HighlightAll: true,\n                        MaxFragments: 3,\n                        FragmentDelimiter: '\u0026hellip;'\n                      }\n                    }\n                  }\nend\n\nPerson.create!(:bio =\u003e \"Born in rural Alberta, where the buffalo roam.\")\n\nfirst_match = Person.search(\"Alberta\").with_pg_search_highlight.first\nfirst_match.pg_search_highlight # =\u003e \"Born in rural \u003cb\u003eAlberta\u003c/b\u003e, where the buffalo roam.\"\n```\n\nThe highlight option accepts all [options supported by\nts_headline](https://www.postgresql.org/docs/current/static/textsearch-controls.html),\nand uses PostgreSQL's defaults.\n\nSee the\n[documentation](https://www.postgresql.org/docs/current/static/textsearch-controls.html)\nfor details on the meaning of each option.\n\n#### :dmetaphone (Double Metaphone soundalike search)\n\n[Double Metaphone](http://en.wikipedia.org/wiki/Double_Metaphone) is an\nalgorithm for matching words that sound alike even if they are spelled very\ndifferently. For example, \"Geoff\" and \"Jeff\" sound identical and thus match.\nCurrently, this is not a true double-metaphone, as only the first metaphone is\nused for searching.\n\nDouble Metaphone support is currently available as part of the [fuzzystrmatch\nextension](http://www.postgresql.org/docs/current/static/fuzzystrmatch.html)\nthat must be installed before this feature can be used. In addition to the\nextension, you must install a utility function into your database. To generate\nand run a migration for this, run:\n\n    $ rails g pg_search:migration:dmetaphone\n    $ rake db:migrate\n\nThe following example shows how to use :dmetaphone.\n\n```ruby\nclass Word \u003c ActiveRecord::Base\n  include PgSearch::Model\n  pg_search_scope :that_sounds_like,\n                  against: :spelling,\n                  using: :dmetaphone\nend\n\nfour = Word.create! spelling: 'four'\nfar = Word.create! spelling: 'far'\nfur = Word.create! spelling: 'fur'\nfive = Word.create! spelling: 'five'\n\nWord.that_sounds_like(\"fir\") # =\u003e [four, far, fur]\n```\n\n#### :trigram (Trigram search)\n\nTrigram search works by counting how many three-letter substrings (or\n\"trigrams\") match between the query and the text. For example, the string\n\"Lorem ipsum\" can be split into the following trigrams:\n\n    [\" Lo\", \"Lor\", \"ore\", \"rem\", \"em \", \"m i\", \" ip\", \"ips\", \"psu\", \"sum\", \"um \", \"m  \"]\n\nTrigram search has some ability to work even with typos and misspellings in\nthe query or text.\n\nTrigram support is currently available as part of the\n[pg_trgm extension](http://www.postgresql.org/docs/current/static/pgtrgm.html) that must be installed before this\nfeature can be used.\n\n```ruby\nclass Website \u003c ActiveRecord::Base\n  include PgSearch::Model\n  pg_search_scope :kinda_spelled_like,\n                  against: :name,\n                  using: :trigram\nend\n\nyahooo = Website.create! name: \"Yahooo!\"\nyohoo = Website.create! name: \"Yohoo!\"\ngogle = Website.create! name: \"Gogle\"\nfacebook = Website.create! name: \"Facebook\"\n\nWebsite.kinda_spelled_like(\"Yahoo!\") # =\u003e [yahooo, yohoo]\n```\n\n##### :threshold\n\nBy default, trigram searches find records which have a similarity of at least 0.3\nusing pg_trgm's calculations. You may specify a custom threshold if you prefer.\nHigher numbers match more strictly, and thus return fewer results. Lower numbers\nmatch more permissively, letting in more results. Please note that setting a trigram\nthreshold will force a table scan as the derived query uses the\n`similarity()` function instead of the `%` operator.\n\n```ruby\nclass Vegetable \u003c ActiveRecord::Base\n  include PgSearch::Model\n\n  pg_search_scope :strictly_spelled_like,\n                  against: :name,\n                  using: {\n                    trigram: {\n                      threshold: 0.5\n                    }\n                  }\n\n  pg_search_scope :roughly_spelled_like,\n                  against: :name,\n                  using: {\n                    trigram: {\n                      threshold: 0.1\n                    }\n                  }\nend\n\ncauliflower = Vegetable.create! name: \"cauliflower\"\n\nVegetable.roughly_spelled_like(\"couliflower\") # =\u003e [cauliflower]\nVegetable.strictly_spelled_like(\"couliflower\") # =\u003e [cauliflower]\n\nVegetable.roughly_spelled_like(\"collyflower\") # =\u003e [cauliflower]\nVegetable.strictly_spelled_like(\"collyflower\") # =\u003e []\n```\n\n##### :word_similarity\n\nAllows you to match words in longer strings.\nBy default, trigram searches use `%` or `similarity()` as a similarity value.\nSet `word_similarity` to `true` to opt for `\u003c%` and `word_similarity` instead.\nThis causes the trigram search to use the similarity of the query term\nand the word with greatest similarity.\n\n```ruby\nclass Sentence \u003c ActiveRecord::Base\n  include PgSearch::Model\n\n  pg_search_scope :similarity_like,\n                  against: :name,\n                  using: {\n                    trigram: {\n                      word_similarity: true\n                    }\n                  }\n\n  pg_search_scope :word_similarity_like,\n                  against: :name,\n                  using: [:trigram]\nend\n\nsentence = Sentence.create! name: \"Those are two words.\"\n\nSentence.similarity_like(\"word\") # =\u003e []\nSentence.word_similarity_like(\"word\") # =\u003e [sentence]\n```\n\n### Limiting Fields When Combining Features\n\nSometimes when doing queries combining different features you\nmight want to search against only some of the fields with certain features.\nFor example perhaps you want to only do a trigram search against the shorter fields\nso that you don't need to reduce the threshold excessively. You can specify\nwhich fields using the 'only' option:\n\n```ruby\nclass Image \u003c ActiveRecord::Base\n  include PgSearch::Model\n\n  pg_search_scope :combined_search,\n                  against: [:file_name, :short_description, :long_description]\n                  using: {\n                    tsearch: { dictionary: 'english' },\n                    trigram: {\n                      only: [:file_name, :short_description]\n                    }\n                  }\n\nend\n```\n\nNow you can succesfully retrieve an Image with a file_name: 'image_foo.jpg'\nand long_description: 'This description is so long that it would make a trigram search\nfail any reasonable threshold limit' with:\n\n```ruby\nImage.combined_search('reasonable') # found with tsearch\nImage.combined_search('foo') # found with trigram\n```\n\n### Ignoring accent marks\n\nMost of the time you will want to ignore accent marks when searching. This\nmakes it possible to find words like \"piñata\" when searching with the query\n\"pinata\". If you set a pg_search_scope to ignore accents, it will ignore\naccents in both the searchable text and the query terms.\n\nIgnoring accents uses the [unaccent extension](http://www.postgresql.org/docs/current/static/unaccent.html) that\nmust be installed before this feature can be used.\n\n```ruby\nclass SpanishQuestion \u003c ActiveRecord::Base\n  include PgSearch::Model\n  pg_search_scope :gringo_search,\n                  against: :word,\n                  ignoring: :accents\nend\n\nwhat = SpanishQuestion.create(word: \"Qué\")\nhow_many = SpanishQuestion.create(word: \"Cuánto\")\nhow = SpanishQuestion.create(word: \"Cómo\")\n\nSpanishQuestion.gringo_search(\"Que\") # =\u003e [what]\nSpanishQuestion.gringo_search(\"Cüåñtô\") # =\u003e [how_many]\n```\n\nAdvanced users may wish to add indexes for the expressions that pg_search\ngenerates. Unfortunately, the unaccent function supplied by this extension\nis not indexable (as of PostgreSQL 9.1). Thus, you may want to write\nyour own wrapper function and use it instead. This can be configured by\ncalling the following code, perhaps in an initializer.\n\n```ruby\nPgSearch.unaccent_function = \"my_unaccent\"\n```\n\n### Using tsvector columns\n\nPostgreSQL allows you the ability to search against a column with type\ntsvector instead of using an expression; this speeds up searching dramatically\nas it offloads creation of the tsvector that the tsquery is evaluated against.\n\nTo use this functionality you'll need to do a few things:\n\n*   Create a column of type tsvector that you'd like to search against. If you\n    want to search using multiple search methods, for example tsearch and\n    dmetaphone, you'll need a column for each.\n*   Create a trigger function that will update the column(s) using the\n    expression appropriate for that type of search. See:\n    [the PostgreSQL documentation for text search triggers](http://www.postgresql.org/docs/current/static/textsearch-features.html#TEXTSEARCH-UPDATE-TRIGGERS)\n*   Should you have any pre-existing data in the table, update the\n    newly-created tsvector columns with the expression that your trigger\n    function uses.\n*   Add the option to pg_search_scope, e.g:\n\n    ```ruby\n    pg_search_scope :fast_content_search,\n                    against: :content,\n                    using: {\n                      dmetaphone: {\n                        tsvector_column: 'tsvector_content_dmetaphone'\n                      },\n                      tsearch: {\n                        dictionary: 'english',\n                        tsvector_column: 'tsvector_content_tsearch'\n                      },\n                      trigram: {} # trigram does not use tsvectors\n                    }\n    ```\n\nPlease note that the :against column is only used when the tsvector_column is\nnot present for the search type.\n\n#### Combining multiple tsvectors\n\nIt's possible to search against more than one tsvector at a time. This could be useful if you want to maintain multiple search scopes but do not want to maintain separate tsvectors for each scope. For example:\n\n```ruby\npg_search_scope :search_title,\n                against: :title,\n                using: {\n                  tsearch: {\n                    tsvector_column: \"title_tsvector\"\n                  }\n                }\n\npg_search_scope :search_body,\n                against: :body,\n                using: {\n                  tsearch: {\n                    tsvector_column: \"body_tsvector\"\n                  }\n                }\n\npg_search_scope :search_title_and_body,\n                against: [:title, :body],\n                using: {\n                  tsearch: {\n                    tsvector_column: [\"title_tsvector\", \"body_tsvector\"]\n                  }\n                }\n```\n\n### Configuring ranking and ordering\n\n#### :ranked_by (Choosing a ranking algorithm)\n\nBy default, pg_search ranks results based on the :tsearch similarity between\nthe searchable text and the query. To use a different ranking algorithm, you\ncan pass a :ranked_by option to pg_search_scope.\n\n```ruby\npg_search_scope :search_by_tsearch_but_rank_by_trigram,\n                against: :title,\n                using: [:tsearch],\n                ranked_by: \":trigram\"\n```\n\nNote that :ranked_by using a String to represent the ranking expression. This\nallows for more complex possibilities. Strings like \":tsearch\", \":trigram\",\nand \":dmetaphone\" are automatically expanded into the appropriate SQL\nexpressions.\n\n```ruby\n# Weighted ranking to balance multiple approaches\nranked_by: \":dmetaphone + (0.25 * :trigram)\"\n\n# A more complex example, where books.num_pages is an integer column in the table itself\nranked_by: \"(books.num_pages * :trigram) + (:tsearch / 2.0)\"\n```\n\n#### :order_within_rank (Breaking ties)\n\nPostgreSQL does not guarantee a consistent order when multiple records have\nthe same value in the ORDER BY clause. This can cause trouble with pagination.\nImagine a case where 12 records all have the same ranking value. If you use a\npagination library such as [kaminari](https://github.com/amatsuda/kaminari) or\n[will_paginate](https://github.com/mislav/will_paginate) to return results in\npages of 10, then you would expect to see 10 of the records on page 1, and the\nremaining 2 records at the top of the next page, ahead of lower-ranked\nresults.\n\nBut since there is no consistent ordering, PostgreSQL might choose to\nrearrange the order of those 12 records between different SQL statements. You\nmight end up getting some of the same records from page 1 on page 2 as well,\nand likewise there may be records that don't show up at all.\n\npg_search fixes this problem by adding a second expression to the ORDER BY\nclause, after the :ranked_by expression explained above. By default, the\ntiebreaker order is ascending by id.\n\n    ORDER BY [complicated :ranked_by expression...], id ASC\n\nThis might not be desirable for your application, especially if you do not\nwant old records to outrank new records. By passing an :order_within_rank, you\ncan specify an alternate tiebreaker expression. A common example would be\ndescending by updated_at, to rank the most recently updated records first.\n\n```ruby\npg_search_scope :search_and_break_ties_by_latest_update,\n                against: [:title, :content],\n                order_within_rank: \"blog_posts.updated_at DESC\"\n```\n\n#### PgSearch#pg_search_rank (Reading a record's rank as a Float)\n\nIt may be useful or interesting to see the rank of a particular record. This\ncan be helpful for debugging why one record outranks another. You could also\nuse it to show some sort of relevancy value to end users of an application.\n\nTo retrieve the rank, call `.with_pg_search_rank` on a scope, and then call\n`.pg_search_rank` on a returned record.\n\n```ruby\nshirt_brands = ShirtBrand.search_by_name(\"Penguin\").with_pg_search_rank\nshirt_brands[0].pg_search_rank #=\u003e 0.0759909\nshirt_brands[1].pg_search_rank #=\u003e 0.0607927\n```\n\n#### Search rank and chained scopes\n\nEach PgSearch scope generates a named subquery for the search rank.  If you\nchain multiple scopes then PgSearch will generate a ranking query for each\nscope, so the ranking queries must have unique names.  If you need to reference\nthe ranking query (e.g. in a GROUP BY clause) you can regenerate the subquery\nname with the `PgScope::Configuration.alias` method by passing the name of the\nqueried table.\n\n```ruby\nshirt_brands = ShirtBrand.search_by_name(\"Penguin\")\n  .joins(:shirt_sizes)\n  .group(\"shirt_brands.id, #{PgSearch::Configuration.alias('shirt_brands')}.rank\")\n```\n\n## ATTRIBUTIONS\n\nPgSearch would not have been possible without inspiration from texticle (now renamed\n[textacular](https://github.com/textacular/textacular)). Thanks to [Aaron\nPatterson](http://tenderlovemaking.com/) for the original version and to Casebook PBC (https://www.casebook.net) for gifting the community with it!\n\n## CONTRIBUTIONS AND FEEDBACK\n\nPlease read our [CONTRIBUTING guide](https://github.com/Casecommons/pg_search/blob/master/CONTRIBUTING.md).\n\nWe also have a [Google Group](http://groups.google.com/group/casecommons-dev)\nfor discussing pg_search and other Casebook PBC open source projects.\n\n## LICENSE\n\nCopyright © 2010–2022 [Casebook PBC](http://www.casebook.net).\nLicensed under the MIT license, see [LICENSE](/LICENSE) file.\n","funding_links":[],"categories":["Searching","Active Record Plugins","Ruby","搜索","Search","Gems"],"sub_categories":["Omniauth","Rails Search","Full-text Search"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FCasecommons%2Fpg_search","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FCasecommons%2Fpg_search","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FCasecommons%2Fpg_search/lists"}