Впровадження динамічного пошуку за допомогою Turbo Frames у Ruby on Rails 7

У веб-розробці важливим аспектом є забезпечення зручного та ефективного пошуку, який позитивно впливає на досвід користувачів. Для мого нещодавнього проєкту на AVIMBU я реалізував функціональність пошуку, яка запитує як локальні дані, так і зовнішні API. Це було досягнуто за допомогою TurboFrames від Hotwire у веб-додатку Ruby on Rails 7.

У цьому блозі я розповім, як реалізувати динамічну панель пошуку, яка отримує дані як локально, так і з зовнішнього джерела (наприклад, API), і все це без перезавантаження сторінки завдяки TurboFrames.

Розділ 1: Початкова настройка проєкту

Оскільки ми будуємо динамічний пошук для проєкту на Ruby on Rails 7, потрібно виконати кілька кроків для налаштування початкового стану проєкту. Для цього виконайте таку команду:

rails new dynamic_search - css=bootstrap -j=esbuild

Ця команда створює проєкт Rails з попередньо налаштованою останньою версією Bootstrap (це необов'язково, але забезпечує кращу стилізацію). Для пакування JavaScript ми використаємо esbuild, який є простим і зручним у використанні. Ви також можете вибрати webpacker або більш сучасний підхід import_maps, але на даний момент найбільш зручним є використання esbuild через його простоту інтеграції з Bootstrap.

Після створення проєкту перейдіть до директорії проєкту і запустіть його за допомогою:

bin/dev

Тепер ваш додаток має бути доступний за адресою localhost:3000.

Ця настройка вже включає Turbo, що є ключовим компонентом для динамічних оновлень сторінки. Перевірте його наявність у файлі app/javascript/application.js:

import "@hotwired/turbo-rails"

Розділ 2: Реалізація функціоналу пошуку

Почнімо з того, що створимо модель Project з атрибутами name і description. Для цього використаємо scaffolding в Rails для швидкого налаштування моделі та операцій CRUD:

rails g scaffold project name description

Після цього ви зможете створювати, оновлювати і видаляти моделі проєктів у вашому додатку.

Тепер давайте додамо функціональність пошуку: я вибрав дію index контролера ProjectsController. Тому я додав форму пошуку до відповідного шаблону виду:

<%= form_with url: projects_path, method: :get, data: { turbo_frame: 'project_listings' } do |form| %>  
 <%= form.search_field :q, class: 'form-control', placeholder: "Пошук за назвою або описом" %>  
<% end %>

Це додає просту панель пошуку, яка дозволяє надіслати запит типу GET до дії projects#index. Параметр пошукового запиту передається через параметр q. Я адаптував дію index наступним чином:

def index  
 @projects = Project.all  
 @search = params[:q]  
 @results = if @search.blank?  
 []  
 else  
 Project.where('name LIKE :search OR description LIKE :search', search: "%#{@search}%")  
 end  
end

Після цієї зміни я отримав дві нові змінні, доступні в поданні index. @search містить пошуковий запит, а @results зберігає масив знайдених моделей Project. Для демонстрації я використав простий запит з LIKE для пошуку в базі даних. Це можна і потрібно покращити, використовуючи більш ефективні рішення, такі як pg_search.

Файл app/views/projects/index.html.erb також потребує деяких змін:

<%= turbo_frame_tag 'project_listings' do %>  
 <% unless @results.empty? %>  
 <% @results&.each do |project| %>  

Name: <%= project.name %> - Description: <%= project.description %>
    <% end %>    <% end %>   <% end %> ```  Я обгорнув результати пошуку в `turbo_frame_tag` з таким самим ID, до якого форма надсилає запит.
Отже, Turbo досить розумний, щоб визначити, яка частина DOM має бути оновлена, і оновлює лише `project_listings` у `turbo_frame_tag`, щоб уникнути повного перезавантаження сторінки.

Тепер, якщо пошук по локальній базі даних повертає результати, ці результати відображаються без необхідності перезавантаження сторінки.

## **Інтеграція зовнішнього пошуку**

Перша частина пошуку тепер реалізована. Тепер давайте додамо функціональність зовнішнього пошуку, щоб інтегрувати зовнішнє API для отримання результатів пошуку на випадок, якщо локальний пошук не дасть бажаних результатів. Для цього я використовую концепцію лінійного завантаження `turbo_frame_tags`. Це дозволяє мені активувати / завантажувати джерело дії TurboFrame, як тільки цей фрейм стає видимим у видимій частині екрану.

Щоб це працювало, потрібно змінити три речі. По-перше, додати `turbo_frame_tag` у перший TurboFrame результатів пошуку так, щоб він завжди перезавантажувався, коли перший TurboFrame буде перезавантажено. Переконайтеся, що опція `src` вказує на зовнішню дію пошуку, яку ми зараз додамо. Для цього додайте наступний маршрут до вашого файлу `routes.rb`:

get 'projects/externalsearchproject' => 'projects#externalsearchproject', as: :loadexternalsearch_project
```

Також додайте цей метод (дію) до контролера проектів:

def external_search_project  
 return if params[:q].blank? || params[:avoid_search] == "true"  

 sleep 1.0 # імітація зовнішнього запиту  
 @external_search_result = [Project.new(name: "External Project", description: "Loaded from wherever")]  
end

Це приклад реалізації, де запит до зовнішнього пошуку імітується за допомогою sleep. Також дія пропускається, якщо не передано пошуковий запит або якщо вказано параметр avoid_search.

Нарешті, додайте зовнішній пошуковий TurboFrame всередину першого TurboFrame, щоб забезпечити його завантаження разом з регулярним оновленням пошуку. Передаючи пошуковий запит і встановлюючи параметр avoid_search залежно від того, чи є результати локального пошуку, зовнішній пошук буде запускатися тільки в разі, якщо локальний пошук не дасть результатів.

<%= turbo_frame_tag 'project_listings' do %>  
 <% unless @results.empty? %>  
 <% @results&.each do |project| %>  

Name: <%= project.name %> - Description: <%= project.description %>
    <% end %>    <% end %>    <%= turbo_frame_tag 'external_search_result', src: load_external_search_project_path(q: @search, avoid_search: @results.any?), loading: :lazy do %>    <% end %>   <% end %> ```  Також, щоб оновити `external_search_result` TurboFrame, додайте цей шаблон виду до файлу `app/views/projects/external_search_project.html.erb`. Це буде відображено при зовнішньому пошуку і виведе результати пошуку, якщо вони доступні. 

<%= turboframetag "externalsearchresult" do %>
<% @externalsearchresult&.each do |project| %>
Name: <%= project.name %> - Description: <%= project.description %>
<% end %> <% end %> ``` Ось і все. Тепер ви маєте контроль над поведінкою пошуку. Це дозволяє адаптувати реалізацію пошуку до ваших потреб. Остаточний продукт має виглядати так: pic Фінальне рішення

Розділ 4: Покращення: Автоматичне надсилання форми

Покращіть досвід пошуку за допомогою автоматичного надсилання форми. Використовуйте контролер Stimulus для надсилання форми після короткої затримки.
Прикріпіть контролер до форми наступним чином:

import { Controller } from "@hotwired/stimulus"  

export default class extends Controller {  

 search() {  
 console.log("Search Submit")  
 clearTimeout(this.timeout)  
 this.timeout = setTimeout(() => {  
 this.element.requestSubmit()  
 }, 300)  
 }  
}

Також потрібно прикріпити цей контролер до форми пошуку ось так:

<%= form_with url: projects_path, method: :get, data: { controller: "formsubmission", turbo_frame: 'project_listings' } do |form| %>  
 <%= form.search_field :q, class: 'form-control', placeholder: "Пошук за назвою чи описом", data: { action: "input->formsubmission#search" } %>  
<% end %>

Тепер після цього додавання, форма автоматично надсилається через 300 мс.

Висновок

Використання TurboFrames таким чином є ефективним рішенням для динамічного пошуку в додатках на Rails.

Для додаткової інформації про TurboFrames і Hotwire, відвідайте документацію Turbo.

Якщо хочете дізнатися більше про мене, не соромтеся відвідати мою домашню сторінку або стежити за мною в Twitter тут.

Додаткові ресурси

Код, використаний у цьому посібнику, можна знайти тут.

Декілька додаткових ресурсів:

Перекладено з: Implementing Dynamic Search with Turbo Frames in Ruby on Rails 7