Rails: field_for dengan indeks?

102

Apakah ada metode (atau cara untuk melakukan fungsi serupa) untuk melakukan a fields_for_with_index ?

Contoh:

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

Parsial yang sedang dirender perlu mengetahui apa indeks saat ini di fields_forloop.

Shpigford
sumber
3
Saya akan + satu ini di rel inti ... Terus temukan kasus untuk yang ini juga.
Nathan Bertram

Jawaban:

92

Ini sebenarnya akan menjadi pendekatan yang lebih baik, mengikuti dokumentasi Rails lebih dekat:

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

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

Ini juga memungkinkan untuk menentukan instance yang akan digunakan:

  <%= 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
sumber
2
Saya berharap ada sesuatu yang dibangun ke dalam fields_for untuk ini, tetapi karena tidak ada jawaban Anda yang menyelamatkan hari saya. Terima kasih.
Anders Kindberg
1
Anda juga dapat menggunakan opsi tidak berdokumen: child_index di field_for, jika Anda membutuhkan lebih banyak kontrol atas indeks mana yang dirender, seperti ini: fields_for (: projects, project, child_index: index)
Anders Kindberg
Ini adalah tautan API Rails yang berfungsi api.rubyonrails.org/classes/ActionView/Helpers/…
Archonic
7
Pengguna Rails 4.0.2+ harus memeriksa jawaban Ben karena indeks sekarang dibangun ke dalam pembangun.
notapatch
1
@Marco Anda adalah raja Pak. Anda menyelamatkan saya dari hari ke hari. :-) Seandainya aku bisa memberimu 10+ ......!
157

Jawabannya cukup sederhana karena solusinya disediakan dalam Rails. Anda dapat menggunakan f.optionsparams. Jadi, di dalam hasil render Anda _some_form.html.erb,

Indeks dapat diakses oleh:

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

Anda tidak perlu melakukan apa pun.


Memperbarui: Sepertinya jawaban saya tidak cukup jelas ...

File HTML Asli:

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

Sub-Formulir yang Diberikan:

<!-- _some_form.html.erb -->
<%= f.options[:child_index] %>
Sheharyar
sumber
10
tidak berfungsi di sini. dapatkah Anda memberikan tautan dokumentasi rel?
Lucas Renan
2
@LucasRenan & @graphmeter - Harap baca pertanyaan itu lagi, Anda perlu memanggil <%= f.options[:child_index] %>dalam sub-formulir yang telah Anda berikan (Dalam hal ini: _some_form.html.erb), bukan ke aslinya builder. Jawaban diperbarui untuk klarifikasi lebih lanjut.
Sheharyar
1
Aneh, saya mendapatkan niluntuk itu
bcackerman
1
@Sheharyar Ini bekerja di Rails 4. Tapi ini memberi saya nilai seperti '1450681048049,1450681050158,1450681056830,1450681141951,1450681219262'. Tapi saya membutuhkan indeks dalam bentuk ini '1,2,3,4,5'. Apa yang harus saya lakukan?
vidal
2
Cemerlang. Ini benar-benar harus menjadi jawaban yang diterima.
jeffdill2
100

Mulai Rails 4.0.2, indeks sekarang disertakan dalam objek FormBuilder:

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

Sebagai contoh:

<%= form_for @person do |person_form| %>
  ...
  <%= person_form.fields_for :projects do |project_fields| %>
    Project #<%= project_fields.index %>
  ...
  <% end %>
  ...
<% end %>
Ben
sumber
Ini bekerja dan lebih sedikit untuk ditulis daripada project_fields.options[:child_index]. Jadi saya lebih suka cara ini!
Casey
17

Untuk Rails 4+

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

Dukungan Monkey Patch For Rails 3

Untuk mulai f.indexbekerja di Rails 3 Anda perlu menambahkan patch monyet ke penginisialisasi proyek Anda untuk menambahkan fungsionalitas inifields_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
sumber
Jawaban yang lebih baik sejauh ini, penggunaan .indexjauh lebih bersih.
Lucas Andrade
7

Checkout Merender koleksi parsial . Jika persyaratan Anda adalah bahwa templat perlu melakukan iterasi pada larik dan merender sub templat untuk setiap elemen.

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

Ini akan merender "_children.erb" dan meneruskan variabel lokal 'children' ke template untuk ditampilkan. Penghitung iterasi secara otomatis akan tersedia untuk templat dengan nama formulir partial_name_counter. Dalam kasus contoh di atas, template akan diumpankan children_counter.

Semoga ini membantu.

Syed Aslam
sumber
Saya mendapatkan "variabel lokal atau metode tidak terdefinisi 'question_comment_form_counter'" saat melakukan itu. question_comment_formmenjadi nama parsial saya ...
Shpigford
Lakukan pertanyaan has_many: komentar dan bagaimana Anda membangun komentar misalnya dalam tindakan baru atau edit pertanyaan coba lakukan 1.times {question.comments.build}
Syed Aslam
5

Saya tidak dapat melihat cara yang layak untuk melakukan ini melalui cara-cara yang disediakan oleh Rails, setidaknya tidak di -v3.2.14

@Sheharyar Naseer membuat referensi ke opsi hash yang dapat digunakan untuk memecahkan masalah tetapi tidak sejauh yang saya lihat dari cara yang tampaknya dia sarankan.

Saya melakukan ini =>

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

atau

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

Dalam kasus saya g.object_namemengembalikan string seperti ini "gallery_set[blog_posts_attributes][2]" untuk bidang ketiga yang diberikan jadi saya hanya mencocokkan indeks dalam string itu dan menggunakannya.


Sebenarnya cara yang lebih keren (dan mungkin lebih bersih?) Untuk melakukannya adalah dengan melewatkan lambda dan memanggilnya ke increment.

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

Dan dalam tampilan

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

Saya tahu ini agak terlambat tetapi saya baru-baru ini harus melakukan ini, Anda bisa mendapatkan indeks field_for seperti ini

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

Saya harap ini membantu :)

MZaragoza
sumber
1

Ditambahkan ke field_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.dll
sumber
Ini adalah jawaban terbaik yang baru.
genkilabs
Adakah orang lain yang mendapatkan bidang duplikat dengan ini?
0

Jika Anda ingin memiliki kendali atas indeks, periksa indexopsinya

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

Ini akan menghasilkan

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

Jika formulir dikirimkan, params akan menyertakan sesuatu seperti

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

Saya harus menggunakan opsi indeks agar multi-dropdown saya berfungsi. Semoga berhasil.

Cruz Nunez
sumber