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

125

У меня вопрос из двух частей о 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 с полем 'text' статьи вместо комментария и представляет html для атрибута article.text в этой текстовой области. Так что я, кажется, тоже ошибаюсь. Мне нужна форма, «submit» которой вызовет действие create в CommentsController с идентификатором article_id в параметрах, например, запрос на публикацию в / article / 1 / comments.

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

Спасибо!

Дэйв Симс
источник

Ответы:

228

Трэвис Р. прав. (Хотел бы я проголосовать за тебя.) Я только что получил эту работу сам. По этим маршрутам:

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

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

cdunn2001
источник
Это работа, но мне пришлось модифицировать контроллер, как сказал Джамураа.
Маркус Беккер
Путь пробки работает, но вы можете получить дополнительные маршруты, о которых вы, вероятно, не знаете. Лучше быть откровенным.
cdunn2001
У меня были вложенные ресурсы, @result внутри @course. Хоть и [@result, @course]сработало, но form_for(@result, url: { action: "create" }) тоже работает. Для этого нужны только последнее название модели и название метода.
Анвар
@ cdunn2001 Пожалуйста, объясните, почему мы должны упоминать здесь вот так "@article" и что это значит? что делает приведенный ниже синтаксис? : <% = form_for [@article, @comment] do | f | %>
Арпит Агарвал
1
Travis / @ cdunn2001 понял это правильно. Не устанавливайте и родительский, и ресурс, когда вы используете вложенные маршруты без дубликатов, иначе он будет думать, что все действия вложены. Точно так же, если вы все вложили, всегда устанавливайте AT.parent. Также, если у вас есть общая форма с кнопкой отмены с частично вложенными маршрутами, используйте следующий путь, чтобы он работал независимо от того, что вы установили (обратите внимание на множественное число дочерних элементов): <% = link_to 'Cancel', parent_children_path (AT.parent || AT.child.parent)%>
iheggie
54

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

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

Тогда на виду:

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

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

Трэвис Ридер
источник
Тревис - немного старый ответ, но я считаю, что он наиболее правильный для Rails 3.2.X. Если вы хотите, чтобы все элементы конструктора форм заполняли поля комментариев, просто используйте массив, вспомогательные URL-адреса не требуются.
Карл
1
Установите только родительский объект, в который вложено действие. Если вы вложили ресурс только частично (например, как в примере), то установка родительского объекта приведет к сбою form_for (подтверждено с помощью rails 5.1 только что)
iheggie
35

В форме не нужно делать ничего особенного. Вы просто правильно создаете комментарий в действии 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страницу.

jamuraa
источник
10
Пришлось использовать form_for([@article, @new_comment])формат. Я думаю, это потому, что я показываю вид comments#new, а не article#new_comment. Я полагаю, что article#new_commentRails достаточно умен, чтобы понять, во что вложен объект комментария, и поэтому вам не нужно его указывать?
Суп