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
Я бы + один это в рельсах ядра ... Продолжайте встречать случай и для этого.
Натан Бертрам

Ответы:


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
Вы также можете использовать недокументированную опцию: child_index для fields_for, если вам нужно больше контроля над отображаемым индексом, например: fields_for (: projects, project, child_index: index)
Андерс

Это рабочая ссылка Rails API api.rubyonrails.org/classes/ActionView/Helpers/…
Archonic

7
Пользователи Rails 4.0.2+ должны проверить ответ Бена, так как индекс теперь встроен в конструктор.
notapatch

1
@ Марко, вы король, сэр. ты спасал мне день ото дня. :-) Хотел бы я дать вам пару 10+ ......!

158

Ответ довольно прост, поскольку решение предоставляется в Rails. Вы можете использовать f.optionsparams. Таким образом, внутри вашей визуализации _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
здесь не работает. Вы можете предоставить ссылку на документацию по рельсам?
Лукас Ренан,

2
@LucasRenan и @graphmeter - пожалуйста, прочтите вопрос еще раз, вам нужно вызвать <%= f.options[:child_index] %>вашу визуализированную подформу (в данном случае: _some_form.html.erb), а не оригинал builder. Ответ обновлен для получения дополнительных разъяснений.
Sheharyar 08

1
Странно, я получаю nilза это
bcackerman

1
@Sheharyar Это работает в Rails 4. Но это дает мне значения вроде «1450681048049,1450681050158,1450681056830,1450681141951,1450681219262». Но мне нужен индекс в таком виде «1,2,3,4,5». Что я должен делать?
Видал

2
Блестяще. Это должен быть абсолютно принятый ответ.
jeffdill2

101

Начиная с Rails 4.0.2, индекс теперь включен в объект FormBuilder:

https://api.rubyonrails.org/classes/ActionView/Helpers/FormBuilder.html#method-i-fields_for

Например:

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

Это работает и меньше писать, чем project_fields.options[:child_index]. Так мне больше нравится!
Кейси

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будучи моим частичным именем ...
Шпигфорд

Задайте вопрос has_many: comments и как вы создаете комментарии, например, в своем новом действии или действии редактирования вопросов попробуйте выполнить 1 раз {question.comments.build}
Сайед Аслам

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]" для третьего отображаемого поля, поэтому я просто сопоставляю индекс в этой строке и использую его.


На самом деле более прохладный (и, может быть, более чистый?) Способ сделать это - передать лямбду и вызвать ее для увеличения.

# /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"
    }
  }
}

Мне пришлось использовать опцию индекса, чтобы заставить работать несколько выпадающих списков. Удачи.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.