Rendering two partials with a form using ajax - ruby-on-rails

I'm rendering a partial that renders a form, using ajax (I can't just render the form directly). Posting a comment works fine when I'm rendering the form without ajax but with ajax it seems the partial can't access the #post variable.
<%= link_to "Render form", submit_comment_path, :remote => true %>
<div id="form">
</div>
I have a submit_comment.js file that looks like this:
$("#form").html("<%= j render(partial: 'comment_partial', locals: {post: #post}) %>");
The comment_partial view:
<%= render 'comments/form' %>
The form view:
<%= simple_form_for [post, Comment.new] %>
...
The submit_comment_path route:
get '/submit_comment', to: 'posts#submit_comment', as: :submit_comment
The posts controller (it's being rendered on the show page):
def show
#post = Post.find(params[:id])
end
def submit_comment
respond_to do |format|
format.html
format.js
end
end
and the comments controller:
def create
#post = Post.find(params[:post_id])
end
If I try to post a new comment it gives me a routing error and takes me to /posts//comment. Putting post.id in the comment_partial gives me an undefined error.

The big piece of the puzzle is understanding that instance variables (#post in this instance) disappear as soon as the Controller renders anything.
You correctly assign #post when you render the show page:
def show
#post = Post.find(params[:id])
end
However, #post disappears the second that show.html.erb is done rendering. When you click on the link to hit the submit comment method, no #post is getting created...
def submit_comment
# No instance variables here! :(
respond_to do |format|
format.html
format.js
end
end
Which means that the submit_comment.js file has no idea which post to be generating a form for.
However, it's not as simple as just throwing another Post.find(params[:id]) into the submit_comment method. You need to:
Define a route that relies on post id
Change the link in show.html.erb to include a specific #post.id
Then find the corresponding post to create a comment for.
It might look something like this...
routes.rb
...
resources :posts do
member do
get 'submit_comment'
end
end
...
Read up on member routes at the Rails Guide. There are other ways of accomplishing a similar route.
posts/show.html.erb
<%= link_to "Render form", submit_comment_post_url(#post), :remote => true %>
Note that the Rails default url helper is different than the one you've got, if you use member routes.
posts_controller.rb
def submit_comment
#post = Post.find(params[:id])
...
end
Hope that helps! Happy form-ing!

Related

Simple_form does not accept the instance variable from action create

I am creating a very simple StackOverflow type of website
written in Ruby on Rails. I created four actions in my questions controller and one of them is 'Create'.
I proceeded in the view page index.html.erb and I create a simple_form where I get input(question) from a user.
I get an error (NoMethodError in Questions#index..undefined method `[]' for nil:NilClass)
The question belongs to the current_user and I think that might be the problem. I thought maybe I need to initialize one more variable in order to get my form to work.
Can please someone tell me what am I missing here?
Thank you in advance!
class QuestionsController < ApplicationController
def index
#questions = Question.all
end
def show
#question = Question.find(params[:id])
end
def new
#question = Question.find(params[:user_id])
#question = Question.new
end
def create
#question = Question.new(accepted_params)
if #question.save
redirect_to questions_show_path, notice: 'Question submitted'
else
#question = Question.find(params[:id])
render :new
end
end
<%= simple_form_for #questions, #user do |f| %>
<%= f.input :title %>
<% f.input :content %>
<%= f.submit :submit, class: 'btn btn-secondary'%>
<% end %>
You should use the following code:
def create
#question = Question.new(accepted_params)
if #question.save
redirect_to question_path(#question), notice: 'Question submitted'
else
render :new
end
end
Let me explain:
on a create action there is no params[:id] (because we are creating a new item)
also: we established that saving failed so trying to retrieve it from the database would only make sense on an edit action
third: simple-form will look at the error-messages and incorporate them into the form, so the user can then fix the errors.
and lastly: I fixed your redirect_to to be more "rails"-like, but this depends on your route-definition. I am assuming you have something like resources :questions in your routes (but if you do not give a parameter that could also never work imho)
E.g. if you have a validate_presence_of :name in your model, this could cause a validation-error upon save, and then we could present the field in red in the form when rerendering.
[TYPO in form?]
Lastly, after your comment I noticed it said simple_form_for #questions, #user and that should be either be the singular simple_form_for #question. If you want to edit the question as a nested path for the user, I think the correct form is simple_form_for [#user, #question].

CRUD of a model within another model

I have Post CRUD. On show.html.erb for Post, I want CRUD for comment. I am using devise for User. I have tried what others have suggested elsewhere on stack overflow but it has not worked. It keeps saying #comment is empty. I'd appreciate your help.
comments controller
def new
#user = current_user
#post = Post.find(params[:post_id])
#comment = #post.comments.new
render :template => 'posts/show'
end
def create
#user = current_user
#post = Post.find(params[:post_id])
#comment = #post.comments.new(post_params)
render :template => 'posts/show'
if #comment.save
redirect_to post_path(post.id), notice: "Success!~"
else
redirect_to post_path(post.id), alert: "Failure!"
end
end
private
def comment_params
params.require(:comment).permit(:text)
end
show.html.erb (within posts folder)
<%= form_for([#user, #post, #comment]) do |form| %>
<p>
<%= form.text_area :text %>
</p>
<p>
<%= form.submit %>
</p>
<% end %>
As far as I understand you, you want to have the CRUD actions for comments in the show view of a post. It's called nested routing or just nested models.
From you posts/show view I can see from your form for you also nested the posts inside the users. That's not necessary. As a general guideline, don't nest models more than one level. I guess in your app posts belong to a user, and you're using devise. So by the creation of a post you can just assign the current_user to the post and then you don't need to nest them inside each other.
Now for your questions: Since you have the form for a new comment on the show page of post, you need to have the code from your comments_controller#new in the posts_controller#show. Why? Because you use the instance variable #comment in the form_for. If in your posts_controller there is no such #comment defined,it will throw the error.
Once your form is then filled and submitted a post request to comments_controller#create is made, where you save the code and redirect to the post#show.

Why is this form redirecting me before giving me a chance to enter data?

Rails controller:
class VenuesController < ApplicationController
def new
#venue = Venue.new
end
def create
#venue = Venue.new(params[:venue])
if #venue.save
redirect_to root_path
end
end
def update
redirect_to search_path
end
end
Rails form:
<%= form_for(#venue) do |f| %>
<%= f.text_field :foursquare_id %>
<%= f.submit "Save" %>
<% end %>
"foursquare_id" is a column in the "venues" table. usually i import a foursquare id from foursquare but i'm typing in text for testing purposes. i am being redirected to "root_path" before even being given a chance to type into the form.
what is my controller/form missing? thank you in advance
The template form should be used by the new action with new.html.erb as the filename. And you should be going to /venues/new to fill out the form.
The create action is used to submit the completed form, which is why you are getting redirected. You should also modify create to handle a model that couldn't save:
def create
#venue = Venue.new(params[:venue])
if #venue.save
redirect_to root_path
else
render :action => :new
end
end
Or you can use the shorthand for this:
def create
#venue = Venue.new(params[:venue])
#venue.save
respond_with #venue, :location => root_path
end
Your form for creating a new venue should be in venues/new.html.erb which will call the Create action in your controller upon form submit. You shouldn't have a create view in this scenario.

Ruby on rails route to a custom method in the controller from the view

I have a controller name posts. In my /config/routes.rb, I have used this -
resources :posts
/app/controllers/posts_controller.rb:
class PostsController < ApplicationController
def new
#post = Post.new
end
def show
#post = Post.find(params[:id])
end
def categoryshow
#post = Post.find(params[:category])
end
def index
#posts = Post.all
end
def create
#post = Post.new(params[:post])
if #post.save
flash.now[:success] = "Your Post Successful"
redirect_to #post
else
render 'new'
end
end
end
I am new to rails and I often get confused with routes. I have another static_pages controller. In there is a home.html.erb file.
What I want to do is to call -
def categoryshow
#post = Post.find(params[:category])
end
'categoryshow' method of posts controller from /app/views/static_pages/home.html.erb
How do I manage that? If I use 'posts_path', it goes to index action instead of categoryshow action.
#
I read through the link and tried a couple of things from there. Here is the problem that I am facing:
When I tried this in the config/routes.rb
resources :posts do
collection do
get 'categoryshow'
end
end
This generates a 'categoryshow_posts_path'
In my view, I used this:
<ul class="users">
<%= Post::CATEGORIES.each do |category| %>
<li>
<%= link_to category,categoryshow_posts_path %>
</li>
<% end %>
</ul>
My posts controller has the following method:
def categoryshow
#post = Post.find(params[:category])
end
In this case, I am getting the following error:
ActiveRecord::RecordNotFound in PostsController#categoryshow
Couldn't find Post without an ID
Secondly, I tried out using non resourceful routes as mentioned in that link you provided:
match ':posts(/:categoryshow(/:category))'
In the view, I am using this:
Categories
<ul class="users">
<%= Post::CATEGORIES.each do |category| %>
<li>
<%= link_to category,"posts/#{category}" %>
</li>
<% end %>
</ul>
In this case, our non resourceful route will match only if no other existing resourceful route matches. However, i am seeing that show action is matched and I get this error message:
This is the show action:
def show
#post = Post.find(params[:id])
end
ActiveRecord::RecordNotFound in PostsController#show
Couldn't find Post with id=Politics
I'd really appreciate any help here!!
Thanks (Sidharth)
Check out the "Adding More RESTful Actions" sections of the routing docs.
resources :posts do
  collection do
    get 'categoryshow'
  end
end
Or:
resources :posts do
get 'categoryshow', :on => :collection
end
The section after that discusses adding arbitrary routes if that doesn't meet your exact needs.
Note that the routes file is explicitly ordered: if you try to match a route after something else would have captured it, the first route wins.

Rails: jQuery Ajax issue with partial refresh of comment section

I currently have a comment section that posts only after the whole page refreshes. Although after the post the page refreshes by itself it feels inefficient for the whole page to refresh. I was wondering if anyone can help me with a js file that would refresh just that partial, I am still shakey with my js. Any help is much appreciated! Thank you!
This is my current js for the create.js:
$("#comments").html("<%= escape_javascript(render(:partial => #micropost.comments)) %>");
comment controller
class CommentsController < ApplicationController
def create
#micropost = Micropost.find(params[:micropost_id])
#comment = #micropost.comments.build(params[:comment])
#comment.user_id = current_user.id
#comment.save
respond_to do |format|
format.html
format.js
end
end
end
Comment Section
<div id='CommentContainer-<%= micropost.id%>' class='CommentContainer Condensed2'>
<div class='Comment'>
<%= render :partial => "comments/form", :locals => { :micropost => micropost } %>
</div>
<div id='comments'>
<%=render micropost.comments %>
</div>
</div>
You should use something like this in your controller. This will trigger both the js and html templates as needed.
class CommentsController < ApplicationController
respond_to :html
respond_to :js, only: [ :create ]
def create
# ...
respond_with #comment if #comment.save
end
def index
#comments = Microcomment.find(params[:id]).comments
respond_with #comments
end
end
This will then require views/comments/create.js to respond with something like:
// create.js.erb
$("#comments").get("/api/micropost/<%= #micropost.id %>/comments");
And the view for the comments will be index.html.erb
# index.html.erb
<% #comments.each do |comment| %>
<!-- Display your comment here -->
<% end %>
Now all you have to do is set up a match for /api/micropost/:id/comments in your routes, and this can then serve the list of comments in the desired html format.
Note that this is not completely restful, but I like to keep the /api there to distinguish calls coming from xhr on a url level.

Resources