Dynamic Autocomplete in Rails 6

This walk-through demonstrates how to build a data-driven autocomplete field in Rails 6 1from start to finish:

This is what we'll build

This sample feature will consist of a single input field that provides autocomplete suggestions from a database. The suggestions will update on each keystroke. Clicking on a suggested result will take the user to a Google search for the relevant name.

This tutorial assumes you have a fundamental understanding of Ruby on Rails, Javascript, and jQuery.

Table of Contents

  1. System check
  2. Create new application
  3. Create model and controller
  4. Seed database
  5. Create input field for autocomplete
  6. Install jQuery
  7. Add EasyAutocomplete plugin
  8. JSON payload
  9. Data-driven autocomplete field
  10. Handling the results

A. System check TOC ↑

(1)
Double check that you’re using Rails 6 with rails -v:
$ rails -v 
Rails 6.0.2.2
(2)
Also check your Ruby version with ruby -v:
$ ruby -v 
ruby 2.6.1p33 (2019-01-30 revision 66950)

You’ll need Ruby 2.5.0 or newer. Rails 6 will throw error messages if you don't have the correct Ruby version. I strongly recommend using a version manager like RVM or rbenv.

B. Create new application TOC ↑

(3)
Navigate to wherever you keep your development repos and create a new Rails app with a postgresql database 2:
$ rails new autocomplete --database=postgresql 
(4)
Navigate into the application directory and create a new database:
$ rails db:create 
(5)
Fire up a server with rails s and navigate to localhost:3000 in your web browser. You should see something like this:

C. Create model and controller TOC ↑

Next, we’ll create a bare bones Person model and People controller to handle the data that will eventually populate our autocomplete field.

(6)
Generate a new model called Person with first_name and last_name attributes:
$ rails g model Person first_name last_name
Running via Spring preloader in process 46385
invoke active_record
create db/migrate/20200428174911_create_people.rb
(7)
Run rails db:migrate to migrate the database:
$ rails db:migrate 
(8)
Generate a People controller with an index action (Rails automagically generates a corresponding view at app/views/pages/index.html.erb):
$ rails g controller People index 

D. Seed database TOC ↑

We’ll need to put a few placeholder people in the database for our autocomplete field to use later.

(9)
Create a handful of seeds for the database in db/seeds.rb:
db/seeds.rb
(10)
Seed database with rails db:seed:
$ rails db:seed 
(11)
Entering Person.count in the console should confirm that 4 people seeded into the database:
> Person.count
(0.6ms) SELECT COUNT(*) FROM "people"
=> 4

Our app now has data for the autocomplete.

E. Create input field for autocomplete TOC ↑

The next step is to get Rails to display a simple input field when a user visits the homepage in a browser (localhost:3000 in development).

(12)
Start by creating a root route to poeple#index in config/routes.rb:
config/routes.rb
(13)
Assuming your server is still running, if you open a browser and go to localhost:3000, your GET request will trigger the index action in app/controllers/people_controller.rb. In turn, this should render the placeholder view that Rails generated at app/views/people/index.html.erb when we created the controller earlier:
Here's what you should see when you visit localhost:3000

This is where we’ll put our form input.

(14)
Open app/views/people/index.html.erb. Replace, what’s there with a simple HTML search input, including a data-behavior attribute set to autocomplete. We’ll use this attribute later to identify this input as an autocomplete field:
app/views/people/index.html.erb

Note here that we are not generating an actual form — just an input. This is not normal. It is specific to this particular walk-through because (a) we’re going to use the autocomplete suggestions as links rather than submitting this form, and (b) handling form submissions is beyond the scope of what I want to cover here.

When you refresh your browser, you should now see a simple input at localhost:3000:

Easy peasy

F. Install jQuery TOC ↑

To help us make the autocomplete field interactive, we will add jQuery and a jQuery plugin called EasyAutocomplete. To less experienced Rails developers, this code may seem foreign. Don’t worry too much about understanding every line of code.

Installing jQuery is where Webpacker comes into play. Before Rails 6, you used to use a gem like jquery-rails and //= require jquery directions in a JS manifest file to add jQuery to a project with Sprockets.

No longer. With Webpacker, you add JS packages with Yarn, a JS package manager.

(15)
To add jQuery to our project, we run yarn add jquery:
$ yarn add jquery
yarn add v1.15.2
[1/4] 🔍 Resolving packages...
[2/4] 🚚 Fetching packages...
[3/4] 🔗 Linking dependencies...
[4/4] 🔨 Building fresh packages...

success Saved lockfile
success Saved 1 new dependency.
info Direct dependencies
﹂ jquery@3.5.0
info All dependencies
﹂ jquery@3.5.0
✨ Done in 6.63s.
(16)
Next, we need to add some code to config/webpack/environment.js to tell Webpacker to include jQuery when it compiles our JS. The final file should look like this:
config/webpack/environment.js

This code came from here.

(17)
One last step. We need to require jQuery in app/javascript/packs/application.js:
app/javascript/packs/application.js
(18)
To check that jQuery installed correctly, restart the server, refresh the page, and type $().jquery in the console:
“3.5.0” indicates that jQuery is installed and that we’re using the current version (you can check the current version here)

jQuery is now installed in our Rails app through Webpacker.

G. Add EasyAutocomplete plugin TOC ↑

Now that jQuery is installed, we’re going to add a plugin called EasyAutocomplete. EasyAutocomplete will do most of the heavy lifting for our autocomplete field.

(19)
Add EasyAutocomplete using Yarn:
$ yarn add easy-autocomplete
yarn add v1.15.2
[1/4] 🔍 Resolving packages...
[2/4] 🚚 Fetching packages...
[3/4] 🔗 Linking dependencies...
[4/4] 🔨 Building fresh packages...

