57

私にはかなり標準的なユースケースがあります。親オブジェクトと子オブジェクトのリストがあります。表の行として、すべての子を一度に編集できる表形式が必要です。また、1つ以上の新しい行を挿入し、送信時にそれらを新しいレコードとして作成できるようにしたいです。

を使用しfields_forて、has-manyによって関連付けられたネストされたレコードの一連のサブフォームをレンダリングすると、railsはフィールド名を生成parent[children_attributes][0][fieldname]parent[children_attributes][1][fieldname]ます。

これにより、Rackは次のようなparamsハッシュを解析します。

{ "parent" => { 
    "children" => {
      "0" => { ... },
      "1" => { ... } } }

新しい(永続化されていない)オブジェクトが渡されるfields_forと、次のようなフィールド名が生成されます。

parent[children_attributes][][fieldname]

[]インデックスが含まれていないことに注意してください。

ラックが混乱して発生するため、、などを含むフィールドと同じ形式で投稿することはできません[0][1]

TypeError: expected Array (got Rack::Utils::KeySpaceConstrainedParams)

「OK」と私は思います。「すべてのフィールドで[]フォームではなくフォームを使用するように[index]します。しかし、これを一貫して行うよう説得する方法がわかりませんfields_for。明示的なフィールド名のプレフィックスを付けてもおよびオブジェクト:

fields_for 'parent[children_attributes][]', child do |f| ...

永続化されている限りchild、フィールド名は自動的に変更され、たとえばparent[children_attributes][0][fieldname]、新しいレコードのフィールド名は。のままになりparent[children_attributes][][fieldname]ます。もう一度、ラックバーフ。

私は途方に暮れています。標準のRailsヘルパーをどのように使用して、既存のレコードとともに複数の新しいfields_forレコードを送信し、それらをparamsの配列として解析し、IDのないすべてのレコードをDBの新しいレコードとして作成するのですか?運が悪かったので、すべてのフィールド名を手動で生成する必要がありますか?

4

8 に答える 8

45

他の人が述べているように、に[]は新しいレコードのキーが含まれている必要があります。そうでない場合は、ハッシュと配列型が混在しているためです。child_indexこれは、 fields_forのオプションで設定できます。

f.fields_for :items, Item.new, child_index: "NEW_ITEM" # ...

私は通常、object_id複数の新しいアイテムがある場合にそれが一意であることを確認するために、代わりにこれを使用してこれを行います。

item = Item.new
f.fields_for :items, item, child_index: item.object_id # ...

これを行う抽象的なヘルパーメソッドを次に示します。item_fieldsこれは、レンダリングする名前のパーシャルがあることを前提としています。

def link_to_add_fields(name, f, association)
  new_object = f.object.send(association).klass.new
  id = new_object.object_id
  fields = f.fields_for(association, new_object, child_index: id) do |builder|
    render(association.to_s.singularize + "_fields", f: builder)
  end
  link_to(name, '#', class: "add_fields", data: {id: id, fields: fields.gsub("\n", "")})
end

このように使えます。引数は、リンクの名前、親のフォームビルダー、および親モデルの関連付けの名前です。

<%= link_to_add_fields "Add Item", f, :items %>

そして、ここにそのリンクのクリックイベントをリッスンし、フィールドを挿入し、オブジェクトIDを現在の時刻で更新して一意のキーを与えるCoffeeScriptがあります。

jQuery ->
  $('form').on 'click', '.add_fields', (event) ->
    time = new Date().getTime()
    regexp = new RegExp($(this).data('id'), 'g')
    $(this).before($(this).data('fields').replace(regexp, time))
    event.preventDefault()

そのコードは、有料サブスクリプションが必要なこのRailsCastsProエピソードから取得されます。ただし、GitHubで無料で入手できる完全な実例があります。

更新:child_indexプレースホルダーの挿入は必ずしも必要ではないことを指摘したいと思います。JavaScriptを使用して新しいレコードを動的に挿入したくない場合は、事前にレコードを作成できます。

def new
  @project = Project.new
  3.times { @project.items.build }
end

<%= f.fields_for :items do |builder| %>

Railsは新しいレコードのインデックスを自動的に挿入するので、正常に機能するはずです。

于 2012-07-12T16:09:43.857 に答える
23

そのため、サーバーまたはクライアント側のJSのいずれかで、新しい要素の疑似インデックスを生成するという、最も頻繁に見たソリューションに満足していませんでした。[]これは、特にRails / Rackがすべて空の括弧( )をインデックスとして使用している限り、アイテムのリストを完全に解析できるという事実に照らして、ごちゃごちゃしたように感じます。これが私が完成させたコードの概算です:

# note that this is NOT f.fields_for.
fields_for 'parent[children_attributes][]', child, index: nil do |f|
  f.label :name
  f.text_field :name
  # ...
end

フィールド名のプレフィックスを[]index: nilオプションと組み合わせて終了すると、インデックス生成Railsが無効になるため、永続化されたオブジェクトを提供しようとします。このスニペットは、新しいオブジェクトと保存されたオブジェクトの両方で機能します。結果のフォームパラメータは、一貫してを使用するため、 :[]内の配列に解析されます。params

params[:parent][:children_attributes] # => [{"name" => "..."}, {...}]

Parent#children_attributes=によって生成されたメソッドは、accepts_nested_attributes_for :childrenこの配列を適切に処理し、変更されたレコードを更新し、新しいレコード(キーがないレコード)を追加"id"し、キーが設定されているレコードを削除し"_destroy"ます。

Railsがこれを非常に難しくし、たとえばを使用する代わりに、ハードコードされたフィールド名のプレフィックス文字列に戻らなければならなかったことに、私はまだ悩んでいますf.fields_for :children, index: nil。記録のために、次のことも行います。

f.fields_for :children, index: nil, child_index: nil do |f| ...

...フィールドインデックスの生成を無効にできません。

これを簡単にするためにRailsパッチを作成することを検討していますが、十分な数の人が気にかけているかどうか、あるいはそれが受け入れられるかどうかさえわかりません。

編集:ユーザー@Macarioは、Railsがフィールド名で明示的なインデックスを好む理由を教えてくれました。ネストされたモデルの3つのレイヤーに入ると、第3レベルの属性がどの第2レベルのモデルに属するかを区別する方法が必要です。

于 2012-07-13T16:07:16.157 に答える
14

一般的な解決策は、プレースホルダーを[]に追加し、フォームにスニペットを挿入するときに一意の番号に置き換えることです。タイムスタンプはほとんどの場合機能します。

于 2012-07-12T07:20:17.413 に答える
3

多分あなたはただごまかすべきです。新しいレコードを、実際のレコードのデコレータである別のフェイク属性に配置します。

parent[children_attributes][0][fieldname]
parent[new_children_attributes][][fieldname]

きれいではありませんが、うまくいくはずです。検証エラーのフォームへのラウンドトリップをサポートするには、追加の作業が必要になる場合があります。

于 2012-07-12T06:12:40.357 に答える
3

長い投稿が削除されました

ライアンはこれに関するエピソードを持っています:http: //railscasts.com/episodes/196-nested-model-form-revised

一意のインデックスを手動で生成する必要があるようです。ライアンはこれにを使用しobject_idます。

于 2012-07-12T08:05:23.573 に答える
3

私は最後のすべてのプロジェクトでこのユーザーケースに遭遇しました。julian7が指摘したように、これが続くことを期待しています。[]内に一意のIDを指定する必要があります。私の意見では、これはjsを介して行う方が適切です。私はこの状況に対処するためにjqueryプラグインをドラッグして改善してきました。これは既存のレコードで機能し、新しいレコードを追加するために機能しますが、特定のマークアップを期待し、正常に低下します。コードと例を次に示します。

https://gist.github.com/3096634

プラグインを使用する際の警告:

  1. fields_for呼び出しは<fieldset>、モデルの複数形の名前に等しいdata-association属性と、クラス'nested_models'でラップする必要があります。

  2. fields_forを呼び出す直前に、ビューにオブジェクトを構築する必要があります。

  3. オブジェクトフィールド自体は、クラス「new」でラップする必要がありますが<fieldset>、レコードが新しい場合に限ります(この要件を削除したかどうかは覚えておいてください)。

  4. ラベル内の「_destroy」属性のチェックボックスが存在する必要があります。プラグインはラベルテキストを使用して破棄リンクを作成します。

  5. クラス'add_record'のリンクは、fieldset.nested_models内に存在する必要がありますが、モデルフィールドを囲むフィールドセットの外部に存在する必要があります。

この厄介な問題からのAppartは、私にとって不思議に働いています。
要旨を確認した後、この要件をより明確にする必要があります。コードを改善した場合、または使用した場合はお知らせください:)。
ところで、私はライアンベイツの最初のネストされたモデルのスクリーンキャストに触発されました。

于 2012-07-12T08:23:58.870 に答える
1

レコードのIDを非表示フィールドとして含めることで機能させることができると思います

于 2012-07-12T07:50:27.343 に答える
1

これを行うための繭と呼ばれる宝石があります、私はよりスリムなモルDIYアプローチに行きますが、それはこの場合のために特別に作られました。

于 2012-07-13T18:01:54.090 に答える