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!