form_for con recursos anidados

125

Tengo una pregunta de dos partes sobre form_for y recursos anidados. Digamos que estoy escribiendo un motor de blog y quiero relacionar un comentario con un artículo. He definido un recurso anidado de la siguiente manera:

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

El formulario de comentarios se encuentra en la vista show.html.erb para artículos, debajo del artículo en sí, por ejemplo, así:

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

Esto da un error, "Se llamó id para nil, que sería erróneamente etc." También he intentado

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

Que se procesa correctamente pero relaciona f.text_area con el campo 'texto' del artículo en lugar de los comentarios, y presenta el html para el atributo article.text en esa área de texto. Así que parece que también me equivoco. Lo que quiero es un formulario cuyo 'envío' llamará a la acción de creación en CommentsController, con un id_artículo en los parámetros, por ejemplo, una solicitud de publicación a / articles / 1 / comments.

La segunda parte de mi pregunta es, ¿cuál es la mejor manera de crear la instancia de comentario para empezar? Estoy creando un @comment en la acción show del ArticlesController, por lo que un objeto de comentario estará dentro del alcance de form_for helper. Luego, en la acción de creación de CommentsController, creo un nuevo @comment usando los parámetros pasados ​​desde form_for.

¡Gracias!

Dave Sims
fuente

Respuestas:

228

Travis R es correcto. (Ojalá pudiera votarte). Acabo de hacer que esto funcione yo mismo. Con estas rutas:

resources :articles do
  resources :comments
end

Obtienes caminos como:

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

enrutado a los controladores en

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

tal como dice en http://guides.rubyonrails.org/routing.html#nested-resources , sin espacios de nombres especiales.

Pero los parciales y las formas se vuelven difíciles. Tenga en cuenta los corchetes:

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

Lo más importante, si desea un URI, puede necesitar algo como esto:

article_comment_path(@article, @comment)

Alternativamente:

[@article, @comment]

como se describe en http://edgeguides.rubyonrails.org/routing.html#creating-paths-and-urls-from-objects

Por ejemplo, dentro de colecciones parciales con comment_itemsuministrado para iteración,

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

Lo que dice jamuraa puede funcionar en el contexto del artículo, pero no me funcionó de otras maneras.

Hay mucha discusión relacionada con los recursos anidados, por ejemplo, http://weblog.jamisbuck.org/2007/2/5/nesting-resources

Curiosamente, acabo de enterarme de que las pruebas unitarias de la mayoría de las personas no están probando todas las rutas. Cuando las personas siguen la sugerencia de jamisbuck, terminan con dos formas de obtener recursos anidados. Sus pruebas unitarias generalmente obtendrán / publicarán lo más simple:

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

Para probar la ruta que prefieran, deben hacerlo de esta manera:

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

Aprendí esto porque mis pruebas unitarias comenzaron a fallar cuando cambié de esto:

resources :comments
resources :articles do
  resources :comments
end

a esto:

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

Supongo que está bien tener rutas duplicadas y perder algunas pruebas unitarias. (¿Por qué hacer una prueba? Porque incluso si el usuario nunca ve los duplicados, sus formularios pueden referirse a ellos, ya sea implícitamente o mediante rutas con nombre). Sin embargo, para minimizar la duplicación innecesaria, recomiendo esto:

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

Perdón por la larga respuesta. No mucha gente es consciente de las sutilezas, creo.

cdunn2001
fuente
Es trabajo, pero tuve que modificar el controlador como dijo jamuraa.
Marcus Becker
El estilo de Jam funciona, pero puedes terminar con rutas adicionales que probablemente no conozcas. Es mejor ser explícito.
cdunn2001
Tenía recursos anidados, @result dentro de @course. Aunque, [@result, @course]funcionó, pero form_for(@result, url: { action: "create" }) también funciona. Esto solo necesita el último nombre del modelo y el nombre del método.
Anwar
@ cdunn2001 ¿Puede explicar por qué tenemos que mencionar "@artículo" de esta manera y qué significa esto? ¿Qué hace la siguiente sintaxis? : <% = form_for [@article, @comment] do | f | %>
Arpit Agarwal
1
Travis / @ cdunn2001 lo hizo bien. No configure tanto el padre como el recurso cuando use rutas anidadas sin duplicados, de lo contrario, pensará que todas las acciones están anidadas. Del mismo modo, si anidó todo, establezca siempre AT.parent. Además, si tiene una forma común con un botón de cancelar con rutas parcialmente anidadas, use una ruta como la siguiente para que funcione según lo que haya configurado (tenga en cuenta la pluralización del elemento secundario): <% = link_to 'Cancel', parent_children_path (AT.parent || AT.child.parent)%>
iheggie
54

Asegúrese de tener ambos objetos creados en el controlador: @posty @commentpara la publicación, por ejemplo:

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

Entonces a la vista:

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

Asegúrese de definir explícitamente la matriz en form_for, no solo separados por comas como lo hizo anteriormente.

Travis Reeder
fuente
La respuesta de Travis es un poco antigua, pero creo que es la más correcta para Rails 3.2.X. Si desea que todos los elementos del generador de formularios llenen los campos Comentario, simplemente use una matriz, no se requieren ayudantes de URL.
Karl
1
Solo establezca el objeto primario donde está anidada la acción. Si solo anidó parcialmente el recurso (por ejemplo, como en el ejemplo), al configurar el objeto padre hará que form_for falle (reconfirmado con rails 5.1 en este momento)
iheggie
35

No necesita hacer cosas especiales en el formulario. Simplemente construye el comentario correctamente en la acción show:

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

y luego haga un formulario para ello en la vista del artículo:

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

de forma predeterminada, este comentario irá a create acción de CommentsController, que probablemente querrá incluir redirect :backpara que se le redirija a la Articlepágina.

jamuraa
fuente
10
Tuve que usar el form_for([@article, @new_comment])formato. Creo que esto se debe a que estoy mostrando la vista para comments#new, no article#new_comment. Supongo que en article#new_commentRails es lo suficientemente inteligente como para determinar en qué está anidado el objeto de comentario y ¿no tiene que especificarlo?
Sopa