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.