Owen Owen - 3 years ago 143
Ruby Question

What is wrong with my link_to tag? NameError. undefined local variable or method

I am making a blog with the rails 4 quickly tutorial by Bala Paranj on p.140 it says to use the following code (Note: the book is a rails 4 tutorial and I am using rails 5).

<%= link_to "Delete Comment" , article_comment(article, comment),
method: :delete %>


This code is for a link to delete the comment associated with an article on the blog. The link never appears because when the article page is loaded i get the following error.

NameError in Articles#show

ActionView::Template::Error (undefined local variable or method `article' for #<#<Class:0x56b9e90>:0xa1d44a0>):
17: </p>
18: <% end %>
19:
20: <%= link_to "Delete Comment" , article_comment(article, comment),
21: method: :delete %>
22:
23: <h2>Add a comment:</h2>


so as I understand it is saying that it doesnt have access to the variable "article". However I have defined this in my controller.

from articles.controller.rb:

def destroy
@article = Article.find(params[:id])
@article.destroy
redirect_to articles_path, notice: "Delete success"
end


so why is the view not recognizing the variable "article" when it is clearly defined in the controller for the view to have access to it?

EDIT show action was as follows

def show
@article = Article.find(params[:id])
end


EDIT 2 routes file as follows:

Rails.application.routes.draw do

get 'welcome/index'

# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
root 'welcome#index'

resources :articles do
resources :comments
end
end


Edit 3: rake routes

article_comments GET /articles/:article_id/comments(.:format) comments#index
POST /articles/:article_id/comments(.:format) comments#create
new_article_comment GET /articles/:article_id/comments/new(.:format) comments#new
edit_article_comment GET /articles/:article_id/comments/:id/edit(.:format) comments#edit
article_comment GET /articles/:article_id/comments/:id(.:format) comments#show
PATCH /articles/:article_id/comments/:id(.:format) comments#update
PUT /articles/:article_id/comments/:id(.:format) comments#update
DELETE /articles/:article_id/comments/:id(.:format) comments#destroy


Edit 4(a): show.html.erb file

<p>
<%= @article.title %><br>
</p>
<p>
<%= @article.description %><br>
</p>

<h2>Comments</h2>
<% @article.comments.each do |comment| %>
<p>
<strong>Commenter:</strong>
<%= comment.commenter %>
</p>
<p>
<strong>Comment:</strong>
<%= comment.description %>
</p>
<% end %>

<%= link_to "Delete Comment" , article_comment(article, comment),
method: :delete %>

<h2>Add a comment:</h2>
<%= form_for([@article, @article.comments.build]) do |f| %>
<p>
<%= f.label :commenter %><br />
<%= f.text_field :commenter %>
</p>
<p>
<%= f.label :description %><br />
<%= f.text_area :description %>
</p>
<p>
<%= f.submit %>
</p>
<% end %>


Edit 4(b) articles controller file

class ArticlesController < ApplicationController
def index
@articles = Article.all
end

def new
@article = Article.new
end

def create
Article.create(params.require(:article).permit(:title, :description))
redirect_to articles_path
end

def edit
@article = Article.find(params[:id])
end

def update
@article = Article.find(params[:id])
permitted_columns = params.require(:article).permit(:title, :description)
@article.update_attributes(permitted_columns)
redirect_to articles_path
end

def show
@article = Article.find(params[:id])
end

def destroy
@article = Article.find(params[:id])
@article.destroy
redirect_to articles_path, notice: "Delete success"
end

end


Edit 4(c) comments controller file

class CommentsController < ApplicationController

def create
@article = Article.find(params[:article_id])
permitted_columns = params[:comment].permit(:commenter, :description)
@comment = @article.comments.create(permitted_columns)
redirect_to article_path(@article)
end

def destroy
@article = Article.find(params[:article_id])
@comment = @article.comments.find(params[:id])
@comment.destroy
redirect_to article_path(@article)
end

end

Answer Source

Add a shallow option to your resources :articles. This will give you more direct routes to your comments along with routes for comments of articles.

resources :articles, shallow: true do
  resources :comments
end

EDIT>>>

when you add the shallow option you get these routes...


   article_comments GET    /articles/:article_id/comments(.:format)     comments#index
                    POST   /articles/:article_id/comments(.:format)     comments#create
new_article_comment GET    /articles/:article_id/comments/new(.:format) comments#new
       edit_comment GET    /comments/:id/edit(.:format)                 comments#edit
            comment GET    /comments/:id(.:format)                      comments#show
                    PATCH  /comments/:id(.:format)                      comments#update
                    PUT    /comments/:id(.:format)                      comments#update
                    DELETE /comments/:id(.:format)                      comments#destroy
           articles GET    /articles(.:format)                          articles#index
                    POST   /articles(.:format)                          articles#create
        new_article GET    /articles/new(.:format)                      articles#new
       edit_article GET    /articles/:id/edit(.:format)                 articles#edit
            article GET    /articles/:id(.:format)                      articles#show
                    PATCH  /articles/:id(.:format)                      articles#update
                    PUT    /articles/:id(.:format)                      articles#update
                    DELETE /articles/:id(.:format)                      articles#destroy

Make sure that you put the destroy link inside the loop. Here is your code...

<h2>Comments</h2>
<% @article.comments.each do |comment| %>
  <p>
    <strong>Commenter:</strong>
    <%= comment.commenter %>
  </p>
  <p>
    <strong>Comment:</strong>
    <%= comment.description %>
  </p>
<% end %>

# the link is outside the loop so the comment variable is not declared.
# once the program leaves the loop, the variable loses all declaration.
# also, you are using a local variable for article. in your show method.
# you are assigning an instance variable @article. the local variable is
# not declared so you will get an "I don't know what this variable is."
# error...same as the comment outside of the loop.

<%= link_to "Delete Comment" , article_comment(article, comment), method: :delete %>

it should look like this. note that the link is inside the loop and the link only uses the comment as the path to destroy the comment and not a regular path...

<h2>Comments</h2>
<% @article.comments.each do |comment| %>
  <p>
    <strong>Commenter:</strong>
    <%= comment.commenter %>
  </p>
  <p>
    <strong>Comment:</strong>
    <%= comment.description %>
  </p>
  # putting the link inside the loop creates a delete link for each comment
  # that is listed by the loop. the correct path for delete of any item is 
  # comment/1 with a delete method so called the comment directly vs using 
  # params is better. unless you prefer not to use the shallow method. ill 
  # add a reference at the bottom to use instead for that case.
  <%= link_to "Delete Comment" , comment, method: :delete %>
<% end %>

of course, this will direct you to the comments controller and not the articles controller. you will probably want to add a redirect back to the article you were using so be sure to add the following to your destroy method...

EDIT>>> I put the wrong way to redirect in the previous version. updated with correct way to redirect in this instance.

def destroy
  # just a quick tip about variables...
  # as you can see you don't have to use instance variables
  # if you are working inside a method. local variables work
  # just fine. you do, however, need instance variables to
  # pass information from one method to another or from a
  # method to a view. i think that is one reason you were getting
  # the error messages in the beginning.

  article = Article.find(params[:article_id])
  comment = article.comments.find(params[:id])
  comment.destroy

  # redirects back to articles#show with article.id as id
  redirect_to article
end

EDIT>>> for use of nested resource without shallow option...

 # you need to add _path to any path listed in rake routes. this is a 
 # helper method in rails that will create the path you are looking for
 # based on your routes config file. then add the params you are needing
 # to send. you were close before but make sure to use the instance
 # variable @article and place the link inside the loop for the variables
 # to work.
 <%= link_to "Delete Comment" , article_comment_path(@article, comment), method: :delete %>
Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download