代码之家  ›  专栏  ›  技术社区  ›  Espen

Rails窗体中的多个对象

  •  48
  • Espen  · 技术社区  · 16 年前

    我想在一个窗体中编辑我的模型照片的多个项目。我不确定如何正确地呈现和发布这个表单,以及如何在控制器中收集更新操作中的项目。

    这就是我想要的:

    <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 回复  |  直到 16 年前
        1
  •  78
  •   mmell Daniel Rearden    11 年前

    <%= form_tag photos_update_path do %>
      <% @photos.each do |photo| %> 
        <%= fields_for "photos[]", photo do |pf| %>
          <%= pf.text_field :caption %>
          ... other photo fields
    
        2
  •  49
  •   austinfromboston    9 年前

    更新 :这个答案适用于Rails 2,或者如果您有需要自定义逻辑的特殊约束。如其他地方所讨论的,可以使用字段\u很好地处理简单的情况。

    Rails在这方面帮不了你多少忙。它违反了标准的视图约定,因此您必须在视图、控制器甚至路由中进行变通。那可不好玩。

    在铁路上处理多模型形式的关键资源是 Stephen Chu's params-foo series Nested Object Forms

    如果你定义一种你正在编辑的单一资源,比如一张照片集,那就容易多了。Photoset可以是一个真实的ActiveRecord类型的模型,也可以只是一个接受数据并抛出错误的facade,就好像它是一个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使用facade模型,您可以保持控制器和视图逻辑简单明了,为专用模型保留最复杂的代码。这段代码可能不会开箱即用,但希望它能给您一些想法,并为您指出解决问题的正确方向。

        3
  •  21
  •   mltsy    12 年前

    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,值是属性哈希。

        4
  •  17
  •   Arslan Ali    9 年前

    您可以使用“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 
    
        5
  •  11
  •   gamov    10 年前

    实际上,正如Turadg所提到的,如果您混合使用新的;在格伦的回答中已有的记录。

    <%= 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 %>
    

    如果你问我这是相当难看,但这是唯一的方法,我发现编辑多个记录,同时混合新的和现有的记录。

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