Build a Filtered Search From Scratch for Your Rails 5 Application

When your search algorithm is good, no one will notice, but when a search algorithm is bad… it’s really bad

Maria Schuessler
Jul 9, 2019 · 5 min read
Photo by Sergey Zolkin on Unsplash

Adding a search to any application is always harder than it looks. The thing is, when your search algorithm is good, no one will notice, but when a search algorithm is bad… it’s really bad. I’m thinking of this particular restaurant app in Shanghai, and how it’s impossible to find a good brunch near you.

There are several ways to build search and filters in a Rails 5 application. I’m in the process of writing an article that integrates filters with named scopes, but I wanted to create an article using acts-as-taggable first. That’s because to me, this is really the most important part of the gem: how do you find completely unrelated information using a specific search criteria?

I’ll start with using my Cocktail app from the Simple Search tutorial I wrote on Medium and add tags to that. If you want to start with my boilerplate, you can find it on GitHub.

Using tags is a powerful way to connect unrelated data models in your Rails app. In this example, we will compare both the type of cocktail (do you serve it before dinner, with dinner, after, etc.) and the taste notes (is the cocktail sweet, sour, fruity?). Both of those things are related to the overall description of the cocktail, but they are independent of each other.

Photo by Thought Catalog on Unsplash

1. Add Tags Into Your Application

I’ll start by installing the gem and runnings its migrations.

#Gemfile
gem 'acts-as-taggable-on', '~> 6.0'
#Terminal
rails acts_as_taggable_on_engine:install:migrations
rails db:migrate

2. Add Tags to Cocktails

Once the tags are in the application, I can add them to my Cocktail model. Typically, Acts As Taggable adds the tags into a generic ‘tag_list,’ but for this app I’m creating two filters — one for the general category of the cocktail (“After Dinner,” “Before Dinner,” etc.) and one for the taste (“sweet,” “sour,” “citrusy”).

#app/models/cocktail.rb acts_as_taggable_on :tags (the typical way of adding tags, you don’t need this line if you are adding the next two lines)acts_as_taggable_on :categories
acts_as_taggable_on :flavors

Now, instead of adding tags to the tag_list, I will be adding two sets of tags — to the category_list and the flavor_list.

Note that there are no tagging columns in the cocktails table: the data lives inside the migrations generated by the gem.


3. Add Cocktail Seeds With Tags

I am seeding my database with cocktails from a JSON list I used for the search tutorial. In order to add the categories, I am adding the drink ‘category’ from the JSON list and randomly attributing flavors to the cocktails to save time.

require 'json'
require 'open-uri'
url = "https://raw.githubusercontent.com/maltyeva/iba-cocktails/master/recipes.json"Cocktail.delete_all if Rails.env.development?flavors = ['sweet', 'citrusy’, 'sour', 'strong', 'bitter']cocktails = JSON.parse(open(url).read)cocktails.each do |cocktail|
Cocktail.create!(name: cocktail["name"],
glass: cocktail["glass"],
preparation: cocktail["preparation"],
category_list: cocktail["category"],
flavor_list: flavors.sample)
end

For this example, there is only one tag per cocktail, but the great thing about acts-as-taggable is that you can add as many items to the list as you’d like.

At this point, you may feel like you need a cocktail. Photo by kyryll ushakov on Unsplash

4. Add the Filter Form

First, let’s understand the form from the front end. I’m using the basic form from the Simple Form tutorial, but let’s break down the input step-by-step:

  • :flavors — this is the name of the field I will use on the back end. It will correspond to params[:search][:flavors] inside the controller
  • label: “Category—the label field will use a custom tag, but leaving it out will just default to the field name (in this case Flavors)
  • collection: $categories — this collection will pull from the Cocktail model and return a global variable containing an array of categories corresponding to my database.
  • as: :check_boxes — this is totally optional, but it sets my filter as a multi-select checkbox, as opposed to the default dropdown menu
#app/views/cocktails/index.html.erb<%= simple_form_for :search, url: root_path, method: "GET" do |f| %>
<%= f.input :flavors, label: "Category", collection: $categories, as: :check_boxes %>
<%= f.input :strengths, label: "Flavor", collection: $flavors, as: :check_boxes %>
<%= f.submit "Search", class: "btn btn-primary" %>
<%= link_to "Reset", root_path %>
<% end %>
#app/models/cocktail.rb$flavors = ['sweet', 'citrusy', 'sour', 'strong', 'bitter']
$categories = ["Before Dinner Cocktail", "All Day Cocktail", "Longdrink", "Sparkling Cocktail", "After Dinner Cocktail", "Hot Drink"]

Once my search form is filled out, I want to send the results to the back end and return the results.


5. Add the Filtering Mechanism Based on Tags

Because I am filtering both on the flavors and the strengths, I want to account for the user filling out either both or one of the parts of the form (I suppose they could also fill out none of the form and submit it as empty, so we will want to take care of that scenario too).

The results will come as two arrays, something like [“Sour,” “Fruity”] and [“Strong”], or occasionally, [“ ”]. To combine these two arrays, I will concatenate and flatten them, and then reject any accidental blank values.

#app/controllers/cocktails_controller.rbdef index
if params["search"]
@filter = params["search"]["flavors"].concat(params["search"]["strengths"]).flatten.reject(&:blank?)
@cocktails = @filter.empty? ? Cocktail.all : Cocktail.all.tagged_with(@filter, any: true)
else
@cocktails = Cocktail.all
end
end

6. That’s It!

The basic filter is complete, but there are still some limitations. Right now, it returns a match of any of the present parameters, which doesn’t help me narrow down the search as much as I want it to.

I also need to refresh the page with every search.

What if we could create a dynamic filter that refreshes the results automatically and limits them to the precise search combination I am looking for?

For that, we need to introduce AJAX and bolster our filtering mechanism.

GitHub Repository here.

Heroku Demo here.

Better Programming

Advice for programmers.

Maria Schuessler

Written by

Full-Stack Developer \\ Traveler \\ Hot Sauce Entrepreneur \\ Le Wagon Mentor \\ Shanghai-based \\ mariacodes.io

Better Programming

Advice for programmers.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade