form_for dengan sumber daya bersarang

125

Saya punya pertanyaan dua bagian tentang form_for dan sumber daya bersarang. Katakanlah saya sedang menulis mesin blog dan saya ingin menghubungkan komentar dengan sebuah artikel. Saya telah mendefinisikan sumber daya bersarang sebagai berikut:

map.resources :articles do |articles|
    articles.resources :comments
end

Formulir komentar ada di tampilan show.html.erb untuk artikel, di bawah artikel itu sendiri, misalnya seperti ini:

<%= render :partial => "articles/article" %>
<% form_for([ :article, @comment]) do |f| %>
    <%= f.text_area :text %>
    <%= submit_tag "Submit" %>
<%  end %>

Ini memberikan kesalahan, "Called id for nil, yang keliru dll." Saya juga sudah mencoba

<% form_for @article, @comment do |f| %>

Yang merender dengan benar tetapi menghubungkan f.text_area ke bidang 'teks' artikel, bukan komentar, dan menyajikan html untuk atribut article.text di area teks itu. Jadi sepertinya saya juga salah. Yang saya inginkan adalah formulir yang 'kirim' akan memanggil tindakan buat di CommentsController, dengan article_id di params, misalnya permintaan posting ke / artikel / 1 / komentar.

Bagian kedua dari pertanyaan saya adalah, bagaimana cara terbaik untuk membuat contoh komentar untuk memulai? Saya membuat @comment dalam aksi pertunjukan dari ArticleController, jadi objek komentar akan berada dalam ruang lingkup untuk form_for helper. Kemudian dalam tindakan create dari CommentsController, saya membuat @comment baru menggunakan params yang dilewatkan dari form_for.

Terima kasih!

Dave Sims
sumber

Jawaban:

228

Travis R benar. (Kuharap aku bisa mendukungmu.) Aku baru saja dapat ini. Dengan rute-rute ini:

resources :articles do
  resources :comments
end

Anda mendapatkan jalur seperti:

/articles/42
/articles/42/comments/99

dialihkan ke pengontrol di

app/controllers/articles_controller.rb
app/controllers/comments_controller.rb

seperti yang tertulis di http://guides.rubyonrails.org/routing.html#nested-resources , tanpa spasi nama khusus.

Tetapi sebagian dan bentuk menjadi rumit. Perhatikan tanda kurung:

<%= form_for [@article, @comment] do |f| %>

Yang paling penting, jika Anda menginginkan URI, Anda mungkin perlu sesuatu seperti ini:

article_comment_path(@article, @comment)

Kalau tidak:

[@article, @comment]

seperti yang dijelaskan di http://edgeguides.rubyonrails.org/routing.html#creating-paths-and-urls-from-objects

Misalnya, di dalam sebagian koleksi dengan yang comment_itemdisediakan untuk iterasi,

<%= link_to "delete", article_comment_path(@article, comment_item),
      :method => :delete, :confirm => "Really?" %>

Apa yang dikatakan jamuraa mungkin berhasil dalam konteks Artikel, tetapi tidak berhasil bagi saya dengan berbagai cara lain.

Ada banyak diskusi terkait dengan sumber daya bersarang, misalnya http://weblog.jamisbuck.org/2007/2/5/nesting-resources

Menariknya, saya baru mengetahui bahwa kebanyakan unit-test tidak benar-benar menguji semua jalur. Ketika orang mengikuti saran jamisbuck, mereka berakhir dengan dua cara untuk mendapatkan sumber daya bersarang. Unit-test mereka umumnya akan mendapatkan / memposting ke yang paling sederhana:

# POST /comments
post :create, :comment => {:article_id=>42, ...}

Untuk menguji rute yang mereka inginkan, mereka perlu melakukannya dengan cara ini:

# POST /articles/42/comments
post :create, :article_id => 42, :comment => {...}

Saya mempelajari ini karena unit-test saya mulai gagal ketika saya beralih dari ini:

resources :comments
resources :articles do
  resources :comments
end

untuk ini:

resources :comments, :only => [:destroy, :show, :edit, :update]
resources :articles do
  resources :comments, :only => [:create, :index, :new]
end

Saya kira tidak masalah untuk memiliki duplikat rute, dan melewatkan beberapa unit-test. (Mengapa uji? Karena bahkan jika pengguna tidak pernah melihat duplikat, formulir Anda dapat merujuk mereka, baik secara implisit atau melalui rute bernama.) Namun, untuk meminimalkan duplikasi yang tidak perlu, saya merekomendasikan ini:

resources :comments
resources :articles do
  resources :comments, :only => [:create, :index, :new]
end

Maaf atas jawaban panjangnya. Tidak banyak orang yang sadar akan kehalusannya, saya pikir.

cdunn2001
sumber
Ini bekerja tetapi saya harus memodifikasi controller seperti kata jamuraa.
Marcus Becker
Cara Jam bekerja, tetapi Anda bisa berakhir dengan rute tambahan yang mungkin tidak Anda ketahui. Lebih baik bersikap eksplisit.
cdunn2001
Saya punya sumber daya bersarang, @result di dalam @course. Meskipun [@result, @course]bekerja, tetapi form_for(@result, url: { action: "create" }) juga bekerja. Ini hanya membutuhkan nama model terakhir dan nama metode.
Anwar
@ cdunn2001 Bisakah Anda jelaskan mengapa kami harus menyebutkan "@article" di sini seperti ini dan apa artinya ini? apa yang dilakukan oleh sintaks di bawah ini? : <% = form_for [@article, @comment] do | f | %>
Arpit Agarwal
1
Travis / @ cdunn2001 melakukannya dengan benar. Jangan tetapkan induk dan sumber daya saat Anda menggunakan rute bersarang tanpa duplikat, jika tidak maka semua tindakan akan bersarang. Demikian juga jika Anda menyarangkan semuanya, maka selalu atur AT.parent. Juga jika Anda memiliki formulir umum dengan tombol batal dengan rute yang sebagian bersarang maka gunakan jalur seperti berikut ini sehingga berfungsi di mana pun yang telah Anda tetapkan (Perhatikan pluralisasi anak): <% = link_to 'Cancel', parent_children_path (AT.parent || AT.child.parent)%>
iheggie
54

Pastikan kedua objek dibuat di controller: @postdan @commentuntuk postingan, misalnya:

@post = Post.find params[:post_id]
@comment = Comment.new(:post=>@post)

Kemudian dalam tampilan:

<%= form_for([@post, @comment]) do |f| %>

Pastikan untuk secara eksplisit mendefinisikan array di form_for, tidak hanya dipisahkan dengan koma seperti yang Anda miliki di atas.

Travis Reeder
sumber
Travis's adalah sedikit jawaban lama, tapi saya yakin itu yang paling benar untuk Rails 3.2.X. Jika Anda ingin semua elemen pembuat formulir untuk mengisi bidang Komentar, cukup gunakan array, pembantu url tidak diperlukan.
Karl
1
Hanya atur objek induk di mana tindakan bersarang. Jika Anda hanya menyarangkan sebagian sumber daya (mis. Sesuai contoh), maka menyetel objek induk akan menyebabkan form_for gagal (dikonfirmasi ulang dengan rel 5.1 sekarang)
iheggie
35

Anda tidak perlu melakukan hal-hal khusus dalam formulir. Anda cukup membuat komentar dengan benar di aksi pertunjukan:

class ArticlesController < ActionController::Base
  ....
  def show
    @article = Article.find(params[:id])
    @new_comment = @article.comments.build
  end
  ....
end

dan kemudian buat formulir untuk itu di tampilan artikel:

<% form_for @new_comment do |f| %>
   <%= f.text_area :text %>
   <%= f.submit "Post Comment" %>
<% end %>

secara default, komentar ini akan menuju ke createtindakan CommentsController, yang mungkin ingin Anda masukkan redirect :backke dalam sehingga Anda diarahkan kembali ke Articlehalaman.

jamuraa
sumber
10
Saya harus menggunakan form_for([@article, @new_comment])format. Saya pikir ini karena saya menunjukkan pandangan comments#new, bukan article#new_comment. Saya pikir article#new_commentRails cukup pintar untuk mengetahui objek komentar yang bersarang dan Anda tidak perlu menentukannya?
Sup