たった10行で簡単に絞込みセレクトボックス（Javascript無し）

# 親子セレクトボックス

1番目のセレクトボックス（ドロップダウンリスト）で選択すると、2番目のセレクトボックスで選択肢が絞り込まれる、あれです。（正式にはなんというのかわかりませんでした。ドリルダウン？）（とりあえず、１番目を親セレクタ、２番目を子セレクタと呼びます）

例としては、親セレクタで都道府県名を選択すると、その都道府県内の市町村に子セレクタの選択肢が絞り込まれるというありがちなパターンです。

その親子セレクタが、Rails ７ で採用された [Turbo](https://turbo.hotwired.dev/)（[Hotwire](https://hotwired.dev/)） であれば、**ものすごく簡単**に実装できます。トータルで10行くらいです。Javascript は使いません（HTML と Javascript の知識は必要。）
環境を Rails 7.0.1 と Ruby 3.1 として、例を作成しながら説明します。
なお、Turbo 自体は Rails に依存しません。→[【Hotwire】HTMLだけで分かる Turbo 🚜](https://qiita.com/ShinKano/items/018e32d25f40bce201de)

![window.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/69840/5f4370ca-d3ce-b584-3655-824fc6187e7f.gif)

# 下準備
```shell-session
rails new drill_down_example
cd drill_down_example
rails g scaffold movie name:string
rails g model character name:string movie:references
```

```ruby:app/models/movie.rb
class Movie < ApplicationRecord
  has_many :characters
end
```
Movie（親モデル）とCharacter（子モデル）の 1 対 N モデルで関連づけ（アソシエーション）を設定します。


```ruby:db/seeds.rb
movies = Movie.create([{ name: "Star Wars" }, { name: "Lord of the Rings" }, { name: "Batman"}])
Character.create(name: "Alfred Pennyworth", movie: movies.third)
Character.create(name: "Aragorn II", movie: movies.second)
Character.create(name: "Boromir", movie: movies.second)
Character.create(name: "Bruce Wayne", movie: movies.third)
Character.create(name: "Catwoman", movie: movies.third)
Character.create(name: "Frodo Baggins", movie: movies.second)
Character.create(name: "Leia", movie: movies.first)
Character.create(name: "Luke", movie: movies.first)
Character.create(name: "Robin", movie: movies.third)
```
サンプルデータを準備します。

```shell-session
rails db:create; rails db:migrate; rails db:seed
```

ここまでが下準備

# 本題

## view: index
scaffold で作られた`index.html.erb`は次のように書き換え。

```erb:app/views/movies/index.html.erb
<h1>Movies</h1>

<div id="movies">
  <%= select :movie, :name, @movies.map{|r| [r.name, movie_path(r.id)]}, {}, {onchange:"Turbo.visit(value)"} %> <%# ① %>
  <%= turbo_frame_tag 'children' do %> <%# ② %>
    <%= collection_select :movie, :name, @characters, :id, :name %> <%# ③ %>
  <% end %> <%# ④ %>
</div>
```

①：親セレクタの部分です。`onchange:"Turbo.visit(value)"`がポイントです。`location.href=value`ではなく[Turbo.visit](https://turbo.hotwired.dev/reference/drive#turbo.visit)でTurboを発火させます。（Turbo はリンクをクリックするかフォームを submit するかで発火するのが原則らしい。）
なので、value の値は`Path(URL)`です。

②：子セレクタの受け皿です。Turbo の要。’children’ の部分が`Identifier`になっていて、個々の`<turbo-frame>`が区別されます（ここでは一つだけしかないけど）
ここが、アクションによって差し替えられたりなどされます。

③：子セレクタの初期状態です。実際には、空配列か disabled にするのでしょうが、例なので、初期状態では全てのデータが見られるようにしています。理屈上はセレクタボックスですらなくてもよいです。

④：受け皿だけでよければ、ブロックではない`<%= turbo_frame_tag "children" %>`でもよいです。


## view: show
scaffold で作られた`show.html.erb`を次のように書き換え。パーシャルでやってもよい（本来ならそっちかも）。ここでは、「あるデータを表示したら、それに関連したデータも表示する」という思考に直裁的なので、GETメソッドの典型例 show を使いました。ルーティングでいうと

| Helper | HTTP Verb | Path | Controller#Action |
|--------|-----------|------|-------------------|
|movie_path|GET|/movies/:id(.:format)|movies#show|


```erb:app/views/movies/show.html.erb
<%= turbo_stream.update 'children' do %>　<%# ⑤ %>
  <%= collection_select(:movie, :name, @characters, :id, :name) %>　<%# ⑥ %>
<% end %> <%# ⑦ %>
```
⑤：`children`を手がかりに、②の turbo-frame の内容を`update`するというアクションになります。Turbo の要です。
[【図解】Hotwire : Turbo 7つのアクション](https://qiita.com/NaokiIshimura/items/d91b10587881107b5f39)が詳しくてわかりやすいです。

⑥：子セレクタの部分です。select タグは`form_with`の中で使うのが通常だろうと思います。が、この時点ではモデルと紐付けされていません。なので`f.collection_select`は使えません。
ここで関連付けされたモデルのデータで子セレクタを構成しています。

⑦：アクションによってはブロックでなくてもよい（例えば`turbo_stream.remove`）

## controller

scaffold で作成された`movies_controller.rb`を次のように書き換え。

```ruby:app/controllers/movies_controller.rb
class MoviesController < ApplicationController
  before_action :set_movie, only: %i[ show ]

  def index
    @movies = Movie.all
    @characters = Character.all # ⑧
  end

  def show
    render layout: false, content_type: "text/vnd.turbo-stream.html" # ⑨
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_movie
      @movie = Movie.find(params[:id])
      @characters = @movie.characters # ⑩
    end

    # Only allow a list of trusted parameters through.
    def movie_params
      params.require(:movie).permit(:name)
    end
end

```

⑧：初期状態の設計方針によってはいらない。

⑨：`<turbo-frame>`部分だけ置き換えられるようなレスポンスになります。これがないと、ブラウザは show.html に丸ごと置き換えてしまいます。Trubo のポイント。

⑩：show.html.erb に渡すためのデータを構成してます

### 最後に、`rails assets:precompile`して、`rails s`して、`http://localhost:3000/movies`で確認。

# HTMLソース

初期状態のソースはこうなります

```html
<body>
  <h1>Movies</h1>

  <div id="movies">
    <select onchange="Turbo.visit(value)" name="movie[name]" id="movie_name">
      <option value="/movies/1">Star Wars</option>
      <option value="/movies/2">Lord of the Rings</option>
      <option value="/movies/3">Batman</option>
    </select>

    <turbo-frame id="children">
      <select name="movie[name]" id="movie_name">
        <option value="1">Alfred Pennyworth</option>
        <option value="2">Aragorn II</option>
        <option value="3">Boromir</option>
        <option value="4">Bruce Wayne</option>
        <option value="5">Catwoman</option>
        <option value="6">Frodo Baggins</option>
        <option value="7">Leia</option>
        <option value="8">Luke</option>
        <option value="9">Robin</option>
      </select>
    </turbo-frame>
  </div>
</body>
```

アップデートされるソースの例（http://localhost:3000/movies/2）

```html
<turbo-stream action="update" target="children">
  <template>
    <select name="movie[name]" id="movie_name">
      <option value="2">Aragorn II</option>
      <option value="3">Boromir</option>
      <option value="6">Frodo Baggins</option>
    </select>
  </template>
</turbo-stream>
```

# 補足
Turbo (Hotwire) の概念・大枠の把握は [Hotwireとは何なのか？
](https://zenn.dev/en30/articles/2e8e0c55c128e0) がとてもわかりやすかったです。

もう少し複雑な処理が必要であれば`Stimulus`で Javascript を使う方法もあります。その場合も、遷移は`Turbo.visit()`を使わなければ Turbo が発火しません。