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

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

Спасибо!


person Dave Sims    schedule 09.01.2010    source источник


Ответы (3)


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

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

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

person cdunn2001    schedule 06.01.2011
comment
Это работа, но мне пришлось модифицировать контроллер, как сказал Джамураа. - person Marcus Becker; 17.05.2014
comment
Путь пробки работает, но вы можете получить дополнительные маршруты, о которых вы, вероятно, не знаете. Лучше быть откровенным. - person cdunn2001; 28.06.2014
comment
У меня были вложенные ресурсы, @result внутри @course. Хотя [@result, @course] работал, но form_for(@result, url: { action: "create" }) тоже работает. Для этого нужны только последнее название модели и название метода. - person Anwar; 04.09.2015
comment
@ cdunn2001 Пожалуйста, объясните, почему мы должны упомянуть @article вот так и что это означает? что делает приведенный ниже синтаксис? : ‹% = Form_for [@article, @comment] do | f | % › - person Arpit Agarwal; 12.10.2016
comment
Трэвис / @ cdunn2001 понял это правильно. Не устанавливайте одновременно родительский объект и ресурс, когда вы используете вложенные маршруты без дубликатов, иначе он будет думать, что все действия вложены. Точно так же, если вы все вложили, всегда устанавливайте AT.parent. Также, если у вас есть общая форма с кнопкой отмены с частично вложенными маршрутами, используйте следующий путь, чтобы он работал независимо от того, что вы установили (обратите внимание на множественное число дочерних элементов): ‹% = link_to 'Cancel', parent_children_path (AT.parent || AT.child.parent)% › - person iheggie; 23.07.2017
comment
Чтобы дать некоторую обратную связь: этот ответ не очень хорошо читается, поскольку он содержит несколько примеров того, что сказал X. Это принятый ответ, и он требует, чтобы я каждый раз прокручивал вниз и сначала понимал упомянутый ответ (и нашел его). Или я должен попытаться угадать из контекста ... - person NobodysNightmare; 03.04.2018

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

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

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

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

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

person Travis Reeder    schedule 27.12.2010
comment
Ответ Трэвиса немного старый, но я считаю, что он наиболее правильный для Rails 3.2.X. Если вы хотите, чтобы все элементы конструктора форм заполняли поля комментариев, просто используйте массив, вспомогательные URL-адреса не требуются. - person Karl; 17.04.2013
comment
Установите только родительский объект, в который вложено действие. Если вы вложили ресурс только частично (например, как в примере), то установка родительского объекта приведет к сбою form_for (подтверждено с помощью rails 5.1 только что) - person iheggie; 23.07.2017

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

person jamuraa    schedule 10.01.2010
comment
Пришлось использовать формат form_for([@article, @new_comment]). Я думаю, это потому, что я показываю представление для comments#new, а не для article#new_comment. Я полагаю, что article#new_comment Rails достаточно умен, чтобы понять, во что вложен объект комментария, и поэтому вам не нужно его указывать? - person Soup; 05.05.2012