A search can be a basic filter of your model, but creating a search that is efficient, fast, and actually gets the results you want to show is much more complex. Think of how you felt last time you searched for something on Bing instead of Google. A good algorithm can make all the difference.
Follow
I’ve seen many search tutorials for Rails 5 that use form_tag
, HTML forms, or form_for
, but there are not as many resources for simple_form
, even though simple_form
has become the gold standard of Rails forms. Besides, both form_for
and form_tag
are on their way to be deprecated after Rails 5.2 and replaced by form_with
.
Here’s a quick overview of how you can add basic search functionality to your app using the simple_form
gem!
I’m creating a basic rails application, without Webpacker, but using PostgreSQL as my database, because it will allow me to do case-insensitive search later on.
rails new simple_search --database=postgresql
rails db:create
p.s. Remember to create the database after you make your rails app because postgres doesn’t create a db by default
#Gemfile
gem 'simple_form'
gem 'bootstrap', '~> 4.2.1'
bundle install
p.s. Don’t forget the extra steps to setup Bootstrap & configure SimpleForm!
For this case, I’m going to create a simple one-model app to search through a list of cocktails that I added to my database via a JSON endpoint (thanks to github.com/teijo for the db!). I’ll go through the data import quickly. If you want a full article on seeding from a third party database, leave a message below!
First create the model with three attributes:
rails g model cocktail name glass preparation:text
rails db:migrate
Then add the seeds:
#db/seeds.rb
require 'json'
require 'open-uri'
url = "https://raw.githubusercontent.com/maltyeva/iba-cocktails/master/recipes.json"
Cocktail.delete_all if Rails.env.development?
cocktails = JSON.parse(open(url).read)
cocktails.each do |cocktail|
Cocktail.create!(name: cocktail["name"], glass: cocktail["glass"], preparation: cocktail["preparation"])
end
Next we’re adding a controller with a single action, to show a list of all the cocktails. Since our app has one page, I’m going to root the application to the cocktails index. There is no /cocktails
path in the application.
First, create the controller:
#terminal
rails g controller cocktails index
#app/controllers/cocktails_controller.rb
class CocktailsController < ApplicationController
def index
@cocktails = Cocktail.all
end
end
Then, add a home route for the index:
#config/routes.rb
root to: "cocktails#index"
Lastly, let’s add a simple front-end for the cocktail:
#app/views/cocktails/index.html.erb
<div class="container">
<div class="row">
<div class="col-xs-12 col-sm-8 offset-sm-2">
<h1>Cocktails</h1>
<% @cocktails.each do |cocktail| %>
<h3><%= cocktail.name %></h3>
<p><%= cocktail.preparation %></p>
<% end %>
</div>
</div>
</div>
Now it’s time to add the search functionality. I love simple_form because of… well… its simplicity, because you can make a form in just four lines. Check out the full documentation here.
#app/views/cocktails/index.html.erb
<%= simple_form_for :search, url: root_path, method: "GET", html: { class: 'form-inline' } do |f| %>
<%= f.input :name, input_html: {value: ""} %>
<%= f.submit "Search", class: "btn btn-primary" %>
<% end %>
:search
. Typically, we create the form for an instance variable when manipulating a model (i.e. @cocktail)
). If I try to call the parameter @search
, the application will tell me that the variable is undefined. By setting it as a symbol with the colon before :search
, I can use it as a key in my params hash.root_path
. The application only has one page (the root page of the cocktails index), so I need to tell my form to show the results on that page.:name
is going to be a key inside of my params hash. The value is the keyword the user searches for. In order to reset the form on page refresh, I am giving it a default value of blank.We’ll start with a super simple search — an exact keyword. This is a basic query, but not really useful, because it won’t find Margarita
if my search phrase is margarita
, or any part of the word.
But for now, let’s start with the basics:
class CocktailsController < ApplicationController
def index
@cocktails = Cocktail.all
@search = params["search"]
if @search.present?
@name = @search["name"]
@cocktails = Cocktail.where(name: @name)
end
end
end
The first thing I want to do is make my search case-insensitive, so Margarita and margarita return the same result.
Here, my simple active record query won’t do and I’ll need to add SQL. One of the reasons I started the app with PostgreSQL as my db adapter is to use ILIKE, which will give me the case-insensitive match:
@cocktails = Cocktail.where("name ILIKE ?", @name)
What about a partial search? What if we want Margarita
to return all margarita recipes like Passionfruit Margarita
or Strawberry Margarita
? We’ll need to add wildcard rules to make that work. Note that to add the % wildcard symbol, I need to interpolate my variable as a string.
@cocktails = Cocktail.where("name ILIKE ?", "%#{@name}%")
We’re done with our basic search. There’s lots of things I can add to this app, like searching through multiple columns, multiple tables, saving my search in the session, or using scopes to make the search more efficient. Let me know below if you’d like an article on advanced search!
GitHub Repository here.
Heroku Demo here.
Further reading:
How To Use PostgreSQL with Your Ruby on Rails Application on macOS
☞ https://morioh.com/p/09f1e543c728
Ruby For Beginners: Learn to Code with Ruby from Scratch
☞ http://class.learnstartup.net/p/GgFNUm4tP
Ruby Metaprogramming Is Even Cooler Than It Sounds
☞ https://morioh.com/p/0116c0dcd0fe/
Ruby on Rails Tutorial: Learn 6 Ruby on Rails SEO Techniques
☞ http://class.learnstartup.net/p/CZBBMrl-_
Ruby Algorithm Documentation with AsciiDoc and Knitr
☞ https://morioh.com/p/b104565ecc94/
Ruby Programming for Beginners
☞ http://class.learnstartup.net/p/vjkBeGb2M
#ruby-on-rails #ruby