I'm trying to implement the Best In Place gem and have followed along with the Railscast, but am having issues. I'm learning Rails and am building a sample blog app with two models, Article and Comment. I'm trying to use Best In Place to edit comments.
_list_comments.html.erb
<% #comments.each do |comment| %>
<hr />
<%= link_to article_comment_path(#article, comment), method: :delete, data: {confirm: 'Are you sure?'}, remote: true do %>
<span class="glyphicon glyphicon-remove"></span>
<% end %>
<%= content_tag :span, '', id: "#{comment.id}", class: "glyphicon glyphicon-edit edit_comment" %>
<!--<%= content_tag :p, content_tag(:small, "#{comment.author}"), id: "comment_author_#{comment.id}" %>-->
<%= best_in_place comment, :author %>
<%= content_tag :p, id: "comment_body_#{comment.id}" do %>
<%= comment.body %>
<% end %>
<% end %>
It is giving me this error: ActionView::Template::Error (undefined method comment_path for #<#<Class:0x007fdc38fb8288>:0x007fdc38fc36b0>):, which is referring to <%= best_in_place comment, :author %>. I'm pretty sure I installed everything correctly, so I don't know what the problem is.
When I change <%= best_in_place comment, :author %> to <%= best_in_place "#{comment}", :author %>, it gives me this error: undefined method 'author' for "#<Comment:0x007fdc3c841820>":String.
comments_controller.html.erb
class CommentsController < ApplicationController
def create
#comment = Comment.new(comment_params)
#comment.article_id = params[:article_id]
if #comment.save
respond_to do |f|
f.html { redirect_to article_path(params[:article_id]), notice: 'Comment created!' }
f.js {
#article = Article.find(params[:article_id])
#comment = #comment
#comments = Comment.where(article_id: params[:article_id])
}
end
else
redirect_to article_path(params[:article_id]), warning: 'Unable to create comment.'
end
end
def destroy
#comment = Comment.find(params[:id]).destroy
respond_to do |f|
f.html { redirect_to article_path(params[:article_id]) }
f.js {
#article = Article.find(params[:article_id])
#comments = Comment.where(article_id: params[:article_id])
}
end
end
def update
#comment = Comment.find(params[:id])
if #comment.update(comment_params)
respond_to do |f|
f.html { redirect_to article_path(#comment.article_id) }
f.json { render head :ok}
end
else
respond_to do |f|
f.html { redirect_to article_path(#comment.article_id) }
f.json { render json: #comment.errors.full_messages, status: :unprocessable_entity }
end
end
end
private
def comment_params
params.require(:comment).permit(:author, :body)
end
end
I was able to clone your project and figure out why you are getting this error:
undefined method comment_path for #<#<Class:0x007fdc38fb8288>:0x007fdc38fc36b0>
Here is what's happening. In this code <%= best_in_place comment, :author %>, you are passing comment object. best_in_place is trying to map it to a route helper named comment_path by appending path to comment (hint: undefined method comment_path).
Your routes are set up this way:
resources :articles do
resources :comments
end
if you do rake routes, you'll notice that you don't have a helper path named comment_path which corresponds to your comments#show. It is looking for comment_path and it can't find it. hence, the error message.
However, since you are using nesting resources the helper path that corresponds to the comments#show is called article_comment_path. here is the complete mapping:
article_comment_path GET /articles/:article_id/comments/:id(.:format) comments#show
To get best_in_place map to the correct helper path, there are two ways you can solve this:
1) create a route that maps to comment_path by simply adding this route to your routes.rb file:
resources :comments, only: [:show]
The full mapping for the above route is:
comment_path GET /comments/:id(.:format) comments#show
you now have comment_path. No more error message.
2) Tweak your code to map to article_comment_path
replace:
<%= best_in_place comment, :author %>
with:
<%= best_in_place [#article, comment], :author %>
The array, [#article, comment], will build the helper path article_comment_path by appending comment to #article
Related
im getting this error:
https://imgur.com/a/Mrvkm2Z
I can create comments in mi course post but i can't edit them because of this error
I don't know how to solve this, I was looking everywhere but nothing that helps me solve the problem
EDIT
here is my code in comments controller
class CommentsController < ApplicationController
before_action :set_comment, only: [:show, :edit, :update, :destroy]
# GET /comments
# GET /comments.json
def index
#comments = Comment.all
end
# GET /comments/1
# GET /comments/1.json
def show
end
# GET /comments/new
def new
#course = Course.find(params[:course_id])
#comment = #course.comments.build
end
# GET /comments/1/edit
def edit
end
# POST /comments
# POST /comments.json
def create
#course = Course.find(params[:course_id])
#comment = #course.comments.build(comment_params)
respond_to do |format|
if #comment.save
format.html { redirect_to #course, notice: 'Comment was successfully created.' }
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
# PATCH/PUT /comments/1
# PATCH/PUT /comments/1.json
def update
respond_to do |format|
if #comment.update(comment_params)
format.html { redirect_to #comment, notice: 'Comment was successfully updated.' }
format.json { render :show, status: :ok, location: #comment }
else
format.html { render :edit }
format.json { render json: #comment.errors, status: :unprocessable_entity }
end
end
end
# DELETE /comments/1
# DELETE /comments/1.json
def destroy
#comment.destroy
respond_to do |format|
format.html { redirect_to comments_url, notice: 'Comment was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_comment
#comment = Comment.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def comment_params
params.require(:comment).permit(:title, :body)
end
end
so i think the problem might be in create and new, i dont know how to connect the models with one-to-many, i was able to create comments in my course but when i wanted to edit 1 of the comments apiers that error and in every comment i click to edit sends me to the same url with the same error id=3 not found
Also this are my html code(maybe the errors can be there too):
<%= form_for #comment, :url => course_comments_path(params[:course_id]) do |f| %>
<% if comment.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(comment.errors.count, "error") %> prohibited this comment from being saved:</h2>
<ul>
<% comment.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :title %>
<%= f.text_field :title, id: :comment_title %>
</div>
<div class="field">
<%= f.label :body %>
<%= f.text_area :body, id: :comment_body %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
and the show.html for my courses:
<% if current_user%>
<p id="notice"><%= notice %></p>
<p>
<h1><%= #course.name %></h1>
</p>
<p>
<h2>Comentarios del curso:</h2>
</p>
<p>
<% #course.comments.each do |comment| %>
<h3><%= comment.title %></h3>
<p><%= comment.body %></p>
<%if current_user.admin%>
<%= link_to 'Edit', edit_course_comment_path(#course) %>
<%end%>
<%end%>
</p>
<%if current_user.admin %>
<%= link_to 'Postear', new_course_comment_path(#course)%>
<%= link_to 'Edit', edit_course_comment_path%>
<%= link_to 'Back', authenticated_root_path %>
<%else%>
<%= link_to 'Postear', new_course_comment_path(#course) %>
<%= link_to 'Back', authenticated_root_path %>
<%end%>
First off you have a strange mix of shallow nesting and deep nesting. Shallow nesting is a routing option that effects the nesting of member routes.
resources :courses do
# GET|PUT|PATCH|DELETE /courses/:course_id/comments/:id
# GET /courses/:course_id/comments/:id/edit
resources :comments
# GET|PUT|PATCH|DELETE /comments/:id
# GET /comments/:id/edit
resources :comments, shallow: true
end
I would generally recommend shallow nesting - unless the child only can exist in the scope of its parent or is only unique in the scope of its parent. And your controller is setup for shallow nesting. Just make sure you edit those comments from the scaffold so that they actually document the right paths.
Besides the routes you also need to use the correct link helpers:
# deep nesting
link_to 'Edit', edit_course_comment_path(#course, #comment)
# shallow nesting
link_to 'Edit', edit_comment_path(#comment)
If you really need to support both you can use the polymorpic route helpers:
link_to 'Edit', [:edit, #course, #comment]
To create forms for nested resources pass an array containing the parent and child:
form_for([#course, #comment])
# or in rails 5+
form_with(model: [#course, #comment])
This works perfectly fine with both deep and shallow nesting as well as Rails compacts the array. This also lets you use the same form partial for creating and updating.
Explicitly passing the URL for a form in Rails is redundant 99% of the time. If you just follow the conventions Rails is smart enough to figure out the correct path for creating and updating.
I think this line
<%= link_to 'Edit', edit_course_comment_path(#course) %>
should be
<%= link_to 'Edit', edit_course_comment_path(#course, comment) %>
You need both ids of the course and the comment.
I'm working on a ROR blog and have encountered some issues along the way. I'm currently learning Rails and just feel completely lost with connecting all the pieces. I've been working on my comments section for days and was finally able to create comments on posts, but I can't edit or delete them. I also referenced the SO questions below but am still running into problems.
Add Comment to User and Post models (Ruby on Rails)
Here's my layout:
Comment model params:
body \ user_id \ post_id
Model associations:
user.rb
has_many :posts
has_many :comments
post.rb
belongs_to :user
has_many :comments
comment.rb
belongs_to :user
belongs_to :post
routes.rb:
Rails.application.routes.draw do
# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
get '/' => 'users#index'
get '/posts' => 'posts#index'
post '/posts/create' => 'posts#new'
post '/posts/edit' => 'posts#edit'
get '/signin' => 'sessions#new', as: :new_session
post '/create-session' => 'sessions#create', as: :create_session
get 'signout' => 'sessions#destroy', as: :destroy_session
resources :users
resources :posts
resources :comments
end
comments controller:
class CommentsController < ApplicationController
def index
#comment = Comment.all
end
def new
user = session[:user_id]
#comment = Comment.new(post_id: params[:post_id])
#post = Post.find(params[:post_id])
end
def create
#comment = Comment.new(comment_params)
#comment.user_id = session[:user_id]
#postid = params[:id]
if #comment.save
flash[:notice] = "comment created."
redirect_to '/posts'
else
flash[:error] = "Error creating comment."
redirect_to '/posts'
end
end
def edit
#post = Post.find(params[:id])
end
def update
#comment = Comment.find_by_id(params[:id])
#comment.update(comment_params)
flash[:notice] = "Comment updated."
redirect_to '/posts'
end
def destroy
#comment = Comment.find(params[:comment_id])
#comment.destroy
redirect_to '/posts'
end
private
def comment_params
params.require(:comment).permit(:body, :user_id, :post_id)
end
end
Posts show.html.erb page in views/posts folder:
<%# show all posts %>
<div id="single-post">
<h1>User - <%= #post.user.username %></h1>
<h2>Post - <%= #post.body %> </h2>
<%= link_to("Edit Post", edit_post_path(#post)) %>
</br>
<%= link_to("Delete Post", #post, method: 'delete') %>
</br>
<%= link_to("Add Comment", new_comment_path(post_id: #post.id)) %>
<%#<%= link_to("Edit Comment", edit_comment_path(post_id: #post.id, comment_id: #comment.id))%>
</div>
<h3><% #post.comments.reverse.each do |c| %> </h3>
<div id="single-comment">
<h4>Comment</h4>
<h5>From - <%= c.user.username %></h5>
<h6><%= c.body %> </h6>
</br>
<%= link_to("Edit Comment", edit_comment_path(#post.id)) %>
</br>
<%= link_to("Delete Comment", comment_path(#post.id), method: :delete) %>
</div>
<% end %>
</div>
new.html.erb form in views/comments folder
<div id="comment-form">
<%= form_for #comment do |f| %>
<%= f.label :body %>
<%= f.text_area :body, class: "text-area" %>
<%= f.hidden_field :post_id %>
<%= f.submit %>
<% end %>
</div>
Again I can add comments to posts. When I hover over the edit tag on the comment I'm seeing this: localhost:3000/comments/72/edit
I see this error when I click on edit
When I hover over the delete button I see this: localhost:3000/comments/72
I see this error when I click on delete
I'm at the point where I'm completely lost and feel I have tried everything possible but nothing seems to work. Please help! Here's the GitHub repo as well: https://github.com/angelr1076/rails-blog
The First argument in form cannot contain nil or be empty is telling you that #comment in <%= form_for #comment do |f| %> is nil. This is because in the edit action of your CommentsController you are setting #post instead of #comment.
Change this to be:
def edit
#comment = Comment.find(params[:id])
end
For deleting a comment, the Couldn't find Comment without an ID is telling you that the value you're passing to find is nil. This is because you're trying to use params[:comment_id] instead of params[:id]. Change the destroy action to:
def destroy
#comment = Comment.find(params[:id])
#comment.destroy
redirect_to '/posts'
end
Update:
Also as per your code, you should change edit and delete links to below
<%= link_to("Edit Comment", edit_comment_path(c)) %>
<%= link_to("Delete Comment", comment_path(c), method: :delete)
You are passing #post.id which is an id of post. Instead you should pass id of the comment using the block variable from your comments.each, noticing that the .id isn't needed here because it can be inferred by Rails.
I am creating nested comments (like you find on Reddit). I am able to create parent comments, but when I try to create a child comment, it simply renders as a parent comment.
In my rails console, the "ancestry" field comes back "nil".
This is my comments controller:
class CommentsController < ApplicationController
before_action :set_comment, only: [:show, :edit, :update, :destroy]
before_filter :authenticate_user!
def show
#comment = Comment.find(params[:id])
end
def new
#link = Link.find(params[:link_id])
#comment = Comment.new(:parent_id => params[:parent_id])
#comments = Comment.all
end
def create
#link = Link.find(params[:link_id])
#parent = Link.find(params[:link_id]) if params[:link_id]
#parent = Comment.find(params[:comment_id]) if params[:comment_id]
#comment = #parent.comments.new(comment_params)
#comment.user = current_user
respond_to do |format|
if #comment.save
format.html { redirect_to #link, notice: 'Comment was successfully created.' }
format.json { render json: #comment, status: :created, location: #comment }
else
format.html { render action: "new" }
format.json { render json: #comment.errors, status: :unprocessable_entity }
end
end
end
def destroy
#comment.destroy
respond_to do |format|
format.html { redirect_to :back, notice: 'Comment was successfully destroyed.' }
format.json { head :no_content }
end
end
private
def set_comment
#comment = Comment.find(params[:id])
end
def comment_params
params.require(:comment).permit(:link_id, :body, :user_id)
end
end
Here is my _comment_form partial
<%= div_for(comment) do %>
<div class="comments_wrapper clearfix">
<div class="pull-left">
<p class="lead"><%= comment.body %></p>
<p><small>Submitted <strong><%= time_ago_in_words(comment.created_at) %> ago</strong> by <%= comment.user.email %></small></p>
<div id="reply" style="display:none;">
<%= form_for [#comment = Comment.new(:parent_id => params[:parent_id])] do |f| %>
<%= f.hidden_field :parent_id %>
<%= f.text_area :body %> <br>
<%= f.submit %>
<% end %>
</div>
</div>
<div class="actions btn-group pull-right">
<button onClick="$('#reply').show()" class="btn btn-sm btn-default">Reply</button>
<% if comment.user == current_user -%>
<%= link_to 'Destroy', comment, method: :delete, data: { confirm: 'Are you sure?' }, class: "btn btn-sm btn-default" %>
<% end %>
</div>
</div>
<% end %>
These are my routes
Rails.application.routes.draw do
resources :comments
devise_for :users
devise_for :installs
resources :links do
member do
put "like", to: "links#upvote"
put "dislike", to: "links#downvote"
end
resources :comments
end
root to: "links#index"
end
Had this problem before; the answer is here:
Ancestry gem in Rails and Mutli Nesting
The problem with ancestry (this is why we changed back to acts_as_tree) is that you have to define all the ancestors in the ancestry column (as opposed to just the parent_id column of acts_as_tree).
Thus, when you call the .children of an object (where you've literally just populated ancestry with top-level parents) is a list of children for that parent (no others).
What you need is to reference the entire ancestry line. This is quite tricky, but can be achieved using the code below:
#app/views/links/index.html.erb
<%= render #link.comments if #post.comments.any? %>
#app/views/links/_comment.html.erb
<%= comment.title %>
<%= render "form", locals: {link: #link} %>
<%= render comment.children if comment.has_children? # > adds recursion (multi level nesting) %>
#app/views/links/_form.html.erb
<%= form_for link.comments.new do |c| %>
<%= c.text_field :body %>
<%= c.submit %>
<% end %>
The controller is as follows:
#app/controllers/comments_controller.rb
class CommentsController < ApplicationController
def create
#link = Link.find params[:link_id]
#comment = #link.comments.new ancesrtry: parent(params[:parent_id])
end
private
def parent(param)
parents = Comment.find(param).pluck(:parent)
"#{parents}/#{param}" #-> ruby automatically returns last line
end
end
This should set the correct path for you, and the partials should give you the appropriate recursion required for multi level nesting.
I have the following models: User, Product and Comment. The user can add, edit and delete comments to the product. I've successfully set up adding and deleting functionality and now I'm struggling with editing, for some reason, it caused me a number of difficulties.
My current code returns this error when I click on the edit comment link:
NoMethodError at /products/800/comments/8/edit
undefined method `comments' for nil:NilClass
Here's how my comment model looks like:
# id :integer not null, primary key
# body :text
# created_at :datetime not null
# updated_at :datetime not null
# user_id :integer
# product_id :integer
class Comment < ActiveRecord::Base
belongs_to :user
belongs_to :product
# validations ...
end
In User model I have has_many :comments and in Product - has_many :comments, dependent: :destroy.
In my routes.rb file I have the following nested resources:
resources :products, only: [:show] do
resources :comments, only: [:create, :edit, :update, :destroy]
end
My ProductsController has the only method show and nothing else, and here's how it looks like:
def show
product = Product.find(params[:id])
photos = ProductsPhoto.where(product: product)
case product.products_category.name
when 'Violin'
#product = [product, Violin.where(product: product).first, photos]
when 'Guitar'
#product = [product, Guitar.where(product: product).first, photos]
when 'Saxophone'
#product = [product, Saxophone.where(product: product).first, photos]
when 'Piano'
#product = [product, Piano.where(product: product).first, photos]
end
#comment = Comment.new
#comments = Comment.where(product_id: product.id).order('created_at DESC')
end
And now here's my CommentsController, which has create, edit, update and destroy:
class CommentsController < ApplicationController
def create
#product = Product.find(params[:product_id])
#comment = #product.comments.create(comment_params)
#comment.user_id = current_user.id
if #comment.save
redirect_to #product, notice: 'Comment Created!'
else
redirect_to #product, notice: 'Something went wrong...'
end
end
def show
end
def edit
#product = Product.find(params[:product_id])
#comment = #product.comments.find(params[:id])
end
def update
#product = Product.find(params[:product_id])
#comment = #product.comments.find(params[:id])
respond_to do |format|
if #comment.update_attributes(comment_params)
format.html do
redirect_to [#comment.product, #comment], notice: 'Comment Updated!'
end
else
format.html { render action: 'edit', notice: 'Something went wrong...' }
end
end
end
def destroy
#product = Product.find(params[:product_id])
#comment = #product.comments.find(params[:id])
#comment.destroy!
redirect_to #product, notice: 'Comment Deleted!'
end
private
def comment_params
params.require(:comment).permit(:body)
end
end
My _form view is located at views/products/_form.html.erb and looks like this:
<%= simple_form_for([#product[0], #product[0].comments.build]) do |f| %>
<%= f.error_notification %>
<%= f.input :body, required: true, placeholder: 'Type in your comment...', input_html: { class: 'form-control'}, label: false %>
<%= f.button :submit, class: 'btn btn-primary btn-block' %>
<% end %>
And in the views/products/show.html.erb I render the partial of comments and there are the links for destroying and editing the comment, they look like this:
<%= link_to edit_product_comment_path(comment.product, comment) do %>
<span class="glyphicon glyphicon-pencil"></span>
<% end %>
<%= link_to [comment.product, comment], method: :delete do %>
<span class="glyphicon glyphicon-remove"></span>
<% end %>
the delete link works fine and the edit link doesn't.
Maybe I've mistaken with routes, here're the routes for comments:
product_comments POST /products/:product_id/comments(.:format) comments#create
edit_product_comment GET /products/:product_id/comments/:id/edit(.:format) comments#edit
product_comment PATCH /products/:product_id/comments/:id(.:format) comments#update
PUT /products/:product_id/comments/:id(.:format) comments#update
DELETE /products/:product_id/comments/:id(.:format) comments#destroy
In my views/comments/edit.html.erb I render the same form:
<%= render 'products/form' %>
however, when I click on the edit link, I get the following error:
NoMethodError at /products/800/comments/8/edit
undefined method `comments' for nil:NilClass
at the very first line of the _form.html.erb.
I hope I've provided enough information to describe the problem.
So, could you please help me with resolving that issue with comments editing?
It's too early this morning...
This is not correct :
<%= simple_form_for([#product, #product.comments.build]) do |f| %>
Try :
<%= simple_form_for [#comment.product_id, #comment], url: product_comment_path(#comment.product_id, #comment) do |f| %>
Added section
(All of this is assuming that we are working with a single #comment, not the #comments collection. Based on your comments about the delete working.)
To reply to your comment, yes, absolutely you do need separate forms for "new" versus "edit" action for your child table, comments.
Form "new" naming convention is comments/_form.html.erb, the "edit" form would be comments/_form_edit.html.erb,
I would open the "new" action form for comments...
<%= simple_form_for([:product, #comment]) do |f| %>
And the "edit" action...
<%= simple_form_for [#comment.product_id, #comment], url: product_comment_path(#comment.product_id, #comment) do |f| %>
Sorry I forgot the URL on the previous version, I have updated the code snippet above to reflect this change as well. Caveat: There may be other ways to construct this link, but this is how I have implemented the case of having a product (parent) with reviews/comments (many children).
Nested attributes
In further response to your comment, I believe some of what you're looking for could be achieved by using nested attributes in forms. This is another topic. I would get the new/edit form simple cases working and then add complexity.
I used this tutorial for making polymorphic comments
https://gorails.com/episodes/comments-with-polymorphic-associations
It works fine but when I set remote true to my comments form I got 505 error(the new comment is not appended to the list) when trying to add a new comment.
But when I reload the page the new comment appears in the list.
What can be wrong here?
I got this files in views/comments folder:
create.js
<% unless #comment.errors.any? %>
$("ul.comments").append('<%= j render "comments/comment" %>')
$("#new_comment")[0].reset();
<% end %>
_form.html.erb
<%= simple_form_for([commentable, Comment.new], remote: true) do |f| %>
<%= f.input :body, label: false %>
<%= f.button :submit, "Submit" %>
<% end %>
comments_controller.rb
def create
#comment = #commentable.comments.new(comment_params)
#comment.user = current_user
#comment.save
respond_to do |format|
format.html { redirect_to #commentable, notice: "Your comment was successfully added."}
format.js
end
end
_comment.html.erb
<li class="comment">
<b><%= comment.user.try(:username) %>:</b>
<%= comment.body%>
</li>
console
UP
I got this routes for post
resources :posts do
resources :comments, module: :posts
end
+
controllers/posts/comments_controller
class Posts::CommentsController < CommentsController
before_action :set_commentable
private
def set_commentable
#commentable = Post.friendly.find(params[:post_id])
end
end
The folder structure looks almost the same as here
https://github.com/excid3/gorails-episode-36/tree/master/app/controllers
the response tab shows this
undefined local variable or method `comment' for #<#<Class:0x007f993d3c5450>:0x007f99450a6190>
Trace of template inclusion: app/views/comments/create.js.erb
and when I replace
<%= j render #comment %> with some text it appends to the list on submit
ok the problem is with _comment.html erb
updated create.js with instance variable for comment and it works now.
<% unless #comment.errors.any? %>
$("ul.comments").append('<%= j render partial: "comments/comment", locals:{comment: #comment} %>')
$("#new_comment")[0].reset();
<% end %>