大ちゃんの駆け出し技術ブログ

RUNTEQ受講生のかわいいといわれるアウトプットブログ

【Vue】【Rails】selectタグのv-modelとActive Hashの更新

はじめに

こんにちは!大ちゃんの駆け出し技術ブログです。

Active Hashを使用して出身地を登録するフォームを作っていたのですが、そこで少しつまずいたので備忘録として残しておきます。

つまづいた箇所

以下のようにActive Hashを使用して県名を登録しています。

class Prefecture < ActiveHash::Base
  self.data = [
    { id: 1, name: '北海道' }, { id: 2, name: '青森県' }, { id: 3, name: '岩手県' },
    { id: 4, name: '宮城県' }, { id: 5, name: '秋田県' }, { id: 6, name: '山形県' },
    { id: 7, name: '福島県' }, { id: 8, name: '茨城県' }, { id: 9, name: '栃木県' },
    { id: 10, name: '群馬県' }, { id: 11, name: '埼玉県' }, { id: 12, name: '千葉県' },
    { id: 13, name: '東京都' }, { id: 14, name: '神奈川県' }, { id: 15, name: '新潟県' },
    { id: 16, name: '富山県' }, { id: 17, name: '石川県' }, { id: 18, name: '福井県' },
    { id: 19, name: '山梨県' }, { id: 20, name: '長野県' }, { id: 21, name: '岐阜県' },
    { id: 22, name: '静岡県' }, { id: 23, name: '愛知県' }, { id: 24, name: '三重県' },
    { id: 25, name: '滋賀県' }, { id: 26, name: '京都府' }, { id: 27, name: '大阪府' },
    { id: 28, name: '兵庫県' }, { id: 29, name: '奈良県' }, { id: 30, name: '和歌山県' },
    { id: 31, name: '鳥取県' }, { id: 32, name: '島根県' }, { id: 33, name: '岡山県' },
    { id: 34, name: '広島県' }, { id: 35, name: '山口県' }, { id: 36, name: '徳島県' },
    { id: 37, name: '香川県' }, { id: 38, name: '愛媛県' }, { id: 39, name: '高知県' },
    { id: 40, name: '福岡県' }, { id: 41, name: '佐賀県' }, { id: 42, name: '長崎県' },
    { id: 43, name: '熊本県' }, { id: 44, name: '大分県' }, { id: 45, name: '宮崎県' },
    { id: 46, name: '鹿児島県' }, { id: 47, name: '沖縄県' }
  ]
end

コントローラーはprefecture_idをパラメーターとして受け取り登録しています。

def create
  @profile = current_user.build_profile(profile_params)

  if @profile.save
    render json: @profile
  else
    render json: @profile.errors, status: :bad_request
  end
end

private

def profile_params
  params.require(:profile).permit(:height, :gender, :blood_type, :prefecture_id)
end

ここで、フロント側に渡すJSONの値として、prefecture_idをそのまま渡してしまうと、Active Hashで定義しているidが返ります。例えば、北海道ならprefecture_idが1と返ってきます。フロント側では県名を表示したいので、ActiveModelSerializersを使用して、prefecture.nameとすることで県名の値(idが1なら北海道)を返却します。

# app/serializers/profile_serializer.rb
class ProfileSerializer < ActiveModel::Serializer
  attributes :id,:height, :gender, :blood_type, :prefecture_id
  belongs_to :user

  def prefecture_id
    object.prefecture.name
  end
end

このActiveModelSerializersの箇所は過去記事で説明しているのでよければ参照ください。

sakitadaiki.hatenablog.com

さて、詰まった箇所は以下の部分です。

県名の編集のためにvue側でも同じようにprefecturesで定義し、それを<template>タグ内で繰り返し処理でoptionに展開していました。

