Searchkick
Intelligent search made easy
Install / Use
/learn @ankane/SearchkickREADME
Searchkick
:rocket: Intelligent search made easy
Searchkick learns what your users are looking for. As more people search, it gets smarter and the results get better. It’s friendly for developers - and magical for your users.
Searchkick handles:
- stemming -
tomatoesmatchestomato - special characters -
jalapenomatchesjalapeño - extra whitespace -
dishwashermatchesdish washer - misspellings -
zuchinimatcheszucchini - custom synonyms -
popmatchessoda
Plus:
- query like SQL - no need to learn a new query language
- reindex without downtime
- easily personalize results for each user
- autocomplete
- “Did you mean” suggestions
- supports many languages
- works with Active Record and Mongoid
Check out Searchjoy for analytics and Autosuggest for query suggestions
:tangerine: Battle-tested at Instacart
Contents
- Getting Started
- Querying
- Indexing
- Intelligent Search
- Instant Search / Autocomplete
- Aggregations
- Testing
- Deployment
- Performance
- Advanced Search
- Reference
- Contributing
Searchkick 6.0 was recently released! See how to upgrade
Getting Started
Install Elasticsearch or OpenSearch. For Homebrew, use:
brew install opensearch
brew services start opensearch
Add these lines to your application’s Gemfile:
gem "searchkick"
gem "elasticsearch" # select one
gem "opensearch-ruby" # select one
The latest version works with Elasticsearch 8 and 9 and OpenSearch 2 and 3. For Elasticsearch 7 and OpenSearch 1, use version 5.5.2 and this readme.
Add searchkick to models you want to search.
class Product < ApplicationRecord
searchkick
end
Add data to the search index.
Product.reindex
And to query, use:
products = Product.search("apples")
products.each do |product|
puts product.name
end
Searchkick supports the complete Elasticsearch Search API and OpenSearch Search API. As your search becomes more advanced, we recommend you use the search server DSL for maximum flexibility.
Querying
Query like SQL
Product.search("apples").where(in_stock: true).limit(10).offset(50)
Search specific fields
fields(:name, :brand)
Where
where(store_id: 1, expires_at: Time.now..)
These types of filters are supported
Order
order(_score: :desc) # most relevant first - default
All of these sort options are supported
Limit / offset
limit(20).offset(40)
Select
select(:name)
These source filtering options are supported
Results
Searches return a Searchkick::Relation object. This responds like an array to most methods.
results = Product.search("milk")
results.size
results.any?
results.each { |result| ... }
By default, ids are fetched from the search server and records are fetched from your database. To fetch everything from the search server, use:
Product.search("apples").load(false)
Get total results
results.total_count
Get the time the search took (in milliseconds)
results.took
Get the full response from the search server
results.response
Note: By default, Elasticsearch and OpenSearch limit paging to the first 10,000 results for performance. This applies to the total count as well.
Filtering
Equal
where(store_id: 1)
Not equal
where.not(store_id: 2)
Greater than (gt), less than (lt), greater than or equal (gte), less than or equal (lte)
where(expires_at: {gt: Time.now})
Range
where(orders_count: 1..10)
In
where(aisle_id: [25, 30])
Not in
where.not(aisle_id: [25, 30])
Contains all
where(user_ids: {all: [1, 3]})
Like
where(category: {like: "%frozen%"})
Case-insensitive like
where(category: {ilike: "%frozen%"})
Regular expression
where(category: /frozen .+/)
Prefix
where(category: {prefix: "frozen"})
Exists
where(store_id: {exists: true})
Combine filters with OR
where(_or: [{in_stock: true}, {backordered: true}])
Boosting
Boost important fields
fields("title^10", "description")
Boost by the value of a field (field must be numeric)
boost_by(:orders_count) # give popular documents a little boost
boost_by(orders_count: {factor: 10}) # default factor is 1
Boost matching documents
boost_where(user_id: 1)
boost_where(user_id: {value: 1, factor: 100}) # default factor is 1000
boost_where(user_id: [{value: 1, factor: 100}, {value: 2, factor: 200}])
Boost by recency
boost_by_recency(created_at: {scale: "7d", decay: 0.5})
You can also boost by:
Get Everything
Use a * for the query.
Product.search("*")
Pagination
Plays nicely with kaminari and will_paginate.
# controller
@products = Product.search("milk").page(params[:page]).per_page(20)
View with kaminari
<%= paginate @products %>
View with will_paginate
<%= will_paginate @products %>
Partial Matches
By default, results must match all words in the query.
Product.search("fresh honey") # fresh AND honey
To change this, use:
Product.search("fresh honey").operator("or") # fresh OR honey
By default, results must match the entire word - back will not match backpack. You can change this behavior with:
class Product < ApplicationRecord
searchkick word_start: [:name]
end
And to search (after you reindex):
Product.search("back").fields(:name).match(:word_start)
Available options are:
Option | Matches | Example
--- | --- | ---
:word | entire word | apple matches apple
:word_start | start of word | app matches apple
:word_middle | any part of word | ppl matches apple
:word_end | end of word | ple matches apple
:text_start | start of text | gre matches green apple, app does not match
:text_middle | any part of text | een app matches green apple
:text_end | end of text | ple matches green apple, een does not match
The default is :word. The most matches will happen with :word_middle.
To specify different matching for different fields, use:
Product.search(query).fields({name: :word_start}, {brand: :word_middle})
Exact Matches
To match a field exactly (case-sensitive), use:
Product.search(query).fields({name: :exact})
Phrase Matches
To only match the exact order, use:
Product.search("fresh honey").match(:phrase)
Stemming and Language
Searchkick stems words by default for better matching. apple and apples both stem to appl, so searches for either term will have the same matches.
Searchkick defaults to English for stemming. To change this, use:
class Product < ApplicationRecord
searchkick language: "german"
end
See the list of languages. A few languages require plugins:
chinese- analysis-ik pluginchinese2- analysis-smartcn pluginjapanese- analysis-kuromoji pluginkorean- analysis-openkoreantext pluginkorean2- analysis-nori pluginpolish- analysis-stempel pluginukrainian- analysis-ukrainian pluginvietnamese- analysis-vietnamese plugin
You can also use a Hunspell dictionary for stemming.
class Product < ApplicationRecord
searchkick stemmer: {type: "hunspell", locale: "en_US"}
end
Disable stemming with:
class Image < ApplicationRecord
searchkick stem: false
end
Exclude certain words from stemming with:
class Image < ApplicationRecord
searchkick stem_exclusion: ["apples"]
end
Or change how words are stemmed:
class Image < ApplicationRecord
searchkick stemmer_override: ["apples => other"]
end
Synonyms
class Product < ApplicationRecord
searchkick search_synonyms: [["pop", "soda"], ["burger", "hamburger"]]
end
Call Product.reindex after changing synonyms. Synonyms are applied at search time before stemming, and can be a single word or multiple words.
For d
Related Skills
node-connect
333.7kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
82.0kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
333.7kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
commit-push-pr
82.0kCommit, push, and open a PR
