Using Searchkick and ElasticSearch in Rails 4 - Tutor Portal

While creating Tutor Portal for Week 6 of the 20/20 Challenge, one of the most important facets of the application was search.  Tutors, clients, and directors had to be able to easily search through their records and find the information they were looking for easily based on date or information about that specific client.

For example, if an admin was searching for a client, and put the student's name instead, I still wanted that client to show up.  Most importantly, I wanted a scalable solution that could not only add more capabilities, but could also handle large amounts of segmented data.  

In this nerd note, I outline how to set up Elasticsearch and searchkick in Rails and common use cases in many applications.

Elasticsearch and Searchkick

Based on previous experience, I decided to use Elasticsearch for this project.  Elasticsearch is a very intricate search tool that can downloaded and used within applications of all types.  It is open-source, used by many large organizations, and is very well documented. However, it is quite difficult to use right out of the box, and I always look for tools that I can learn quickly.

As always, there is someone in the Rails community that has made Elasticsearch much easier to use in the context of a standard application.  I decided to use the Searchkick, which was created by the developers at Instacart to help customers sort through a ton of groceries.  Searchkick is a Ruby gem that runs on top of Elasticsearch and makes it easy to make searches in a Rails-friendly fashion.  In addition, it allows you to add more features including analytics, autocomplete, and personalized results.

To get started, make sure that you have Elasticsearch installed on your home computer.  Depending on your operating system, the installation process is slightly different.  If you are on Mac OS X and have Homebrew installed, simply run the following in your terminal (make sure you have at least Java 7):


    brew install elasticsearch

Otherwise, head here and install from the website.

Once you have that done, add and install Searchkick to your Rails application by adding the following to your Gemfile and running bundle install.


    gem 'searchkick'

Once you have both installed and ready to go, you have to indicate which models you would like to have the ability to search through in your application.  For us, we wanted our User model, Student Model, and Lessons Model to be searchable.  Just add searchkick to the model file to make it work.  Then, we need to reindex the models so that Elasticsearch can run properly.  In your terminal, run:


    rake searchkick:reindex:all

Once you have these few steps done, searchkick is integrated and ready for use!

Searching

Searching with searchkick is quite simple.  Simply run YourModel.search, followed by the parameters of the search and any filters that you want to add on.  As an example, one of more complex searches is below:


    @lessons = Lesson.search params[:search], 
      page: params[:page], per_page: 10, 
      order: {starttime: :desc}, 
      fields: [{tutor_name: :word_start}, {student_name: :word_start}, :token], 
      where: {
        starttime: {
        gte: DateTime.strptime(params[:fromdate], '%m/%d/%Y'), 
        lte: DateTime.strptime(params[:todate], '%m/%d/%Y')
        },
        account_id: ActsAsTenant.current_tenant.id
      }

In this search, we take the search query of the user with params[:search], and look through of the lessons with the following conditions:

  • Paginationpage: params[:page], per_page: 10 - This segment of code returns the results of the search into pages of 10.   You can customize how many search results you want on each page using this method.  This works right out of the box with will_paginate and kaminari.
  • Orderorder: {starttime: :desc} - This method determines what order the results are returned in.  In this case, we returned the most recent results first.  
  • Fields - [{tutor_name: :word_start}, {student_name: :word_start}, :token] - In this method, we allowed users to search by tutor name, student name, or the lesson token.  The word_start method allows for users to only type in the first part of the students name and still get the student in search results.  For example, a search of "Bo" would still reveal "Bob".  You can also use word_middle and word_end.  For longer text, searchkick also supports text_start, text_middle, and text_end.
  • Wherestarttime: { gte: DateTime.strptime(params[:fromdate], '%m/%d/%Y'), lte: DateTime.strptime(params[:todate], '%m/%d/%Y') }... - In my opinion, the where method is the most user-friendly and generalizable method in searchkick.  It allows users to easily use filters in their application.  We wanted to filter between dates, we used starttime to find the lessons that fell within the customers' search query.  In addition, we filtered by the account id in order ensure that the search was only within the current tenant -- meaning that a tutoring agency wouldn't get a result from a different tutoring agency.  To learn more about tenants using the acts_as_tenant gem, check out my previous nerd note here.

There are a ton of search customizations available to you through the gem and Elasticsearch.  Be sure to check them all out and let me know which ones you found very useful in your app!

Setting Custom Search Attributes

One last great feature in searchkick is the search_data method that you can add to your models.  This allows you to add or replace attributes that the user can search for.  By doing this, you can create custom search parameters or search based on the associations of a model.  Below is an example of one of the search_data methods we implemented.


    def search_data
      attributes.merge(
        student_name: client_managed_students.map(&:name),
        student_id: students.map(&:id)
      )
    end

In the above example, we add the ability to search by student name when searching for a client or a tutor.  Searchkick maps the data based on associations to the resulting query.  IMPORTANT:  Make sure that you reindex searchkick each time that you edit code within your model that impacts the searchkick line or the search_data method.  Check out the Github page for more info on reindexing.

Using attributes.merge adds fields to the potential search results.  If you would like to completely replace the attributes and not use the defaults, simply do not write attributes.merge.  This process shines the most when working with associations.  Below I have outlined how to map search_data for a couple of the primary associations:

has_many :students (or has_many :students, through: :tutor_student_relationships


    student_name: students.map(&:name)

belongs_to :client or has_one


    client_name: client(&:name)

Everything above only scratches the surface of what searchkick is capable of.  You can boost fields, add a ton more filters, and even add autocomplete.  However, this will give you a good start towards creating dynamic search in your application.  Let me know or comment below with interesting ways that you have implemented searchkick in your application.

Hope I was able to help you guys out! If you found this week's nerd notes useful, here's a handy link to tweet it on the twitter.

See you soon for our next project!

Ruben