Rails表单中的多个对象

时间:2009-06-09 22:40:12

标签: ruby-on-rails forms post

我想以一种形式编辑模型照片的多个项目。我不确定如何使用表单正确显示和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>

参数只是一个例子,就像我上面所说:我不确定以这种形式发布这些值的最佳方法。

在控制器中,我希望这样:

@photos = Photo.find( params[photos] )
@photos.each do |photo|
    photo.update_attributes!(params[:photos][photo] )
end

5 个答案:

答案 0 :(得分:75)

在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

答案 1 :(得分:48)

UPDATE :此答案适用于Rails 2,或者如果您有需要自定义逻辑的特殊约束。使用其他地方讨论过的fields_for可以很好地解决这些简单的案例。

Rails不会帮助你做很多事情。它违反了标准视图约定,因此您必须在视图,控制器甚至路径中进行变通方法。这没什么好玩的。

处理多模型形式Rails方式的关键资源是Stephen Chu's params-foo series,或者如果您使用的是Rails 2.3,请查看Nested Object Forms

如果您定义正在编辑的某种单一资源(如Photoset),则会变得更加容易。 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 %>

您的模型应该验证每个孩子的照片并汇总他们的错误。您可以查看a good article on how to include Validations in any class。它可能看起来像这样:

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的外观模型,您可以使控制器和视图逻辑简单明了,为专用模型保留最复杂的代码。这段代码可能不会开箱即用,但希望它会给你一些想法,并指出你正确的方向来解决你的问题。

答案 2 :(得分:18)

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]中找到一个哈希值,其中的键是照片ID,值是属性哈希值。

答案 3 :(得分:15)

您可以使用“model name []”语法来表示多个对象。

在视图中,使用“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 

答案 4 :(得分:8)

事实上,正如Turadg所说,如果你混合使用新的&amp; amp; 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 %>

如果你问我,这是非常难看的,但这是我发现在混合新记录和现有记录时编辑多个记录的唯一方法。 这里的技巧是,params散列不是拥有一个记录数组,而是获取一个哈希数组(用i,0,1,2等编号)和记录哈希中的id。 Rails将相应地更新现有记录并创建新记录。

还有一点需要注意:您仍然需要单独处理控制器中的新记录和现有记录(检查是否:id.present?)