Как отправить несколько НОВЫХ предметов через массовое назначение в Rails 3.2

У меня довольно стандартный вариант использования. У меня есть родительский объект и список дочерних объектов. Я хочу иметь табличную форму, где я могу редактировать все дочерние элементы одновременно, как строки в таблице. Я также хочу иметь возможность вставить один или несколькоnew строки и при отправке должны быть созданы как новые записи.

Когда я используюfields_for для рендеринга ряда подформ для вложенных записей, связанных с has-many, rails генерирует имена полей, например,parent[children_attributes][0][fieldname], parent[children_attributes][1][fieldname] и так далее.

Это заставляет Rack анализировать хэш params, который выглядит следующим образом:

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

Когда прошлоnew (непостоянный) объект, тот жеfields_for сгенерирует имя поля, которое выглядит так:

parent[children_attributes][][fieldname]

Обратите внимание[] без индекса в нем.

этоcannot размещаться в одной форме с полями, содержащими[0], [1]и т. д. потому что стойка запутывается и поднимает

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

«Хорошо», думает я. «Я просто убедился»all поля используют[] форма вместо[index] форма. Но я не могу понять, как убедитьfields_for делать это последовательно. Даже если я дам ему явный префикс имени поля и объекта:

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

До тех пор, покаchild сохраняется, он автоматически изменяет имена полей, чтобы они стали, например,parent[children_attributes][0][fieldname]оставляя имена полей для новых записей какparent[children_attributes][][fieldname], Еще раз, Стойка Barfs.

Я в растерянности. Как, черт возьми, я использую стандартные помощники Rails, такие какfields_for представить несколькоnew записи, наряду с существующими записями, должны ли они анализироваться как массив в параметрах, и должны ли все записи, не имеющие идентификаторов, создаваться как новые записи в БД? Мне не повезло, и мне просто нужно сгенерировать все имена полей вручную?

 rdvdijk12 июл. 2012 г., 08:44
Какую версию Rack вы используете? Это также сломано в предыдущих версиях?
 KL-712 июл. 2012 г., 10:07
Не самое элегантное решение, но вы можете использовать:child_index вариант и передать некоторый случайный индекс для каждой новой записи. Увидетьthis пример. Речь идет о добавлении полей для новых записей на стороне клиента, но вы должны понять это. Хотя странно, что на стороне сервера он работает не так, как вы.
 Frederick Cheung12 июл. 2012 г., 08:16
Так как выглядит ваш вызов fields_for?

Ответы на вопрос(8)

Общее решение заключается в добавлении заполнителя в [] и замене его уникальным номером при вставке фрагмента в форму. Отметка времени работает большую часть времени.

 23 сент. 2013 г., 16:45
Он имел в виду «для большинства случаев использования», а не «непредсказуемо».
 04 апр. 2013 г., 18:23
Временная метка работает большую часть времени? ... которая запрашивает ошибку, которую действительно трудно воссоздать, не так ли?

long post deleted

У Райана есть эпизод по этому поводу: http://railscasts.com/episodes/196-nested-model-form-revised

Похоже, вам нужно создать уникальный индекс вручную. Райан используетobject_id за это.

 12 июл. 2012 г., 10:09
Тогда просто удали свой ответ.
 12 июл. 2012 г., 10:17
Ссылка для удаления отсутствует по какой-либо причине. Вместо этого я обновил пост снова.

Для этого есть драгоценный камень под названием кокон, я бы предпочел более компактный подход, но он был специально создан для этих случаев.

Решение Вопроса

Как уже упоминалось,[] должен содержать ключ для новых записей, потому что в противном случае он смешивает хэш с типом массива. Вы можете установить это с помощью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 %>

А вот немного CoffeeScript для прослушивания события click этой ссылки, вставки полей и обновления идентификатора объекта с текущим временем, чтобы дать ему уникальный ключ.

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()

Этот код взят изэто RailsCasts Pro эпизод которая требует платной подписки. Тем не менее, есть полный рабочий пример в свободном доступена GitHub.

Update: Я хочу отметить, что вставкаchild_index заполнитель не всегда необходим. Если вы не хотите использовать JavaScript для динамического добавления новых записей, вы можете создать их заранее:

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

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

Rails автоматически вставит индекс для новых записей, поэтому он должен просто работать.

 12 июл. 2012 г., 19:44
object_id не является необходимой реализацией, вы можете использовать любое случайное значение:id = SecureRandom.hex, Однако это не гарантирует, что это уникально. Возможно, хэш object_id будет лучше?
 12 июл. 2012 г., 23:47
@Avdi, я понимаю ваше раздражение, но как только вы начнете с более глубоких уровней вложенности, вам понадобится способ связать дочерние поля с их родительскими моделями. СчитатьExam has many questions, question has many possible answers и в одной форме вы хотите создать много вопросов и много ответов для каждого вопроса, поэтому имя поля ответа должно бытьexam[questions_attributes][123][answers_attributes][321][answer_text] и за вопросexam[questions_attributes][123][question_text], Обратите внимание, что псевдоидентификатор (123) должен соответствовать между вопросом и соответствующими ответами.
 12 июл. 2012 г., 19:58
