Rails: fields_for con index?

102

¿Existe algún método (o forma de lograr una funcionalidad similar) para hacer una fields_for_with_index?

Ejemplo:

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

Ese renderizado parcial necesita saber cuál es el índice actual en el fields_forciclo.

Shpigford
fuente
3
Yo haría + uno en esto en los rieles centrales ... Sigue encontrando un caso para este también.
Nathan Bertram

Respuestas:

92

En realidad, este sería un mejor enfoque, siguiendo más de cerca la documentación de 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 %>

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

También es posible especificar la instancia que se utilizará:

  <%= 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 %>
Marco Lazzeri
fuente
2
Ojalá hubiera algo integrado en fields_for para esto, pero como no hay tu respuesta me salvó el día. Gracias.
Anders Kindberg
1
También puede usar la opción no documentada: child_index en fields_for, si necesita más control sobre qué índice se representa, así: fields_for (: proyectos, proyecto, child_index: index)
Anders Kindberg
Este es el enlace de la API de Rails en funcionamiento api.rubyonrails.org/classes/ActionView/Helpers/…
Archonic
7
Los usuarios de Rails 4.0.2+ deben consultar la respuesta de Ben, ya que el índice ahora está integrado en el constructor.
notapatch
1
@Marco Usted es el rey señor. salvaste mi día a día. :-) ¡Ojalá pudiera darte un par de 10 + ......!
157

La respuesta es bastante simple ya que la solución se proporciona dentro de Rails. Puede utilizar f.optionsparams. Entonces, dentro de tu renderizado _some_form.html.erb,

Se puede acceder al índice mediante:

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

No necesitas hacer nada más.


Actualización: Parece que mi respuesta no fue lo suficientemente clara ...

Archivo HTML original:

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

Subformulario renderizado:

<!-- _some_form.html.erb -->
<%= f.options[:child_index] %>
Sheharyar
fuente
10
no funciona aquí. ¿Puede proporcionar un enlace de documentación de rieles?
Lucas Renan
2
@LucasRenan & @graphmeter: lea la pregunta nuevamente, debe llamar <%= f.options[:child_index] %>en su subformulario renderizado (en este caso: _some_form.html.erb), no en el original builder. Respuesta actualizada para mayor aclaración.
Sheharyar
1
Extraño, me sale nilpor eso
bcackerman
1
@Sheharyar Esto está funcionando en Rails 4. Pero esto me está dando valores como '1450681048049,1450681050158,1450681056830,1450681141951,1450681219262'. Pero necesito un índice en esta forma '1,2,3,4,5'. ¿Qué tengo que hacer?
Vidal
2
Brillante. Esta debería ser absolutamente la respuesta aceptada.
jeffdill2
100

A partir de Rails 4.0.2, ahora se incluye un índice en el objeto FormBuilder:

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

Por ejemplo:

<%= form_for @person do |person_form| %>
  ...
  <%= person_form.fields_for :projects do |project_fields| %>
    Project #<%= project_fields.index %>
  ...
  <% end %>
  ...
<% end %>
Ben
fuente
Esto funciona y es menos para escribir que project_fields.options[:child_index]. ¡Así que me gusta más de esta manera!
Casey
17

Para rieles 4+

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

Soporte de Monkey Patch para Rails 3

Para empezar f.indexa trabajar en Rails 3, debe agregar un parche de mono a los inicializadores de sus proyectos para agregar esta funcionalidad afields_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
Weston Ganger
fuente
Mejor respuesta hasta ahora, el uso .indexes mucho más limpio.
Lucas Andrade
7

Realizar pedido Representación de una colección de parciales . Si su requisito es que una plantilla necesita iterar sobre una matriz y representar una subplantilla para cada uno de los elementos.

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

Esto representará “_children.erb” y pasará la variable local 'niños' a la plantilla para su visualización. Se pondrá automáticamente a disposición de la plantilla un contador de iteraciones con un nombre del formulario partial_name_counter. En el caso del ejemplo anterior, se alimentaría la plantilla children_counter.

Espero que esto ayude.

Syed Aslam
fuente
Obtengo "variable local indefinida o método 'question_comment_form_counter'" al hacer eso. question_comment_formsiendo mi nombre parcial ...
Shpigford
Haga la pregunta has_many: comentarios y cómo está creando comentarios, por ejemplo, en su acción nueva o de edición de las preguntas, intente hacer 1 veces {question.comments.build}
Syed Aslam
5

No veo una forma decente de hacer esto a través de las formas proporcionadas por Rails, al menos no en -v3.2.14

@Sheharyar Naseer hace referencia a las opciones hash que se pueden usar para resolver el problema, pero no tanto como puedo ver en la forma en que parece sugerir.

Hice esto =>

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

o

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

En mi caso, g.object_namedevuelve una cadena como esta "gallery_set[blog_posts_attributes][2]" para el tercer campo renderizado, así que simplemente hago coincidir el índice en esa cadena y lo uso.


En realidad, una forma más fresca (¿y tal vez más limpia?) De hacerlo es pasar un lambda y llamarlo para incrementar.

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

Y en la vista

<%= 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 %>
Inultos
fuente
1

Sé que esto es un poco tarde, pero recientemente tuve que hacer esto, puedes obtener el índice de los campos_para esto

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

Espero que esto ayude :)

MZaragoza
fuente
1

Agregado a 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 %>
gilcierweb
fuente
Esta es la nueva mejor respuesta.
genkilabs
¿Alguien más obtiene campos duplicados con esto?
0

Si desea tener control sobre los índices, consulte la indexopción

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

Esto producirá

<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">

Si se envía el formulario, los parámetros incluirán algo como

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

Tuve que usar la opción de índice para que mis menús desplegables múltiples funcionaran. Buena suerte.

Cruz Núñez
fuente