web-dev-qa-db-fra.com

Rails: fields_for avec index?

Existe-t-il une méthode (ou un moyen de retirer une fonctionnalité similaire) pour créer un fields_for_with_index?

Exemple: 

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

Ce rendu partiel doit savoir quel est l'index actuel dans la boucle fields_for.

86
Shpigford

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 le exemple à 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 %>
85
Marco Lazzeri

La réponse est assez simple car la solution est fournie dans Rails. Vous pouvez utiliser f.options params. Donc, dans votre rendu _some_form.html.erb

Index peut être consulté 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 original:

<!-- 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] %>
142
Sheharyar

A partir de Rails 4.0.2, un index est maintenant inclus dans l'objet FormBuilder:

http://apidock.com/Rails/v4.0.2/ActionView/Helpers/FormHelper/fields_for

Par exemple:

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

Pour Rails 4+

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

Monkey Patch pour Rails 3 Support

Pour que f.index fonctionne 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
12
Weston Ganger

Commander Rendre une collection de partiels . Si votre exigence est qu'un modèle doit parcourir un tableau et générer 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 transmettra la variable locale "enfants" au modèle pour l'affichage. Un compteur d'itérations sera automatiquement mis à la disposition du modèle avec un nom de la forme 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.

7
Syed Aslam

Je ne vois pas comment faire de manière décente à travers les méthodes fournies par Rails, du moins pas dans -v3.2.14.

@ Sheharyar Naseer fait référence au hachage des options qui peut être utilisé pour résoudre le problème, mais pas aussi loin que je puisse le voir dans la façon dont il semble le suggérer.

J'ai fait ceci =>

<%= 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_name renvoie une chaîne telle que celle-ci "gallery_set[blog_posts_attributes][2]" pour le troisième champ affiché, je ne fais donc que correspondre à l'index de cette chaîne et l'utiliser.


En fait, une façon plus froide (et peut-être plus propre?) De le faire est de passer un lambda et de l'appeler pour augmenter.

# /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 %>
6
iNulty

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

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

J'espère que ca aide :) 

1
MZaragoza

Si vous souhaitez contrôler les index, cochez l’option 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 =>

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, params inclura quelque chose comme

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

J'ai dû utiliser l'option index pour que mes multi-menus déroulants fonctionnent. Bonne chance.

0
Cruz Nunez