Giter Site home page Giter Site logo

autosuggest's Introduction

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, and use activerecord-import for upserts with Rails < 6.

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

Upgrading

0.2.0

Suggestions are now filtered by default, and only the query and score are returned. Get all queries and fields with:

autosuggest.suggestions(filter: false)

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

autosuggest's People

Contributors

ankane avatar cspeer avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

autosuggest's Issues

Ideas

Please create a new issue to discuss any ideas or share your own.

0.3.0

  • Switch to Mittens for stemming - mittens branch

Ideas

Please create a new issue to discuss any ideas.

  • Add method that excludes duplicates, misspellings, blocked, etc (added option)
  • Add generator for model for storing suggestions
  • Drop support for Ruby < 2.6

Issue with really long query strings

Noticed that when queries are too long, calling Autosuggest.new(top_queries).suggestions will hang up

After deleting (in my case searchjoy searches), everything functions as normal.

Any ideas?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.