A Ruby gem that provides seamless Typesense integration for ActiveRecord models with automatic syncing and search capabilities.
Add to your Gemfile:
gem 'typesense_model'
Or install directly:
$ gem install typesense_model
Initialize Typesense connection in your Rails application:
# config/initializers/typesense.rb
TypesenseModel.configure do |config|
config.api_key = 'your_api_key'
config.host = 'localhost'
config.port = 8108
config.protocol = 'http'
end
The gem automatically extends ActiveRecord models with Typesense capabilities. Simply add uses_typesense
to any model:
class Product < ApplicationRecord
uses_typesense collection: 'products' do
field :id, :string
field :name, :string
field :description, :string, optional: true, index: false
field :price, :float, sort: true
field :categories, "string[]", optional: true, facet: true
field :tags, "string[]", optional: true, facet: true
field :brand, :string, facet: true
field :in_stock, :bool, facet: true
field :created_at, :int64, sort: true
field :updated_at, :int64, sort: true
end
# Optional: Custom JSON serialization for Typesense
def as_json_typesense
{
id: id,
name: name,
description: description,
price: price,
categories: categories,
tags: tags,
brand: brand,
in_stock: in_stock,
created_at: created_at.to_i,
updated_at: updated_at.to_i
}
end
end
When you save or destroy ActiveRecord records, they automatically sync to Typesense:
# Create a product - automatically synced to Typesense
product = Product.create!(
name: "iPhone 15",
price: 999.99,
categories: ["Electronics", "Phones"],
brand: "Apple",
in_stock: true
)
# Update a product - automatically synced to Typesense
product.update!(price: 899.99)
# Destroy a product - automatically removed from Typesense
product.destroy!
Search your ActiveRecord models using Typesense:
# Basic search
results = Product.search("iPhone")
# Advanced search with filters and sorting
results = Product.search("smartphone",
filter_by: "brand:Apple && price:< 1000",
sort_by: "price:asc",
per_page: 20,
page: 1
)
# Search with facets
results = Product.search("phone")
results.facet_values("brand") # Get brand facet counts
results.facet_values("categories") # Get category facet counts
Get the Typesense document for any ActiveRecord instance:
product = Product.find(123)
typesense_doc = product.typesense_model
# Returns a TypesenseModel::Base instance with the Typesense document data
Field options available in the schema definition:
optional: true
- Field is optionalindex: false
- Field is not indexed (not searchable)facet: true
- Field can be used for faceted searchsort: true
- Field can be used for sortingdefault_sort: true
- Field is the default sorting field
You can customize how your model data is serialized for Typesense:
class Product < ApplicationRecord
uses_typesense collection: 'products', model_json: :to_typesense_hash do
# schema definition
end
def to_typesense_hash
{
id: id,
name: name,
price: price,
# Add computed fields
searchable_text: "#{name} #{description} #{brand}".downcase,
price_range: case price
when 0..100 then "budget"
when 101..500 then "mid-range"
else "premium"
end
}
end
end
Or use a Proc for dynamic serialization:
class Product < ApplicationRecord
uses_typesense collection: 'products',
model_json: ->(record) { record.as_json.merge(computed_field: record.compute_something) } do
# schema definition
end
end
Create and manage Typesense collections:
# Create the collection in Typesense
Product.search("") # This will create the collection if it doesn't exist
# Or explicitly create/update
proxy = TypesenseModel::ActiveRecordExtension::TypesenseProxy.for(Product)
proxy.create_collection
proxy.update_collection
proxy.delete_collection
Import existing ActiveRecord records to Typesense without N+1 queries:
# Basic import (default batch_size: 1000)
results = Product.import_all_to_typesense
# With preloads to avoid N+1
results = Product.import_all_to_typesense(preloads: [:brand, :images])
# With custom transformer and options
results = Product.import_all_to_typesense(
preloads: { variants: [:prices, :stock_items] },
transform: :to_typesense_hash,
batch_size: 2000,
import_options: { action: 'upsert' }
)
# results => { success: 500, failed: 0, errors: [...] }
Available as open source under the MIT License. Copyright (c) 2025 Ruby Dev SRL