Rails: fields_for avec index?

102

Existe-t-il une méthode (ou un moyen d'obtenir des fonctionnalités similaires) pour faire un fields_for_with_index?

Exemple:

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

Ce partiel rendu a besoin de savoir quel est l'index actuel dans la fields_forboucle.

Shpigford
la source
3
Je ferais + un ceci dans les rails de base ... Continuez à trouver un cas pour celui-ci aussi.
Nathan Bertram

Réponses:

92

Ce serait en fait une meilleure approche, en suivant de plus près la documentation 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

Il est également possible de spécifier l'instance à utiliser:

  <%= 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
la source
2
J'aurais aimé qu'il y ait quelque chose de construit dans fields_for pour cela, mais comme il n'y a pas de réponse, ma réponse m'a sauvé la journée. Merci.
Anders Kindberg
1
Vous pouvez également utiliser l'option non documentée: child_index sur fields_for, si vous avez besoin de plus de contrôle sur l'index rendu, comme ceci: fields_for (: projects, project, child_index: index)
Anders Kindberg
Ceci est le lien API Rails fonctionnel api.rubyonrails.org/classes/ActionView/Helpers/…
Archonic
7
Les utilisateurs de Rails 4.0.2+ devraient consulter la réponse de Ben car l'index est maintenant intégré au constructeur.
notapatch
1
@Marco Vous êtes le roi Monsieur. tu m'as sauvé au jour le jour. :-) J'aimerais pouvoir vous donner quelques 10 + ......!
157

La réponse est assez simple car la solution est fournie dans Rails. Vous pouvez utiliser des f.optionsparamètres. Donc, à l'intérieur de votre rendu _some_form.html.erb,

L'index est accessible par:

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

Vous n'avez rien d'autre à faire.


Mise à jour: Il semble que ma réponse n'était pas assez claire ...

Fichier HTML d'origine:

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

Sous-formulaire rendu:

<!-- _some_form.html.erb -->
<%= f.options[:child_index] %>
Sheharyar
la source
10
ça ne marche pas ici. pouvez-vous fournir un lien de documentation sur les rails?
Lucas Renan
2
@LucasRenan & @graphmeter - Veuillez relire la question, vous devez appeler <%= f.options[:child_index] %>votre sous-formulaire rendu (dans ce cas: _some_form.html.erb), pas dans l'original builder. Réponse mise à jour pour plus de précisions.
Sheharyar
1
Odd, je suis nilpour ça
bcackerman
1
@Sheharyar Cela fonctionne dans Rails 4. Mais cela me donne des valeurs comme '1450681048049,1450681050158,1450681056830,1450681141951,1450681219262'. Mais j'ai besoin d'un index sous cette forme «1,2,3,4,5». Que devrais-je faire?
vidal
2
Brillant. Cela devrait absolument être la réponse acceptée.
jeffdill2
17

Pour Rails 4+

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

Support de Monkey Patch pour Rails 3

Pour vous mettre f.indexau travail dans Rails 3, vous devez ajouter un patch monkey aux initialiseurs de vos projets pour ajouter cette fonctionnalité à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
Weston Ganger
la source
Meilleure réponse jusqu'à présent, l'utilisation .indexest beaucoup plus propre.
Lucas Andrade
7

Checkout Rendu d'une collection de partiels . Si votre exigence est qu'un modèle doit itérer sur un tableau et rendre un sous-modèle pour chacun des éléments.

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

Cela rendra «_children.erb» et passera la variable locale «children» au modèle pour affichage. Un compteur d'itérations sera automatiquement mis à disposition du modèle avec un nom de formulaire partial_name_counter. Dans le cas de l'exemple ci-dessus, le modèle serait alimenté children_counter.

J'espère que cela t'aides.

Syed Aslam
la source
J'obtiens "une variable locale ou une méthode non définie 'question_comment_form_counter'" en faisant cela. question_comment_formétant mon nom partiel ...
Shpigford
Faites la question has_many: commentaires et comment construisez-vous des commentaires, par exemple dans votre action nouvelle ou d'édition des questions, essayez de faire 1. fois {question.comments.build}
Syed Aslam
5

Je ne vois pas de moyen décent de le faire à travers les moyens fournis par Rails, du moins pas dans -v3.2.14

@Sheharyar Naseer fait référence aux options de hachage qui peuvent être utilisées pour résoudre le problème, mais pas aussi loin que je puisse voir dans la manière qu'il semble suggérer.

J'ai fait ça =>

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

ou

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

Dans mon cas, g.object_namerenvoie une chaîne comme celle-ci "gallery_set[blog_posts_attributes][2]" pour le troisième champ rendu afin que je fasse simplement correspondre l'index de cette chaîne et l'utilise.


En fait, une façon plus fraîche (et peut-être plus propre?) De le faire est de passer un lambda et de l'appeler à incrémenter.

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

Et le dans la vue

<%= 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 %>
iNulty
la source
1

Je sais que c'est un peu tard mais j'ai récemment dû le faire vous pouvez obtenir l'index des champs_pour comme ça

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

J'espère que ca aide :)

MZaragoza
la source
1

Ajouté à 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
la source
C'est la nouvelle meilleure réponse.
genkilabs
Quelqu'un d'autre obtient-il des champs dupliqués avec ça?
0

Si vous souhaitez contrôler les index, cochez l' indexoption

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

Cela produira

<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 le formulaire est soumis, les paramètres incluront quelque chose comme

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

J'ai dû utiliser l'option d'index pour faire fonctionner mes menus déroulants multiples. Bonne chance.

Cruz Nunez
la source