Showing duplicate comment on page refresh (after initial AJAX append) [Rails] - ruby-on-rails

Can't seem to figure this one out... In my rails app, I have Post and Comment resources. When a new comment is created, I want to reload the list of comments using AJAX. That part seems to work appropriately—however, when the full page is subsequently reloaded, the list also shows a duplicate of the comment, as shown in this screenshot. Any thoughts on what may be causing this?
(note: deleting one of the comments also deletes the duplicate)
views/users/show.html.haml
= form_for([post, post.comments.build], remote: true) do |f|
= f.text_field :content, placeholder: 'Press ENTER to submit...', class: "comment_content", id: "comment_content_#{post.id}"
- if post.comments
.comments{ id: "comments_#{post.id}" }
- post.comments.each do |comment|
= render post.comments, post: post
views/comments/_comment.html.haml
- unless comment.content == nil
.comment{ id: "comment_#{comment.id}" }
.user-name
= link_to comment.user.identities.first.nickname, comment.user
.comment-content
= comment.content
- if comment.user == current_user
= link_to post_comment_path(post, comment), data: { confirm: "Are you sure?" }, method: :delete, remote: true do
%i.fa.fa-close
controllers/comments_controller.rb
class CommentsController < ApplicationController
before_action :set_post
def create
#comment = #post.comments.build(comment_params)
#comment.user_id = current_user.id
if #comment.save
respond_to do |format|
format.html { redirect_to :back }
format.js
end
else
render root_path
end
end
...
views/comments/create.js.erb
$('#comments_<%= #post.id %>').append("<%=j render #post.comments, post: #post, comment: #comment %>");
$('#comment_content_<%= #post.id %>').val('')
Update
Following #uzaif's suggestion, I replaced ".append" with ".html". This fixed the problem only if I also moved the code out of the _comment partial. Not really an ideal solution... I'd still like to know if/how I could fix the problem and keep my individual comment partial.

The Problem
You are seeing duplicate comments because you are inadvertently rendering your comments twice.
- post.comments.each do |comment|
= render post.comments, post: post
Calling render and passing a collection as the first argument instructs Rails to render all of the comments. Internally Rails will iterate over each comment and render it using the object's partial.
By wrapping this render call (which already has a loop in it) within another loop, you are actually rendering each comment twice.
The Solution
Remove the outer post.comments.each loop and just let the render method do it's thing.
= render post.comments
Rails knows to pass along an local variable named comment to each partial, and you should be able to reference the original post by calling comment.post (assuming comment belongs_to :post).
Be careful how you call render
There are a couple different ways to render partials with data from a collection. Make sure you know which one you are using and don't mix-and-match them.
I describe this in another StackOverflow post.
Resources
http://guides.rubyonrails.org/layouts_and_rendering.html#rendering-collections
https://stackoverflow.com/a/40412677/1219460

Related

Unable To Displaying Comments On A Post

I am adding comments to a post which save fine but I have been struggling to get them show more than one comments on a post. When I save it only shows the first comment posted.
Comments Controller:
class CommentsController < ApplicationController
def create
#micropost = Micropost.find_by(id: params[:micropost_id])
#comment =
#micropost.comments.create(params[:comment].permit(:body))
if #comment.save
flash[:success] = "Comment Posted"
end
redirect_to current_user
end
end
def show
#comment = Comment.find_by(id: params[:id])
end
end
In the view I have:
<%= #comments.body %>
I can show one comment, but when I write another the first one only shows. I tried to loop over them in the view with a do loop but I then get an error stating undefined method .each for "text from comment":String
Maybe my show #comment needs improving or is there something I have to do in the view to get them all to display. The same do loops works for the posts, but I can seem to get it working for the comments.
I also have another minor issue when there is no comments saved, I can't view the post at all because there is no body existing. I assume I will have to write some sort of if/else to state if there is no body, display anyway.
In your posts controller's show method you should fetch comments and then you can show it in html page.
For example,
def show
#post = Post.includes(:comments).find(params[:id])
end
and in your view file, you can iterate over comments like below,
#post.comments.each do |comment|
# you logic here
end
what you are doing is fetching single comment and try to loop through that object , which is not possible.
what you want to do is display all the comments posted on 'Micropost' so what you need to do is just pass micropost_id in params and user where query like below
#comments = Comment.where(micropost_id: params[:micropost_id])
then you can loop through that object in your .erb template
<% #comments.each do|comment| %>
<%= comment.body %>
<% end %>

