Rails with full-text Search
Your no-BS weekly brief on software engineering.
Join 100,000+ developers
When building a search feature for your application, integrating full-text search functionality can be challenging. With the pg_search
gem, however, you can add powerful and flexible search capabilities with minimal effort. Let’s dive into an example of how we use pg_search
to create a robust search scope and explore what it would look like if you didn’t have this gem in your toolbox.
Defining a Search Scope with pg_search
Here’s how we define a search scope in our Rails model using pg_search
:
pg_search_scope :search,
against: {
title: 'A',
excerpt: 'B',
summary: 'C'
},
using: {
tsearch: {
prefix: true,
dictionary: "english",
any_word: true
}
}
Capabilities Explained:
Ranked Search: Fields like title
, excerpt
, and summary
are assigned weights (A
,B
, and C
) to influence the ranking of search results. These weights are part of PostgreSQL’s full-text search system, where A represents the highest importance, followed by B, and then C. For example, matches in title are considered more important than matches in excerpt, which in turn are prioritized over matches in summary. This allows you to fine-tune how search results are ranked, ensuring that the most relevant content surfaces first.
Prefix Matching: Enables searching for partial words. For example, searching for dev
will match developer
or development
. This is handled by PostgreSQL’s to_tsquery
under the hood.
English Dictionary: Uses PostgreSQL’s english dictionary to handle stemming and stopwords (e.g., walking
matches walk
) and removes stopwords like and
or the
.
Any Word Matching: Returns results if any word in the query matches, instead of requiring all words to match.
A query like Model.search("quick brown")
returns results ranked by relevance, considering these features.
What Would You Do Without pg_search?
Without pg_search, implementing this functionality manually in PostgreSQL involves more effort and complexity. Here's how it could be done:
Setting Up a tsvector Column
Add a tsvector
column to store a precomputed search index:
ALTER TABLE models ADD COLUMN search_vector tsvector;
CREATE INDEX search_vector_idx ON models USING gin(search_vector);
Creating a Trigger to Update tsvector
CREATE OR REPLACE FUNCTION update_search_vector() RETURNS trigger AS $$
BEGIN
NEW.search_vector :=
setweight(to_tsvector('english', NEW.title), 'A') ||
setweight(to_tsvector('english', NEW.excerpt), 'B') ||
setweight(to_tsvector('english', NEW.summary), 'C');
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER update_search_vector_trigger
BEFORE INSERT OR UPDATE ON models
FOR EACH ROW EXECUTE FUNCTION update_search_vector();
This ensures the search_vector
column remains up-to-date, but introduces additional database overhead during writes.
Writing a Raw SQL Query for Search
Performing a search involves writing SQL queries manually:
Model.where("search_vector @@ plainto_tsquery(?)", query)
.order("ts_rank(search_vector, plainto_tsquery(?)) DESC", query)
Handling Advanced Features'
- Prefix Matching: Use
to_tsquery
with wildcards:
to_tsquery('quick:* & brown:*')
This allows partial word matching but requires constructing queries manually.
- Field weights: Manage weights using
setweight
in thetsvector
trigger function as shown above. Adjust the weights as needed for each field.
This approach is time-consuming, prone to error, and harder to maintain. Each change to your search requirements could involve updating database triggers and raw SQL, making iteration slower and more complex.
Why pg_search is a Game-Changer
The pg_search
gem simplifies all of the above by abstracting PostgreSQL’s complexity into Rails model methods. Key advantages include:
- Avoid Boilerplate: No need to manually manage tsvector columns, triggers, or SQL queries.
- Iterate Quickly: Update search logic directly in your Rails models without touching the database schema or triggers.
- Leverage Advanced Features: Easily enable prefix matching, dictionaries, and weighted fields without custom SQL.
- Stay Focused on Business Logic: Spend less time on plumbing and more time delivering features that matter.
Conclusion
pg_search
makes full-text search in Rails both powerful and accessible. It handles the heavy lifting of integrating PostgreSQL’s search capabilities, allowing you to focus on building great user experiences. Without it, you’d need to dive deep into PostgreSQL’s internals, adding significant complexity to your application.
If your application requires robust and flexible search functionality, pg_search
is an invaluable tool in your stack.