mmyoji's diary

プログラミングとか日々のどうでもいいこととか

Railsのnested attributes formを動的に追加するのがつらい(without jQuery)

先週の金曜日から掲題の内容を考えて色々試してはいるんですが、イマイチ綺麗に書けない。

やりたいこととしては

  • has_many のテーブルを複数個登録・削除できる
  • itemの個数は最低限表示しておき、「追加する」ボタン的なのでjsで増やす
  • jQueryを使わない(VueとかReactで考えてます)

でとりあえず一旦Railsfields_for とか使って作法に乗っとろうとしましたが、そうするとinputを追加する際に生DOMを生成する必要があり「これは...」となったので、とりあえずidとかnameあたりを把握した上で同じDOMを生成できるようにjsで同じようなnested attributesのformを作成すればいいんじゃないかと思ってやってみました。

データは確かに送れてるんですが、submit失敗した際にinputのvalueに入ってほしい値が消える。(これを解決するために受け取ったrubyのobjectを to_json とかでjsに渡したりしたがかなりキモい。)

今回の場合、データの有効性をクライアント側で100%保証できないのでどうしても一回サーバーサイドに渡さないといけないのがさらにつらい。

雑な例

ユーザーが色々な種類の芋を持てるとします🍠

models

class User < ActiveRecord::Base
  has_many :potatoes
  accepts_nested_attributes_for :potatoes, allow_destroy: true
end

class Potato < ActiveRecord::Base
  belongs_to :user
end

html

<%= form_for @user do |f| %>

  <%= f.label :username %>
  <%= f.text_field :username %>

  <% @user.potatoes.each do |p| %>
    <%= f.fields_for :potatoes, p do |pf| %>
      <%= pf.label :name %>
      <%= pf.text_field :name %>
      <%= pf.hidden_field :p_id %>
    <% end %>
  <% end %>

  <%= f.submit "Update user" %>

<% end %>

Vue.jsでガリガリ描いていくぱてぃーん

<%= form_for @user do |f| %>

  <%= f.label :username %>
  <%= f.text_field :username %>

  <div v-repeat="p in potatoes">
    <label for="{{p.nameId}}">{{p.labelText}}</label>
    <input type="text" v-model="p.nameInput" name="user[potatoes_attributes][{{$index}}][name]" id="{{p.nameId}}"/>
    <input type="hidden" v-model="p.pIdInput" name="user[potatoes_attributes][{{$index}}][p_id]" />
  </div>

  <%= f.submit "Update user" %>

<% end %>

汚い...

例は結構適当なので雰囲気で察してください。

まとまってないまとめ

特に仕様が決まってはいないんですが、決まってないが故にこっちで決めたら決めたでPOがうるさい(可能性がある)ので、どうしようという感じです。

個人的にはそんなにフォームの数が増えるわけでもないので最初から上限まで表示しておく方向でほぼjs書かないのが負債をためずに済むかなという気がしています。

綺麗に書けるときはいいんですが、ちょっとでも汚くなりそうなときはjs書かないのが一番ですね。

jQueryを使う例