Rails 5.0 - How to implement in-place editing without using best_in_place gem

I'm building an Events site using RoR which I've just upgraded to v5.0.1 (not moving to 5.1 just yet). My events show page has a comments section at the foot of the page which, so far, only has a create and delete functionality.
I want to add an Edit/Update function to comments so only the user who created the comment can edit the comment if they see fit. I want them to be able to edit their comment whilst on the same page.
I'm trying to implement these changes using remote: true & Ajax, rather than rely on a gem as it doesn't appear too complex, but I've never done this before and there doesn't appear to be a clear guide via the internet/SO. Here's my views & controller code -
comments_controller.rb
def create
#event = Event.find(params[:event_id])
#comment = #event.comments.create!(params[:comment].permit(:name, :body))
#comment.user_id = current_user.id
redirect_to #event
end
def update
#comment.user = current_user
respond_to do |format|
if #comment.update
format.html { redirect_to #comment, notice: 'Comment was successfully updated.' }
format.js { }
format.json { render :show, status: :created, location: #comment }
else
format.html { render :new }
format.json { render json: #comment.errors, status: :unprocessable_entity }
end
end
end
def destroy
#event = Event.find(params[:event_id])
#comment = #event.comments.find(params[:id])
#comment.destroy
redirect_to event_path(#event)
end
_comments.html.erb
<% if user_signed_in? %>
<p><%= link_to "Edit", remote: true %></p>
<p><%= link_to 'Delete', [comment.event, comment],
method: :delete,
class: "button",
data: { confirm: 'Are you sure?' } %></p>
<% end %>
My understanding is that I need to include a .js.erb file in my views/comments folder to deal with this ( edit.js.erb ?) but what I'm not clear on is exactly what javascript code I need to include in order for this to work. Also, I don't think my controller code above seems right - should this go in the Edit action? Do I also need an Event#show action in my events controller as this is where it sits in the views?
Any assistance would be greatly appreciated.
I realize this doesn't answer your question, but hopefully it'll solve headaches down the road for you as far as reinventing the wheel goes. It's also why (I believe) you're seeing downvotes.
I don't want to use best_in_place gem as it appears to not have been updated for a while and I'm not sure if its the best fit for Rails 5.0.
Gems don't need to have activity to still be useful. By "for a while", you must mean mean "less than 24 hours ago" because I'm seeing plenty of activity over the last month. Once a Gem solves its usually good to go.
https://github.com/bernat/best_in_place/commits/master
Rails 5 still handles POST requests right? Then it should work. best_in_place is more javascript-heavy than anything. https://github.com/bernat/best_in_place/tree/master/lib/best_in_place
The most "daunting" code in there is the helper.rb file, which renders the HTML that hooks into the JS library.
Update:
The comments/edit.js.erb file would be responsible for inserting the form to edit the comment, such as $('div#new_comment').append('<%= j render 'form' %>');
This assumes you're using Rails' conventions in regards to element/ID naming.
If you have a link_to("Edit", edit_comment_path(comment), :remote => true) everything should fire automatically.
Here's an example repo using Rails 4; should be the same for Rails 5, with the exception of (maybe) respond_to blocks? I'm not sure, haven't used Rails 5 yet. Just do a bundle, rake db:setup, and then a rails s; navigate to http://localhost:3000/events and enjoy.
You're on the right track with edit.js.erb. The file should contain JS code to set up the screen for editing, which might be hiding a DIV and displaying an INPUT or TEXTAREA instead, for example.
Of course, the file can also contain Ruby code as well, inside <% %> tags.

rails 4, nested resource doesn't updates without page refreshing, using Ajax

So, I'm having Events model which has_many Questions. Relation is the same as in Post->Comment. I've tried to implement some Ajax for Questions, but it seems like I'm missing something, because View doesn't updates without refreshing the page. The same problem with delete method.
Here's my question_controller.rb:
def create
#question = #event.questions.create(question_params)
#question.user_id = current_user.id if current_user
if #question.save
respond_to do |format|
format.html { redirect_to event_path(#event) }
format.js # render questions/create.js.erb
end
else
render 'events/show'
end
end
Question _form.haml:
= simple_form_for [#event, #event.questions.new], remote: true do |f|
= f.input :content, label: 'Your question:', id: 'question_content'
= f.submit class: 'submit_btn'
Question partial _question.haml:
%h4
= question.content
This is how it looks in Events show.haml:
%section#question_section
%h1 Questions:
#questions
= render #event.questions
#question-form
%h1 Submit a question:
= render 'questions/form'
And create.js.erb file:
$('#questions').append("<%= j render partial: #question %>");
$('#question_content').val('');
I've tried few different tutorials, but always had the same problem. View updates only after refreshment. Also it is my second post on Stackoverflow so would be grateful for any suggestions if something is wrong with it.
EDIT 1: mostly I've been following this tutorial: https://pragmaticstudio.com/blog/2015/3/18/rails-jquery-ajax
EDIT 2: create.js.erb seems not to respond, tried to test it with alerts or console log, but without any success. After triggering a button in the console having: "Processing by QuestionsController#create as JS" (so it runs a proper file, but file itself doesn't executes any code). Have no idea why that happening.
So the problem was because of HAML. Had to change my format.js line in the controller to:
formta.js { render layout: false }
Here's the thread that helped to solve this: Rails update.js.erb not executing javascript

Rails way to hide a new instance of a model that is not saved to the database yet

I have a Book resource and a Comment resource that belongs to Book.
On app/views/book/show.html.erb, I show the book information and all the comments for the book. A user can add comments directly from that page.
So the BooksController#show action looks like this:
def show
#book = Book.find(params[:id])
#comment = Comment.new
end
And the app/views/book/show.html.erb looks like this:
# ... book info from #book
<%= form_for [#book, #comment] do |f| %>
# ... rest of the form_for
<% #book.comments.each do |comment| %>
# ... print all the comments
So this goes to CommentsController#create action:
def create
#book = Book.find(params[:book_id]
#comment = #book.comments.new(comment_params)
if #comment.save
redirect_to #book
else
render 'book/show'
end
end
Now all this is good, but when this action fails and renders book/show, I can see the new comment in the comments list, because it's added to that book's comments collection, even though it's not saved to the database yet.
What's the rails way to handle this?
You can add a scope to only render persisted comments
app/models/comment.rb
class Comment < ActiveRecord::Base
scope :persisted, -> { where "id IS NOT NULL" }
end
app/views/book/show.html.erb
<% #book.comments.persisted.each do |comment| %>
You could try redirecting to book/show instead of rendering it directly.
Instead of your conditional, just always redirect. You may want to inform the user of errors though, so you can set a flash message.
if #comment.save
redirect_to #book
else
redirect_to #book, :flash => { :error => "Something went wrong!" }
end
You could build your comment differently:
def create
#book = Book.find(params[:book_id]
#comment = #comments.new(comment_params.merge { book_id: #book.id })
if #comment.save
redirect_to #book
else
render 'book/show'
end
end
I first tried using the following in the app/views/books/show.html.erb:
<% #book.reload.comments.each do |comment| %>
# ... print all the comments
This reloads the book object from the database so the new comment that is not saved to the database yet is not shown. But the downside is that this hits the database one more time, which is unnecessary.
So I ended up using this:
<% #book.comments.select(&:persisted?).each do |comment| %>
# ... print all the comments
This only shows the persisted comments.
I'll probably take this logic off the view template, and put it in a view helper.

Give an error when a model is deleted in Rails

Is there a way to give back an error if a particular model is deleted in Rails 3.x+? If it's deleted via the web, an error code is given back. If it's deleted via console then a message is given with the reason it can't be deleted.
You can use a before_destroy. The link is here. Returning false from this validation message prevents the destruction of the object.
Well, the way that I usually delete something is...
from the view:
<%= link_to "Delete Post", #post, method: :delete, data: { confirm: "You're sure you want to DELETE the post '#{#post.title}'?" } %>
then in the post controller:
def destroy
Post.find(params[:id]).destroy
flash[:success] = "Blog entry deleted!"
redirect_to posts_path
end
So, the method: :delete just gets to the destroy route of the controller. Then you do what you want, right?
So instead of .destroy after you find the model, you could do:
def destroy
#post = Post.find(params[:id])
flash[:notice] = "Blog entry can't be deleted..."
puts "#{#post.name} can't be deleted because..." #this line is what shows up in the console
redirect_to posts_path
end

Resources