How To Integrate ElasticSearch in your Rails Application

April 18, 2018
admin

Say you have a basic rails application up and running such as a Blog. Your client has given a new requirement to implement a feature where users can search for posts. What do you do?

Do you use the following cliched search method where you search by title of the post?

post_controller.rb:

def index
 @posts = Post.all
 if params[:search]
  @posts = Post.search(params[:search]).order("created_at DESC")
 else
  @posts = Post.all.order("created_at DESC")
 end
end

post.rb:

def self.search(search)
 where("title LIKE ?", "%#{search}%")
end

No! That’s a terrible idea because this is not proper search and your client won’t be happy when he realizes that. There are way better search implementations out there and this article talks about one of the best: ElasticSearch.

Why use ElasticSearch?

ElasticSearch is a full-text search engine that examines all of the words in every stored document as it tries to match search criteria (text specified by a user). ElasticSearch has been growing in popularity due to some modern awesomeness such as instant indexing which is the main challenge when it comes to storing data and making it available for users. Also, ElasticSearch is super fast! However, to fully take advantage of all of its features one has to know how to implement it. Therefore, we will talk about how to incorporate elasticsearch in your rails application.

Installing ElasticSearch

ElasticSearch 6.0 has been released recently and here’s how you can install it on your machine to aid you in your development work. Before installation makes sure you have java-8.0 installed as this is a prerequisite.
deb:

sudo apt-get install openjdk-8-jre
curl -L -O https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-6.0.0.deb
sudo dpkg -i elasticsearch-6.0.0.deb
sudo /etc/init.d/elasticsearch start

rpm:

sudo yum install java-1.8.0-openjdk
curl -L -O https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-6.0.0.rpm
sudo rpm -i elasticsearch-6.0.0.rpm
sudo service elasticsearch start

mac:

# install Java, e.g. from: https://www.java.com/en/download/manual.jsp
curl -L -O https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-6.0.0.zip
unzip elasticsearch-6.0.0.zip
cd elasticsearch-6.0.0
./bin/elasticsearch

Verify successful installation by hitting: http://localhost:9200 from your browser. There should a nice output like this:

{
 "name" : "QZQnCeK",
 "cluster_name" : "elasticsearch",
 "cluster_uuid" : "xBi7a3k_Spu4k5iijCwihQ",
 "version" : {
  "number" : "6.0.0",
  "build_hash" : "8f0685b",
  "build_date" : "2017-11-10T18:41:22.859Z",
  "build_snapshot" : false,
  "lucene_version" : "7.0.1",
  "minimum_wire_compatibility_version" : "5.6.0",
  "minimum_index_compatibility_version" : "5.0.0"
 },
 "tagline" : "You Know, for Search"
}

Hook up ElasticSearch with Rails Application

Assuming you already have a basic blog application running in Rails, you will be glad to know that there is a sweet gem out there that does all the work for you to hook up elasticsearch in your rails app so that you can start playing with it in code level right away.

Install the following gems in your Gemfile :

gem 'elasticsearch-model'
gem 'elasticsearch-rails'

Create the Search Controller:

$ rails g controller search

Add this method to app/controller/search_controller.rb:

def search
 if params[:q].nil?
  @posts = []
 else
  @posts = Post.search params[:q]
 end
end
Integrate Search into Post:

To add the ElasticSearch integration to the Article model, require elasticsearch/model and include the main module in Post class.

Modify app/models/post.rb:

require 'elasticsearch/model'

class Post < ActiveRecord::Base
 include Elasticsearch::Model
 include Elasticsearch::Model::Callbacks
end
Post.import # for auto sync model with elastic search
Search View:

Create a new file at app/views/search/search.html.erb:

<h1>Posts Search</h1>

<%= form_for search_path, method: :get do |f| %>
 <p>
  <%= f.label "Search for" %>
  <%= text_field_tag :q, params[:q] %>
  <%= submit_tag "Go", name: nil %>
 </p>
<% end %>

<ul>
 <% @posts.each do |article| %>
  <li>
   <h3>
    <%= link_to post.title, controller: "posts", action: "show", id: post._id%>
   </h3>
  </li>
 <% end %>
</ul>
Search Route:

Add the search route to _config/routes.rb:
get ‘search’, to: ‘search#search’
With all these steps completed, you are ready to utilize the basic search of ElasticSearch.

Enhance the Search

You may notice that much like the cliched search technique, while searching for part of a word, such as “rub” or “roby” instead of “ruby”, ElasticSearch is giving you zero results. It’d be nice if the search engine gave results that include words similar to your search term.

ElasticSearch provides a lot of features to enhance your search. I will give some examples.

Custom Query

So far we have just been using the default search query of ElasticSearch. There are different types of queries that we can use to enhance our search. To enhance the search, we need to modify our default search query.
Let’s add a custom search method to our article model in app/models/post.rb:

def self.search(query)
 __elasticsearch__.search(
  {
   query: {
    multi_match: {
     query: query,
     fields: ['title^10', body]
    }
   }
  }
 )
end

Note: ^10 boosts by 10 the score of hits when the search term is matched in the title, meaning it gives more priority to matching the title with the user entered the search term.

Custom Mapping

Mapping is the process of defining how a document should be mapped to the Search Engine, including its searchable characteristics like which fields are searchable and if/how they are tokenized.
We will improve the search so that you can search for a term like “search” and receive results also including “searches” and “searching” ..etc. This will use the built-in English analyzer in ElasticSearch to apply word stemming (follow the link to know more about stemming algorithm) before indexing.
Add this mapping to the Post class at app/models/post.rb:

settings index: { number_of_shards: 1 } do
 mappings dynamic: 'false' do
  indexes :title, analyzer: 'english'
  indexes :body, analyzer: 'english'
 end
end

This was a quick example for integrating ElasticSearch into a Rails app. We added basic search, then mixed things up a little using custom queries and custom mapping.

References

Contributor: Mohammad Rahber-E-AlamNascenia

A high-level architecture for Rails, Trailblazer sits on top of Rails to introduce better abstraction layers, structure and reasonable encapsulation. To know more read our other blog on HOW TRAILBLAZER MAKES RUBY ON RAILS BETTER?

No comments

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.