22
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Qiita株式会社Advent Calendar 2022

Day 5

Hotwireで検索機能を作ってみる

Last updated at Posted at 2022-12-04

Rails7 でデフォルトとなったHotwireを使って、検索機能を作ってみようと思います。
今回の完成形はこんな感じです。jsは使いません!
eee.gif

Hotwireとは

モダンなWebアプリケーションを構築するための代替アプローチです。
シングルページアプリケーション(以下SPA)を作る時に React や Vue が選択肢に上がると思いますが、それとは別のアプローチを取ります。
以下のような特徴があります。

Hotwire は複数のライブラリから構成されている

HotwireはHTML OVER THE WIREの略称で、以下の複数のライブラリをまとめた概念になります。

  • Turbo
    • Hotwire の核となるライブラリ
    • Turbolinks の後継者で3つの技術で構成されている
      • Turbo Drive
      • Turbo Frames
      • Turbo Streams
    • 今回は各々の技術の違いを意識しないで機能を作っていきます
  • Stimulus
    • 控えめなJavascriptフレームワーク
    • HTML のレンダリングには関与しない
    • 今回はほぼ使わないです
  • Strada
    • モバイルアプリ作成用のフレームワーク
    • 近日公開予定

HTML を中心に置いている

SPAを作るときには、バックエンドから JSON でデータを渡してフロントエンドで HTML を構築することが多いと思います。
Hotwire では JSON ではなく HTML を返します。普通の Rails アプリと同じですが、フロントにいる Turbo が部分書き換えもしてくれるのでSPAのような UX を実現できます。
また HTML を返すので既に構築済みの Rails アプリに Hotwire を適応することも簡単にできます。必要な箇所だけに導入できることは大きなメリットになります。

試してみる

それでは検索機能を作っていきます。
検索ボタンをクリックしなくても、フォームに入力した値でユーザーリストをフィルタリングします。
Hotwire を使うと js を書かずに HTML だけで表現できます。

Hotwire を使わずに作る

まずは Hotwire を使わずに構築していきます。
Railsアプリ作成して、コントローラー、モデルを作ります。

$ rails new sample-hotwire -j esbuild
$ cd sample-hotwire

$ bin/rails g controller users index
$ bin/rails g model user name:string
$ bin/rails db:migrate

リスト表示するユーザーのデータを作ります。

db/seed.rb
User.create([
  { name: "Alice" },
  { name: "Bob" },
  { name: "Carol" },
])
$ bin/rails db:seed

あとはコントローラーでデータ取得、ビューを作ります。

app/controllers/users_controller.rb
def index
  @users = if params[:name].present?
             User.where('name LIKE ?', "%#{params[:name]}%")
           else
             User.all
           end
end
app/views/users/index.html.erb
<%= form_with url: users_index_path, method: :get do |form| %>
  <%= form.text_field :name, autocomplete: :off %>
  <%= form.submit :search %>
<% end %>

<% @users.each do |user| %>
  <p>
    <strong>name:</strong>
    <%= user.name %>
  </p>
<% end %>

ここで動作確認します。
http://localhost:3000/users/index

$ bin/dev

search ボタンをクリックして、リストをフィルタリングできました。
ここまでは通常の Rails アプリと同じです。
ccc.gif

Hotwire を適用する

次に Hotwire を適用していきます。
まずは Stimulus の controller を作ります。

$ bin/rails g stimulus form

Stimulus のコントローラーで処理を作っていくこともできますが、今回は submit メソッドのみ作成します。
通常のsubmitでは Turbo がリクエストをインターセプトしないためrequestSubmitに変更しています。

app/javascript/controllers/form_controller.js
export default class extends Controller {
  connect() {
  }
  submit() {
    this.element.requestSubmit()
  }
}

最後にビューを変更します。

  • form_with の変更
    • data-turbo-frame に書き換えたい turbo-frame を指定(user-list
    • data-controller を指定(form
    • data-action を指定(input->form#submit
  • list の変更
    • listのみ書き換えたいためturbo_frame_tagで囲みます
      • <turbo-frame></turbo-frame>に変換されます
app/views/users/index.html.erb
<%= form_with url: users_index_path,
              method: :get,
              html: { data: {
                turbo_frame: "user-list",
                controller: "form",
                action: "input->form#submit",
              }} do |form| %>
  <%= form.text_field :name, autocomplete: :off %>
  <%= form.submit :search %>
<% end %>

<%= turbo_frame_tag "user-list" do %>
  <% @users.each do |user| %>
    <p>
      <strong>name:</strong>
      <%= user.name %>
    </p>
  <% end %>
<% end %>

これだけでフォームに入力したタイミングで、リストのフィルタリングができます。

eee.gif

まとめ

js を使わずにSPAのような UX を実現できました。
React や Vue と比べればやれることの幅や、コードの書き心地はよくないかもしれないです。
ただ小中規模のアプリで少人数のチームであれば、バックエンドに処理を寄せることにより開発スピードとUXの両方を得ることができると思います。
学習コストもそこまで高くないので Turbo と Stimulus でどこまでやれるか試していきたいと思います。

Reference

22
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
22
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?