data() {
    return {
      prefectures: [
        { text: "北海道", value: "1" },
        { text: "青森県", value: "2" },
        { text: "岩手県", value: "3" },
        { text: "宮城県", value: "4" },
        { text: "秋田県", value: "5" },
        { text: "山形県", value: "6" },
        { text: "福島県", value: "7" },
        { text: "茨城県", value: "8" },
        { text: "栃木県", value: "9" },
        { text: "群馬県", value: "10" },
        { text: "埼玉県", value: "11" },
        { text: "千葉県", value: "12" },
        { text: "東京都", value: "13" },
        { text: "神奈川県", value: "14" },
        { text: "新潟県", value: "15" },
        { text: "富山県", value: "16" },
        { text: "石川県", value: "17" },
        { text: "福井県", value: "18" },
        { text: "山梨県", value: "19" },
        { text: "長野県", value: "20" },
        { text: "岐阜県", value: "21" },
        { text: "静岡県", value: "22" },
        { text: "愛知県", value: "23" },
        { text: "三重県", value: "24" },
        { text: "滋賀県", value: "25" },
        { text: "京都府", value: "26" },
        { text: "大阪府", value: "27" },
        { text: "兵庫県", value: "28" },
        { text: "奈良県", value: "29" },
        { text: "和歌山県", value: "30" },
        { text: "鳥取県", value: "31" },
        { text: "島根県", value: "32" },
        { text: "岡山県", value: "33" },
        { text: "広島県", value: "34" },
        { text: "山口県", value: "35" },
        { text: "徳島県", value: "36" },
        { text: "香川県", value: "37" },
        { text: "愛媛県", value: "38" },
        { text: "高知県", value: "39" },
        { text: "福岡県", value: "40" },
        { text: "佐賀県", value: "41" },
        { text: "長崎県", value: "42" },
        { text: "熊本県", value: "43" },
        { text: "大分県", value: "44" },
        { text: "宮崎県", value: "45" },
        { text: "鹿児島県", value: "46" },
        { text: "沖縄県", value: "47" },
      ],

<template>タグ内で繰り返し処理で展開

<select
  v-model="editProfile.prefecture_id"
  name="profile[prefecture_id]"
>
  <option
    v-for="prefecture in prefectures"
    :key="prefecture.value"
    :value="prefecture.value"
  >
    {{ prefecture.text }}
  </option>
</select>

:value="prefecture.value"とすることでサーバー側に渡すパラメータはidで渡すようにしています。これはActive Hashで更新するためのパラメータがidであるためです。

※この部分については少し冗長かなと思ったので、のちほど改善したいと思っています。例えば、Active Hashで登録している値をAPIレスポンスで受け取りそれをフロント側で表示することで、サーバー側のActive Hashの値とフロント側の値を同じにすることができます。今だと、フロント側とサーバー側で別々で県名を指定しているので冗長かなと思います。

ここで問題となったのがv-modelの値はoptionの中のvalueと一致しないと編集画面を表示したときに、何も選択されていない状態で表示されてしまうことです。

https://i.gyazo.com/d873656fca281710872e0dd3bf51b1a6.png

editProfile.prefecture_idは上述したように県名を表示するために、値はidではなく県名となっています。しかし、selectタグ内ではvalueidとなっているために県名がどのoptionのvalueにも当てはまらない状態です。そのため、editProfile.prefecture_idには値が格納されていますが、図のようにフォームに何も選択されていないような状態で表示されてしまうのです。

解決策

ベストプラクティスかは不明なのですが、optionvalueを県名と一致させる方法を取りました。つまり、:value="prefecture.text"とすることで、valueが県名になるようにしました。

<option
  v-for="prefecture in prefectures"
  :key="prefecture.value"
  :value="prefecture.text"
>
  {{ prefecture.text }}
</option>

これで編集画面を開いたときに、valuev-modelの値が一致するために初期状態で値が選択されているようになります。

https://i.gyazo.com/29b86400058f90ed491247fd2f7e5550.png

しかし、これだとサーバー側で受け取るパラメーターとしてはidではなく県名になってしまい、正常に値を更新できなくなってしまいます。そこで、更新するときにパラメータを変更するようにしました。

const selectedPrefecture = this.prefectures.find(
  (prefecture) => prefecture.text == editBasicProfile.prefecture_id
);
editBasicProfile.prefecture_id = selectedPrefecture.value;

dataで登録している県名の中からパラメーターとして渡っているeditBasicProfile.prefecture_idと一致する県名のオブジェクトを取得します。そして、そのオブジェクトに格納されているvalue(id)を代入することで値をidに無理やり変更しています。

これにより無事値を更新することができました!