Form_for с вложенными ресурсами

У меня есть двухчастный вопрос о form_for и вложенных ресурсах. Скажем, я пишу блок-блог, и я хочу связать комментарий к статье. Я определил вложенный ресурс следующим образом:

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

Форма комментария находится в представлении show.html.erb для статей под самой статьей, например:

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

Это дает ошибку: "Вызывается id для nil, что ошибочно и т.д." Я также пробовал

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

Что корректно отображает, но связывает f.text_area с полем "текст" статьи вместо комментария и представляет html для атрибута article.text в этой области текста. Поэтому, похоже, я тоже ошибаюсь. То, что я хочу, - это форма, чья "submit" будет вызывать действие create в CommentsController со статьей_id в параметрах, например, запрос на отправку /articles/ 1/comments.

Вторая часть моего вопроса: какой лучший способ создать экземпляр комментария для начала? Я создаю @comment в действии show ArticlesController, поэтому объект комментария будет доступен для помощника form_for. Затем в действии createController, я создаю новый @comment, используя params, переданные из form_for.

Спасибо!

Ответ 1

Тревис R правильный. (Хотелось бы, чтобы я мог упрекнуть тебя.) Я только что занялся этим. С помощью этих маршрутов:

resources :articles do
  resources :comments
end

Вы получаете такие пути, как:

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

перенаправляется на контроллеры в

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

так как он говорит в http://guides.rubyonrails.org/routing.html#nested-resources без специальных пространств имен.

Но частичные формы и формы становятся сложными. Обратите внимание на квадратные скобки:

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

Самое главное, если вы хотите URI, вам может понадобиться что-то вроде этого:

article_comment_path(@article, @comment)

В качестве альтернативы:

[@article, @comment]

как описано в http://edgeguides.rubyonrails.org/routing.html#creating-paths-and-urls-from-objects

Например, внутри коллекций, частичных с comment_item, поставляемых для итерации,

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

То, что говорит джамураа, может работать в контексте статьи, но это не сработало для меня различными способами.

Существует много обсуждений, связанных с вложенными ресурсами, например. http://weblog.jamisbuck.org/2007/2/5/nesting-resources

Интересно, что я только что узнал, что большинство модульных тестов на самом деле не тестируют все пути. Когда люди следуют предложению jamisbuck, у них есть два способа получить доступ к вложенным ресурсам. Их модульные тесты обычно получаются/публикуются до самых простых:

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

Чтобы проверить маршрут, который они могут предпочесть, они должны сделать это следующим образом:

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

Я узнал об этом, потому что мои модульные тесты начали сбой, когда я переключился с этого:

resources :comments
resources :articles do
  resources :comments
end

:

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

Я думаю, это нормально, чтобы иметь повторяющиеся маршруты и пропустить несколько модульных тестов. (Почему тест? Потому что, даже если пользователь никогда не видит дубликаты, ваши формы могут ссылаться на них либо неявно, либо через именованные маршруты.) Тем не менее, чтобы свести к минимуму ненужное дублирование, я рекомендую следующее:

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

Извините за длинный ответ. Думаю, мало кто знает о тонкостях.

Ответ 2

Убедитесь, что оба объекта созданы в контроллере: @post и @comment для сообщения, например:

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

Тогда в поле зрения:

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

Обязательно явно определяйте массив в form_for, а не только запятую, как вы уже выше.

Ответ 3

Вам не нужно делать специальные вещи в форме. Вы правильно строите комментарий в действии show:

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

а затем создайте для него форму в представлении статьи:

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

по умолчанию этот комментарий перейдет к действию create CommentsController, которое вы, вероятно, захотите поместить redirect :back, чтобы вы перенаправлялись на страницу Article.