Full-text, vector, and hybrid search for ActiveRecord, without leaving SQLite. Declare which columns are searchable and get a query scope. No search cluster to run, no second copy of your data to keep in sync.
It covers three kinds of search behind one DSL. Full-text uses SQLite's FTS5 module with BM25 relevance ranking. Vector (semantic) search uses sqlite-vec through the neighbor gem, with your app supplying embeddings via a callback. Hybrid search fuses the two with Reciprocal Rank Fusion and an optional reranking step. Works on Rails 8.0+.
# Gemfile
gem "sqlite_search"
Then bundle install. In a Rails app the model concern and the migration helpers are wired in automatically.
Declare a search index in a migration, add one line to the model, and query it:
# db/migrate/xxxx_create_posts_search.rb
class CreatePostsSearch < ActiveRecord::Migration[8.0]
def change
create_fts5_index :posts, :search, against: { title: 2.0, body: 1.0 }, backfill: true
end
end
# app/models/post.rb
class Post < ApplicationRecord
fts5_scope :search, against: { title: 2.0, body: 1.0 }
end
# anywhere
Post.search("morning coffee") # a normal, chainable relation
Post.search("coffee").order_by_rank.first.search_rank
Post.where(author_id: 7).search("coffee") # searches only that author's posts
The scope is an ordinary ActiveRecord::Relation, so it composes with the rest of your query. User input is sanitized into a safe MATCH expression for you, so you can pass params[:q] straight through.
There are no database triggers and no search server to run. The virtual tables round-trip through schema.rb like any other table.