0.06
A long-lived project that still receives updates
Generate autocomplete suggestions based on what your users search
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Runtime

 Project Readme

Autosuggest

Generate autocomplete suggestions based on what your users search

🍊 Battle-tested at Instacart

Build Status

Installation

Add this line to your application’s Gemfile:

gem "autosuggest"

Getting Started

Prepare your data

Start with a hash of queries and their popularity, like the number of users who have searched it.

top_queries = {
  "bananas" => 353,
  "apples"  => 213,
  "oranges" => 140
}

With Searchjoy, you can do:

top_queries = Searchjoy::Search.group(:normalized_query)
  .having("COUNT(DISTINCT user_id) >= 5").distinct.count(:user_id)

Then pass them to Autosuggest.

autosuggest = Autosuggest::Generator.new(top_queries)

Filter duplicates

Stemming is used to detect duplicates like apple and apples.

Specify the stemming language (defaults to english) with:

autosuggest = Autosuggest::Generator.new(top_queries, language: "spanish")

The most popular query is preferred by default. To override this, use:

autosuggest.prefer ["apples"]

To fix false positives, use:

autosuggest.not_duplicates [["straws", "straus"]]

Filter misspellings

We tried open-source libraries like Aspell and Hunspell but quickly realized we needed to build a corpus specific to our application.

There are two ways to build the corpus, which can be used together.

  1. Add words
autosuggest.parse_words Product.pluck(:name)

Use the min option to only add words that appear multiple times.

  1. Add concepts
autosuggest.add_concept "brand", Brand.pluck(:name)

Filter words

Profanity is blocked by default. Add custom words with:

autosuggest.block_words ["boom"]

Generate suggestions

Generate suggestions with:

suggestions = autosuggest.suggestions

Save suggestions

Save suggestions in your database or another data store.

With Rails, you can generate a simple model with:

rails generate autosuggest:suggestions
rails db:migrate

And update suggestions with:

now = Time.now
records = suggestions.map { |s| s.slice(:query, :score).merge(updated_at: now) }
Autosuggest::Suggestion.transaction do
  Autosuggest::Suggestion.upsert_all(records, unique_by: :query)
  Autosuggest::Suggestion.where("updated_at < ?", now).delete_all
end

Leave out unique_by for MySQL.

Show suggestions

Use a JavaScript autocomplete library like typeahead.js to show suggestions in the UI.

If you only have a few thousand suggestions, it’s much faster to load them all at once instead of as a user types (eliminates network requests).

With Rails, you can load all suggestions with:

Autosuggest::Suggestion.order(score: :desc).pluck(:query)

And suggestions matching user input with:

input = params[:query]
Autosuggest::Suggestion
  .order(score: :desc)
  .where("query LIKE ?", "%#{Autosuggest::Suggestion.sanitize_sql_like(input.downcase)}%")
  .pluck(:query)

You can also cache suggestions for performance.

Rails.cache.fetch("suggestions", expires_in: 5.minutes) do
  Autosuggest::Suggestion.order(score: :desc).pluck(:query)
end

Additional considerations

You may want to have someone manually approve suggestions:

Autosuggest::Suggestion.where(status: "approved")

Or filter suggestions without results:

Autosuggest::Suggestion.find_each do |suggestion|
  suggestion.results_count = Product.search(suggestion.query, load: false).count
  suggestion.save! if suggestion.changed?
end

Autosuggest::Suggestion.where("results_count > 0")

You can add additional fields to your model/data store to accomplish this.

Example

top_queries = Searchjoy::Search.group(:normalized_query)
  .having("COUNT(DISTINCT user_id) >= 5").distinct.count(:user_id)
product_names = Product.pluck(:name)
brand_names = Brand.pluck(:name)

autosuggest = Autosuggest::Generator.new(top_queries)
autosuggest.parse_words product_names
autosuggest.add_concept "brand", brand_names
autosuggest.prefer brand_names
autosuggest.not_duplicates [["straws", "straus"]]
autosuggest.block_words ["boom"]

suggestions = autosuggest.suggestions

now = Time.now
records = suggestions.map { |s| s.slice(:query, :score).merge(updated_at: now) }
Autosuggest::Suggestion.transaction do
  Autosuggest::Suggestion.upsert_all(records, unique_by: :query)
  Autosuggest::Suggestion.where("updated_at < ?", now).delete_all
end

History

View the changelog

Contributing

Everyone is encouraged to help improve this project. Here are a few ways you can help:

To get started with development:

git clone https://github.com/ankane/autosuggest.git
cd autosuggest
bundle install
bundle exec rake test