Autocomplete with Rails & Turbo

Update: February 22nd 2023 – Autocomplete for categories is now implemented

Ruby on Rails LogoTurbo provides the technology in a Ruby on Rails web application to interact more dynamically with users. How does this make it easier to enter the author of a quotation? There are many tutorials on the web for implementing Autocomplete, for example the YouTube video Ruby on Rails #60 Hotwire Turbo Streams Autocomplete Search by Yaroslav Shmarov. I had a little trouble adopting what I found into my real-world web application. But on the second try I was able to put the pieces together better, and that’s what I want to show here. Maybe it will help other beginners a little bit?

Screenshot-Ausschnitt zum AutocompleteAuthor’s last name in zitat-service.de should be automatically extended if the input is unique and also be clickable from the list. The first implementation was done with an additional form for the author’s name, session variables to synchronize with the form of the other fields of the citation. This was not rails-like at all, error-prone and had the basic problem that when selecting from the list, the previously made entries were lost when clicking on the link.

2nd implementation

The implementation is explained here in a simplified way. The complete source code and the possibility to set up a Docker test instance can be found at github.com/muhme/quote.

Koen Handekyn’s comment in Syntax for returning multiple turbo streams is a helpful hint on how to trigger multiple turbo stream updates at the same time with one render call. Other pieces of the puzzle are:

  • in the partial _search_author.html.erb in a turbo frame there is the hidden field quotation[author_id] for the ID of the selected author ID Quotation.author_id
  • this partial also contains the visible input field author, in which the first letters of the author’s name are entered and in which the selected author is displayed
  • by oninput: "this.form.requestSubmit()" an HTML event is triggered with the input of each letter and a turbo stream request is sent to the controller
  • with the second partial _search_author_results.html.erb div#authors_list is filled with a list of up to ten authors found, or remains empty and disappears with it
  • the create() und update() methods in the QuotationsController use the :commit parameter of the submit button to distinguish whether the request comes from submitting the entire form or from entering text in the author field
  • if exactly one author is found by entering the first letters, then two updates are triggered:
    • in the partial _search_author.html.erb the hidden field quotation[author_id] is set and the author field is filled with name, firstname and description
    • the second partial _search_author_results.html.erb is passed an empty array and thus the list disappears completely
    • using CSS, a 5-second animation is played starting from green to indicate that the input was successful
  • if more than one author is found for the first letters, then the list of up to 10 authors is updated with a Turbo Stream update
  • the entries in the list are ordinary HTML links <li>, the GET requests when clicking on a link call the controller method author_selected(), which then also triggers the two updates as for a found exactly one author
  • if the form for creating a quotation is sent before exactly one author has been selected, then author is set to Unknown in case of creating a new quotation and a hint is given: “author … was not changed” in case of changing a quotation

Here the main section from QuotationsController.update():

    if params[:commit]
      logger.debug { "update() quotation #{@quotation.inspect}" }
      if @quotation.update(quotation_params)
        hint = 'The quote has been updated.'
        # give hint, e.g. if author autocomplete field is not completed
        hint << " The author „#{Author.find(@quotation.author_id).name}” was not changed." if @authors.count != 1
        return redirect_to @quotation, notice: hint
      else
        render :edit, status: :unprocessable_entity
      end
    else
      turbo_author(old_author_id)
      turbo_category(nil, old_category_ids, Category.check(@quotation))
      render turbo_stream: turbo_stream_do_actions
    end


With this solution, session variables are no longer necessary. The selected author is in the hidden field with his ID. The code for creating and updating does not differ significantly. Clicking on a link in the list of found authors triggers a GET request. Only the two turbo frames parts are updated within the page, other form input stands remaining.

Update

Meanwhile, the input of the categories has also received an autocomplete :)

Screenshot-Ausschnitt zum Autocomplete der Kategorien