form_for mit verschachtelten Ressourcen

125

Ich habe eine zweiteilige Frage zu form_for und verschachtelten Ressourcen. Angenommen, ich schreibe eine Blog-Engine und möchte einen Kommentar zu einem Artikel verknüpfen. Ich habe eine verschachtelte Ressource wie folgt definiert:

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

Das Kommentarformular befindet sich in der Ansicht show.html.erb für Artikel unter dem Artikel selbst, beispielsweise wie folgt:

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

Dies gibt einen Fehler aus, "ID für Null aufgerufen, was fälschlicherweise usw. wäre." Ich habe es auch versucht

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

Dies wird korrekt gerendert, bezieht sich jedoch f.text_area auf das Textfeld des Artikels anstelle des Kommentarfelds und zeigt den HTML-Code für das Attribut article.text in diesem Textbereich an. Also scheine ich das auch falsch zu haben. Was ich möchte, ist ein Formular, dessen 'Senden' die Erstellungsaktion auf CommentsController aufruft, mit einer Artikel-ID in den Parametern, zum Beispiel einer Post-Anfrage an / articles / 1 / Kommentare.

Der zweite Teil meiner Frage lautet: Wie kann die Kommentarinstanz am besten erstellt werden? Ich erstelle einen @ -Kommentar in der Show-Aktion des ArticlesController, sodass ein Kommentarobjekt für den form_for-Helfer in den Bereich fällt. Dann erstelle ich in der Aktion create des CommentsController ein neues @comment mit den aus form_for übergebenen Parametern.

Vielen Dank!

Dave Sims
quelle

Antworten:

228

Travis R ist richtig. (Ich wünschte, ich könnte dich positiv bewerten.) Ich habe das gerade selbst zum Laufen gebracht. Mit diesen Routen:

resources :articles do
  resources :comments
end

Sie erhalten Pfade wie:

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

an Controller unter weitergeleitet

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

genau wie es unter http://guides.rubyonrails.org/routing.html#nested-resources heißt , ohne spezielle Namespaces.

Aber Teiltöne und Formen werden schwierig. Beachten Sie die eckigen Klammern:

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

Am wichtigsten ist, wenn Sie eine URI möchten, benötigen Sie möglicherweise Folgendes:

article_comment_path(@article, @comment)

Alternative:

[@article, @comment]

wie unter http://edgeguides.rubyonrails.org/routing.html#creating-paths-and-urls-from-objects beschrieben

Zum Beispiel innerhalb einer Sammlung, die teilweise mit comment_itemzur Iteration geliefert wurde,

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

Was Jamuraa sagt, mag im Kontext des Artikels funktionieren, aber es hat bei mir auf verschiedene andere Arten nicht funktioniert.

Es gibt viele Diskussionen zu verschachtelten Ressourcen, z. B. http://weblog.jamisbuck.org/2007/2/5/nesting-resources

Interessanterweise habe ich gerade erfahren, dass die Unit-Tests der meisten Leute nicht alle Pfade testen. Wenn Leute dem Vorschlag von Jamisbuck folgen, haben sie zwei Möglichkeiten, an verschachtelte Ressourcen zu gelangen. Ihre Unit-Tests werden in der Regel am einfachsten sein / posten:

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

Um die Route zu testen, die sie bevorzugen, müssen sie dies folgendermaßen tun:

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

Ich habe dies gelernt, weil meine Komponententests fehlgeschlagen sind, als ich von hier gewechselt bin:

resources :comments
resources :articles do
  resources :comments
end

dazu:

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

Ich denke, es ist in Ordnung, doppelte Routen zu haben und ein paar Unit-Tests zu verpassen. (Warum testen? Auch wenn der Benutzer die Duplikate nie sieht, können Ihre Formulare implizit oder über benannte Routen auf sie verweisen.) Um unnötige Duplikate zu minimieren, empfehle ich Folgendes:

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

Entschuldigung für die lange Antwort. Ich denke, nicht viele Menschen sind sich der Feinheiten bewusst.

cdunn2001
quelle
Es ist Arbeit, aber ich musste den Controller modifizieren, wie Jamuraa sagte.
Marcus Becker
Jam's Weg funktioniert, aber Sie können zusätzliche Routen erhalten, die Sie wahrscheinlich nicht kennen. Es ist besser, explizit zu sein.
cdunn2001
Ich hatte verschachtelte Ressourcen, @ Ergebnis in @ Kurs. Hat zwar [@result, @course]funktioniert, form_for(@result, url: { action: "create" }) funktioniert aber auch. Dies benötigt nur den letzten Modellnamen und den Methodennamen.
Anwar
@ cdunn2001 Kannst du bitte erklären, warum wir hier so "@article" erwähnen müssen und was dies bedeutet? Was macht die folgende Syntax? : <% = form_for [@article, @comment] do | f | %>
Arpit Agarwal
1
Travis / @ cdunn2001 hat es richtig gemacht. Legen Sie nicht sowohl das übergeordnete Element als auch die Ressource fest, wenn Sie verschachtelte Routen ohne Duplikate verwenden. Andernfalls werden alle Aktionen als verschachtelt angesehen. Wenn Sie alles verschachtelt haben, setzen Sie immer AT.parent. Wenn Sie ein gemeinsames Formular mit einer Schaltfläche zum Abbrechen mit teilweise verschachtelten Routen haben, verwenden Sie einen Pfad wie den folgenden, damit er funktioniert, je nachdem, was Sie festgelegt haben (Beachten Sie die Pluralisierung des untergeordneten Elements): <% = link_to 'Cancel', parent_children_path (AT.parent || AT.child.parent)%>
iheggie
54

Stellen Sie sicher, dass beide Objekte im Controller erstellt wurden: @postund @commentfür den Beitrag, z.

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

Dann im Blick:

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

Stellen Sie sicher, dass Sie das Array explizit in form_for definieren und nicht nur wie oben durch Kommas getrennt.

Travis Reeder
quelle
Travis's ist eine alte Antwort, aber ich glaube, dass sie für Rails 3.2.X die richtigste ist. Wenn Sie möchten, dass alle Elemente des Formular-Generators die Kommentarfelder füllen, verwenden Sie einfach ein Array. URL-Helfer sind nicht erforderlich.
Karl
1
Legen Sie nur das übergeordnete Objekt fest, in dem die Aktion verschachtelt ist. Wenn Sie die Ressource nur teilweise verschachtelt haben (z. B. gemäß Beispiel), führt das Festlegen des übergeordneten Objekts dazu, dass form_for fehlschlägt (erneut mit Rails 5.1 bestätigt)
iheggie
35

Sie müssen keine besonderen Dinge in der Form tun. Sie erstellen den Kommentar einfach korrekt in der Show-Aktion:

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

und erstellen Sie dann ein Formular dafür in der Artikelansicht:

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

Standardmäßig geht dieser Kommentar zu der createAktion von CommentsController, die Sie dann wahrscheinlich einfügen möchten, redirect :backdamit Sie zurück zur ArticleSeite geleitet werden.

Jamuraa
quelle
10
Ich musste das form_for([@article, @new_comment])Format verwenden. Ich denke, das liegt daran, dass ich die Aussicht für comments#newnicht zeige article#new_comment. Ich denke, in article#new_commentRails ist es klug genug, herauszufinden, in was das Kommentarobjekt verschachtelt ist, und müssen Sie es nicht angeben?
Suppe