Create a Simple Search Form in Rails

  • rails, ruby, gems, tutorials
  • 15 Comments

This is an easy tutorial for implementing a keyword search form in a Rails app. Often found in a website's navbar, the search form enhances usability and gives a website or app a more polished feel.

I'm going to implement the search form on a simple recipe-sharing app with a Recipes Controller and Recipe Model. The search form will take the terms entered by a user and look for matches in the recipe titles, ingredients, and cooking instructions. The app will display all of the recipes that contain those keywords or inform the user that there aren't any recipes that contain those terms.

I want to have the search available on every page's navbar so I am going to put the search form in the layout view found in app/views/layouts/application.html.erb.

<%= form_tag(recipes_path, :method => "get", id: "search-form") do %>
    <%= text_field_tag :search, params[:search], placeholder: "Search Recipes" %>
    <%= submit_tag "Search", :name => nil %>
<% end %>

I am using form_tag, a generic form helper, instead of form_for because the latter is intended for situations when you want to edit a model's attributes, but that's not what I am doing here. I just want to search the recipes index and form_tag allows me to do that. After the form_tag I pass it recipes_path, the action I want it to go to along with :method => 'get'. I have to declare the get method because by default the form_tag submits a post request and that's not the functionality I'm looking for.

In the text_field_tag I pass params[:search] as a value and this allows the search query to persist in the search box when the results page loads. This is not critical to the search functionality but it adds a little sugar to the user experience.

Finally, for the submit_tag I want to remove the default name of the search form name="commit" because this appears in the url with every search: www.totaste.org/recipes?search=pancetta&commit=Search. By declaring :name => nil, I end up with prettier urls like www.totaste.org/recipes?search=pancetta

To finish this up I need to put a search method in the model and declare which fields the method should search for matching queries. In /app/models/recipe.rb I put the following code that looks for matches in the name, ingredients, and cooking instructions fields in the database. Note: The LIKE syntax is used for MySQL, but if you are deploying to Heroku or another platform that uses PostgreSQL use the ILIKE syntax instead.

def self.search(search)
  where("name LIKE ? OR ingredients LIKE ? OR cooking_instructions LIKE ?", "%#{search}%", "%#{search}%", "%#{search}%", "%#{search}%") 
end

And in the Recipes Controller, app/controllers/recipes_controller.rb, I will use the following code to display matching recipes in descending order from the time they were created.

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

One last thing—if there aren't any recipes that match the search query I want to display a warning on the index page. In /app/views/recipes/index.html.erb I put the following code to alert the user that there aren't any matching recipes. If I skip this step, a user might think something went wrong with the search. Again, it's not a critical function of the search but it makes the user experience a little bit nicer.

<% if @recipes.blank? %>
  <h4>There are no recipes containing the term <%= params[:search] %>.</h4>
<% end %>