Comments on Post model will not render, no error message displayed - ruby-on-rails

I'm learning and trying to add functionality to a simple app. I'm adding a comments ability to each micropost and creating comments works fine, however the comments will not render. No error message, application runs perfectly fine except the comments partials just isn't included in the final view.
I've looked at similar questions here and tried so many solutions that I'm not even sure where the problem might be, any help would be much appreciated.
I have 3 relevant models: Users, Microposts, Comments.
Microposts belong to Users, has_many comments.
Comments belongs to Microposts and Users.
Comments model includes columns for "comment_content", "micropost_id", "user_id" and "created_at".
Micropost.rb
class Micropost < ActiveRecord::Base
belongs_to :user
has_many :comments, dependent: :destroy
default_scope -> { order(created_at: :desc) }
validates :user_id, presence: true
validates :content, presence: true, length: { maximum: 1000 }
acts_as_votable
Comment.rb
class Comment < ActiveRecord::Base
belongs_to :user
belongs_to :micropost
validates :comment_content, presence: true
validates :user_id, presence: true
validates :micropost_id, presence: true
User.rb
class User < ActiveRecord::Base
has_many :microposts, dependent: :destroy
has_many :comments
User Controller#show
def show
#user = User.find(params[:id])
#microposts = #user.microposts.paginate(page: params[:page])
#micropost = Micropost.find(params[:id])
#comment = Comment.new
#comments = Micropost.find(params[:id]).comments.paginate(page: params[:page])
end
Comments Controller#create
def create
#micropost = Micropost.find(params[:micropost_id])
#comment = Comment.new(comment_params)
#comment.micropost = #micropost
#comment.user = current_user
if #comment.save
flash[:success] = "Comment created!"
redirect_to current_user
else
render 'shared/_comment_form'
end
end
To render in view from _micropost partial
<ul>
<%= render #comment %>
</ul>
Comments partial _comments
<%= link_to gravatar_for(#micropost.user, size: 15), #micropost.user %>
<%= link_to #micropost.user.name, #micropost.user %>
<% #micropost.comments.each do |comment| %>
<%= comment.comment_content %>
<% end %>
The comments form displays just fine. After logging in and creating a comment I check the console and confirm a comment is created, has the correct content, is associated with the User that made the post and the micropost it was commented on.
So the comment is sitting there in the DB, it just won't come out!
SOLUTION
UsersController:
def show
#user = User.find(params[:id])
end
Views
<% #user.microposts.each do |micropost| %>
<%= link_to gravatar_for(#user, size: 15), #user %>
<%= link_to #user.name, #user %>
<% micropost.comments.each do |comment| %>
<%= comment.comment_content %>
<% end %>
<% end %>
That's basically it, I was overloading my UsersController#show and that was throwing the whole thing off. As the advice below states the Userscontroller was doing the heavy lifting, and I shouldn't have been defining so many variables there.
Can't express enough thanks to #thedanotto and #fylooi. They were both correct. All of the troubleshooting they walked me through taught me so much. It's intimidating learning Rails and I've always refrained from asking stupid questions online, but this experience showed me just how awesome some people can be! I've got a lot to learn, reading through the pragmatic programmers series, I'll do my best to give back to the community!

Your has_many and belongs_to relationships appear to be set up correctly. This means you can rely upon rails to do most of the heavy lifting in your controller and views.
User Controller#show becomes...
def show
#user = User.find(params[:id])
end
views/users/show.html.erb becomes...
<% #user.microposts.each do |micropost| %>
<%= link_to gravatar_for(#user, size: 15), #user %>
<%= link_to #user.name, #user %>
<!-- You have access to micropost here as well, you could still do micropost.user, micropost.user.name instead of #user -->
<% micropost.comments.each do |comment| %>
<%= comment.comment_content %>
<% end %>
<% end %>

this can't work
<%= micropost.comment.comment_content %>
you have a has_many relation in your micropost model.
you could solve this to add a has_one relation to your micropost model with an order by created_at
or temporary (really temporary)
<%= micropost.comments.last.comment_content %>
update:
your partial _comments just need to talk to the comment variable.
so change your micropost.comment.comment_content to comment.comment_content
the name of the user should delegate to the usermodel via
delegate :name, to: :user, prefix: true, allow_nil: true
in your comment model. now you can use comment.user_name to display the name of the user
best

A few things:
You're loading both User and Micropost in your UsersController#show based on ID. Doesn't look right to me, unless your User and Micropost database IDs move in lockstep.
#user = User.find(params[:id])
#micropost = Micropost.find(params[:id])
A Micropost should be shown in a MicropostsController, not a UsersController. Eg. /microposts/1 should return the first Micropost resource, while /users/1 returns the first User resource.
I'm going to assume you're trying to render the user's microposts and their related comments from UsersController#show
Based on that, your variable structure can be arranged as per the following:
# users_controller.rb
class UsersController
def show
#user = User.find(params[:id])
#microposts = #user.microposts
# better practice is to have only one # instance variable in the
# controller, but let's go with this for academic purposes
end
end
# views/users/show.html.erb
<%= render #microposts %>
# this can also be written as
# <% #microposts.each do |micropost|
# <%= render partial: 'micropost', locals: { micropost: micropost }
# <% end %>
# views/users/_micropost.html.erb
<%= link_to gravatar_for(micropost.user, size: 15), micropost.user %>
<%= link_to micropost.user.name, micropost.user %>
<ul>
<%= render micropost.comments %>
</ul>
# views/users/_comment.html.erb
<li>
<%= comment.content
</li>

Related

gem devise how to add comment created user email?

I am using gem devise for creating users profile
Each user can create a comment. I need to add the user name beside each comment something like this <%= #comment.user.name %>
in user.rb
has_many :comments, dependent: :destroy
in comment.rb
belongs_to :users
in comment controller
before_action :find_comment ,only:[:show,:update,:edit,:destroy]
def new
#user =User.find(params[:id])
#comment = #user.comments.build
end
def create
#user =User.find(params[:id])
#comment = #user.comments.build(comment_params)
#comment.user = current_user
if #comment.save
redirect_to doctor_path(:id => #user.id)
end
end
private
def find_comment
#comment = Comment.find(params[:id])
end
def comment_params
params.require(:comment).permit(:text)
end
user controller
def show
#user = User.find(params[:id])
end
user show.html.erb
<% for item in #user.comments %>
<% if item.text.present? %>
<%= item.text %><br>
<%= #comment.user.name %>
<br><hr>
<% end %>
I got this error
undefined method `user' for nil:NilClass
You could do it the other way around, in your show method:
#comments = Comment.all
in your show view:
<% #comments.each do |comment| %>
<%= comment.text %>
<%= comment.user.name %>
<% end %>
Since your question is not really clear I'll specifiy that if you want to show just the comments posted by the user:
def show
user_id = User.find(params[:id]).id
#comments = Comment.where(user_id: user_id)
end
Just some quick rules to start with
A user has many comments, this will be the relationship between the user and a comment that the user has made.
You already have this
A user has many profile comments, this is the relationship between a user and the comments that have been left for that user on their profile
Now you have that distinction things start to be come clearer.
Start by creating a single xref table to act as the go between users and comments that have been left for a profile and call it profile_comments
this profile_comments table needs a user_id and a comment_id of type integer to store the primary keys from user and comments tables, where the user_id is the id of the user that is having a comment left about them on their profile
You can now setup a profile_comment model that with the following relationships
belongs_to comment
belongs_to user
So now you need to change your user model relationships to the following
user.rb
has_many :comments
has_many :profile_comments, dependent: :destroy
comment.rb
belongs_to :user #not users as you have defined in your question
has_many :profile_comments, dependent: :destroy
and the new profile_comment.rb model needs the two belongs_to clauses for comment and user
profile_comment.rb
belongs_to :user
belongs_to :comment
Now when you create a comment you need to assign to to the user, and also to the profile_comment
So now your comments controller needs to setup these relationships so instead of
before_action :find_comment ,only:[:show,:update,:edit,:destroy]
def new
#user =User.find(params[:id])
#comment = #user.comments.build
end
def create
#user =User.find(params[:id])
#comment = #user.comments.build(comment_params)
#comment.user = current_user
if #comment.save
redirect_to doctor_path(:id => #user.id)
end
end
You need something like this
def create
#user =User.find(params[:id])
#comment = current_user.comments.build(comment_params)
#profile_comment = ProfileComment.new
#user.profile_comment < #profile_comment
#comment.profile_comment < #profile_comment
if #comment.save
redirect_to doctor_path(:id => #user.id)
end
end
Your update action will need to also change accordingly
Now in your view instead of
<% for item in #user.comments %>
<% if item.text.present? %>
<%= item.text %><br>
<%= #comment.user.name %>
<br><hr>
<% end %>
<% end %>
You want this, it's a little complex because you need to get from the profile comment to the comment then to the user that created the comment
<% #user.profile_comments.each do | profile_comment |%>
<%comment = profile_comment.comment%>
<% if comment.text.present? %>
<%= comment.text %><br>
<%if comment.user.blank?%>
No user assigned to this comment
<%else%>
<%= comment.user.name #or email or whatever%>
<%end%>
<br><hr>
<% end %>
<% end %>
Although text is a reserved word and is an actual column type so you might want to change the column name text to something else
Any questions on this feel free to get back to me but I won't be around for the next 24 hours. Hope it's clear and helps you understand what has gone wrong with your initial setup
In the controller and the view I have assumed that the current_user is the person making the comment and #user is the person that is being commented on. Just switch that round if I have that wrong

Ancestry Gem for Nested Comments with Rails causing undefined method error

I have been trying to fix an error associated with using the Ancestry gem for comments on my app for Rails 4. I used railscast episode 262 as a guide. However, unlike the episode, my comments model is a nested resource inside another model.Before I go further, I will supply the necessary code for reference. If you like to read the error right away, it is mentioned right after all the code snippets.
The Relevant Models:
class Comment < ActiveRecord::Base
has_ancestry
belongs_to :user
belongs_to :scoreboard
end
class Scoreboard < ActiveRecord::Base
#scoreboard model is like an article page on which users can post comments
belongs_to :user
has_many :teams, dependent: :destroy
has_many :comments, dependent: :destroy
end
Relevant code in the route file:
resources :scoreboards do
resources :comments
resources :teams, only: [:edit, :create, :destroy, :update]
end
The Scoreboards Controller Method for the page on which one can post comments:
def show
#scoreboard = Scoreboard.find_by_id(params[:id])
#team = #scoreboard.teams.build
#comment = #scoreboard.comments.new
end
The Comments Controller:
class CommentsController < ApplicationController
def new
#scoreboard = Scoreboard.find(params[:scoreboard_id])
#comment = #scoreboard.comments.new(:parent_id => params[:parent_id])
end
def create
#scoreboard = Scoreboard.find(params[:scoreboard_id])
#comment = #scoreboard.comments.new comment_params
if #comment.save
redirect_to scoreboard_url(#comment.scoreboard_id)
else
render 'new'
end
end
private
def comment_params
params.require(:comment).permit(:body, :parent_id).merge(user_id: current_user.id)
end
end
I will include the migration for the ancestry gem if any mistakes were made on that :
class AddAncestryToComments < ActiveRecord::Migration
def change
add_column :comments, :ancestry, :string
add_index :comments, :ancestry
end
end
The following code shows the view code:
Scoreboard#show View which is giving me the error in the last line:
<div class= "comment-section">
<%= form_for [#scoreboard, #comment] do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<%= f.text_area :body, class: "comment-field" %>
<%= f.hidden_field :parent_id %> #is it needed to include this here? because this form is for new comments not replies
<%= f.submit "Join the discussion...", class: " comment-button btn btn-primary" %>
<% end %>
<%= nested_comments #scoreboard.comments.reject(&:new_record?).arrange(:order => :created_at) %>
</div>
The (comments partial)_comment.html.erb View:
<div class=" comment-div">
<p> Posted by <%= link_to "#{comment.user.name}", comment.user %>
<%= time_ago_in_words(comment.created_at) %> ago
</p>
<div class="comment-body">
<%= comment.body %>
<%= link_to "Reply", new_scoreboard_comment_path(#scoreboard, comment, :parent_id => comment) %>
</div>
</div>
The helper method to render comments:
def nested_comments(comments)
comments.map do |comment, sub_comment| #the comments.map also gives me an error if I choose to render the comments without the .arrange ancestry method
render(comment) + content_tag(:div, nested_comments(sub_comment), class: "nested_messages")
end.join.html_safe
end
The new.html.erb for Comments which one is redirected to for the replies form submission:
<%= form_for [#scoreboard, #comment] do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<%= f.text_area :body, class: "comment-field" %>
<%= f.hidden_field :parent_id %>
<%= f.submit "Join the discussion...", class: " comment-button btn btn-primary" %>
<% end %>
Upon creating a scoreboard, I am redirected to the show page, where i get the following error:
undefined method `arrange' for []:Array
Even though the array of comments is empty, I get the same error if it wasnt. I have tried .subtree.arrange but that gives me the same error. Also, the ancestry documentation said that .arrange works on scoped classes only. I don't know what that means. I would appreciate some help on making the page work so the comments show properly ordered with the replies after their parent comments. If this is the wrong approach for threaded comments(replies and all), I would appreciate some guidance on what to research next.
.reject(&:new_record?) this will return an array. The error sounds like arrange is a scope on ActiveRecord. So move the reject to the end and it should work.
#scoreboard.comments.arrange(:order => :created_at).reject(&:new_record?)
In regards your comment nesting, I have implemented this before, and found the Railscasts recommendation of a helper to be extremely weak.
Passing parent_id to a comment
Instead, you're better using a partial which becomes recursive depending on the number of children each comment has:
#app/views/scoreboards/show.html.erb
<%= render #comments %>
#app/views/scoreboards/_comment.html.erb
<%= link_to comment.title, comment_path(comment) %>
<div class="nested">
<%= render comment.children if comment.has_children? %>
</div>

Nested sign up form does not display the error messages for the nested object

Organization and User have a many-to-many relationship through Relationship. There's a joined signup form. The sign up form works in that valid information is saved while if there's invalid information it rolls back everything.
The problem is that the form does not display the error messages for the nested User object. Errors for Organization are displayed, the form correctly re-renders if there are errors for User, but the errors for User are not displayed.
Why are the errors when submitting invalid information for users not displayed? Any help is appreciated.
The signup form/view:
<%= form_for #organization, url: next_url do |f| %>
<%= render partial: 'shared/error_messages', locals: { object: f.object, nested_models: f.object.users } %>
... fields for organization...
<%= f.fields_for :users do |p| %>
...fields for users...
<% end %>
<%= f.submit "Register" %>
<% end %>
The shared error messages partial:
<% if object.errors.any? %>
<div id="error_explanation">
<div class="alert alert-danger">
The form contains <%= pluralize(object.errors.count, "error") %>.
</div>
<ul>
<% object.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<% if defined?(nested_models) && nested_models.any? %>
<div id="error_explanation">
<ul>
<% nested_models.each do |nested_model| %>
<% if nested_model.errors.any? %>
<ul>
<% nested_model.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
<% end %>
<% end %>
</ul>
</div>
<% end %>
The controller method:
def new
#organization = Organization.new
#user = #organization.users.build
end
def create
#organization = Organization.new(new_params.except(:users_attributes))
#organization.transaction do
if #organization.valid?
#organization.save
begin
#user = #organization.users.create!(users_attributes)
#relationship = #organization.relationships.where(user: #user).first
#relationship.update_attributes!(member: true, moderator: true)
rescue
raise ActiveRecord::Rollback
end
end
end
if #organization.persisted?
if #organization.relationships.where('member = ? ', true).any?
#organization.users.where('member = ? ', true).each do |single_user|
single_user.send_activation_email
end
end
flash[:success] = "A confirmation email is sent."
redirect_to root_url
else
#user = #organization.users.build(users_attributes) if #organization.users.blank?
render :new
end
end
The Organization model:
has_many :relationships, dependent: :destroy
has_many :users, through: :relationships, inverse_of: :organizations
accepts_nested_attributes_for :users, :reject_if => :all_blank, :allow_destroy => true
validates_associated :users
The Relationship model:
belongs_to :organization
belongs_to :user
The User model:
has_many :relationships, dependent: :destroy
has_many :organizations, through: :relationships, inverse_of: :users
Update: If I add an additional line to def create as below, it seems to work, i.e., then it does display the error messages. However, then it for some reason doesn't save when valid information is submitted. Any ideas how to deal with that?
def create
#organization = Organization.new(new_params.except(:users_attributes))
#user = #organization.users.new(users_attributes)
#organization.transaction do
...
Maybe try this:
<%= render partial: 'shared/error_messages',
locals: { object: f.object, nested_models: [ #user ] } %>
I guess the call to #organization.users.blank? doesn't work in the way you expected it to do, as the user is not correctly created, because #create! threw an exeption. Rails probably does a check on the database, to see if there are any users now, and thinks there is still nothing in there. So your #organization.users.build(users_attributes) gets called, but this doesn't trigger validation.
In general I would also recommend you the use of a form object (like in the other answer), when creating complex forms, as this clarifies things like that and makes the view more clean.
This is classic use case for form objects. It is convenient from many perpectives (testing, maintainance ...).
For example:
class Forms::Registration
extend ActiveModel::Naming
include ActiveModel::Conversion
include ActiveModel::Validations
def persisted?
false
end
def initialize(attributes = {})
%w(name other_attributes).each do |attribute|
send("#{attribute}=", attributes[attribute])
end
end
validates :name, presence: true
validate do
[user, organization].each do |object|
unless object.valid?
object.errors.each do |key, values|
errors[key] = values
end
end
end
end
def user
#user ||= User.new
end
def organization
#organization ||= Organization.new
end
def save
return false unless valid?
if create_objects
# after saving business logic
else
false
end
end
private
def create_objects
ActiveRecord::Base.transaction do
user.save!
organization.save!
end
rescue
false
end
end
the controller:
class RegistrationsController < ApplicationController
def new
#registration = Forms::Registration.new
end
def create
#registration = Forms::Registration.new params[:registration]
if #registration.save
redirect_to root_path
else
render action: :new
end
end
end
and the view in HAML:
= form_for #registration, url: registrations_path, as: :registration do |f|
= f.error_messages
= f.label :name
= f.text_field :name
= f.submit
It is worth to read more about form objects.
Nested attributes bit me SOOO hard every time I decided it's a good time to use them, and I see you know a bit of what I'm talking about.
Here's a suggestion of a different approach, use a form object instead of nested attributes: http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/ see under section 3. Extract Form Objects
You can extract your existing validations on the User model into a module and import that, to expand on the solution from the blog:
https://gist.github.com/bbozo/50f8638787d6eb63aff4
With this approach you can make your controller code super-simple and make simple and fast unit tests of the not-so-simple logic that you implemented inside and save yourself from writing integration tests to test out all different possible scenarios.
Also, you might find out that a bunch of the validations in the user model are actually only within the concern of the signup form and that those validations will come and bite in later complex forms, especially if you're importing data from a legacy application where validations weren't so strict, or when you one day add additional validators and make half of your user records invalid for update.
I had a similar problem. everything seemed to work fine, but I was not getting any errors The solution i found is to build the comment in article#show instead of the view:
#article = Article.find(params[:id])
#comment = #article.comments.build(params[:comment])
and in your articles#show don't use #article.comments.build but #comment:
<%= form_for([#article, #comment]) do |f| %>
<%= render 'shared/error_messages', :object => f.object %>
<p><%= f.submit %></p>
<% end %>
make sure you build the comment in your comment#create as well (you really have no choice though :P)
I think you need to pass f.object instead of #comment.
In case someone might be looking for a solution to render form errors in a form, try:
f.object.errors["account.address"].present?`
The address is the nested attribute here.

Unable to extract username by ID that comes from another model

Seems so simple, but this one makes me crazy by now:
I got a Topics table, which has an user_id that is written during the creation of a new topic, and comes from the User table.
class User < ActiveRecord::Base
attr_accessible :name, :email, :password, :password_confirmation
has_secure_password
has_many :topics
has_many :comments
validates_presence_of :password, :on => :create
validates_uniqueness_of :email
end
class Topic < ActiveRecord::Base
attr_accessible :active, :id, :opened_at, :title, :description
belongs_to :user
has_many :comments
end
Now the first thing I tried is writing the view like this:
<% #topics.each do |topic| %>
<%= topic.user.name %>
<% end %>
Which drops:
undefined method `name' for nil:NilClass
Then I tried another approach:
topics_controller.rb
def index
#topics=Topic.all
#author=User.find(#topics.user_id)
end
But this goes like:
undefined method `user_id' for #<Array:0x3c46fb0>
(If I hardcode any number instead of #topics.user_id then it shows the given user's name properly).
Any help is appreciated.
PS.: This is the way I save the Topic:
def create
#topic = Topic.new(params[:topic])
#topic.active = true
#topic.user_id = session[:user_id]
if #topic.save
redirect_to topics_url
flash[:notice] = 'Success!'
else
render "new"
end
end
I guess the association is OK, because when I put
<%=h topic.user_id %>
then it shows the proper IDs. It's just that I cannot translate the ID to the user name that is stored in the User table.
Use the following in case topic.user is nil:
<% #topics.each do |topic| %>
<%= topic.user.try(:name) %>
<% end %>
You should also remove #author=User.find(#topics.user_id). This will raise an error because #topic is a collection of all the Topic instances and not a single instance (like Topic.all.first as an example).
You are using #topics.user_id, which is where things are going wrong; you are trying to call user_id on an array instead of a single Topic object. Use #pluck:
Controller Code:
#topics = Topic.all
#topics_ids = Topic.pluck(:user_id).compact #will remove nils
#authors = User.find(#topics_ids)
View Code:
<b>All Topics: </b>
<% #topics.each do |topic| %>
<%= topic.user.name %>
<% end %>
<b>All Authors: </b>
<% #authors.each do |author| %>
<%= author.name %>
<% end %>

Adding comments to specific model in rails?

Currently i am creating Blog using rails,where i want to add comments to post model. I am using acts_as_commentable,its works great on rails console but when i try to implement it in MVC,I got confused !! how can i add comments to Post model.
What should i do ? is there need to create any new controller for handle comments?
I want add comment form below the post->show view,so that user can add comments on the posts#show page.
Sorry for my english !
with acts_as_commentable as Paulo suggested or polymorphic-association
http://asciicasts.com/episodes/154-polymorphic-association
or with PRO account on railscasts: http://railscasts.com/episodes/154-polymorphic-association-revised (repo: https://github.com/railscasts/154-polymorphic-association-revised/tree/master/blog-after)
a little modified code below, this code will let you add comments to Post only as we load #commentable with #commentable = Post.find(params[:id]), if you will go through tutorial you'll be able to add comments to any other models in the app where User and Post share the same Comment model.
I used acts_as_commentable in my app before, nice gem, but I am using polymorphic-association now cause it is much more customizable.
post.rb
attr_accessible :content, :name
has_many :comments, as: :commentable
comment.rb
attr_accessible :content
belongs_to :commentable, polymorphic: true
show.html.erb
<h1>Comments</h1>
<ul id="comments">
<% #comments.each do |comment| %>
<li><%= comment.content %></li>
<% end %>
</ul>
<h2>New Comment</h2>
<%= form_for [#commentable, #comment] do |f| %>
<ol class="formList">
<li>
<%= f.label :content %>
<%= f.text_area :content, :rows => 5 %>
</li>
<li><%= f.submit "Add comment" %></li>
</ol>
<% end %>
posts_controller
def show
#post = Post.find(params[:id])
#commentable = #post
#comments = #commentable.comments
#comment = Comment.new
end
comments_controller
def create
#commentable = Post.find(params[:id])
#comment = #commentable.comments.new(params[:comment])
if #comment.save
redirect_to #commentable, notice: "Comment created."
else
render :new
end
end
routes.rb
resources :posts do
resources :comments
end
As you can see by the acts_as_commentable documentation,
Also make sure you have the migrations to create the database structure.
In your model:
class Post < ActiveRecord::Base
acts_as_commentable
end
By your comment I see you are giving the first steps on Rails. You need to create the controller and views. In you controller you'll need to initialize the variables and call the respecting view.
My best advise for you, is before starting doing your own blog, take a look at this Rails tutorial, which will cover most of the aspects you'll need.

Resources