2016年07月15日

Simple Formについて

書こうとしたことが長すぎて、うまくタイトルがまとまらなかったので本文の方に書くと
「simple formのassociationを使ってセレクトボックスを作成した時、JSで使うためにoptionにvalue以外の要素を持たせたい」
というのが今回の内容。

なぜこんなことをしたいかと言うと、valueには選択されたオブジェクトのIDが使われているようなので、
" このセレクトボックスで選択された内容によって、画面内の要素をリアルタイムで表示/非表示切り替えしたい " となったとき、
$(“#id”).val() みたいなメソッドで値を取って、値が◯◯だったら〜みたいにして使うと、当たり前だけどうまく動作しない。
(IDすなわちvalueは1かもしれないし、4357かもしれない。 決め打ち不可能)

なので、セレクトボックスのoptionにvalue以外の環境によらず変動しない要素を持たせたいと考えたのだった。
(他にやりようはある気がしますので教えてくださいmm)

正直何をしたいのか上の文章だけでは理解できないと思いますので、具体的に書いていきます。

基本形

まずは基本の形から

= simple_form_for(@user) do |f|
  = f.association :project

みたいなコードがあるとします。

これはUserモデルと関連するProjectモデルを使っていますが、これで生成されるHTMLはざっくり書いて以下のような感じ
(相当端折ってますので、実際にはもっと色々書かれてます)

<form id= class=>
  <label> プロジェクト </label>
  <select id=“project_id”>
    <option value=“1”>案件1</option>
    <option value=“2”>案件2</option>
  </select>
</form>

これで更新対象のユーザーを選択したプロジェクトに所属させられます。
(current_user.project.name -> 案件1 みたいな)

やりたいことの再確認

普通に更新するだけならこれでいいのですが、例えばプロジェクト以外にも入力フォームがあって、
"案件2が選択された時は◯◯の入力フォームは隠す" とかやりたいとしたら、JS側で、

  • $(“#project_id”).on “change”〜で変更を検知して
  • $(“#project_id”).val() とかで値を取得し
  • ifを使いvalueが2だったら〜

などとしたくなると思います。

ですが冒頭でも述べたように、これだとvalueがIDに依存するため、環境によってはval()で得られる値が変化してしまいます。
これをなんとか他の方法で回避したい!というのが今回のテーマです(長い)

僕はこう書いた

simple formにはcollectionというヘルパーがあり、こいつに配列を指定するとセレクトボックスになってくれます。
また、多重配列を渡すと、valueやテキストも指定することができるようです。

ですので、以下のようにcollectionにmapを使って配列を渡します。

= simple_form_for(@user) do |f|
  = f.association :project, collection: Project.all.map { |project| [project.name, project.id, { class: project.code_name } }

こう書くことによって、以下のようなHTMLが生成されるようになります

<form id= class=>
  <label> プロジェクト </label>
  <select id=“project_id”>
    <option value=“1” class=“alpha”>案件1</option>
    <option value=“2” class=“beta”>案件2</option>
  </select>
</form>

これならばプロジェクトが持つ固有の一意のコードネームを使用し、以下のように使うことができます。

  • $(“#project_id”).on “change”〜で変更を検知
  • $(“#project_id option:selected”).attr(“class”) で値を取得

あとは煮るなり焼くなり・・・

if ["alpha", "gamma"].indexOf($("#project_id option:selected").attr("class")) >= 0
  $("表示切り替えしたい要素").show()
else
  $("表示切り替えしたい要素").hide()

みたいにして切り替えを行えばとりあえず機能的には動作します。
(これが正しいかはわからないっす)

最後に

ある目的を達成するにはいろんな方法がありますが、経験が浅い僕はまだまだ技が少ないです。
今回のももっと他にエレガントな方法があると思うのですが、今はこれしか思い浮かびませんでした。

「最善ではないけど、まずはとにかく動かせるところまで頑張る。 美を求めるのはそれからだ!」

の精神で頑張ります。
もっといい方法あったら更新しよう。