Rails:带索引的fields_for?


102

是否有一种方法(或实现类似功能的方法)来完成fields_for_with_index

例:

<% f.fields_for_with_index :questions do |builder, index| %>  
  <%= render 'some_form', :f => builder, :i => index %>
<% end %>

呈现的部分内容需要知道fields_for循环中的当前索引。


3
我会在核心导轨中加一个……继续为这个加装一个保护套。
内森·伯特拉姆

Answers:


92

实际上,按照Rails的相关文档,这实际上是一种更好的方法:

<% @questions.each.with_index do |question,index| %>
    <% f.fields_for :questions, question do |fq| %>  
        # here you have both the 'question' object and the current 'index'
    <% end %>
<% end %>

来自:http : //railsapi.com/doc/rails-v3.0.4/classes/ActionView/Helpers/FormHelper.html#M006456

也可以指定要使用的实例:

  <%= form_for @person do |person_form| %>
    ...
    <% @person.projects.each do |project| %>
      <% if project.active? %>
        <%= person_form.fields_for :projects, project do |project_fields| %>
          Name: <%= project_fields.text_field :name %>
        <% end %>
      <% end %>
    <% end %>
  <% end %>

2
我希望在fields_for中为此内置一些内容,但是由于没有您的答案,这使我不胜其烦。谢谢。
安德斯·金伯格

1
如果您需要对呈现的索引进行更多控制,也可以在fields_for上使用未公开的选项:child_index,例如:fields_for(:projects,project,child_index:index)
Anders Kindberg


7
Rails 4.0.2+用户应该检查Ben的答案,因为索引已内置到构建器中。
notapatch 2014年

1
@马可你是国王先生。你救了我的日常。:-)希望我能给你十个以上……!

157

答案非常简单,因为该解决方案是在Rails中提供的。您可以使用f.options参数。因此,在您渲染的内部_some_form.html.erb

可以通过以下方式访问索引:

<%= f.options[:child_index] %>

您无需执行其他任何操作。


更新:看来我的回答还不够清楚...

原始HTML文件:

<!-- Main ERB File -->
<% f.fields_for :questions do |builder| %>  
  <%= render 'some_form', :f => builder %>
<% end %>

呈现的子表格:

<!-- _some_form.html.erb -->
<%= f.options[:child_index] %>

10
它在这里不工作。您可以提供Rails文档链接吗?
卢卡斯·雷南

2
@LucasRenan&@graphmeter-请再次阅读问题,您需要调用<%= f.options[:child_index] %>呈现的子表单(在这种情况下为_some_form.html.erb),而不是原始表单builder。答案已更新,以进一步说明。
Sheharyar

1
奇怪,我明白nil
bcackerman 2014年

1
@Sheharyar,这在Rails 4中工作。但这给了我诸如'1450681048049,1450681050158,1450681056830,1450681141951,1450681219262262'的值。但是我需要这种形式的索引'1,2,3,4,5'。我该怎么办?
vidal

2
辉煌。这绝对是公认的答案。
jeffdill2 '16


17

对于Rails 4+

<%= form_for @person do |person_form| %>
  <%= person_form.fields_for :projects do |project_fields| %>
    <%= project_fields.index %>
  <% end %>
<% end %>

猴子补丁的Rails 3支持

为了f.index在Rails 3中工作,您需要在项目初始值设定项中添加一个猴子补丁,才能将该功能添加到fields_for

# config/initializers/fields_for_index_patch.rb

module ActionView
  module Helpers
    class FormBuilder

      def index
        @options[:index] || @options[:child_index]
      end

      def fields_for(record_name, record_object = nil, fields_options = {}, &block)
        fields_options, record_object = record_object, nil if record_object.is_a?(Hash) && record_object.extractable_options?
        fields_options[:builder] ||= options[:builder]
        fields_options[:parent_builder] = self
        fields_options[:namespace] = options[:namespace]

        case record_name
          when String, Symbol
            if nested_attributes_association?(record_name)
              return fields_for_with_nested_attributes(record_name, record_object, fields_options, block)
            end
          else
            record_object = record_name.is_a?(Array) ? record_name.last : record_name
            record_name   = ActiveModel::Naming.param_key(record_object)
        end

        index = if options.has_key?(:index)
                  options[:index]
                elsif defined?(@auto_index)
                  self.object_name = @object_name.to_s.sub(/\[\]$/,"")
                  @auto_index
                end

        record_name = index ? "#{object_name}[#{index}][#{record_name}]" : "#{object_name}[#{record_name}]"
        fields_options[:child_index] = index

        @template.fields_for(record_name, record_object, fields_options, &block)
      end

      def fields_for_with_nested_attributes(association_name, association, options, block)
        name = "#{object_name}[#{association_name}_attributes]"
        association = convert_to_model(association)

        if association.respond_to?(:persisted?)
          association = [association] if @object.send(association_name).is_a?(Array)
        elsif !association.respond_to?(:to_ary)
          association = @object.send(association_name)
        end

        if association.respond_to?(:to_ary)
          explicit_child_index = options[:child_index]
          output = ActiveSupport::SafeBuffer.new
          association.each do |child|
            options[:child_index] = nested_child_index(name) unless explicit_child_index
            output << fields_for_nested_model("#{name}[#{options[:child_index]}]", child, options, block)
          end
          output
        elsif association
          fields_for_nested_model(name, association, options, block)
        end
      end

    end
  end
