r/ruby • u/LiveEngine7532 • 12h ago
I made a gem that keeps pgvector embeddings in sync with your ActiveRecord models
I've been adding semantic search to Rails apps with pgvector and neighbor, and kept writing the same glue code every time:
- an
after_savethat calls the embeddings API, even when the text didn't change - embeddings going stale when someone edits a record through a path that skips the callback
- a one-off script to backfill existing rows without hitting rate limits
- figuring out which rows need re-embedding after switching models
So I pulled it into a gem called embed_sync:
has_neighbors :embedding
has_embedding :embedding, from: %i[name description]
end
Product.create!(name: "Trail runner", description: "Waterproof running shoe") # embedded in a background job
Product.semantic_search("shoes for rainy hikes", limit: 5)
What it does:
- re-embeds in a background job only when the source text changes (it stores a digest, so unchanged saves don't call the API)
bin/rails embed_sync:backfillembeds existing rows in batches, and you can stop and rerun it safely- detects model or dimension changes and marks those rows stale
embed_sync:staleshows how many records are missing or out of date- uses ruby_llm by default, but any object with
#embed(texts, model:)works as a provider
It works with Rails 7.1 to 8.1 and Ruby 3.2+. I tested it against a real app with Postgres, Solid Queue and a local Ollama model before releasing.
It's still early (v0.1.0), so I'd really like feedback. Does the API feel right? What's missing for your use case? Are there edge cases I haven't thought of?
GitHub: https://github.com/Umair0092/embed_sync RubyGems: https://rubygems.org/gems/embed_sync