TL;DR

We see search functionality as a crucial point for user experience and engagement. The activerecord-mysql-search gem provides a powerful, easy-to-use solution for implementing full-text search in Ruby on Rails applications using MySQL’s native capabilities. It offers fast, scalable and flexible search without the complexity of any external services.

Visit Github Page

Common text search problems we faced in Rails

When we see the implementation of search in Rails applications, many default to ActiveRecord and SQL queries with the LIKE operator. This approach seems simple and quick but it also comes with a side effect. It does not scale well.

LIKE queries do not use full-text indexes, scan entire tables and are slow with tens or hundreds of thousands of records

  • standard LIKE only matches exact substrings, lacks relevance ranking and does not support morphology or synonyms
  • LIKE ignores language nuances and doesn’t support word forms
  • searching across multiple fields, associations, filters, and sorting quickly leads to messy, unmaintainable code

As data grows, search slows down, and database load becomes critical.

Our pain might prevent you from suffering as well

Our main concerns that brought us all in to the full text search approach:

  • we needed a solution that can scale with our clients systems
  • we prefer a maintainable structure that can be taken over by anyone in the team
  • speed is an important key aspect

After integrating our full-text search into our projects and learning from trial and error, we created the activerecord-mysql-search gem. Our goal was to give Rails devs not another wrapper for FULLTEXT, but a tool that integrates seamlessly, reduces maintenance and unlocks MySQL’s built-in search potential.

Elasticsearch is powerful, sure. But for most projects, it is an overkill. Too complex, too costly and way more than you need if you are already using MySQL.

Key features

  • uses MySQL FULLTEXT indexing for fast and relevant searches across large datasets
  • search data updates automatically when objects change. It works with flexible strategies, synchronous or background indexing via ActiveJob
  • define which fields to index—simple fields, nested associations, dates, calendar weeks or custom transformations via Proc: all declaratively in source classes
  • create separate search columns for different user roles (e.g. buyer/seller/admin): critical for multi-tenant apps and data privacy
  • comes with generators for quick setup, migrations, rake tasks for bulk reindexing and scopes for search—all in the Rails way
  • indexed fields, formatting and sources are moved to separate source classes: it improves code transparency and maintainability

How our approach differs from others

  • complex SQL and Arel magic are hidden: you work with familiar scopes and Ruby classes
  • tools like synchronous/background indexing, scheduled tasks and SQL triggers ensure search data stays current
  • source classes let you quickly add fields, associations or custom indexing rules without rewriting migrations or SQL
  • index any associated data alongside the main entity, e.g. product reviews or related orders
  • multi-column architecture ensures privacy—different users see different results and indexes don’t store unnecessary data
  • our full-text-search approach can be added to large apps in hours

Implementing full-text search in 5 minutes

We designed activerecord-mysql-search to make integration as fast and “Rails-like” as possible. Here’s how it works:

Step 1: Install the gem

Add to your Gemfile:

gem 'activerecord-mysql-search'

Run:

bundle install

Step 2: Generate configuration and migrations

Run:

rails generate mysql:search:install

This creates:

  • config/initializers/mysql_search.rb: Search configuration.
  • app/models/search_index.rb: Search index model.
  • A migration for the search index table.

Step 3: Run the migration

rails db:migrate

Step 4: Enable search in your model

Add to your model (e.g., Article):

class Article < ApplicationRecord
  include MySQL::Search::Searchable
  belongs_to :news_digest
end

Step 5: Define the indexing schema (source class)

Create app/search_sources/article_source.rb:

class ArticleSource < MySQL::Search::Source
  schema content: {
    title: :text,
    content: :text,
    type: ->(value) { I18n.t("article.types.#{value}") },
    news_digest: {
      title: :text,
      published_at: [:date, :calendar_week]
    }
  }
end

Step 6: Index existing data

rails mysql:search:reindex

Step 7: Use search in controllers or services

results = Article.full_text_search("Ruby on Rails")

That is it! Users now get fast, scalable and relevant search.

Advanced scenarios: multi-column search for roles and contexts

Real projects rarely need “single-column search.” Business logic often requires showing different data to different users, supporting flexible filters and ensuring privacy. activerecord-mysql-search supports this out of the box. For example, clients, sellers and admins each need their own “view of the world.” The gem lets you create separate indexes per role:

class ProductSource < MySQL::Search::Source
  schema content: {
    name: :text,
    description: :text,
    brand: :text,
    reviews: { content: :text, rating: :text }
  },
  seller_extra: {
    sku: :text,
    internal_notes: :text,
    supplier: { name: :text, contact_info: :text },
  },
  admin_extra: {
    created_by: { name: :text, email: :text }
  }
end

Add columns and indexes to SearchIndex:

class ExtraContentForSearchIndices < ActiveRecord::Migration[7.1]
  def change
    add_column :search_indices, :seller_extra, :text
    add_column :search_indices, :admin_extra, :text

    add_index :search_indices, [:content, :seller_extra], type: :fulltext
    add_index :search_indices, [:content, :seller_extra, :admin_extra], type: :fulltext
  end
end

Now, sellers search with:

results = Product.full_text_search("Ruby on Rails", search_column: [:content, :seller_extra])

Admins use:

results = Product.full_text_search("Ruby on Rails", search_column: [:content, :seller_extra, :admin_extra])

You can completely separate search contexts for different roles. In this case, there is no need to create combined indexes, just use different columns and separate indexes for each role.

What if I use methods that don’t trigger ActiveRecord callbacks?

Using #update_column and other methods that don’t trigger ActiveRecord callbacks can lead to search index desynchronization. Solution: use #update or #save to update records to ensure indexes remain current. If you don’t have this option, the gem provides the following tool to maintain index consistency.

In this case, the gem relies on the updated_at column. You can delegate keeping this column up-to-date to the database itself using a trigger. Create a migration using the generator:

rails generate mysql:search:create_triggers

This migration will create a trigger in each table that will update the updated_at column when records are modified, and will also add a monkey-patch to ActiveRecord’s #timestamps method in migrations (to automatically add this trigger to future tables). This allows maintaining search index relevance using one or more of the following tools:

  • Rake task rails mysql:search:actualize[1.hour] - periodically checks and updates indexes, syncing them with the current database state. You can configure it to run via cron.
  • MySQL::Search::Jobs::ScheduledUpdaterJob - a background job that periodically checks and updates indexes. Example for Solid Queue:
  # config/recurring.yml
  actualize_search_indices:
    class: MySQL::Search::Jobs::ScheduledUpdaterJob
    args: [:daily]
    schedule: every day at noon
  • Full reindexing via rake task mysql:search:reindex - if you want to completely refresh indexes, for example after migrations or schema changes. In this case, adding triggers isn’t required.

Why this gem just works: for you, your users and your team

It’s fast, easy to use and built for modern Rails. No black boxes, no vendor lock-in, just clean, open-source search that respects your data and your time. We built this because we were tired of clunky, over-engineered search. It is battle-tested, shaped by real pain and already making teams faster. Open-source and well-documented.

Search is essential, but it should not be a burden. activerecord-mysql-search gives Rails devs a simple, powerful way to build fast and flexible search right on top of MySQL.

Give it a try, plug it into your app and let us know what you think. Questions, feedback or ideas are always welcome.

Stay in shape 🦄!