form_for z zagnieżdżonymi zasobami

125

Mam dwuczęściowe pytanie dotyczące form_for i zagnieżdżonych zasobów. Powiedzmy, że piszę silnik bloga i chcę powiązać komentarz z artykułem. Zdefiniowałem zagnieżdżony zasób w następujący sposób:

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

Formularz komentarza znajduje się w widoku show.html.erb dla artykułów, pod samym artykułem, na przykład w ten sposób:

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

Daje to błąd „Called id for nil, co mogłoby być błędne itd.” Ja też próbowałem

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

Który renderuje się poprawnie, ale łączy f.text_area z polem „text” artykułu zamiast komentarza i przedstawia kod HTML atrybutu article.text w tym obszarze tekstowym. Więc wydaje mi się, że to też się mylę. To, czego chcę, to formularz, którego 'wyślij' wywoła akcję tworzenia w CommentsController, z parametrem article_id, na przykład żądanie wpisu do / article / 1 / comments.

Druga część mojego pytania brzmi: jaki jest najlepszy sposób na utworzenie wystąpienia komentarza na początek? Tworzę @comment w akcji show elementu ArticlesController, więc obiekt komentarza będzie w zakresie pomocnika form_for. Następnie w akcji tworzenia w CommentsController tworzę nowy @comment, używając parametrów przekazanych z form_for.

Dzięki!

Dave Sims
źródło

Odpowiedzi:

228

Travis R. ma rację. (Chciałbym móc zagłosować za tobą.) Właśnie to działa. Z tymi trasami:

resources :articles do
  resources :comments
end

Otrzymujesz ścieżki takie jak:

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

kierowane do kontrolerów pod adresem

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

tak jak jest napisane na http://guides.rubyonrails.org/routing.html#nested-resources , bez specjalnych przestrzeni nazw.

Ale części i formy stają się trudne. Zwróć uwagę na nawiasy kwadratowe:

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

Co najważniejsze, jeśli chcesz mieć identyfikator URI, możesz potrzebować czegoś takiego:

article_comment_path(@article, @comment)

Alternatywnie:

[@article, @comment]

zgodnie z opisem na http://edgeguides.rubyonrails.org/routing.html#creating-paths-and-urls-from-objects

Na przykład wewnątrz kolekcji częściowej z comment_itemdostarczoną do iteracji,

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

To, co mówi jamuraa, może zadziałać w kontekście artykułu, ale na inne sposoby nie zadziałało.

Istnieje wiele dyskusji związanych z zagnieżdżonymi zasobami, np. Http://weblog.jamisbuck.org/2007/2/5/nesting-resources

Co ciekawe, właśnie się dowiedziałem, że testy jednostkowe większości ludzi nie testują w rzeczywistości wszystkich ścieżek. Kiedy ludzie podążają za sugestią jamisbucka, mają dwa sposoby na dotarcie do zagnieżdżonych zasobów. Ich testy jednostkowe generalnie trafiają / wysyłają do najprostszych:

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

Aby przetestować preferowaną trasę, muszą to zrobić w następujący sposób:

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

Nauczyłem się tego, ponieważ moje testy jednostkowe zaczęły kończyć się niepowodzeniem po przełączeniu z tego:

resources :comments
resources :articles do
  resources :comments
end

do tego:

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

Myślę, że wystarczy mieć zduplikowane trasy i pominąć kilka testów jednostkowych. (Dlaczego testować? Ponieważ nawet jeśli użytkownik nigdy nie widzi duplikatów, twoje formularze mogą się do nich odwoływać, niejawnie lub za pośrednictwem nazwanych tras.) Mimo to, aby zminimalizować niepotrzebne powielanie, zalecam:

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

Przepraszam za długą odpowiedź. Myślę, że niewielu ludzi zdaje sobie sprawę z subtelności.

cdunn2001
źródło
To jest praca, ale musiałem zmodyfikować kontroler, jak powiedział jamuraa.
Marcus Becker
Sposób Jamu działa, ale możesz skończyć z dodatkowymi trasami, o których prawdopodobnie nie wiesz. Lepiej mówić wprost.
cdunn2001
Miałem zagnieżdżone zasoby, @result wewnątrz @course. Choć [@result, @course]działał, ale form_for(@result, url: { action: "create" }) też działa. Potrzebna jest tylko ostatnia nazwa modelu i nazwa metody.
Anwar
@ cdunn2001 Czy możesz wyjaśnić, dlaczego musimy wspomnieć tutaj o „@article” w ten sposób i co to oznacza? co robi poniższa składnia? : <% = form_for [@article, @comment] do | f | %>
Arpit Agarwal,
1
Travis / @ cdunn2001 ma rację. Nie ustawiaj zarówno obiektu nadrzędnego, jak i zasobu, gdy używasz zagnieżdżonych tras bez duplikatów, w przeciwnym razie uzna, że ​​wszystkie akcje są zagnieżdżone. Podobnie, jeśli wszystko zagnieżdżasz, zawsze ustaw AT.parent. Również jeśli masz wspólny formularz z przyciskiem anulowania z częściowo zagnieżdżonymi trasami, użyj ścieżki takiej jak poniżej, aby działała niezależnie od ustawionej (Zwróć uwagę na liczbę mnogą elementu potomnego): <% = link_to 'Anuluj', parent_children_path (AT.parent || AT.child.parent)%>
iheggie
54

Upewnij się, że w kontrolerze zostały utworzone oba obiekty: @posti @commentdla postu, np .:

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

Następnie na widoku:

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

Upewnij się, że jawnie zdefiniowałeś tablicę w form_for, a nie tylko oddzielonych przecinkami, jak powyżej.

Travis Reeder
źródło
Travis jest trochę starą odpowiedzią, ale uważam, że jest najbardziej poprawna w przypadku Railsów 3.2.X. Jeśli chcesz, aby wszystkie elementy kreatora formularzy wypełniały pola Komentarz, po prostu użyj tablicy, pomoce URL nie są wymagane.
Karl
1
Ustaw obiekt nadrzędny tylko tam, gdzie akcja jest zagnieżdżona. Jeśli tylko częściowo zagnieżdżałeś zasób (np. Jak na przykładzie), to ustawienie obiektu nadrzędnego spowoduje niepowodzenie form_for (ponownie potwierdzone przez rails 5.1 właśnie teraz)
iheggie
35

Nie musisz robić specjalnych rzeczy w formularzu. Po prostu poprawnie budujesz komentarz w akcji show:

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

a następnie utwórz dla niego formularz w widoku artykułu:

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

Domyślnie ten komentarz przejdzie do createczynności CommentsController, którą prawdopodobnie zechcesz umieścić redirect :back, abyś został przekierowany z powrotem na Articlestronę.

jamuraa
źródło
10
Musiałem użyć form_for([@article, @new_comment])formatu. Myślę, że to dlatego, że pokazuję widok comments#new, a nie article#new_comment. Wydaje mi się, że w article#new_commentRailsach jest wystarczająco inteligentny, aby dowiedzieć się, w czym jest zagnieżdżony obiekt komentarza, a więc nie musisz go określać?
Zupa