[Rails]セレクトボックスの選択肢を動的に表示する
はじめに
Turbo FramesとStimulusを使用して、Railsアプリ内でセレクトボックスの選択肢を動的に表示するように実装していきます。
環境
Ruby 3.2.1
Rails 7.0.8
tl;dr
- scaffoldでAddressモデルを作成し、DBのマイグレーションを実行する
- gem
city-state
をインストールする - セレクトフォームを作る
- gem
requestjs-rails
をインストールする - リクエストURLを追加する
- Addressesコントローラーに対応するメソッドを追加する
- turbo-streamレスポンスを作成する
- JSコントローラーを作成する
- リファクタリング
scaffoldでAddressモデルを作成する
country, stateを持つAddressモデルを作成します。
➜ rails g scaffold Address country state
DBのマイグレーションを実行します。
city-state
をインストールする
gem gem 'city-state'
bundle install
city-state
は国、州、都市に関する情報を提供するgemです。
セレクトフォームを作る
text_field
で作成されたフォーム要素をセレクトボックスに変えます。
# selectコントローラーと接続する
<div data-controller="select">
<div>
<%= form.label :country, style: "display: block" %>
<%= form.select :country,
# 国を取得する
CS.countries.invert,
{prompt: '国を選択してください'},
# selectコントローラーのchangeアクションを発火させる
{data: { action: "change->select#change" }} %>
</div>
<div>
<%= form.label :state %>
# targetとして指定する
<%= form.select :state, [], {data: { select_target: "stateSelect" }} %>
</div>
CS.countries
で国とコードのハッシュを取得することができます。
irb(main):001> CS.countries
=>
{:AD=>"Andorra",
:AE=>"United Arab Emirates",
:AF=>"Afghanistan",
:AG=>"Antigua and Barbuda",
:AI=>"Anguilla",
:AL=>"Albania",
:AM=>"Armenia",
:AO=>"Angola",
:AQ=>"Antarctica",
:AR=>"Argentina",
...
requestjs-rails
をインストールする
gem requestjs-rails
は、RailsでJavaScriptのHTTPリクエストを行うためのラッパーgemです。
➜ bin/importmap pin @rails/request.js
Pinning "@rails/request.js" to https://ga.jspm.io/npm:@rails/request.js@0.0.9/src/index.js
append app/javascript/application.js
リクエストURLを追加する
ajaxリクエスト用URLを追加します。
Rails.application.routes.draw do
resources :addresses do
collection do
get :states
end
end
end
Prefix Verb | URI Pattern | Controller#Action |
---|---|---|
states_addresses GET | /addresses/states(.:format) | addresses#states |
Addressesコントローラーに対応するメソッドを追加する
class AddressesController < ApplicationController
def states
# 更新する部分を指定する
@target = params[:target]
# params[:country]に対応する州(states)の情報を取得する
@states = CS.get(params[:country]).invert
respond_to do |format|
format.turbo_stream
end
end
end
turbo-streamレスポンスを作成する
@target
要素に取得した@states
を表示させます。
<%= turbo_stream.update @target do %>
<%= options_for_select @states %>
<% end %>
JSコントローラーを作成する
countryを選択したらajaxリクエストを送り、選択した国のstatesの値をレスポンスとして受け取ります。
import { Controller } from "@hotwired/stimulus"
import { get } from "@rails/request.js"
export default class extends Controller {
static targets = ["stateSelect"]
change(event){
# 選択した国を取得する
let select = event.target.selectedOptions[0].value;
# stateを指定する
let target = this.stateSelectTarget.id;
get(`/addresses/states?target=${target}&country=${select}`, {
# 取得したstate値をturbo-streamで返す
responseKind: "turbo-stream"
})
}
}
Started GET "/addresses/states?country=AD&target=address_state" for 127.0.0.1 at 2023-11-02 14:34:23 +0900
Processing by AddressesController#states as TURBO_STREAM
Parameters: {"country"=>"AD", "target"=>"address_state"}
Rendering addresses/states.turbo_stream.erb
Rendered addresses/states.turbo_stream.erb (Duration: 0.2ms | Allocations: 302)
Completed 200 OK in 1ms (Views: 0.5ms | ActiveRecord: 0.0ms | Allocations: 648)
リファクタリング
セレクトボックスの選択肢を動的に表示するようになったが、再利用性が低いのでリファクタリングします。
valueプロパティを使う
values
プロパティを使って、コントローラーのurl
プロパティを定義します。
static values
プロパティを使用することで、コントローラーにプロパティ(url
、param
)を追加し、それを初期化することができます。
コントローラーが接続されたときに指定されたURLに対してHTTPリクエストを行うことができます。
static values = {
url: String,
param: String
}
# urlを書き換える
get(`${this.urlValue}?target=${target}&country=${this.paramValue}`, {
responseKind: "turbo-stream"
})
urlをフォームに定義することによって、コントローラーを複数のフォームに利用されることが可能になり、再利用性が向上します。
<div data-controller="select"
data-select-url-value="<%= states_addresses_path %>"
data-select-param-value="country">
URLSearchParams:append()を使う
URLSearchParams
インターフェースは、URLの検索パラメーターを操作するための一連のメソッドを提供します。
URLSearchParams
インターフェースの append()
メソッドは、URLのクエリ文字列内に新しいキーと値を追加するために使用されるメソッドです。
let url = new URL("https://example.com?foo=1&bar=2");
let params = new URLSearchParams(url.search);
// 2番目の foo パラメーターを追加します。
params.append("foo", 4);
// クエリー文字列はこうなる: 'foo=1&bar=2&foo=4'
URLにあるtarget
とcountry
をappend()メソッドを使ってクエリパラメーターとして追加します。
change(event){
let params = new URLSearchParams()
params.append(this.paramValue, event.target.selectedOptions[0].value)
params.append("target",this.stateSelectTarget.id)
get(`${this.urlValue}?${params}`, {
responseKind: "turbo-stream"
})
}
終わりに
requestjs
を初めて使ってみました。
レスポンスフォーマットをturbo-stream
を指定することができて便利です!
Discussion