Rails: fields_for mit Index?

100

Gibt es eine Methode (oder eine Möglichkeit, ähnliche Funktionen abzurufen), um eine fields_for_with_indexauszuführen?

Beispiel:

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

Dieser Teil, der gerendert wird, muss wissen, was der aktuelle Index in der fields_forSchleife ist.

Shpigford
quelle
3
Ich würde dies in Kernschienen eins ... ... Auch für diesen Fall stoßen wir immer wieder auf einen Fall.
Nathan Bertram

Antworten:

91

Dies wäre tatsächlich ein besserer Ansatz, wenn Sie die Rails-Dokumentation genauer verfolgen:

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

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

Es ist auch möglich, die zu verwendende Instanz anzugeben:

  <%= 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
quelle
2
Ich wünschte, es wäre etwas in fields_for eingebaut, aber da es keine Antwort gibt, hat das meinen Tag gerettet. Vielen Dank.
Anders Kindberg
1
Sie können auch die undokumentierte Option verwenden: child_index für fields_for, wenn Sie mehr Kontrolle darüber benötigen, welcher Index gerendert wird, wie folgt: fields_for (: Projekte, Projekt, child_index: index)
Anders Kindberg
Dies ist der funktionierende Rails API Link api.rubyonrails.org/classes/ActionView/Helpers/…
Archonic
7
Benutzer von Rails 4.0.2+ sollten Bens Antwort lesen, da der Index jetzt in den Builder integriert ist.
Notapatch
1
@ Marco Sie sind der König Sir. Du hast meinen Tag für Tag gerettet. :-) Ich wünschte, ich könnte dir ein paar 10 + geben ......!
157

Die Antwort ist recht einfach, da die Lösung in Rails bereitgestellt wird. Sie können f.optionsParameter verwenden. Also, in deinem gerenderten _some_form.html.erb,

Auf den Index kann zugegriffen werden durch:

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

Sie müssen nichts anderes tun.


Update: Es scheint, dass meine Antwort nicht klar genug war ...

Original HTML Datei:

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

Gerendertes Unterformular:

<!-- _some_form.html.erb -->
<%= f.options[:child_index] %>
Sheharyar
quelle
10
es funktioniert hier nicht. Können Sie einen Link zur Schienen-Dokumentation bereitstellen?
Lucas Renan
2
@LucasRenan & @graphmeter - Bitte lesen Sie die Frage noch einmal. Sie müssen <%= f.options[:child_index] %>Ihr gerendertes Unterformular (in diesem Fall: _some_form.html.erb) aufrufen, nicht das Original builder. Die Antwort wurde zur weiteren Klärung aktualisiert.
Sheharyar
1
nil
Seltsam
1
@ Sheharyar Dies funktioniert in Rails 4. Aber dies gibt mir Werte wie '1450681048049,1450681050158,1450681056830,1450681141951,1450681219262'. Aber ich brauche Index in dieser Form '1,2,3,4,5'. Was soll ich machen?
Vidal
2
Brillant. Dies sollte unbedingt die akzeptierte Antwort sein.
Jeffdill2
100

Ab Rails 4.0.2 ist jetzt ein Index im FormBuilder-Objekt enthalten:

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

Beispielsweise:

<%= form_for @person do |person_form| %>
  ...
  <%= person_form.fields_for :projects do |project_fields| %>
    Project #<%= project_fields.index %>
  ...
  <% end %>
  ...
<% end %>
Ben
quelle
Das funktioniert und ist weniger zu schreiben als project_fields.options[:child_index]. Also ich mag diesen Weg besser!
Casey
16

Für Schienen 4+

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

Monkey Patch für Rails 3-Unterstützung

Um f.indexin Rails 3 arbeiten zu können, müssen Sie Ihren Projektinitialisierern einen Affen-Patch hinzufügen, um diese Funktionalität hinzuzufügenfields_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
quelle
Bessere Antwort bisher, Verwendung .indexist viel sauberer.
Lucas Andrade
7

Kasse Rendern einer Sammlung von Partials . Wenn Ihre Anforderung lautet, dass eine Vorlage über ein Array iterieren und für jedes der Elemente eine Untervorlage rendern muss.

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

Dadurch wird „_children.erb“ gerendert und die lokale Variable 'children' zur Anzeige an die Vorlage übergeben. Der Vorlage wird automatisch ein Iterationszähler mit dem Namen des Formulars zur Verfügung gestellt partial_name_counter. Im obigen Beispiel würde die Vorlage eingezogen children_counter.

Hoffe das hilft.

Syed Aslam
quelle
Dabei erhalte ich "undefinierte lokale Variable oder Methode 'question_comment_form_counter'". question_comment_formals mein
Teilname
Frage stellen has_many: Kommentare und wie erstellen Sie Kommentare zum Beispiel in Ihrer neuen oder Bearbeitungsaktion der Fragen? Versuchen Sie, 1.times {question.comments.build}
Syed Aslam
5

Ich sehe keinen vernünftigen Weg, dies durch die von Rails bereitgestellten Wege zu tun, zumindest nicht in -v3.2.14

@Sheharyar Naseer verweist auf den Options-Hash, der zur Lösung des Problems verwendet werden kann, aber nicht so weit ich sehen kann, wie er es vorzuschlagen scheint.

Ich habe das gemacht =>

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

oder

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

In meinem Fall wird g.object_nameeine solche Zeichenfolge "gallery_set[blog_posts_attributes][2]" für das dritte gerenderte Feld zurückgegeben, sodass ich nur mit dem Index in dieser Zeichenfolge übereinstimme und ihn verwende.


Eine kühlere (und vielleicht sauberere?) Methode besteht darin, ein Lambda zu übergeben und es zum Inkrementieren aufzurufen.

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

Und das in der Aussicht

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

Ich weiß, dass dies etwas spät ist, aber ich musste dies kürzlich tun. Sie können den Index der Felder dafür abrufen

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

Ich hoffe das hilft :)

MZaragoza
quelle
1

Zu fields_for child_index hinzugefügt: 0

<%= form_for @person do |person_form| %>
  <%= person_form.fields_for :projects, child_index: 0 do |project_fields| %>
    <%= project_fields.index %>
  <% end %>
<% end %>
Gilcierweb
quelle
Dies ist die neue beste Antwort.
Genkilabs
Bekommt sonst noch jemand doppelte Felder damit?
0

Wenn Sie die Kontrolle über die Indizes haben möchten, aktivieren Sie die 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 =>

Dies wird produzieren

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

Wenn das Formular gesendet wird, enthalten die Parameter so etwas wie

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

Ich musste die Indexoption verwenden, damit meine Multi-Dropdowns funktionieren. Viel Glück.

Cruz Nunez
quelle