Algolia Realtime Search

This add-on is operated by Algolia

A powerful API delivering relevant results from the first keystroke

Algolia Realtime Search

Last Updated: 20 March 2014

Table of Contents

Algolia Search is an add-on that provides hosted full-text, numerical and faceted search.

Algolia’s Search API makes it easy to deliver a great search experience in your apps & websites providing:

  • REST and JSON-based API
  • search among infinite attributes from a single searchbox
  • instant-search after each keystroke
  • relevance & popularity combination
  • mobile compatibility
  • 99.99% SLA
  • first-class data security

Algolia’s official API clients are available on github: Ruby, Rails, Python, Node.js, PHP, JavaScript, Objective-C, Java, Android, C#, Shell.

Installing the add-on

AlgoliaSearch can be installed using the following addons:add command, replacing PLAN with the name of the Algolia plan you’ve chosen:

$ heroku addons:add algoliasearch:PLAN
-----> Adding algoliasearch:PLAN to myapp... done

Once installed, the add-on provides you 3 configuration variables that you’ll need to setup your API/REST client:

$ heroku config | grep ALGOLIASEARCH
ALGOLIASEARCH_API_KEY:           67c7681237e9c6059bc651d916da891f
ALGOLIASEARCH_API_KEY_SEARCH:    58f9095a2ff3e1df04c1e547256104f5
ALGOLIASEARCH_APPLICATION_ID:    97KEISL1BB

Using with Rails

The algoliasearch-rails gem let you easily integrate the Algolia Search API to your favorite ORM. It’s based on the algoliasearch-client-ruby gem.

$ gem install algoliasearch-rails

If you are using Rails 3, add the gem to your Gemfile:

gem "algoliasearch-rails"

And run:

$ bundle install

Setup

Create a new file config/initializers/algoliasearch.rb to setup your APPLICATION_ID and API_KEY.

AlgoliaSearch.configuration = { application_id: ENV['ALGOLIASEARCH_APPLICATION_ID'], api_key: ENV['ALGOLIASEARCH_API_KEY'] }

We support both will_paginate and kaminari as pagination backend. For example to use :will_paginate, specify the :pagination_backend as follow:

AlgoliaSearch.configuration = { application_id: ENV['ALGOLIASEARCH_APPLICATION_ID'], api_key: ENV['ALGOLIASEARCH_API_KEY'], pagination_backend: :will_paginate }

Quick Start

The following code will create a Contact index and add search capabilities to your Contact model:

class Contact < ActiveRecord::Base
  include AlgoliaSearch

  algoliasearch do
    attribute :first_name, :last_name, :email
  end
end

You can either specify the attributes to send (here we restricted to :first_name, :last_name, :email) or not (in that case, all attributes are sent).

class Product < ActiveRecord::Base
  include AlgoliaSearch

  algoliasearch do
    # all attributes will be sent
  end
end

You can also use the add_attribute method, to send all model attributes + extra ones:

class Product < ActiveRecord::Base
  include AlgoliaSearch

  algoliasearch do
    # all attributes + extra_attr will be sent
    add_attribute :extra_attr
  end

  def extra_attr
    "extra_val"
  end
end

A search returns ORM-compliant objects reloading them from your database.

p Contact.search("jon doe")

If you want to retrieve the raw JSON answer from the API, without re-loading the objects from the database, you can use:

p Contact.raw_search("jon doe")

By the way, we recommend the usage of our JavaScript API Client to perform queries.

Notes: All methods injected by the AlgoliaSearch include are prefixed by algolia_ and aliased to the associated short names if they aren’t already defined.

Contact.algolia_reindex! # <=> Contact.reindex!

Contact.algolia_search("jon doe") # <=> Contact.search("jon doe")

Options

Each time a record is saved; it will be - asynchronously - indexed. On the other hand, each time a record is destroyed, it will be - asynchronously - removed from the index.

You can disable auto-indexing and auto-removing setting the following options:

class Contact < ActiveRecord::Base
  include AlgoliaSearch

  algoliasearch auto_index: false, auto_remove: false do
    attribute :first_name, :last_name, :email
  end
