I have a two-part question about form_for and nested resources. Let's say I'm writing a blog engine and I want to relate a comment to an article. I've defined a nested resource as follows:
map.resources :articles do |articles|
articles.resources :comments
end
The comment form is in the show.html.erb view for articles, underneath the article itself, for instance like this:
<%= render :partial => "articles/article" %>
<% form_for([ :article, #comment]) do |f| %>
<%= f.text_area :text %>
<%= submit_tag "Submit" %>
<% end %>
This gives an error, "Called id for nil, which would mistakenly etc." I've also tried
<% form_for #article, #comment do |f| %>
Which renders correctly but relates f.text_area to the article's 'text' field instead of the comment's, and presents the html for the article.text attribute in that text area. So I seem to have this wrong as well. What I want is a form whose 'submit' will call the create action on CommentsController, with an article_id in the params, for instance a post request to /articles/1/comments.
The second part to my question is, what's the best way to create the comment instance to begin with? I'm creating a #comment in the show action of the ArticlesController, so a comment object will be in scope for the form_for helper. Then in the create action of the CommentsController, I create new #comment using the params passed in from the form_for.
Thanks!
Travis R is correct. (I wish I could upvote ya.) I just got this working myself. With these routes:
resources :articles do
resources :comments
end
You get paths like:
/articles/42
/articles/42/comments/99
routed to controllers at
app/controllers/articles_controller.rb
app/controllers/comments_controller.rb
just as it says at http://guides.rubyonrails.org/routing.html#nested-resources, with no special namespaces.
But partials and forms become tricky. Note the square brackets:
<%= form_for [#article, #comment] do |f| %>
Most important, if you want a URI, you may need something like this:
article_comment_path(#article, #comment)
Alternatively:
[#article, #comment]
as described at http://edgeguides.rubyonrails.org/routing.html#creating-paths-and-urls-from-objects
For example, inside a collections partial with comment_item supplied for iteration,
<%= link_to "delete", article_comment_path(#article, comment_item),
:method => :delete, :confirm => "Really?" %>
What jamuraa says may work in the context of Article, but it did not work for me in various other ways.
There is a lot of discussion related to nested resources, e.g. http://weblog.jamisbuck.org/2007/2/5/nesting-resources
Interestingly, I just learned that most people's unit-tests are not actually testing all paths. When people follow jamisbuck's suggestion, they end up with two ways to get at nested resources. Their unit-tests will generally get/post to the simplest:
# POST /comments
post :create, :comment => {:article_id=>42, ...}
In order to test the route that they may prefer, they need to do it this way:
# POST /articles/42/comments
post :create, :article_id => 42, :comment => {...}
I learned this because my unit-tests started failing when I switched from this:
resources :comments
resources :articles do
resources :comments
end
to this:
resources :comments, :only => [:destroy, :show, :edit, :update]
resources :articles do
resources :comments, :only => [:create, :index, :new]
end
I guess it's ok to have duplicate routes, and to miss a few unit-tests. (Why test? Because even if the user never sees the duplicates, your forms may refer to them, either implicitly or via named routes.) Still, to minimize needless duplication, I recommend this:
resources :comments
resources :articles do
resources :comments, :only => [:create, :index, :new]
end
Sorry for the long answer. Not many people are aware of the subtleties, I think.
Be sure to have both objects created in controller: #post and #comment for the post, eg:
#post = Post.find params[:post_id]
#comment = Comment.new(:post=>#post)
Then in view:
<%= form_for([#post, #comment]) do |f| %>
Be sure to explicitly define the array in the form_for, not just comma separated like you have above.
You don't need to do special things in the form. You just build the comment correctly in the show action:
class ArticlesController < ActionController::Base
....
def show
#article = Article.find(params[:id])
#new_comment = #article.comments.build
end
....
end
and then make a form for it in the article view:
<% form_for #new_comment do |f| %>
<%= f.text_area :text %>
<%= f.submit "Post Comment" %>
<% end %>
by default, this comment will go to the create action of CommentsController, which you will then probably want to put redirect :back into so you're routed back to the Article page.
Related
I have a two-part question about form_for and nested resources. Let's say I'm writing a blog engine and I want to relate a comment to an article. I've defined a nested resource as follows:
map.resources :articles do |articles|
articles.resources :comments
end
The comment form is in the show.html.erb view for articles, underneath the article itself, for instance like this:
<%= render :partial => "articles/article" %>
<% form_for([ :article, #comment]) do |f| %>
<%= f.text_area :text %>
<%= submit_tag "Submit" %>
<% end %>
This gives an error, "Called id for nil, which would mistakenly etc." I've also tried
<% form_for #article, #comment do |f| %>
Which renders correctly but relates f.text_area to the article's 'text' field instead of the comment's, and presents the html for the article.text attribute in that text area. So I seem to have this wrong as well. What I want is a form whose 'submit' will call the create action on CommentsController, with an article_id in the params, for instance a post request to /articles/1/comments.
The second part to my question is, what's the best way to create the comment instance to begin with? I'm creating a #comment in the show action of the ArticlesController, so a comment object will be in scope for the form_for helper. Then in the create action of the CommentsController, I create new #comment using the params passed in from the form_for.
Thanks!
Travis R is correct. (I wish I could upvote ya.) I just got this working myself. With these routes:
resources :articles do
resources :comments
end
You get paths like:
/articles/42
/articles/42/comments/99
routed to controllers at
app/controllers/articles_controller.rb
app/controllers/comments_controller.rb
just as it says at http://guides.rubyonrails.org/routing.html#nested-resources, with no special namespaces.
But partials and forms become tricky. Note the square brackets:
<%= form_for [#article, #comment] do |f| %>
Most important, if you want a URI, you may need something like this:
article_comment_path(#article, #comment)
Alternatively:
[#article, #comment]
as described at http://edgeguides.rubyonrails.org/routing.html#creating-paths-and-urls-from-objects
For example, inside a collections partial with comment_item supplied for iteration,
<%= link_to "delete", article_comment_path(#article, comment_item),
:method => :delete, :confirm => "Really?" %>
What jamuraa says may work in the context of Article, but it did not work for me in various other ways.
There is a lot of discussion related to nested resources, e.g. http://weblog.jamisbuck.org/2007/2/5/nesting-resources
Interestingly, I just learned that most people's unit-tests are not actually testing all paths. When people follow jamisbuck's suggestion, they end up with two ways to get at nested resources. Their unit-tests will generally get/post to the simplest:
# POST /comments
post :create, :comment => {:article_id=>42, ...}
In order to test the route that they may prefer, they need to do it this way:
# POST /articles/42/comments
post :create, :article_id => 42, :comment => {...}
I learned this because my unit-tests started failing when I switched from this:
resources :comments
resources :articles do
resources :comments
end
to this:
resources :comments, :only => [:destroy, :show, :edit, :update]
resources :articles do
resources :comments, :only => [:create, :index, :new]
end
I guess it's ok to have duplicate routes, and to miss a few unit-tests. (Why test? Because even if the user never sees the duplicates, your forms may refer to them, either implicitly or via named routes.) Still, to minimize needless duplication, I recommend this:
resources :comments
resources :articles do
resources :comments, :only => [:create, :index, :new]
end
Sorry for the long answer. Not many people are aware of the subtleties, I think.
Be sure to have both objects created in controller: #post and #comment for the post, eg:
#post = Post.find params[:post_id]
#comment = Comment.new(:post=>#post)
Then in view:
<%= form_for([#post, #comment]) do |f| %>
Be sure to explicitly define the array in the form_for, not just comma separated like you have above.
You don't need to do special things in the form. You just build the comment correctly in the show action:
class ArticlesController < ActionController::Base
....
def show
#article = Article.find(params[:id])
#new_comment = #article.comments.build
end
....
end
and then make a form for it in the article view:
<% form_for #new_comment do |f| %>
<%= f.text_area :text %>
<%= f.submit "Post Comment" %>
<% end %>
by default, this comment will go to the create action of CommentsController, which you will then probably want to put redirect :back into so you're routed back to the Article page.
Example: a Survey has many Questions. A request is made to create a survey, and the request contains the survey title and metadata, plus all the questions.
This all happens on one page -- when the user clicks submit, the survey and its questions are created.
So far I have all the logic in the SurveysController but I'm not sure if this is MVC, especially because I have methods like add_question and remove_question.
Is there a preferred way of doing this?
If you're creating the questions through accepts_nested_attributes_for, then it would be okay.
--
When you mention add_question / remove question - this would be best handled in a separate questions controller (with nested resource routing):
#config/routes.rb
resources :surveys do
resources :questions, only: [:create, :destroy]
end
This allows you to use the following:
#app/controllers/surverys_controller.rb
class SurveysController < ApplicationController
def show
#survey = Survey.find params[:id]
#new_question = #survey.questions.new
end
end
#app/views/surveys/show.html.erb
<%= #survey.title %>
<% #survey.questions.each do |question| %>
<%= link_to "Remove", surveys_question_path(#survey, question), method: :delete %>
<% end %>
<%= form_for #question do |f| %>
<%= f.text_field :text %>
<%= f.submit %>
<% end %>
This would keep your controllers conventional.
Nested resources:
resources :surveys do
resources :questions # check http://localhost:3000/rails/info/routes for generated routes
end
Now you can create QuestionsController with normal CRUD actions and survey_id in params. I.e:
class QuestionsController
def create
#question = Question.new(survey_id: params[:survey_id], ... )
end
end
And to create survey with all questions at once use accepts_nested_attributes_for on Survey model http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html
Here is what I have in my view:
<%= simple_form_for :artist, :url => url_for(:action => 'upvote', :controller => 'artists'),
:method => 'post' do |f| %>
<%= f.input :choose_an_artist, :selected => "first artist", collection: [["first artist", 1], ["second artist", 2], ["third artist", 3], ["fourth artist", 4]] %>
<%= f.submit "Vote" %>
<% end %>
My ArtistsController:
def upvote
#artist = Artist.find(params[:choose_an_artist])
#artist.liked_by current_user
respond_to do |format|
format.html {redirect_to :back }
end
end
routes.rb:
resources :artists do
member do
put "like", to: "artists#upvote"
end
end
I am getting the following error:
No route matches {:action=>"upvote", :controller=>"artists"}
What could be causing this? How do I get this to work so that the user can select an artist from a collection and vote for that artist?
There are several issues in your code:
First, you defined your route as PUT and you are forcing
simple_form to produce a POST form. Change method: :post
to method: :put in your view and you should be all set.
Second, you need to define your route according to your controller and action name:
resources :artists do
member do
put :upvote
end
end
Third, you defined your route as on: :member. That means that it needs an artist_id to generate. In your setup, you need to define the route on: :collection. I'd also better use the route path method instead of url_for, it's way easier to spot this errors.
resources :artists do
collection do
put :upvote
end
end
And change the url_for part for update_artists_path (if that's the correct route from rake routes).
Another issue not related to your question: :choose_an_artist is not an attribute defined in Artist model. This will cause another error when rendering the form.
I'd either rename that per the actual attribute name you are selecting, :id and change the controller accordingly (my choice), or change the form helper from f.input to a non-model related select_tag and keep the names as they are.
When I try to submit the form its giving me the error
No route matches [POST] "/articles/new"
the files are: new.html.erb
this file which contains the form with a text field and text area:
<%= form_for :article, url: articles_path do |f| %>
here the url: is to match post request which is a create
form's title
<%= f.label :title %><br>
form's text field
<%= f.text_field :title %></p>
form's title
<%= f.label :text %><br>
form's text area
<%= f.text_area :text %></p>
<p>
<%= f.submit %>
</p>
<% end %>
route file is
Rails.application.routes.draw do
resources :article
end
controller is
the controller with its methods new and create
whenever I submit the form its giving the error, even I used the URL: articles_path which for default post request, I used #articles also in the form but it is giving me the same error. I am new to the Rails so I tried many ways but I could not find the solution
class ArticlesController < ApplicationController
def new #new method which is a get request
end
def create #create method which is a post request
end
end
whenever I submit the form its giving the error, even I used the url: articles_path which for default post request. and I kept
def create
end
in the controller
The reason why this occurs for many people using this tutorial is that they don't reload the form after changing the url option in the form_for helper.
Be sure to reload a fresh copy of the form before trying to submit it (you need the form to have the most recent form submission url).
change:
resources :article
to:
resources :articles #plural
that way it will map to:
articles_path POST /articles(.:format) articles#create
I changed app/views/articles/new.html.erb from:
<%= form_for :article do |f| %>
to:
<%= form_for :article, url: articles_path do |f| %>
You can find the answer on the official guideline.
Because this route goes to the very page that you're on right at the moment, and that route should only be used to display the form for a new article.
Edit the form_with line inside app/views/articles/new.html.erb to look like this:
<%= form_with scope: :article, url: articles_path, local: true do |form| %>
Your actions/methods in the controller do nothing. It should be something like:
class ArticlesController < ApplicationController
def new
#article = Article.new
end
def create
#article = Article.new(article_params)
if #article.save
redirect_to #article
else
render 'new'
end
end
private
def article_params
params.require(:article).permit()# here go you parameters for an article
end
end
In the view:
<%= form_for #article do |f| %>
RedZagogulins answer is right - that is the code you need to use in your articles controller
--
Routes
The clue to your problem is here:
No route matches [POST] "/articles/new"
Typically, when using the correct routing structure:
#config/routes.rb
resources :articles #-> needs to be controller name
You'll find that the new action is GET, not POST. This leads me to believe your system is set up incorrectly (it's trying to send the form data to articles/new, when it should just send to [POST] articles
If you follow the steps outlined by RedZagogulin, it should work for you
I was continually running into this problem and it came down to the fact that I had the following code in my navbar:
<%= link_to "Blog", controller: 'articles' %>
I switched to this and it all started working:
<%= link_to "Blog", articles_path %>
I'm still new to this so the different ways of doing things sometimes gets confusing.
In your case,
at first change the
#/config/routes.rb
resources :article
to
#/config/routes.rb
resources :articles #plural
# Changing this in Plural naming if your file name
# in #/app/view
# is plural
# (ex: /app/view/articles)
#
# and controller and it's class is also in plural naming
# (ex:
# in #/app/controllers/articles_controller.rb file
# class ArticlesController < ApplicationController
#
# end)
In some cases, we need to add a Post routes to make a Post Request of the form. Just add the lines in routes file:
#/config/routes.rb
Rails.application.routes.draw do
resources :articles #plural
post '/articles/new' => 'articles#create'
end
In order to get this working, I needed to combine instructions for two answers. First, I added a specific route in routes.rb to handle POST requests to '/articles/new':
Rails.application.routes.draw do
root "articles#index"
resources :articles
post '/articles/new' => 'articles#create'
end
And then I had to change the first line of the form to this <%= form_with model: #aricle do |form| %>:
<h1>New Article</h1>
<%= form_with model: #aricle do |form| %>
<p>
<%= form.label :title %><br>
<%= form.text_field :title %>
</p>
<p>
<%= form.label :body %><br>
<%= form.text_area :body %>
</p>
<p>
<%= form.submit %>
</p>
<% end %>
I have three models associated between: User, Post, Comment. Comment is nested resource with Post.
routes.rb
resources :posts do
resources :comments
end
User model:
has_many :comments
Post model:
has_many :comments
Comment model:
belonsg_to :user
belonsg_to :post
The goal is when User makes new Comment it creates association with that user. So you can see it like User knows all comments he has made.
comments_controller.rb
def create
#post = Post.find(params[post_id]
#comment = #post.comments.build[:comment]
current_user.comments >> #comment
....
end
new.html.erb
<% form_for [#post, #post.comment.build] do |f| %>
.....
<% end %>
This gives me an error no method comments. What should I make to avoid this?
Most likely you are missing "S" letter in new.html.erb. Should be comments:
<% form_for [#post, #post.comments.build] do |f| %>
.....
<% end %>
If there is some more logic behind you didn't post let us know. Your create action looks fine. Try to look in console student_id attribute, if its populated with ID than you are fine.cheers.
Use
#post.comments.build
Instead of
#post.comment.build (x)
this should work, if possible move this line of code from view to controller
for more info
http://guides.rubyonrails.org/association_basics.html#detailed-association-reference
In new.html.erb file, you are using "s" for build method.
It should be,
<% form_for [#post, #post.comments.build] do |f| %>
.....
<% end %>