form_for avec des ressources imbriquées

125

J'ai une question en deux parties sur form_for et les ressources imbriquées. Disons que j'écris un moteur de blog et que je souhaite relier un commentaire à un article. J'ai défini une ressource imbriquée comme suit:

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

Le formulaire de commentaire se trouve dans la vue show.html.erb pour les articles, sous l'article lui-même, par exemple comme ceci:

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

Cela donne une erreur, "Called id for nil, qui serait par erreur etc." J'ai aussi essayé

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

Ce qui rend correctement mais relie f.text_area au champ 'text' de l'article au lieu du commentaire, et présente le html de l'attribut article.text dans cette zone de texte. Il me semble donc que je me trompe également. Ce que je veux, c'est un formulaire dont 'submit' appellera l'action de création sur CommentsController, avec un article_id dans les paramètres, par exemple une demande de publication à / articles / 1 / comments.

La deuxième partie de ma question est la suivante: quelle est la meilleure façon de créer l'instance de commentaire pour commencer? Je crée un @comment dans l'action show de ArticlesController, donc un objet de commentaire sera dans la portée de l'aide form_for. Ensuite, dans l'action create du CommentsController, je crée un nouveau @comment en utilisant les paramètres transmis depuis form_for.

Merci!

Dave Sims
la source

Réponses:

228

Travis R a raison. (J'aurais aimé pouvoir vous voter.) Je viens de faire fonctionner ça moi-même. Avec ces itinéraires:

resources :articles do
  resources :comments
end

Vous obtenez des chemins comme:

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

routé vers les contrôleurs à

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

comme indiqué sur http://guides.rubyonrails.org/routing.html#nested-resources , sans espaces de noms spéciaux.

Mais les partiels et les formes deviennent délicats. Notez les crochets:

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

Plus important encore, si vous voulez un URI, vous aurez peut-être besoin de quelque chose comme ceci:

article_comment_path(@article, @comment)

Alternativement:

[@article, @comment]

comme décrit à http://edgeguides.rubyonrails.org/routing.html#creating-paths-and-urls-from-objects

Par exemple, à l'intérieur d'une collection partielle avec comment_itemfourni pour l'itération,

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

Ce que jamuraa dit peut fonctionner dans le contexte de l'article, mais cela n'a pas fonctionné pour moi de diverses autres manières.

Il y a beaucoup de discussions liées aux ressources imbriquées, par exemple http://weblog.jamisbuck.org/2007/2/5/nesting-resources

Fait intéressant, je viens d'apprendre que les tests unitaires de la plupart des gens ne testent pas réellement tous les chemins. Lorsque les gens suivent la suggestion de jamisbuck, ils se retrouvent avec deux façons d'accéder aux ressources imbriquées. Leurs tests unitaires obtiendront / publieront généralement au plus simple:

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

Afin de tester l'itinéraire qu'ils préfèrent, ils doivent le faire de cette façon:

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

J'ai appris cela parce que mes tests unitaires ont commencé à échouer lorsque je suis passé de ceci:

resources :comments
resources :articles do
  resources :comments
end

pour ça:

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

Je suppose que c'est normal d'avoir des routes en double et de rater quelques tests unitaires. (Pourquoi tester? Parce que même si l'utilisateur ne voit jamais les doublons, vos formulaires peuvent y faire référence, soit implicitement, soit via des itinéraires nommés.) Néanmoins, pour minimiser la duplication inutile, je recommande ceci:

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

Désolé pour la longue réponse. Peu de gens sont conscients des subtilités, je pense.

cdunn2001
la source
C'est du travail mais j'ai dû modifier le contrôleur comme l'a dit jamuraa.
Marcus Becker
Le chemin de Jam fonctionne, mais vous pouvez vous retrouver avec des itinéraires supplémentaires que vous ne connaissez probablement pas. Il vaut mieux être explicite.
cdunn2001
J'avais des ressources imbriquées, @result dans @course. Bien, a [@result, @course]fonctionné, mais form_for(@result, url: { action: "create" }) fonctionne aussi. Cela n'a besoin que du dernier nom de modèle et du nom de la méthode.
Anwar le
@ cdunn2001 Pouvez-vous expliquer pourquoi nous devons mentionner "@article" ici comme ceci et ce que cela signifie? que fait la syntaxe ci-dessous? : <% = form_for [@article, @comment] faire | f | %>
Arpit Agarwal
1
Travis / @ cdunn2001 a bien compris. Ne définissez pas à la fois le parent et la ressource lorsque vous utilisez des itinéraires imbriqués sans doublons, sinon il pensera que toutes les actions sont imbriquées. De même, si vous avez tout imbriqué, définissez toujours AT.parent. De plus, si vous avez un formulaire commun avec un bouton d'annulation avec des routes partiellement imbriquées, utilisez un chemin comme celui-ci pour qu'il fonctionne selon ce que vous avez défini (notez la pluralisation de l'enfant): <% = link_to 'Cancel', parent_children_path (AT.parent || AT.child.parent)%>
iheggie
54

Assurez-vous d'avoir les deux objets créés dans le contrôleur: @postet @commentpour le message, par exemple:

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

Alors en vue:

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

Assurez-vous de définir explicitement le tableau dans form_for, pas seulement séparés par des virgules comme vous l'avez fait ci-dessus.

Travis Reeder
la source
Travis est un peu une vieille réponse, mais je pense que c'est la plus correcte pour Rails 3.2.X. Si vous souhaitez que tous les éléments du générateur de formulaire remplissent les champs Commentaire, utilisez simplement un tableau, les assistants d'URL ne sont pas nécessaires.
Karl
1
Définissez uniquement l'objet parent où l'action est imbriquée. Si vous n'avez imbriqué que partiellement la ressource (par exemple, comme par exemple), la définition de l'objet parent entraînera l'échec de form_for (reconfirmé avec rails 5.1 tout à l'heure)
iheggie
35

Vous n'avez pas besoin de faire des choses spéciales dans le formulaire. Vous venez de créer le commentaire correctement dans l'action show:

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

puis créez un formulaire pour cela dans la vue de l'article:

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

par défaut, ce commentaire ira à l' createaction de CommentsController, que vous voudrez probablement ensuite insérer redirect :backpour que vous soyez redirigé vers la Articlepage.

jamuraa
la source
10
J'ai dû utiliser le form_for([@article, @new_comment])format. Je pense que c'est parce que je montre la vue pour comments#new, non article#new_comment. Je pense que article#new_commentRails est assez intelligent pour déterminer dans quoi l'objet de commentaire est imbriqué et que vous n'avez pas à le spécifier?
Soupe