end

You can temporary disable auto-indexing using the without_auto_index scope. This is often used for performance reason.

Contact.delete_all
Contact.without_auto_index do
  1.upto(10000) { Contact.create! attributes } # inside the block, auto indexing task will noop
end
Contact.reindex! # will use batch operations

You can force indexing and removing to be synchronous by setting the following option:

class Contact < ActiveRecord::Base
  include AlgoliaSearch

  algoliasearch synchronous: true do
    attribute :first_name, :last_name, :email
  end
end

You can force the index name using the following option:

class Contact < ActiveRecord::Base
  include AlgoliaSearch

  algoliasearch index_name: "MyCustomName" do
    attribute :first_name, :last_name, :email
  end
end

You can suffix the index name with the current Rails environment using the following option:

class Contact < ActiveRecord::Base
  include AlgoliaSearch

  algoliasearch per_environment: true do # index name will be "Contact_#{Rails.env}"
    attribute :first_name, :last_name, :email
  end
end

You can use a block to specify a complex attribute value

class Contact < ActiveRecord::Base
  include AlgoliaSearch

  algoliasearch do
    attribute :email
    attribute :full_name do
      "#{first_name} #{last_name}"
    end
  end
end

By default, the objectID is based on your record’s id. You can change this behavior specifying the :id option (be sure to use a uniq field).

class UniqUser < ActiveRecord::Base
  include AlgoliaSearch

  algoliasearch id: :uniq_name do
  end
end

You can add constraints controlling if a record must be indexed by using options the :if or :unless options.

class Post < ActiveRecord::Base
  include AlgoliaSearch

  algoliasearch if: :published?, unless: :deleted? do
  end

  def published?
    # [...]
  end

  def deleted?
    # [...]
  end
end

Configuration example

Here is a real-word configuration example (from HN Search):

class Item < ActiveRecord::Base
  include AlgoliaSearch

  algoliasearch per_environment: true do
    # the list of attributes sent to Algolia's API
    attribute :created_at, :title, :url, :author, :points, :story_text, :comment_text, :author, :num_comments, :story_id, :story_title, :

    # integer version of the created_at datetime field, to use numerical filtering
    attribute :created_at_i do
      created_at.to_i
    end

    # `title` is more important than `{story,comment}_text`, `{story,comment}_text` more than `url`, `url` more than `author`
    # btw, do not take into account position in most fields to avoid first word match boost
    attributesToIndex ['unordered(title)', 'unordered(story_text)', 'unordered(comment_text)', 'unordered(url)', 'author', 'created_at_i']

    # list of attributes to highlight
    attributesToHighlight ['title', 'story_text', 'comment_text', 'url', 'story_url', 'author', 'story_title']

    # tags used for filtering
    tags do
      [item_type, "author_#{author}", "story_#{story_id}"]
    end

    # use associated number of HN points to sort results (last sort criteria)
    customRanking ['desc(points)', 'desc(num_comments)']

    # controls the way results are sorted sorting on the following 4 criteria (one after another)
    # I removed the 'exact' match critera (improve 1-words query relevance, doesn't fit HNSearch needs)
    ranking ['typo', 'proximity', 'attribute', 'custom']

    # google+, $1.5M raises, C#: we love you
    separatorsToIndex '+#$'
  end

  def story_text
    item_type_cd != Item.comment ? text : nil
  end

  def story_title
    comment? && story ? story.title : nil
  end

  def story_url
    comment? && story ? story.url : nil
  end

  def comment_text
    comment? ? text : nil
  end

  def comment?
    item_type_cd == Item.comment
  end

  # [...]
end

Indexing

You can trigger indexing using the index! instance method.

c = Contact.create!(params[:contact])
c.index!

And trigger index removing using the remove_from_index! instance method.

c.remove_from_index!
c.destroy

To reindex all your records, use the reindex! class method:

Contact.reindex!

To clear an index, use the clear_index! class method:

Contact.clear_index!

Tags

