Ответ 1
В Rails 4 только этот
<%= form_tag photos_update_path do %>
<% @photos.each do |photo| %>
<%= fields_for "photos[]", photo do |pf| %>
<%= pf.text_field :caption %>
... other photo fields
Я хочу редактировать несколько элементов моей фото модели в одной форме. Я не уверен, как правильно представлять и POST это с формой, а также как собирать элементы в действии обновления в контроллере.
Это то, что я хочу:
<form>
<input name="photos[1][title]" value="Photo with id 1" />
<input name="photos[2][title]" value="Photo with id 2" />
<input name="photos[3][title]" value="Custom title" />
</form>
Параметры - это всего лишь пример, как я уже говорил выше: я не уверен в наилучшем способе POST этих значений в этой форме.
В контроллере я хочу что-то вроде этого:
@photos = Photo.find( params[photos] )
@photos.each do |photo|
photo.update_attributes!(params[:photos][photo] )
end
В Rails 4 только этот
<%= form_tag photos_update_path do %>
<% @photos.each do |photo| %>
<%= fields_for "photos[]", photo do |pf| %>
<%= pf.text_field :caption %>
... other photo fields
Rails не поможет вам многое сделать. Это противоречит стандартным соглашениям о просмотре, поэтому вам придется делать обходные пути в представлении, контроллере, даже маршрутах. Это не весело.
Ключевыми ресурсами при работе с мультимодельными формами Rails являются Stephen Chu params-foo series, или если вы находитесь на Rails 2.3, проверьте Вложенные объектные формы
Это становится намного проще, если вы определяете какой-то особый ресурс, который вы редактируете, например, Photoset. A Photoset может быть реальной моделью типа ActiveRecord или может быть просто фасадом, который принимает данные и выдает ошибки, как если бы это была модель ActiveRecord.
Теперь вы можете написать форму вида, как это:
<%= form_for :photoset do |f|%>
<% f.object.photos.each do |photo| %>
<%= f.fields_for photo do |photo_form| %>
<%= photo_form.text_field :caption %>
<%= photo_form.label :caption %>
<%= photo_form.file_field :attached %>
<% end %>
<% end %>
<% end %>
Ваша модель должна проверять каждый ребенок, который входит в систему, и агрегировать свои ошибки. Вы можете проверить хорошую статью о том, как включать проверки в любой класс. Это может выглядеть примерно так:
class Photoset
include ActiveRecord::Validations
attr_accessor :photos
validate :all_photos_okay
def all_photos_okay
photos.each do |photo|
errors.add photo.errors unless photo.valid?
end
end
def save
photos.all?(&:save)
end
def photos=(incoming_data)
incoming_data.each do |incoming|
if incoming.respond_to? :attributes
@photos << incoming unless @photos.include? incoming
else
if incoming[:id]
target = @photos.select { |t| t.id == incoming[:id] }
end
if target
target.attributes = incoming
else
@photos << Photo.new incoming
end
end
end
end
def photos
# your photo-find logic here
@photos || Photo.find :all
end
end
Используя фасадную модель для Photoset, вы можете легко и просто сохранить логику контроллера и просмотреть, зарезервировав наиболее сложный код для выделенной модели. Этот код, вероятно, не будет исчерпан, но, надеюсь, он даст вам некоторые идеи и укажет вам в правильном направлении, чтобы решить ваш вопрос.
У Rails есть способ сделать это - я не знаю, когда это было введено, но в основном описано здесь: http://guides.rubyonrails.org/form_helpers.html#using-form-helpers
Потребовалось немного поиграть, чтобы правильно изменить конфигурацию для случая, когда нет родительского объекта, но это кажется правильным (это в основном то же самое, что и ответ gamov, но более чистый и не позволяет "новые" записи смешанный с "обновляющими" записями):
<%= form_tag photos_update_path do %>
<% @photos.each do |photo| %>
<%= fields_for "photos[#{photo.id}]", photo do |pf| %>
<%= pf.text_field :caption %>
... [other fields]
<% end %>
<% end %>
<% end %>
В вашем контроллере вы получите хеш в params[:photos]
, где ключи - это идентификаторы фотографий, а значения - хэши-атрибуты.
Вы можете использовать синтаксис имени модели [] для представления нескольких объектов.
В поле зрения, используйте "photo []" в качестве имени модели.
<% form_for "photo[]", :url => photos_update_path do |f| %>
<% for @photo in @photos %>
<%= render :partial => "photo_form", :locals => {f => f} %>
<%= submit_tag "Save"%>
<% end %>
<% end %>
Это заполнит поля ввода так же, как вы описали.
В вашем контроллере вы можете выполнять массовые обновления.
def update
Photo.update(params[:photo].keys, params[:photo].values)
...
end
В самом деле, как сказал Турадж, Rack (Rails 3.0.5) терпит неудачу, если вы смешиваете новые и существующие записи в ответе Глена. Вы можете обойти это, сделав fields_for работать вручную:
<%= form_tag photos_update_path do %>
<% @photos.each_with_index do |photo,i| %>
<%= fields_for 'photos[#{i}]', photo do |pf| %>
<%= pf.hidden_field :id %>
... [other photo fields]
<% end %>
<% end %>
Это довольно уродливо, если вы спросите меня, но это единственный способ, который я нашел для редактирования нескольких записей при смешивании новых и существующих записей. Трюк здесь заключается в том, что вместо массива записей хэш hash получает массив хэшей (пронумерованный с i, 0,1,2 и т.д.) И идентификатор в хэш-записи. Rails соответственно обновит существующие записи и создаст новые.
Еще одно примечание: вам все равно нужно обрабатывать новые и существующие записи в контроллере отдельно (проверьте, есть ли: id.present?)