success Saved lockfile
success Saved 1 new dependency.
info Direct dependencies
﹂ easy-autocomplete@1.3.5
info All dependencies
﹂ easy-autocomplete@1.3.5
✨ Done in 6.34s.

For future reference, I’d also recommend bookmarking https://yarnpkg.com/. This page allows you to easily search for Yarn packages. This is how we know to use easy-autocomplete in the yarn command above.

(20)
Now require easy-autocomplete in app/javascript/packs/application.js:
app/javascript/packs/application.js
(21)
EasyAutocomplete also includes some CSS files that we’ll need to require. Do this in app/assets/stylesheets/application.css:
app/assets/stylesheets/application.css

Note: if you’re using SCSS (this walkthrough is not), you would use this to import the necessary styles:

app/assets/stylesheets/application.scss
(22)
To check that it worked, reload the page and open the console. From here, we can set the options variable to an array of strings:
"When you find yourself in the thick of it, help yourself to a bit of what is all around you." 🎵
(23)
Now, we’re going to use jQuery to call the input by its data-behavior attribute and apply EasyAutocomplete's easyAutocomplete method with the options array we just defined:

There are a few details to notice here:

First, we are calling the form element by its data-behavior attribute with jQuery. To understand what’s happening here, check out this StackOverflow thread.

Second, we are passing the options variable we set above. This tells the autocomplete form to use the data values we gave it (e.g., John, Paul, George, and Ringo).

Third, you will notice that the formatting of the input field changes when we apply easyAutocomplete. This is EasyAutocomplete's CSS kicking in.

(24)
The autocomplete also should work now. Confirm that it does by typing something in the input:

You will notice that all names in the options variable appear regardless of what we type in the input. We’ll fix this later when we start pulling in data dynamically.

Finally, we will move what we just did into code. This is a two step process: first we create a new JS file; and second, we import it with Webpacker.

(25)
Create a file called app/javascripts/packs/people.js and add the following code:
app/javascript/packs/people.js

Here, jQuery is telling the browser to: (1) wait until turbolinks finishes loading, (2) apply the easyAutocomplete method to all inputs where the data-behavior attribute equals autocomplete, (3) and pass it the data stored in the option variable.

(26)
Import this file via Webpacker so it gets loaded with the app:
app/javascript/packs/application.js
(27)
Now, when you refresh the page, the autocomplete loads with the data we hard-coded into the options variable in app/javascripts/packs/people.js:

H. JSON payload TOC ↑

Our next goal is to prepare JSON so we can populate the autocomplete from the database.

(28)
First, we will create a route for EasyAutocomplete to fetch JSON data via GET request:
config/routes.js
(29)
We also need a corresponding controller action to handle the requests. We’ll start with a placeholder search action and apply a private before filter to force Rails to deliver JSON payloads:
app/controllers/people_controller.rb
(30)
Then, we’ll define an instance variable to hold the results of a postgres fuzzy query 3 on Person.first_name or Person.last_name:
app/controllers/people_controller.rb

This search returns results based on params[:q], which is passed in through a variable in the GET request. (e.g., localhost:3000/people/search?q=lick) This example GET request will search for any record containing lick (not case sensitive).

(31)
Next, we need a view to render the JSON. Create app/views/people/search.json.jbuilder:
app/views/people/search.json.jbuilder

This code will return a JSON array of strings. The strings will be in last_name, first_name format.

(32)
Now, when you visit localhost:3000?q=lick, you should see this:
JSON displayed in the brower

I. Data-driven autocomplete field TOC ↑

Now we will populate the autocomplete field from our database. EasyAutocomplete will do all the heavy lifting for us.

(33)
To get the job done, we'll replace the existing code in app/javascript/packs/people.js with this:
app/javascript/packs/people.js

This code replaces the options variable with an easyAutocomplete function that will (1) pull data via JSON using the controller action and view we created above, and (2) grab the name part of the JSON.

Notice how jQuery and EasyAutocomplete have abstracted all of the AJAX details? You don't need to worry about listening for keystrokes.

(34)
Now, when you reload the page, the autocomplete results will return data from the database:

J. Handling the results TOC ↑

One last detail: what do we do when the user selects a result?

Let’s look at one obvious and simple option: directing the user to a Google search for the relevant person’s name.

(35)
Start by modifying app/views/people/search.json.jbuilder to include a link to Google search results:
app/views/people/search.json.jbuilder

Remember that this link needs to be URI-encoded, hence the call to CGI.escape.

(36)
Confirm that the link is included with the JSON payload:
(37)
Finally, pass the links via the option variable's template attribute in app/javascript/packs/people.js 4:
app/javascript/packs/people.js
(38)
Autocomplete suggestions should now be clickable links::
(39)
These links will take you directly to the Google search results for the relevant person:

Congrats - you've reached the end of this tutorial. From here, you can easy modify easyAutocomplete to handle a number of useful tasks like updating database records, returning information, or reordering records on a page.

1 This tutorial is unique to Rails 6. You can achieve similarly functionality in earlier versions, but the process would be different. Specifically, Rails 6 dramatically changed the process of working with JS files, libraries, and plugins. It transitioned from a Sprockets-based asset pipeline to Webpacker. This means that JS files that used to live at app/assets/javascript are now managed with Webpacker and live at app/javascript. To read more about the change from Sprockets to Webpacker, I recommend Prathamesh Sonpatki’s excellent article called Understanding Webpacker in Rails 6.
2 You'll need PostgreSQL to run the ILIKE fuzzy query in this tutorial.
3 According to the PostgreSQL documentation, ILIKE "is not in the SQL standard but is a PostgreSQL extension." ILIKE searches will not work in SQLite. There are a variety of potential workarounds, including one here.
4 This code comes verbatim from EasyAutocomplete's examples.