Rails表单中的多个对象

Esp*_*pen 51 forms post ruby-on-rails

我想以一种形式编辑我的模特照片的多个项目.我不确定如何使用表单正确显示和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>
Run Code Online (Sandbox Code Playgroud)

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

在控制器中我想要这样的东西:

@photos = Photo.find( params[photos] )
@photos.each do |photo|
    photo.update_attributes!(params[:photos][photo] )
end
Run Code Online (Sandbox Code Playgroud)

mme*_*ell 76

在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
Run Code Online (Sandbox Code Playgroud)


aus*_*ton 48

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

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

处理多模型形式Rails方式的关键资源是Stephen Chu的params-foo系列,或者如果你在Rails 2.3上,请查看嵌套对象表单

如果您定义正在编辑的某种奇异资源(如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 %>
Run Code Online (Sandbox Code Playgroud)

您的模型应验证每个进入的儿童照片并汇总其错误.您可能想查看一篇关于如何在任何课程中包含Validations的好文章.它可能看起来像这样:

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
Run Code Online (Sandbox Code Playgroud)

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


mlt*_*tsy 19

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 %>
Run Code Online (Sandbox Code Playgroud)

在你的控制器中,你最终会得到一个哈希值params[:photos],其中键是照片ID,值是属性哈希值.


小智 16

您可以使用"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 %>
Run Code Online (Sandbox Code Playgroud)

这将填充输入字段,就像您描述的那样.

在您的控制器中,您可以进行批量更新.

def update
  Photo.update(params[:photo].keys, params[:photo].values)
  ...
end 
Run Code Online (Sandbox Code Playgroud)

  • 请注意,如果您混合新记录和现有记录,Rack 会报错:`TypeError (expected Hash (got Array) for param 'photo')` (2认同)

gam*_*mov 8

事实上,正如Turadg所提到的,如果你在Glen的答案中混合新的和现有的记录,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 %>
Run Code Online (Sandbox Code Playgroud)

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

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