end

到目前为止,答案更好,使用.index起来更清洁。
卢卡斯·安德拉德

7

签出呈现局部集合。如果您的要求是模板需要遍历数组并为每个元素呈现一个子模板。

<%= f.fields_for @parent.children do |children_form| %>
  <%= render :partial => 'children', :collection => @parent.children, 
      :locals => { :f => children_form } %>
<% end %>

这将呈现“ _children.erb”,并将局部变量“ children”传递到模板以进行显示。带有模板名称的模板将自动为模板提供迭代计数器partial_name_counter。在上面的示例中,模板将被馈送children_counter

希望这可以帮助。


这样做时,我得到“未定义的局部变量或方法'question_comment_form_counter'”。question_comment_form是我的部分名字...
Shpigford

做has_many问题:评论,以及如何在问题的新动作或编辑动作中构建评论,例如尝试进行1.times {question.comments.build}
Syed Aslam

5

我看不到通过Rails提供的方式做到这一点的一种体面的方法,至少在-v3.2.14中没有。

@Sheharyar Naseer引用了可用于解决问题的选项哈希,但据我看来,他似乎并不建议这样做。

我这样做=>

<%= f.fields_for :blog_posts, {:index => 0} do |g| %>
  <%= g.label :gallery_sets_id, "Position #{g.options[:index]}" %>
  <%= g.select :gallery_sets_id, @posts.collect  { |p| [p.title, p.id] } %>
  <%# g.options[:index] += 1  %>
<% end %>

要么

<%= f.fields_for :blog_posts do |g| %>
  <%= g.label :gallery_sets_id, "Position #{g.object_name.match(/(\d+)]/)[1]}" %>
  <%= g.select :gallery_sets_id, @posts.collect  { |p| [p.title, p.id] } %>
<% end %>

在我的情况下, 为呈现的第三个字段g.object_name返回这样的字符串"gallery_set[blog_posts_attributes][2]",因此我只匹配该字符串中的索引并使用它。


实际上,一种更凉爽(也许更清洁?)的方法是传递一个lambda并将其称为递增。

# /controller.rb
index = 0
@incrementer = -> { index += 1}

而在视图

<%= f.fields_for :blog_posts do |g| %>
  <%= g.label :gallery_sets_id, "Position #{@incrementer.call}" %>
  <%= g.select :gallery_sets_id, @posts.collect  { |p| [p.title, p.id] } %>
<% end %>

1

我知道这有点晚了,但是我最近不得不这样做,您可以像这样获取fields_for的索引

<% f.fields_for :questions do |builder| %>
  <%= render 'some_form', :f => builder, :i => builder.options[:child_index] %>
<% end %>

我希望这个对你有用 :)


1

添加到fields_for child_index:0

<%= form_for @person do |person_form| %>
  <%= person_form.fields_for :projects, child_index: 0 do |project_fields| %>
    <%= project_fields.index %>
  <% end %>
<% end %>

这是新的最佳答案。
genkilabs

有人得到与此重复的字段吗?

0

如果您想控制索引,请查看该index选项

<%= f.fields_for :other_things_attributes, @thing.other_things.build do |ff| %>
  <%= ff.select :days, ['Mon', 'Tues', 'Wed'], index: 2 %>
  <%= ff.hidden_field :special_attribute, 24, index: "boi" %>
<%= end =>

这将产生

<select name="thing[other_things_attributes][2][days]" id="thing_other_things_attributes_7_days">
  <option value="Mon">Mon</option>
  <option value="Tues">Tues</option>
  <option value="Wed">Wed</option>
</select>
<input type="hidden" value="24" name="thing[other_things_attributes][boi][special_attribute]" id="thing_other_things_attributes_boi_special_attribute">

如果提交表单,params将包含类似

{
  "thing" => {
  "other_things_attributes" => {
    "2" => {
      "days" => "Mon"
    },
    "boi" => {
      "special_attribute" => "24"
    }
  }
}

我必须使用index选项才能使多个下拉菜单正常工作。祝好运。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.