loading...

Adding a simple search feature to a Ruby on Rails web app

bruck1701 profile image Bruno Kümmel ・4 min read

If you are using Bootstrap in your project, you might have noticed that one of the templates for the navbar comes with a search bar. Since it is already there, you might as well use it, right?

This is one of my first projects in Rails, and I have been developing it following an online course and some tutorials.

The project is a simple blog (the Hello World of web frameworks!) and the idea, proposed by the instructor, was to try to code the solution ourselves as much as possible. This way, the students could understand the flow of data and how the whole app comes together at the end.

Nevertheless, at the end of the video instruction, the search bar of the template was left there with no function attached to it! I looked online and found out there is a lot of gems that give search ability to your app: Sunspot, Searchkick, Thinking Sphinx, Searchlogic, Ransack...and the list goes on.

But if the premise of this beginners course was to, as much as possible, not to use gems, how hard can it be for a beginner to add a search function to a blog? Surprisingly not very hard!

Front-end change: navbar

First, we remove the built-in code from the navbar template that comes with Botstrap:

<form class="form-inline my-2 my-lg-0">
  <input  class="form-control mr-sm-2"
    type="search"
    placeholder="Search"
    aria-label="Search"
   />
   <button 
     class="btn btn-outline-success my-2 my-sm-0" 
     type="submit">
       Search
   </button>
</form>

And we add a more RoR-like form:

<%= form_tag(search_path, 
             :method => "get", 
             :class=>"form-inline my-2 my-lg-0") do %>
  <%= text_field_tag :search, 
                      params[:search], 
                      placeholder: "Search Articles", 
                      class: "form-control mr-sm-2" %>

    <span class="input-group-btn">
      <%= submit_tag "Search", class: 'btn btn-info' %>
    </span>
<% end %>

I decided to send the search query to a specific search_path in routes, since my original idea was to add a method search in articles_controller.rb.

However, I got a hint from a more experienced developer that it made more sense to move the search feature to a Service, so I kept the search_path in the form, but redirected it to index of the controller. The index, then, will use the search Service to fetch the articles based on the values of params

So, I added a route in routes.rb

  get "search", :to => "articles#index"

Or , in my proposed solution, you can send the form to articles_path instead and not change routes.rb, I guess.

The params that will come from the search form will be different from the ones that come when we just list the articles.
We can check the params by using byebug at beginning of the index method, as shown below:

params from listing articles with search

Fig1: params from listing articles with search

params from listing articles with no search

Fig2: params from listing articles normally

Search Module

To keep our controller skinny, we create a Search module as a service inside of app folder. Everything under the app folder is automatically initialized when the rails server comes up, so the module will be up and visible to the controllers in our application. In order to create our service:

we create a folder services:

mkdir services
cd services
touch search.rb

And we create our search class inside of search.rb.

module Search
  class SearchArticle
    def initialize(params)
      @params = params
      @title = Article.arel_table[:title]
      @content = Article.arel_table[:content]
    end

    def find
      if(@params.has_key?(:search))
        Article.where(@content.matches("%#{@params[:search]}%"))
               .or(Article.where(@title.matches("%#{@params[:search]}%")))
               .paginate(page: @params[:page],per_page:5)
      else
        Article.paginate(page: @params[:page],per_page:5)
      end
    end
  end
end

If params comes with :search key in it, it is coming from the search in the navbar.

For the search function, I could have used SQL syntax, but they are less legible, and harder to maintain later, could be bound to a specific DB-syntax AND you need to sanitize the input.

xkcd comic about sql injection

Therefore, it is considered a better practice to use Arel, a library used for constructing SQL queries. It is db-agnostic and has some built-in input cleansing. Besides, it is more consistent to the Rails syntax.

The find function look for articles that has the content of the params[:search], if the index is being called from the search bar. If the index is being called by a normal request to list all articles, then is returns the paginated Articles normally to the controller.

Articles_controller.rb change

In order to get the Articles fetched by the find function in our Search module, we should change the code in the index method to:

def index 
  @articles = Search::SearchArticle.new(params).find
end 

and there you have it! A simple search solution with no extra installations!

The next step would be to replace the ".where" calls in our search to use scopes from the Article model, but maybe this is something for a new post.

Any comments, suggestions, or tips are more than welcome!

Posted on Jun 24 by:

bruck1701 profile

Bruno Kümmel

@bruck1701

Freelance developer and researcher.

Discussion

markdown guide