Adding advanced search functionality to your Rails app with Elasticsearch
Elasticsearch is an external service that offers powerful searching features. You can build a simple search feature for your Rails App using standard SQL queries. However when it comes to advanced features such as working with AND, OR statements and ranked results it is better to rely on a industrial strength external solution. Search is often at the core of any CRUD Rails App.
Set-up
Set up Elasticsearch locally
- Install Java and Elasticsearch via Brew
$ brew update
$ brew install Caskroom/cask/java
$ brew install elasticsearch
- To run Elasticsearch simply type
$ elasticsearch
in your Terminal.
By default Elasticsearch runs on localhost:9200
. You can test it with $ curl -X GET http://localhost:9200/
Set up Elasticsearch with your Rails App in development and test environments
- Install the following gems from this Elasticsearch Rails resource:
gem 'elasticsearch-model'
gem 'elasticsearch-rails'
Run
$ bundle install
Go into rails console and create the index
$ rails console
Post.__elasticsearch__.create_index!
Post.__elasticsearch__.import
Set up Elasticsearch in production
- If you use CircleCI for continuous deployment add
elasticsearch
to yourcircle.yml
file.
machine:
ruby:
version: 2.1.6
services:
- elasticsearch
deployment:
...
- On Heroku you can use the SearchBox add-on. Install it with
$ heroku addons:create searchbox
If you have an extra app for staging remember to install it there as well ($ heroku addons:create searchbox --app staging-app-name
)
- On your Heroku environment you also have to create the index
$ heroku run rails console
Post.__elasticsearch__.create_index!
Post.__elasticsearch__.import
- Create an initializer in your Rails app
config/initializers/elasticsearch.rb
Elasticsearch::Model.client =
if Rails.env.staging? || Rails.env.production?
Elasticsearch::Client.new url: ENV['SEARCHBOX_URL']
elsif Rails.env.development?
Elasticsearch::Client.new log: true
else
Elasticsearch::Client.new
end
- If you use RSpec add the following in your RSpec.configure block
config.before(:each, elasticsearch: true) do
Video.__elasticsearch__.create_index! force: true
end
You will also have to tag all your Elasticsearch related specs with :elasticsearch
describe ".search", :elasticsearch do
let(:refresh_index) do
Post.import
Post.__elasticsearch__.refresh_index!
end
# ...
Use
- Index the model you want to search in rails console
Post.__elasticsearch__.create_index!
Post.__elasticsearch__.import
- Include the Elastic libraries in your model
# app/models/post.rb
class Post < ActiveRecord::Base
include Elasticsearch::Model
include Elasticsearch::Model::Callbacks
index_name ["app_name", Rails.env].join("_")
# ...
- Add a search method to the same model and parse the search as json for Elasticsearch. All objects in Elasticsearch are in json.
# app/models/post.rb
# ...
def self.search(query)
search_definition = {
query: {
multi_match: {
query: query,
fields: ["title", "description"],
operator: "and"
}
}
}
__elasticsearch__.search(search_definition)
end
def as_indexed_json(options={})
as_json(only: [:title, :description])
end
- Add a search method to your controller
# app/controllers/posts_controller.rb
# ...
def advanced_search
if params[:query]
@posts = Post.search(params[:query]).records.to_a
else
@posts = []
end
end
- In the view your search form can perform a simple get request
# app/views/posts/advanced_search.html.haml
%section.advanced_search.container
%form.form-horizontal
= form_tag advanced_search_posts_path, class: "form-horizontal", method: :get do
%header
%h1 Advanced Search
.form-group
.col-sm-6
= search_field_tag :query, params[:query], placeholder: "Search posts by title, description", class: "form-control"
.form-group
.col-sm-6
= button_tag(type: "submit", class: "btn btn-primary") do
%span.glyphicon.glyphicon-search
Search
- Don't forget to add a route for the search action
# routes.rb
# ...
resources :posts, only: [:show, :index] do
collection do
get :advanced_search, to: 'videos#advanced_search'
end
resources :comments, only: [:create]
end
# ...
The example above only scratches the surface of what you can do with Elasticsearch. To learn more check out the documentation.