質問: ### 1つのカラムに複数のテキストデータを入れたい すごく初歩的な質問だと思います。 例)ユーザー登録フォームで"行ったことのある国"を入力してもらうケース ユーザーAさんの行ったことのある国が「アメリカ」「イギリス」「中国」だったとし、そのデータをUserモデルのvisitedカラムに入れたいのですが、どうすればいいでしょうか。 そのデータをもとにやりたいことは... 1. ユーザーAさんが何か国行ったことがあるかカウントしたい。 2. "行ったことのある国"でユーザーの絞り込み検索がしたい。 この場合、あらかじめ選択肢で国名を設けておくと200近い選択肢が必要になりますよね。 かと言って、フォームに直接入力してもらおうとすると、「アメリカ、イギリス、中国」と書かれてはこれで一つの国になってしまいカウントできませんし、「アメリカ」に行ったことがある人としてユーザーAさんを取り出せません。 ※質問の趣旨から逸れないために、ユーザーによる国名表記は別にガイド等を設けて一致しているものと仮定します。(「アメリカ」「アメリカ合衆国」「米国」など書き方は様々ですがみんな「アメリカ」と書いてくれる、などとここでは仮定する) ユーザーAさんのvisitedカラムに "アメリカ", "イギリス", "中国" と入れられれば上記1,2は解決するのですが、それにはどうしたらいいのでしょうか。 ### 教えていただきたいこと * 「複数選択可の選択肢を設ける」がベストソリューションの場合、たぶんcheckboxを使用すると思うのですが、複数選択してカラムに配列でデータを送る書き方がわかりません。 * 仮に登録できる上限を5か国までとし、text_fieldを5つ設け、それぞれをvisitedカラムに関連付けるとしたらどう書けばいいのでしょうか。 質問内容からお察しいただけるかと思いますが、全くの初心者ですのでお手柔らかにお願いします。 ### 例示用コード コメントありがとうございます。 以下にコードを追記します。 たとえばチェックボックスで複数選択可にする場合、今の自分の知識では下記のように書くしかありません。 ``` <%= form_for(@users) do |f| %> アメリカ<%= f.check_box 'visited1', {}, "アメリカ" %> イギリス<%= f.check_box 'visited2', {}, "イギリス" %> 中国<%= f.check_box 'visited3', {}, "中国" %> <%= f.submit %> <% end %> ``` つまり、カラムをvisited1、visited2、visited3...と用意してそこに値を入れていくという何とも非効率なやり方です。選択肢が3つ程度ならこれでもいいのでしょうが、国名全てを選択肢にするとなると、これでは到底現実的ではなく、もっといい書き方があるはずです。 そこで、登録できる上限を5か国までとし、下記のようにtext_fieldを使ったほうがいいのかなと思ったのですが、、、 ``` <%= form_for(@users) do |f| %> 行ったことのある国1 <%= f.text_field :visited %> 行ったことのある国2 <%= f.text_field :visited %> 行ったことのある国3 <%= f.text_field :visited %> 行ったことのある国4 <%= f.text_field :visited %> 行ったことのある国5 <%= f.text_field :visited %> <%= f.submit %> <% end %> ``` これでは最後のフィールドに入れた値しか受け取れませんよね... この5つのフィールドに入れられた値をvisitedカラムに "アメリカ", "イギリス", "中国" といった形で保存したいのです。 1つのtext_fieldで「ユーザーが単語をスペースで区切って入力すれば配列で保存できる」なんてことはできないのでしょうかね。 ド素人のレベルの低い質問で恐縮です。 コードのサンプルなど無くても、「〇〇使えば出来るよ」「こんな感じで書けば?」的なヒントだけでもいただければ自力で調べてみますので何とぞよろしくお願いいたします。
回答: こんにちは。 質問への追記・修正依頼にお応え頂いてのソースコードの掲示、ありがとうございます。 > > 「〇〇使えば出来るよ」「こんな感じで書けば?」的なヒントだけでもいただければ自力で調べてみますので > > > とありましたので、以下、「自分ならこう作るかな」というアイディアレベルの、あくまで私見になります。 (なお当方は、ふだんの業務時間の7〜8割がたフロントエンド開発に従事し、残り2〜3割をバックエンド開発のヘルプに入る的な立ち位置です。バックエンドは近年、Railsが多いです。) 1.バックエンド開発 =========== 1.1 国マスターテーブルの作成 * `Country` モデルを作成し、 `countries`テーブルに国のマスターデータを格納する。 * 最低限、名前を入れる `name` カラムを用意する。 * 元になるデータは、たとえば ISO 3166-1 の表などをスクレイピングさせて頂くなどして入手 例) **countries テーブル** | id | name | | --- | --- | | 1 | アメリカ合衆国 | | 2 | 中華人民共和国 | | 3 | オーストラリア | | 4 | フランス | 1.2 users テーブルの visited カラムに入れる配列データの形式 * visitedカラムの型は文字列で、countriesテーブルの id をCSV形式(カンマ区切りの文字列)で入れることにします。 例) 中国(country_id:2)とフランス(country_id:4)に行ったことのある鈴木(id=100)さんの場合 | id | name | visited | | --- | --- | --- | | 100 | 鈴木 | 2,4 | 他の形式も考えられます。 たとえばJSONの配列形式にするのであれば、visitedには `[2,4]`という文字列が入ります。 1.3 ユーザーの訪問したことのある国として、ある国を追加または削除するAPIの設計と実装 ### 1.3.1 設計 メソッドとURLはざっくり以下のようなものになるかと思います。 * 鈴木さん(user_id:100)が行ったことのある国のリストに オーストラリア(country_id:3)を追加する。 ``` POST /users/100/countries/3 ``` * 鈴木さん(user_id:100)が行ったことのある国のリストから オーストラリア(country_id:3)を削除する。 ``` DELETE /users/100/countries/3 ``` 上記のURLとメソッド、さらにこれにパラメータの加わった、APIの設計は諸説意見の別れがちな所ではありますが、この回答では、とりあえず上記で進めます。 ### 1.3.2 実装 上記 1.3.1 のURLとメソッドからルーティングされる `UsersController` のアクションでは、以下を行うことになるでしょう。 #### 国の追加 * user_id :**100** のUserインスタンスを取得。 その `visited`のCSVに **3** が無ければ追加したCSVを作って、この新しいCSV文字列で`visited`を更新 #### 国の除外 * user_id : **100** のUserインスタンスを取得。 その `visited`のCSVに **3** があればこれを除外したCSVを作って、この新しいCSV文字列で`visited`を更新 2. フロントエンド開発 ============= * 先の1.3 で設計したAPIを使って、各チェックボックスの選択状態が変更されたら、すぐにDBに反映されるようなUIを想定します。 * もちろん、すべてのチェックボックスについて「チェックする・しない」を入力し終わったあとに、「保存」ボタンをクリックしてもらって、各国の「訪問した or していない」を、まとめていっぺんに送る方法もありますが、この回答では、そのための説明は割愛します。 2.1 チェックボックス 並べ方として最も簡単なのは、以下のように `Country.order(:id)` で得られる配列を単純にループして、チェックボックスを作成します。 ``` アメリカ合衆国: 中華人民共和国: オーストラリア: フランス: ``` * 各チェックボックスの classは “country_check” で統一し、value属性の値は country_id とします。 * また、画面のどこかに user_id と ユーザー名を持っている hidden の`` を書いておきます。以下は鈴木さん(user_id: 100)の場合です。 ``` ``` 2.2 チェックボックスの変化からAPIをリクエスト チェックボックスの選択状態の変化にあわせて、1.3 で設計、実装したAPIを呼ぶようにします。JQuery を使うとすると、以下のような感じです。(APIエラー時の処理等、細かいところは書いていません。あくまで概要を示すコードです。) ``` $(".country_check").on("change", function() { const countryId = $(this).val(); // country id の取得 const userId = $("#user_id").val(); // ユーザーidの取得 const userName = $("#user_id").data("user-name"); // ユーザー名の取得 const apiUrl = `/users/${userId}/countries/${countryId}`; // APIのURL const method = $(this).prop("checked") ? "POST" : "DELETE"; // APIのメソッド fetch(apiUrl, { method }) .then(res => { if(res.ok) { console.log(`${userName}さんの行ったことのある国が変更されました。`); } }); }); ``` ざっと概要を示すと以上です。 それともう一点、この質問の主旨である「1つのカラムにデータを配列で入れるには?」から外れますが、補足を以下に書きます。 補足: has_many :through関連付けの利用 * `User` と`Country` は Many to Many の関係なので、users と countries のインターセクションテーブル(たとえば visits という名前とします。)を作成し、これを介して 以下のように、`has_many` アソシエーションを追加するのが自然かなと思います。 ``` class User < ActiveRecord::Base has_many :countries, through: :visits end ``` ``` class Country < ActiveRecord::Base has_many :users, through: :visits end ``` Rails が用意してくれているモデル操作の仕組みをなるべく使う(いわゆる、"Rails Wayに乗る")と、やりたいことが楽に実現できることが多いので、私がこのRailsアプリを作るとしたら、上記のように `has_many :through` を使うことによって、「ユーザーが行ったことのある国」を表すことを考えると思います。 has_many :through関連付けの利用については以下をご参照ください。 * Railsガイド 2.4 has_many :through関連付け 以上参考になれば幸いです。