How to create a basic search form and how to use Elasticsearch as an alternative.(Ruby on Rails)
Tags: ruby, rails, tutorial
Published: 2020-03-05 15:22:23

In this the tutorial, we will review how to build a search form and then we'll look at how to implement a search form using ElasticSearch. I'll be using a book review app that I built recently. 

I want to implement a search function that will allow users to search the database for a book that has been reviewed. 

# Basic Rails Search

In the app, a user can have more than one book review and a book review belongs to a user. 

Let's look at what the respective models look like. 

This is the book review model. 
```ruby 
# frozen_string_literal: true
class BookReview < ApplicationRecord
  belongs_to :user
end
```

This is the user model. 

```ruby
# frozen_string_literal: true
class User < ApplicationRecord
  has_many :book_reviews
end
```

We'll have to work with the model, view and controller in this app. 

The first thing we'll do is add the search function to our view. We'll be using the index view to add the search function. 

## Modifying the view

```ruby
  <%= form_with url: book_reviews_path, method: :get, local: true  do |form| %>
    <%= form.label :search, "Search Books:"%>
    <%= form.text_field :search, id: :search, class: "form-control" %>
    <br />
    <%= form.submit "Submit", class: 'btn btn-primary'%>
  <%end%>
```

This will create our search form and our controller is waiting to receive the params[:search] and that is set in the form.text_field. 



## Modifying the Book Review Model

The next thing we want to do is add the logic for our search to the model. The reason we are adding it to our model and not our controller is that it is good practice to keep your controllers lean and add logic to your models. 

```ruby
# frozen_string_literal: true

# BookReview Model with validations
class BookReview < ApplicationRecord
  belongs_to :user

  def self.search(search)
    if search
      book_reviews = BookReview.where('title LIKE ?', "%#{search}%")
      if book_reviews.present?
        where(id: book_reviews)
      else
        all
      end
    else
      all
    end
  end
end
```
(I'm using Rails 5.1.7 and with this comes the ability to use all instead of typing BookReview.all)

Our search function will render on the index page. If there isn't a search parameter entered it will render all the books. It will also render all the books if the search doesn't find a match. 

Now that we have seen how a basic search form is added to a rails project, let's look at what we can use that will allow us to scale and have a more efficient and robust search. 

The current search we are using works well for a small project but large scale applications will need something else. 

## ElasticSearch

Elasticsearch is an open-source search and analytics engine. It uses a REST API that makes it easy to use. 

It has multiple uses but for this blog and tutorial, were are going to use it as a search engine. 

If you are using brew you can issue this command to install elasticsearch

```brew install elasticsearch```

To use elasticsearch in our rails app, we'll need to add this gem and run bundle install. 

```gem 'searchkick"```


Then we need to add searchkick to the model that we would like to search. In our example, we would add it to your book_reviews model.

```ruby
# frozen_string_literal: true

class BookReview < ApplicationRecord
  searchkick
end
```

Then you'll want to add data to the search index.

```rails searchkick:reindex CLASS=BookReview"```

Type elasticsearch in the command line and then you can type

```curl http://localhost:9200```

in a separate window to make sure elasticsearch is up and running.

We don't need to make any changes to our index view, however, we will need to make changes to our model and controller. 

Let's add the search logic to our model so that we are adhering to the design principle of fat models and skinny controllers.

```ruby
  def self.advanced_search(search_results)
    if search_results
      BookReview.search(search_results, fields: [:title])
    else
      all
    end
```
Let's add this method to our controller

```ruby
 def index
    @book_reviews = BookReview.advanced_search(params[:search])
  end
```

This is all we need to do to add elasticsearch. ElasticSearch will yield results out-of-the-box even if the user hasn't spelled the word correctly.

ElasticSearch can do some much more but I hope this tutorial has given you the knowledge on how to add it to your rails application.