Reputation: 189
In this application, I have a Recipe model, User model(Devise), and a Comment model.
class User < ActiveRecord::Base
has_many :recipes
has_many :comments
end
class Recipe < ActiveRecord::Base
belongs_to :user
has_many :comments, :dependent => :destroy
end
class Comment < ActiveRecord::Base
belongs_to :user
belongs_to :recipe
end
This is what my Comments Controller looks like.
class CommentsController < ApplicationController
def create
@recipe = Recipe.find(params[:recipe_id])
@comment = @recipe.comments.build(comment_params)
@comment.user = current_user
if @comment.save
redirect_to root_path #change this the the appropriate path later
end
end
private
def comment_params
params.require(:comment).permit(:content)
end
end
This is the error that I am getting
ActionView::Template::Error (undefined method `first_name' for nil:NilClass):
68: <p><%= @recipe.comments.count %> Comments</p>
69: <% @recipe.comments.each do |comment| %>
70: <p><%= comment.content %><p>
71: <%= comment.user.first_name %>
72: <% end %>
73: </div>
74: </div>
So the problem occurs when I try accessing the first_name attribute. If I do just 'comment.user' it spits out # User:0x007f8f4f9b6780.
I looked in the Rails Console and saw that my comments are saving. In the Rails Console, I am able to do
@recipe = Recipe.first
@recipe.comments.first.user.first_name => "John"
This is the actual code that's failing
<% @recipe.comments.each do |comment| %>
<% byebug %>
<p><%= comment.content %><p>
<p><%= comment.user.first_name %></p>
<% end %>
I tried using byebug to debug, and I'm able to do 'comment.user.first_name' => John
I'm not sure what I have wrong here and would appreciate the help. Rails 4.2.0 btw
Edit: RecipeController#Show
class RecipesController < ApplicationController
before_action :find_user, only: [:edit, :update, :show, :destroy]
before_action :find_recipe, only: [:edit, :update, :show, :destroy]
...
def show
end
private
def find_user
@user = User.find(params[:user_id])
end
def find_recipe
@recipe = Recipe.find(params[:id])
end
end
Comment Form Partial
<div class="container">
<% if user_signed_in? %>
<%= form_for([@recipe.user, @recipe, @recipe.comments.build]) do |f| %>
<%= f.label :content %><br>
<%= f.text_area :content %><br>
<br>
<%= f.submit class: "btn btn-default" %>
<% end %>
<% end %>
</div>
Upvotes: 1
Views: 490
Reputation: 9649
Because you're iterating over the collection of Comments
@recipe.comments.each
The error you've mentioned occurs, because ONE of comments doesn't have user
set (which causes first_name
call on nil
, and the error you've mentioned).
Try modifying your template as follows, to track the "problematic" comment:
<% @recipe.comments.each do |comment| %>
<p><%= comment.content %><p>
<% if comment.user.nil? %>
Anonymous
<% else %>
<%= comment.user.first_name %>
<% end %>
<% end %>
Hope that helps!
Update
Try updating RecipesController
:
class RecipesController < ApplicationController
def show
@comment = Comment.new
end
end
And replace @recipe.comments.build
in partial:
<div class="container">
<% if user_signed_in? %>
<%= form_for([@recipe.user, @recipe, @comment]) do |f| %>
<%= f.label :content %><br>
<%= f.text_area :content %><br>
<br>
<%= f.submit class: "btn btn-default" %>
<% end %>
<% end %>
</div>
You don't need "linking" @recipe
and Comment
at that point, as it will be properly handled in CommentsController#create
.
That was nice exercise!
Upvotes: 2
Reputation: 42909
A better way to handle that is using delegation and allowing nil, let me explain
class Comment < ActiveRecord::Base
belongs_to :user
belongs_to :recipe
delegate :first_name, to: :user, prefix: true, allow_nil: true
end
This will create a method called user_first_name
(user
is the prefix
), and allow_nil
means if user doesn't exist return nil
The view code will be like this
<% @recipe.comments.each do |comment| %>
<p><%= comment.content %><p>
<p><%= comment.user_first_name %></p> # notice it's 1 dot
<% end %>
When the loop reaches a comment that doesn't have a user, it will return nil
which will turn into an empty string,
While when the user exists, the comment will go ask the user to return it's first_name
Upvotes: 0
Reputation: 1619
Technically, you never check if the current_user
exists.
So when you iterate in each comments, if a someone, who's not log in, posts a comment, the user will be nil.
To prevents this error, you can add this Devise Helper in your controller :
class CommentsController < ApplicationController
before_filter :authenticate_pro_user!
It will automaticaly redirect to the sign in view if someone try to access the comments controller.
Upvotes: 0