Use the tags method to add tags to your record:

class Contact < ActiveRecord::Base
  include AlgoliaSearch

  algoliasearch do
    tags ['trusted']
  end
end

or using dynamical values:

class Contact < ActiveRecord::Base
  include AlgoliaSearch

  algoliasearch do
    tags do
      [first_name.blank? || last_name.blank? ? 'partial' : 'full', has_valid_email? ? 'valid_email' : 'invalid_email']
    end
  end
end

At query time, specify { tagFilters: 'tagvalue' } or { tagFilters: ['tagvalue1', 'tagvalue2'] } as search parameters to restrict the result set to specific tags.

A search returns ORM-compliant objects reloading them from your database. We recommend the usage of our JavaScript API Client to perform queries to decrease the overall latency and offload your servers.

hits =  Contact.search("jon doe")
p hits
p hits.raw_answer # to get the original JSON raw answer

If you want to retrieve the raw JSON answer from the API, without re-loading the objects from the database, you can use:

json_answer = Contact.raw_search("jon doe")
p json_answer
p json_answer['hits']
p json_answer['facets']

Search parameters can be specified either through the index’s settings statically in your model or dynamically at search time specifying search parameters as second argument of the search method:

class Contact < ActiveRecord::Base
  include AlgoliaSearch

  algoliasearch do
    attribute :first_name, :last_name, :email

    # default search parameters stored in the index settings
    minWordSizeForApprox1 4
    minWordSizeForApprox2 8
    hitsPerPage 42
  end
end
# dynamical search parameters
p Contact.search("jon doe", { :hitsPerPage => 5, :page => 2 })

Faceting

Facets can be retrieved calling the extra facets method of the search answer.

class Contact < ActiveRecord::Base
  include AlgoliaSearch

  algoliasearch do
    # [...]

    # specify the list of attributes available for faceting
    attributesForFaceting [:company, :zip_code]
  end
end
hits = Contact.search("jon doe", { :facets => '*' })
p hits                    # ORM-compliant array of objects
p hits.facets             # extra method added to retrieve facets
p hits.facets['company']  # facet values+count of facet 'company'
p hits.facets['zip_code'] # facet values+count of facet 'zip_code'
raw_json = Contact.raw_search("jon doe", { :facets => '*' })
p raw_json['facets']

Use the geoloc method to localize your record:

class Contact < ActiveRecord::Base
  include AlgoliaSearch

  algoliasearch do
    geoloc :lat_attr, :lng_attr
  end
end

At query time, specify { aroundLatLng: "37.33, -121.89", aroundRadius: 50000 } as search parameters to restrict the result set to 50KM around San Jose.

Typeahead UI

Require algolia/algoliasearch.min (see algoliasearch-client-js) and algolia/typeahead.jquery.js somewhere in your JavaScript manifest, for example in application.js if you are using Rails 3.1+:

//= require algolia/algoliasearch.min
//= require algolia/typeahead.jquery

We recommend the usage of hogan, a JavaScript templating engine from Twitter.

//= require hogan

Turns any input[type="text"] element into a typeahead, for example:

<input name="email" placeholder="test@example.org" id="user_email" />

<script type="text/javascript">
  $(document).ready(function() {
    var client = new AlgoliaSearch('YourApplicationID', 'SearchOnlyApplicationKey');
    var template = Hogan.compile('{{{_highlightResult.email.value}}} ({{{_highlightResult.first_name.value}}} {{{_highlightResult.last_name.value}}})');
    $('input#user_email').typeahead(null, {
      source: client.initIndex('<%= Contact.index_name %>').ttAdapter(),
      displayKey: 'email',
      templates: {
        suggestion: function(hit) {
          return template.render(hit);
        }
      }
    });
  });
</script>

Dashboard

You can monitor your consumption at any time opening your Algolia dashboard:

$ heroku addons:open algoliasearch

We do not provide you any login/password; we use Heroku’s SSO to log you in.## Support

Support

All Algolia Search support and runtime issues should be submitted via on of the Heroku Support channels.