Я согласен, что поведение вставки идентификатора при передаче в[] вfields_for странно AFAIK это было так сfields_for был впервые добавлен. Вы можете увидеть это вthis episode примерно в 3:00.
 Avdi13 июл. 2012 г., 19:59
Постскриптум Я все еще думаю, что это слишком запутанно для новичков. & quot; О, & quot; вам нужно будет изменить имена полей в JS & quot; не похоже на способ работы с батареями в этом довольно распространенном случае: - /
 Avdi12 июл. 2012 г., 19:35
Это ответ, который я видел много, и меня действительно беспокоит использование некоторого псевдослучайного числа, такого как object_id, в качестве дифференциатора. Не упоминать: выставление внутренних идентификаторов объекта в клиентском интерфейсе? Страшно! Так много атак основано на использовании непреднамеренно обнаженных внутренних органов. Что меня действительно беспокоит, так это то, что в Rails есть отличный способ представить массивы param hases: the 'apos; []' apos ;. И если вы можете заставить его покинуть «[]»; один, это прекрасно работает! Он просто очень старается не позволить вам использовать «[]»; во всех именах полей.

Может быть, вы должны просто обмануть. Поместите новые записи в другой атрибут faux, который является декоратором для фактического.

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

Это не красиво, но должно работать. Может потребоваться дополнительное усилие для поддержки повторных обращений к форме для ошибок проверки.

 Avdi12 июл. 2012 г., 08:25
Это ошеломляет меня, что я должен был бы сделать это. И, на самом деле, я мог бы поклясться, что мне не нужно было делать это в прошлом.
 Avdi12 июл. 2012 г., 08:28
О, а также: способность кaccepts_nested_attributes_forсгенерированные сеттеры для принятия нескольких новых (без ID) хеш-атрибутов и создания новых записей для всех из них при сохранении обновленных записейexplicitly documented, предполагая, что это должно быть полностью выполнимо.

Я сталкивался с этим случаем пользователя во всех моих последних проектах, и я ожидаю, что это продолжится, как указал julian7, необходимо предоставить уникальный идентификатор внутри []. На мой взгляд, это лучше сделать через JS. Я перетаскивал и улучшал плагин jquery для работы с этими ситуациями. Он работает с существующими записями и для добавления новых записей, но ожидает определенную разметку и изящно ухудшается, вот код и пример:

https://gist.github.com/3096634

Предостережения по использованию плагина:

The fields_for call should be wrapped in a <fieldset> with data-association attribute equal to the pluralized name of the model, and a class 'nested_models'.

an object should be built in the view just before calling fields_for.

the object fields perse should be wrapped in a <fieldset> with class "new" but only if the record is new (cant remember if I removed this requirement).

A checkbox for the '_destroy' attribute inside a label must exist, the plugin will use the label text to create a destroy link.

A link with class 'add_record' should exist within the fieldset.nested_models but outside the fieldset enclosing the model fields.

Appart от этой неприятности его творит чудеса для меня.
После проверки сути это требование должно быть более четким. Пожалуйста, дайте мне знать, если вы улучшаете код или используете его :).
Кстати, я был вдохновлен скринкастом первых вложенных моделей Райана Бейтса.

Поэтому меня не устраивало наиболее часто встречающееся решение, которое заключалось в создании псевдоиндекса для новых элементов либо на сервере, либо в 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 предпочитает явные индексы в именах полей: как только вы попадаете в три слоя вложенных моделей, необходимо найти способ определить, к какой модели второго уровня относится атрибут третьего уровня.

 13 июл. 2012 г., 18:29
Тоже самое. Это то, с чем я борюсь, каждый раз, когда мне нужно создавать формы master-children в Rails.
 13 июл. 2012 г., 18:33
Я считаю, что причина, по которой Rails предпочитает хеш-код массиву, состоит в том, чтобы несколько атрибутов работали надежно:foo[bar][][zap] а такжеfoo[bar][][zip] должны быть связаны с той же записи. Эта проблема усиливается, когда есть глубоко вложенные записи. Макарио упоминает об этом в другом комментарии.
 13 июл. 2012 г., 18:27
Я сталкивался с этой проблемой несколько раз и всегда заканчивал тем, что сам генерировал имена полей. Я бы определенно использовал эту опцию, если бы она была там и работала.
 13 июл. 2012 г., 18:52
УДАЛЕНО, это вызовет проблемы, если порядок детей не детерминирован. Например, в отношении «есть много», если вы не всегда упорядочиваете дочерние элементы по одному и тому же столбцу, ваша реляционная база данных может вернуть дочерние элементы в неожиданном порядке. Это приведет к тому, что к детям будут применены неверные значения параметров.
 Avdi13 июл. 2012 г., 19:51
@ryanb спасибо за это разъяснение. Похоже, в моих тестах работает нормально; Я предполагаю, что это работает на основе порядка. Однако теперь я вспоминаю предупреждение Макарио о множественных вложенных моделях в одной форме.

Я думаю, что вы можете сделать это, включив идентификатор записи в качестве скрытого поля

 12 июл. 2012 г., 20:55
Таким образом, вы будете передавать ноль для тех, кто имеет тот же эффект.
 12 июл. 2012 г., 10:10
Новая запись еще не имеет идентификатора.

Ваш ответ на вопрос