This add-on is operated by Algolia
A powerful Search API delivering relevant results from the first keystroke
Algolia Realtime Search
Last updated July 07, 2021
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:create
command, replacing PLAN
with the name of the Algolia plan you’ve chosen:
$ heroku addons:create algoliasearch:PLAN
-----> Adding algoliasearch:PLAN to myapp... done
The PLAN
variable can be either:
- free
- starter
- growth
- pro
for example:
$ heroku addons:create algoliasearch:starter
-----> Adding algoliasearch:starter 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
Upgrading the add-on
AlgoliaSearch can be upgraded using the following addons:upgrade
command, replacing PLAN
with the name of the Algolia plan you’ve chosen:
$ heroku addons:upgrade algoliasearch:PLAN
-----> Adding algoliasearch:PLAN to myapp... done
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: 'YourApplicationID', api_key: 'YourAPIKey' }
We support both will_paginate and kaminari as pagination back-end. For example to use :will_paginate
, specify the :pagination_backend
as follow:
AlgoliaSearch.configuration = { application_id: 'YourApplicationID', api_key: 'YourAPIKey', 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
Front-end Search (realtime experience)
We recommend the usage of our JavaScript API Client to perform queries. The JS API client is part of the gem, just require algolia/algoliasearch.min
somewhere in your JavaScript manifest, for example in application.js
if you are using Rails 3.1+:
//= require algolia/algoliasearch.min
Back-end Search
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")
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
Auto-indexing & asynchronism
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
Custom index name
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
Per-environment indexes
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
Custom attribute definition
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
Custom objectID
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
Restrict indexing to a subset of your data
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
Notes: As soon as you use those constraints, deleteObjects
calls will be performed in order to keep the index synced with the DB (The state-less gem doesn’t know if the object don’t match your constraints anymore or never matched, so we force DELETE operations, even on never-indexed objects).
You can index a subset of your records using either:
# will generate batch API calls (recommended)
MyModel.where('updated_at > ?', 10.minutes.ago).reindex!
or
MyModel.index_objects MyModel.limit(5)
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
Manual indexing
You can trigger indexing using the index!
instance method.
c = Contact.create!(params[:contact])
c.index!
Manual removal
And trigger index removing using the remove_from_index!
instance method.
c.remove_from_index!
c.destroy
Reindexing
To safely reindex all your records (index to a temporary index + move the temporary index to the current one atomically), use the reindex
class method:
Contact.reindex
To reindex all your records (in place, without deleting out-dated records), use the reindex!
class method:
Contact.reindex!
Clearing an index
To clear an index, use the clear_index!
class method:
Contact.clear_index!
Master/slave
Where possible, we changed noninclusive terms to align with our company value of Equality. We retained noninclusive terms to document a third-party system, but we encourage the developer community to embrace more inclusive language. We will update the term when it’s no longer required for technical accuracy.
You can define slave indexes using the add_slave
method:
class Book < ActiveRecord::Base
attr_protected
include AlgoliaSearch
algoliasearch per_environment: true do
attributesToIndex [:name, :author, :editor]
# define a slave index to search by `author` only
add_slave 'Book_by_author', per_environment: true do
attributesToIndex [:author]
end
# define a slave index to search by `editor` only
add_slave 'Book_by_editor', per_environment: true do
attributesToIndex [:editor]
end
end
end
Target multiple indexes
You can index a record in several indexes using the add_index
method:
class Book < ActiveRecord::Base
attr_protected
include AlgoliaSearch
PUBLIC_INDEX_NAME = "Book_#{Rails.env}"
SECURED_INDEX_NAME = "SecuredBook_#{Rails.env}"
# store all books in index 'SECURED_INDEX_NAME'
algoliasearch index_name: SECURED_INDEX_NAME do
attributesToIndex [:name, :author]
# convert security to tags
tags do
[released ? 'public' : 'private', premium ? 'premium' : 'standard']
end
# store all 'public' (released and not premium) books in index 'PUBLIC_INDEX_NAME'
add_index PUBLIC_INDEX_NAME, if: :public? do
attributesToIndex [:name, :author]
end
end
private
def public?
released && !premium
end
end
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.
Search
Notes: We recommend the usage of our JavaScript API Client to perform queries directly from the end-user browser without going through your server.
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']
Geo-Search
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>
Caveats
This gem makes intensive use of Rails’ callbacks to trigger the indexing tasks. If you’re using methods bypassing after_validation
, before_save
or after_save
callbacks, it will not index your changes. For example: update_attribute
doesn’t perform validations checks, to perform validations when updating use update_attributes
.
Note on testing
To run the specs, please set the ALGOLIA_APPLICATION_ID
and ALGOLIA_API_KEY
environment variables. Since the tests are creating and removing indexes, DO NOT use your production account.
You may want to disable all indexing (add, update & delete operations) API calls, you can set the disable_indexing
option:
class User < ActiveRecord::Base
include AlgoliaSearch
algoliasearch :per_environment => true, :disable_indexing => Rails.env.test? do
end
end
class User < ActiveRecord::Base
include AlgoliaSearch
algoliasearch :per_environment => true, :disable_indexing => Proc.new { Rails.env.test? || more_complex_condition } do
end
end
Or you may want to mock Algolia’s API calls. We provide a WebMock sample configuration that you can use including algolia/webmock
:
require 'algolia/webmock'
describe 'With a mocked client' do
before(:each) do
WebMock.enable!
end
it "shouldn't perform any API calls here" do
User.create(name: 'My Indexed User') # mocked, no API call performed
User.search('').should == {} # mocked, no API call performed
end
after(:each) do
WebMock.disable!
end
end
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
All Algolia Search support and runtime issues should be submitted via on of the Heroku Support channels.