Comments for microposts in Rails 4 - ruby-on-rails

After successfully completeling a micropost system. Logged in users can post through the form and create a post. I begun to add to this but allowing any user to comment on a post as well.
I get a multitude of errors. And for the most part I don't know what the problem is so would like some advise on how to get this finished. Right now the error is:
NameError in StaticPages#home
undefined local variable or method `micropost'
StaticPages#home is the page that displays everyones microposts, and thus the comments form as well. users#show is the public profile of users and displays the same as statispages (have yet to work out how to make posts to a users wall, eventually main page will be a general feed and user page will be a user-to-user feed).
Comments model
create_table "comments", force: true do |t|
t.string "content"
t.integer "user_id"
t.integer "micropost_id"
t.datetime "created_at"
t.datetime "updated_at"
end
User.rb
class User < ActiveRecord::Base
has_many :microposts, dependent: :destroy
has_many :comments
micropost.rb
belongs_to :user
has_many :comments, dependent: :destroy
comment.rb
belongs_to :user
belongs_to :micropost
comments_controller.rb
class CommentsController < ApplicationController
before_filter :signed_in_user, only: [:create, :destroy]
def create
#micropost = Micropost.find(params[:micropost_id])
#comment = Comment.new(params[:comment])
#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
end
microposts_controller.rb
class MicropostsController < ApplicationController
before_action :authenticate_user!
before_action :correct_user, only: :destroy
def index
#microposts = Micropost.all
#comment = #micropost.comments.build(params[:comment])
#comment.user = current_user
end
def create
#micropost = current_user.microposts.build(micropost_params)
if #micropost.save
flash[:success] = "Micropost created!"
redirect_to root_url
else
#feed_items = []
render root_url
end
end
def destroy
#micropost.destroy
redirect_to root_url
end
private
def micropost_params
params.require(:micropost).permit(:content)
end
def correct_user
#micropost = current_user.microposts.find_by(id: params[:id])
redirect_to root_url if #micropost.nil?
end
end
users_controller.rb
def show
#user = User.find(params[:id])
#microposts = #user.microposts
#comment = Comment.new
if user_signed_in?
#micropost = current_user.microposts.build
#feed_items = current_user.feed
end
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => #user }
end
end
Anyplace calling the comment form
<%= render 'shared/comment_form', micropost: micropost %>
_comment_form.html.erb
<%= form_for([micropost, #comment]) do |f| %> #Here's the current error#
<%= render 'shared/error_messages', object: f.object %>
<div class="field">
<%= f.text_area :content, :placeholder => "Leave a comment" %>
</div>
<button class="btn" type="submit">
Create
</button>
<% end %>
Schema shows all the correct fields of the model. All DB migrations have been done. I've seen it say that in form_for([micropost, #comment]) the first field cannot be nil. I'm assuming this means that the comments aren't correctly filling micropost_id with the id of the micropost they attach to.

Related

Adding a comment model to associate with a post/user model

I finally was able to associate a user model with a post model with:
def create
#post = Post.new(post_params)
#post.user = current_user
Right now I'm trying to create a comment in the Post#Show page but continue to receive an error regarding my form. The two errors I'm running into are:
NoMethodError in Posts#show undefined method `comments_path'
when I have #comment = Comment.new in Post#show. When it's removed I get:
ArgumentError in Posts#show First argument in form cannot contain nil or be empty
What could possibly be wrong with my form? Also if someone can recommend a better way to associate my 3 models (basically have a user_id and a post_id when comment is created I'm open for suggestions). My comment form is going to appear within the Post#Show page. My current code is:
Comment Model
belongs_to :user
belongs_to :post
User Model
has_many :posts
has_many :comments
Post Model
has_many :comments
belongs_to :user
Comment Controller
class CommentsController < ApplicationController
before_action :find_comment, only: [:show, :edit, :update, :destroy]
def index
#comments = Comment.all
end
def new
#comment = Comment.new
end
def create
#post = Post.find(params[:id])
#comment = #post.comments.build(comment_params)
#comment.user = current_user
if #comment.save
flash[:notice] = "Successfully created..."
redirect_to comments_path
else
flash[:alert] = "failed"
redirect_to root_path
end
end
def show
end
def edit
end
def update
if #comment.update
flash[:notice] = "You updated your comment"
else
flash[:alert] = "Failed to update"
end
def destroy
#comment.destroy
redirect_to '/'
end
private
def find_comment
#comment = Comment.find(params[:id])
end
def comment_params
params.require(:comment).permit(:comment)
end
end
Post Controller
class PostsController < ApplicationController
before_action :find_post, only: [:show, :edit, :update, :destroy]
def index
#posts = Post.all
end
def new
#post = Post.new
end
def create
#post = Post.new(post_params)
#post.user = current_user
if #post.save!
flash[:notice] = "Successfully created..."
redirect_to posts_path
else
flash[:danger] = "failed to add a post"
render 'new'
end
end
def show
end
def edit
end
def update
if #post.update
flash[:notice] = "Successfully updated"
redirect_to post_path
else
flash[:alert] = "Failed to update Post"
redirect_to :back
end
end
def destroy
if #post.destroy
flash[:notice] = "Successfully delete"
redirect_to posts_path
else
flash[:danger] = "Wasn't able to delete Blog post."
redirect_to :back
end
end
private
def find_post
#post = Post.find(params[:id])
end
def post_params
params.require(:post).permit(:title, :body, :description)
end
end
Post#Show View
<%= render '/comments/form' %>
Comment#form
<%= form_for #comment do |f| %>
<%= f.label :comment, "Title" %>
<%= f.text_field :comment, placeholder: "Write a comment" %>
<%= f.submit "Submit" %>
<% end %>
If there is something missing please ask me to update my post. Thank you all who help me better understand my problem.
Your associations seem fine. The error
NoMethodError in Posts#show undefined method `comments_path'
means that you don't have the route comments_path or Comments#Create. Basically, when you have a form_for with a Comment as the parameter, it assumes you are wanting to go to the create or update route, depending if it's a new record. The easiest thing to do would be to add
resources :comments
to your routes file. However, since you want a comment associated with a post, you want to modify your routes file to have:
resources :posts do
resources :comments
end
Then, change your form to look like this:
<%= form_for [#post, #comment] do |f| %>
So when you submit a form, you will have a params[:post_id] and a params[:id] to play with. Find the Post with the params[:post_id]. Ignore the params[:id] when you're creating a comment, but use it when you're updating a comment.
Edit: Here is a link to some help regarding Nested Resourcing.

Empty item displaying from nested resource

I'm new to Rails and am building a todo app.
For some reason an empty Item instance is displaying within all lists by default. Item.count is returning 0, but <%= render #list.items %> always has one "instance" of _item.html.erb.
I have #list assigned to List.find(params[:id]) in the list controller's show method and am calling <%= render #list.items %> in its show view.
I have this link in _item.html.erb
<%= link_to "Delete", [item.list, item],
method: :delete,
data: { confirm: "Are you sure?" }
%>
and it will display the delete link, but link to lists/*/items (as opposed to list/*/items/*) in the empty item, which gives a routing error. The link works fine in actual items though.
Controllers:
lists_controller.rb:
class ListsController < ApplicationController
before_action :set_list, only: [:show, :edit, :update, :destroy]
def index
#lists = policy_scope(List).where(author: current_user)
end
def show
#item = #list.items.build
end
def new
#list = List.new
authorize #list
end
def create
#list = List.new(list_params)
#list.author = current_user
authorize #list
if #list.save
flash[:notice] = "Your list was created."
redirect_to #list
else
flash[:error] = "Your list was not created."
render "new"
end
end
def edit
end
def update
if #list.update(list_params)
flash[:notice] = "Your list was updated."
redirect_to #list
else
flash[:error] = "Your list was not updated."
render "edit"
end
end
def destroy
if #list.destroy
flash[:notice] = "Your list was deleted."
redirect_to root_path
else
flash[:error] = "Your list was not deleted."
redirect_to #list
end
end
private
def list_params
params.require(:list).permit(:title, :description, :author_id)
end
def set_list
#list = List.find(params[:id])
authorize #list
end
end
items_controller.rb:
class ItemsController < ApplicationController
before_action :set_list
def create
#item = #list.items.create(item_params)
authorize #item
if #item.save
flash[:notice] = "Your item was added."
redirect_to #list
else
flash.now[:error] = "Your item was not added."
end
end
def destroy
#item = #list.items.find(params[:id])
authorize #item
if #item.destroy
flash[:notice] = "Your item was deleted."
redirect_to #list
else
flash.now[:error] = "Your item was not deleted."
end
end
private
def item_params
params.require(:item).permit(:name, :list_id)
end
def set_list
#list = List.find(params[:list_id])
end
end
Models:
list.rb:
class List < ActiveRecord::Base
validates :title, presence: true
validates :description, presence: true
belongs_to :author, class_name: "User"
has_many :items, dependent: :destroy
end
item.rb:
class Item < ActiveRecord::Base
validates :name, presence: true
belongs_to :list
end
Routes:
Rails.application.routes.draw do
root "lists#index"
resources :lists do
resources :items
end
devise_for :users
end
Reference migration:
class AddListsToItems < ActiveRecord::Migration
def change
add_reference :items, :list, index: true, foreign_key: true
end
end
The issue was caused by the way I passed the instance variable to the form.
This line in ListsController is building a new item on each page refresh.
def show
#item = #list.items.build
end
I removed that assignment, as well as its passing to the form.
<%= render "items/form", list: #list, item: #item %> becomes <%= render "items/form", list: #list %>
simple_form_for([list, item] becomes simple_form_for([#list, #list.items.build]
#item = #list.items.build is your problem.
The issue is that building a nested item will always yield an "empty" item, especially if it's nested.
Whilst you can use the code in the new action with impunity, using in show will basically just create a blank object that you have to contend with.
--
If you want to create a new item object when viewing your #list, you'll be better building / invoking on the fly (as you're doing already):
<%= form_for [#list, #list.items.new] ...
try
<%= render 'item' %>
and on your _item.html.erb (which is rendered), use the following code:
<%= #list.items %>

Rails user rating, can only rate myself

I am trying to set up a 5 star rating system so users can rate other users. At the moment everything is working, (create, delete, update etc...) but only the logged in user can rate himself. I cannot rate other users. I get no errors, it just redirects to the user profile page as it should but without added a rating to that user.
user.rb
class User < ActiveRecord::Base
has_many :reviews
review.rb
class Review < ActiveRecord::Base
belongs_to :user
end
reviews_controller.rb
class ReviewsController < ApplicationController
before_action :find_user
before_action :find_review, only: [:edit, :update, :destroy]
def new
#review = Review.new
end
def create
#review = Review.new(review_params)
#review.user_id = current_user.id
if #review.save
redirect_to user_path(#user)
else
render 'new'
end
end
def edit
end
def update
if #review.update(review_params)
redirect_to user_path(#user)
else
render 'edit'
end
end
def destroy
#review.destroy
redirect_to user_path(#user)
end
private
def review_params
params.require(:review).permit(:rating, :comment)
end
def find_user
#user = User.find(params[:user_id])
end
def find_review
#review = Review.find(params[:id])
end
end
_form which then gets rendered on show page:
<%= simple_form_for([#user, #user.reviews.build]) do |f| %>
<div id="rating-form">
<label>Rating</label>
</div>
<%= f.input :comment %>
<%= f.button :submit %>
<% end %>
<script>
$('#rating-form').raty({
path: '/assets/',
scoreName: 'review[rating]'
});
</script>
Any help getting this to work would be greatly appreciated!!
Do this:
#config/routes.rb
resources :users do
resources :reviews, only: [:new, :create]
end
#app/models/review.rb
class Review < ActiveRecord::Base
belongs_to :user
belongs_to :reviewed, class_name: "User", foreign_key: :reviewed_id
end
#app/controllers/reviews_controller.rb
class ReviewsController < ApplicationController
def new
#review = current_user.reviews.new
end
def create
#review = current_user.reviews.new review_params
#review.save
end
private
def review_params
params.require(:review).permit(:rating, :comment).merge(reviewed_id: params[:user_id])
end
end
#app/views/reviews/new.html.erb
<%= form_for #review do |f| %>
<%= f.number_field :rating %>
<%= f.text_field :comment %>
<%= f.submit %>
<% end %>
This would mean you'll have to include a reviewed_id column in your reviews table.
You'll be able to access it using: url.com/users/:user_id/reviews/new
The application will automatically fill the user_id and reviewed_id fields, so the rest of your code should work with the upgrade.
The big problem you have is that you're basically recording the user_id (presumably of who created the review)... but have no way of stipulating who the review is about.
The above code fixes that for you.

Associating nested attributes to user

I'm trying to build a small expense tracking app using Rails 4.1. Using devise for authorization. Expense and it's nested attribute, comments belong to a user. The associations are set up in the model and expenses are getting associated with the user. Here's the Expense controller:
class ExpensesController < ApplicationController
def new
#expense = Expense.new
#item = #expense.items.build
##comment = #expense.comments.build
end
def index
#expenses = Expense.all
##items = Item.where(:expense_id => #expense.id)
end
def show
#expense = Expense.find(params[:id])
#items = Item.where(:expense_id => #expense.id)
end
def create
#expense = current_user.expenses.new(expense_params)
respond_to do |format|
if #expense.save
ExpenseMailer.expense_submission(#expense).deliver
format.html { redirect_to #expense, notice: 'Expense Report Submitted.' }
format.json { render :show, status: :created, location: #expense }
else
format.html { render :new }
format.json { render json: #expense.errors, status: :unprocessable_entity }
end
end
end
def edit
#expense = Expense.find(params[:id])
end
def update
#expense = Expense.find(params[:id])
##comment = #expense.comments.build
if #expense.update(expense_params)
#if #comment.save
#ExpenseMailer.comments_added(#expense).deliver
flash[:notice] = "Expense Report Updated"
redirect_to expenses_path
#else
# flash[:notice] = "Expense Report Updated"
#redirect_to expenses_path
##end
else
render 'edit'
end
end
The form from where the comment attributes are built looks like:
<%= nested_form_for (#expense) do |f| %>
<div class="form-group">
<%= f.label :state %><br />
<%= f.select :state, Expense.states, :include_blank => false, class: "form-control" %>
</div>
<%= f.fields_for :comments, #expense.comments.build do |comment| %>
<div class="form-group">
<%= comment.label :comment%>
<%= comment.text_area :comment, class: "form-control" %>
</div>
<%= comment.hidden_field :commenter %>
<% end %>
<%= f.submit "Submit", class: "btn btn-primary" %>
<% end %>
</div>
</div>
The #comment.commenter = current_user isn't adding the current user id to the database. Should I include it in the expense controller somewhere?
You have to add:
#comment.commenter = current_user
below that if statement. Like this:
def create
#article = Expense.find(params[:expense_id])
if #comment = #expense.comments.create(comment_params)
#comment.commenter = current_user
#comment.save
ExpenseMailer.comments_added(#expense).deliver
redirect_to expenses_path
end
end
And then save the comment again. In your current code you're overwriting the #comment object with the newly created object by doing:
#comment = #expense.comments.create(comment_params)
but you haven't set the commenter on that new object anywhere yet.
Model
I just tried to create better code for your strong params, but I couldn't work out how to include the param in your nested attributes
I would therefore recommend using the inverse_of: method in your Comment model to get it sorted properly:
#app/models/expense.rb
Class Expense < ActiveRecord::Base
belongs_to :user
has_many :comments, inverse_of: :expense
accepts_nested_attributes_for :comments
end
#app/models/comment.rb
Class Comment < ActiveRecord::Base
belongs_to :expense, inverse_of: :comments
before_create :populate_expense, on: :create
private
def populate_expense
self.commenter_id = self.expense.user_id
end
end
This should work if you're populating the comments from the accepts_nested_attributes_for directive
Comments
I don't understand why you've created two create actions for both your expenses and comments controllers - the controller action is meant to be independent of the Model
What I'm trying to say is that if you think the comments#create controller action will be invoked by your nested attribute creation, you'd be mistaken - it is only invoked when you send a request to it through the Rails router :)
If you're creating Comments and Expenses separately, you'll be able to use these two different actions; but they won't be invoked by each other. Only Model methods can be invoked by the controller (you shouldn't be calling other controller methods)
If you wanted to create a Comment from the expenses#show page, here's how you'd set it up:
#config/routes.rb
resources :expenses do
resources :comments #-> domain.com/expenses/:expense_id/comments/new
end
#app/controllers/expenses_controller.rb
Class CommentsController < ApplicationController
def new
#expense = Expense.find params[:expense_id]
#comment = #expense.comments.new
end
def create
#expense = Expense.find params[:expense_id]
#comment = #expense.comments.new(comment_params)
#comment.save
end
private
def comment_params
params.require(:comment).permit(:comment, :params).merge(commenter_id: current_user.id)
end
end
This will work if you wanted to create a comment from the expenses#show page. If you do this, you need to ensure you are calling the comments#new / comments#create actions, rather than those of the expenses controller

ActiveRecord::RecordNotFound - Couldn't find without an ID

I'm working on an app that allows users to comment on a single "work" (think blog post). The associations in the models are as follows:
class User < ActiveRecord::Base
has_many :works
has_many :comments
class Work < ActiveRecord::Base
belongs_to :user
has_many :comments
class Comment < ActiveRecord::Base
belongs_to :user
belongs_to :post
belongs_to :work
There's a form on the Works show page that allows users to post a comment:
<%= form_for(#comment) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<div class="field">
<%= f.text_area :content, placeholder: "Post a comment!" %>
</div>
<%= f.submit "Post", class: "btn btn-small btn-primary" %>
<% end %>
The Works controller is as follows. Note that I'm adding the build comment functionality here so that the form on the Works page functions:
class WorksController < ApplicationController
#before_filter :current_user, only: [:edit, :update]
def index
#works = Work.all
#comment = #work.comments.build(params[:comment])
#comment.user = current_user
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => #works }
end
end
def create
#work = current_user.works.create(params[:work])
redirect_to current_user
end
def edit
#work = current_user.works.find(params[:id])
end
def new
#work = current_user.works.new
end
def destroy
#work = current_user.works.find(params[:id]).destroy
flash[:success] = "Work deleted"
redirect_to current_user
end
def update
#work = current_user.works.find(params[:id])
if #work.update_attributes(params[:work])
flash[:success] = "Profile updated"
redirect_to #work
else
render 'edit'
end
end
def show
#work = Work.find(params[:id])
#comment = #work.comments.build
#comment.user = current_user
#activities = PublicActivity::Activity.order("created_at DESC").where(trackable_type: "Work", trackable_id: #work).all
#comments = #work.comments.order("created_at DESC").where(work_id: #work ).all
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => #work }
end
end
end
And lastly, here is the Comments controller:
class CommentsController < ApplicationController
before_filter :authenticate_user!
def index
#comments = Comment.all
end
def show
#comment = Comment.find(params[:id])
#activities = PublicActivity::Activity.order("created_at DESC").where(trackable_type: "Comment", trackable_id: #comment).all
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => #comment }
end
def update
#comment = current_user.comments.find(params[:id])
if #comment.update_attributes(params[:comment])
flash[:success] = "Comment updated"
redirect_to #comment
end
end
def create
#work = Work.find(params[:id])
#comment = #work.comments.build(params[:comment])
#comment.user = current_user
if #comment.save
#flash[:success] = "Post created!"
redirect_to #work
else
render 'home#index'
end
end
end
end
When I attempt to submit a comment using the comment form on the works show view page, I get the following error:
Activerecord::RecordNotFound in CommentsController#create
Couldn't find Work without an ID
Why can't the application find the Work so that it can associate the comment to it?
EDIT 1:
Thanks to the answers below I edited the comment form:
<%= form_for(#work, #comment) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<div class="field">
<%= f.text_area :content, placeholder: "Post feedback or contribute content
to this work!" %>
</div>
<%= f.submit "Post", class: "btn btn-small btn-primary" %>
<% end %>
I'm still getting the same error after making the change to the form and adding the nested route.
I edited the routes file to include a nest for work comments:
authenticated :user do
root :to => 'activities#index'
end
root :to => "home#index"
devise_for :users
resources :users do
member do
get :following, :followers, :posts, :comments
end
end
resources :works do
resources :comments
end
resources :relationships, only: [:create, :destroy]
resources :posts
resources :activities
resources :comments
Rake routes shows the following for Comments#create:
POST /comments(.:format)
The POST URL (where the error shows up) is appURL/works/1/comments
Doesn't seem right. What do I need to change? Thank you so much for the help so far!!
Your form needs to be form_for([#work, #comment]) so that Rails knows to build a URL like /works/123/comments. Right now it would just be posting to /comments.
Check your rake routes to see the route for your CommentsController#create action. You might also need to tweak the controller to read params[:work_id] instead of params[:id].
The view helper form_for(#comment) will post to '/comments' by default. You can specify a url (see the guides) that includes the :id of the work record. The typical approach is to use form_for([#work, #comment]) and Rails will do this for you so long as you've set up your routes with comments as a nested resource of work.

Resources