Not a subscriber?

Join thousands of others who are building self-directed lives through creativity, grit, and digital strategy—breaking free from the 9–5.
Receive one free message a week

PgSearch multisearch with ActionText::RichText

If your Rails model has an ActionText rich text field, and you want to make it searchable with PgSearch (pg_search) you’ll need a way to index those records. This post will show you how to do that.

Since ActionText::RichText objects are stored in the database you can access them via the rails console quite easily to inspect them.

Here’s an example that gets the count of how many you have: ActionText::RichText.count

An Example Model

Typically you’ll have an ActiveRecord model that will include the rich text field like this:

# == Schema Information
#
# Table name: discussions
#
#  id              :bigint           not null, primary key
#  title           :string           not null
#  created_at      :datetime         not null
#  updated_at      :datetime         not null
#
class Discussion < ApplicationRecord
  include PgSearch::Model
  multisearchable against: [:title]
  
  has_rich_text :content

  # ... all the other model stuff/relationships/etc omitted for brevity
end

This code above will allow PgSearch to search to search agains the Discussion models’s title attribute, but nothing else. You would most likely want PgSearch to also search against the has_rich_text :content field as well.  How do you do that?

Make ActionText::RichText Multisearcable

To make ActionText:RichText multisearchable you’ll want to add an initializer:

Create a file called /config/initializers/action_text_rich_text.rb

In this file you’ll include the following:

# Allows ActionText to be multisearchable with PgSearch.multisearch
ActiveSupport.on_load :action_text_rich_text do
  include PgSearch::Model

  multisearchable against: :body
end

This allows us to add some behavior to ActionText::RichText through its on load initializer.

This code includes the PgSearch::Model as well as tells PgSearch that the body of ActionText is to be indexed. Remember, the body of ActionText is where the rich text content is stored.

You’re now ready to start indexing and searching your ActionText::RichText models.

Indexing and Searching with PgSearch.multisearch

You’ve told ActionText::RichText what to multisearch against, but you now need to get the index set up. After you’ve run the migration and setup as shown in the multisearch setup you will see a pg_search_documents table. This is where the indexing happens for PgSearch.

First you’ll want to index your Discussion model.

Start a  rails console session and then type the following to index the Discussions and RichText fields:

PgSearch::Multisearch.rebuild(Discussion)

Now index the RichText fields:

PgSearch::Multisearch.rebuild(ActionText::RichText)

Now all of your Discussions and RichText fields are indexed. Any time you create or update a discussion the index will automatically be updated via PgSearch’s ActiveRecord callbacks.

Performing a Search

To perform a multisearch run this command:

PgSearch.multisearch("some query")

This will return you all the search results that match.

To access the actual searchable value (the model in which the search is related to you’ll do this:

PgSearch.multisearch("some query").first.searchable

This will give you the Discussion or ActionText::RichText model that is related to the search.

Search Results Caveat

When the results are returned from multisearch, you’ll get your model back (in this case a Discussion or an ActionText::RichText  model back. It’s highly likely that you don’t want to return an ActionText::RichText model to your search results … you probably want the record that the RichText is attached to (in this case a Discussion).

You can do that by calling the .record on the ActionText::RichText result, like this:

# Asssuming the first result is a 'ActionText::RichText` model
PgSearch.multisearch("hello").includes(:searchable).first.searchable.record

However, you probably don’t want to iterate over all the results and most likely you’ll be paginating your results, so you’ll want a result set that contains all the models that match (Discussion) as well as the models that had matching RichText in them (again, in this case a Discussion). You could have multiple rich text fields in your application (for comments, bio in a profile, etc. So you’d want those models returned.

Mapping the Search Results to Return the RichText record Model

PgSearch.multisearch("hello")
        .includes(:searchable)
        .limit(10)
        .map { |result| result.searchable.is_a?(ActionText::RichText) ? result.searchable.record : result.searchable }
        .uniq # The ActionText::RichText might reference a record that is already returned in the result set due to matches elsewhere in the model

The code above will return the Discussion model (result.searchable.record) if the search result is an ActionText::RichText value, otherwise it will return whatever is the searchable type (in this case, its just Discussion). This allows you to have an array of all of your results that are  of your model types, not just ActionText::RichText models.

You could put this behind another class and then simply call something like this:

MyAppSearch.search('hello world', limit: 10)

Then you’d just get back your results you want without having to type that code all over the place.

Enjoy!