Only allow signed_in user to comment in the post - ruby-on-rails

Currently, I am using devise gem, I want only a signed_in user to comments in the post and display the flash message like "you must log_in to comment for the post" for the not sign_in user if they comment in the post.
This is my comments_controller.rb file
class CommentsController < ApplicationController
before_action :correct_user, only: :destroy
def create
#post = Post.find(params[:post_id])
#comment = #post.comments.create(params[:comment].permit(:name, :body))
#comment.user_id = current_user.id
#comment.save
redirect_to post_path(#post)
end
def destroy
#post = Post.find(params[:post_id])
#comment = #post.comments.find(params[:id])
#comment.destroy
redirect_to post_path(#post)
end
private
def comment_params
params.require(:comment).permit(:name, :body)
end
def correct_user
#comment = current_user.comments.find_by(id: params[:id])
if #comment.nil?
flash[:alert] = "Not your comment!"
redirect_to :back
end
end
end
Below is my _comment.html.erb file under views/comments folder
<div class="comment clearfix">
<div class="comment_content">
<p class="comment_name"><strong><%= comment.name %></strong></p>
<p class="comment_body"><%= comment.body %></p>
<p class="comment_time"><%= time_ago_in_words(comment.created_at) %> Ago</p>
</div>
<% if user_signed_in? %>
<p><%= link_to 'Delete', [comment.post, comment],
method: :delete,
class: "button",
data: { confirm: 'Are you sure?' } %>
</p>
<% end -%>
and my comment model
class Comment < ApplicationRecord
belongs_to :post
belongs_to :user
end

In your new or create action you can use. You can also make it as a before_action filter
unless user_signed_in?
flash[:notice] = "you must log_in to comment for the post"
redirect_to #your login page
end
Spend some time going through documentation http://github.com/plataformatec/devise

Related

How to Edit a Pic Comment without redirecting to an Edit view page in Rails

I am building an app for practice,(Instagram replica), and I am having a really hard time getting a feature to work.
What I want to happen is, a user is able to edit or delete their own comments about a picture. I was able to get the delete feature to work, but I am unable to figure out the 'Edit comment' feature. I want the user to be able to edit the comment from within the picture show page. Code is below.
pics_controller.rb
class PicsController < ApplicationController
before_action :find_pic, only: [:show, :edit, :update, :destroy, :upvote]
before_action :authenticate_user!, except: [:index, :show]
before_action :require_same_user, only: [:edit, :update, :destroy]
def index
#pics = Pic.all.order("created_at DESC")
end
def show
end
def new
#pic = current_user.pics.build
end
def create
#pic = current_user.pics.build(pic_params)
if #pic.save
redirect_to #pic, notice: "Your pic has been posted!"
else
render :new
end
end
def edit
end
def update
if #pic.update(pic_params)
redirect_to #pic, notice: "Awesome! Your Pic was updated!"
else
render :edit
end
end
def destroy
if #pic.destroy
redirect_to root_path
end
end
def upvote
#pic.upvote_by current_user
redirect_back fallback_location: root_path
end
private
def pic_params
params.require(:pic).permit(:title, :description, :image)
end
def find_pic
#pic = Pic.find(params[:id])
end
def require_same_user
if current_user != #pic.user
flash[:danger] = "You can only edit or delete your own pictures"
redirect_to root_path
end
end
end
comments_controller.rb
class CommentsController < ApplicationController
def create
#pic = Pic.find(params[:pic_id])
#comment = #pic.comments.create(params[:comment].permit(:name, :body))
redirect_to pic_path(#pic)
end
def edit
#pic = Pic.find(params[:pic_id])
#comment = #pic.comments.find(params[:id])
redirect_to #pic
end
def update
#comment = #pic.comments.find(params[:id])
#comment.update_attributes(comment_params)
if #comment.save
redirect_to #pic
end
end
def destroy
#pic = Pic.find(params[:pic_id])
#comment = #pic.comments.find(params[:id])
#comment.destroy
redirect_to pic_path(#pic)
end
def show
#pic = Pic.find(params[:pic_id])
end
private
def comment_params
params.require(:comment).permit(:body)
end
end
And here is the (_comment.html.erb) partial being called from the show page
<div class="card" style="width: 18rem;">
<div class="card-header">
<span class="badge badge-dark"><%= comment.name %></span>
</div>
<ul class="list-group list-group-flush">
<p><%= comment.body %></p>
</ul>
</div>
<% if user_signed_in? && comment[:body].present? %>
<p><%= link_to 'Delete Comment', [comment.pic, comment], method: :delete, class: "btn btn-danger",
data: { confirm: "Are you sure?" } %></p>
<p><%= link_to 'Edit Comment', edit_pic_comment_url(#pic, comment), class: 'btn btn-primary' %></p>
<% end %>
Any help is greatly appreciated. TIA
You need to ajaxify for that scenario. The steps will be:
Create the edit link with data-remote=true option. It'll enable the link to hit on the server with a ajax request. Also, add a editable div.
Edit
<div id="comment_#{comment.id}_form"></div>
In the edit method respond the request using js. Like:
def edit
respond_to do |format|
format.js # actually means: if the client ask for js -> return edit.js
end
end
Create edit.js file. In there, render the comment _form.html.erb on the defined div with ID - comment_4_form (Lets assume you're now editing comment with id 4).
It's not elaborated I know. The solution will be much bigger if I elaborate. But you are good to go if you understand the cycle.

Rails: ActionController::InvalidAuthenticityToken

I'm currently working on a project involving users, likes, and posts. I have a like/unlike button that I finally got to work some of the time, but on certain user's profiles when I go to unlike a post, I get thrown this error, which says that it is coming from my destroy action in my likes controller:
ActionController::InvalidAuthenticityToken
I'm using devise, but don't know if that has to do with the cause of the issue.
Right now this is what I'm working with:
<h4>All of <%= #user.email %>'s posts:</h4>
<% #user.posts.order('created_at DESC').each do |post| %>
<li><%= post.content %></li>
<% unless current_user.likes.pluck(:post_id).include?(post.id) %>
<%= form_tag likes_path do %>
<%= hidden_field_tag 'post_id', post.id %>
<%= submit_tag "Like", :class => "like_button" %>
<% end %>
<% else %>
<% like = post.likes.where(user_id: current_user.id).first %>
<div class="unlike_button">
<%= form_tag like_path(like) do %>
<%= hidden_field_tag 'post_id', post.id %>
<%= button_to "Unlike", like_path(post), method: :delete %>
</div>
<% end %>
class LikesController < ApplicationController
def create
#post = Post.find(params[:post_id])
#like = Like.new(user_id: current_user.id, post_id: #post.id)
if #like.save
flash[:success] = "Post Liked!"
redirect_back(fallback_location: root_path)
else
flash[:notice] = "Couldn't like post"
redirect_back(fallback_location: root_path)
end
end
def destroy
#like = Like.find(params[:id])
#like.destroy
flash[:success] = "Post unliked"
redirect_back(fallback_location: root_path)
end
end
class PostsController < ApplicationController
def index
#posts = Post.all
#user = User.find(params[:user_id])
end
def new
#post = Post.new
#user = User.find(params[:user_id])
end
def create
#post = current_user.posts.build(post_params)
if #post.save
flash[:success] = "Posted!"
redirect_to user_path(current_user)
else
flash[:notice] = "Post could not be submitted"
redirect_to users_path
end
end
private
def post_params
params.require(:post).permit(:content)
end
end
There is a comment in application_controller.rb..
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
so ,you may try changing..
protect_from_forgery with: :exception
to this
protect_from_forgery with: :null_session
Hope it helps :)
I think I have figured it out.. At least have gotten it to work. I wasusing a form_for helper as well as button_to helper. I deleted the form_for helper and just stuck with
<%= button_to "Unlike", like_path(like), method: :delete %>
and it is now working
What helps me solve this problem is adding the Forward Slash in the URL
From:
= bootstrap_form_tag url: 'signup_with_phone' do |form|
To:
= bootstrap_form_tag url: '/signup_with_phone' do |form|

ArgumentError in Topics#show Possible Devise error?

I'm a bit of a rails newb here and need some help figuring this out. I have an argument error that is thrown every time I try and create or edit a new "topic."
ArgumentError
Here is the code for the "show:"
<h1><%= #topic.title %></h1>
<%= link_to "Edit", edit_topic_path(#topic), class: 'btn btn-success' %>
<%= link_to "Delete Topic", #topic, method: :delete, class: 'btn btn-danger', data: {confirm: 'Are you sure you want to delete this topic?'} %>
<% if policy(Bookmark.new).create? %>
<%= link_to "New Bookmark", new_topic_bookmark_path(#topic), class: 'btn btn-success' %>
<% end %>
<% #topic.bookmarks.each do |bookmark| %>
<div class="media-body">
<div class="row">
<div class="col-md-2">
<div class="container">
<img src="http://icons.better-idea.org/icon?url=<%= bookmark.url %>&size=120">
<div class="media-heading">
<%= link_to bookmark.name, topic_bookmark_path(#topic, bookmark) %>
</div>
</div>
</div>
</div>
<div class="col-md-1">
<%= render partial: 'likes/like', locals: {bookmark: bookmark} %>
</div>
</div>
<% end %>
Here is the "topics" controller:
class TopicsController < ApplicationController
def index
#topics = Topic.all
end
def show
#topic = Topic.find(params[:id])
end
def new
#topic = Topic.new
end
def create
#topic = Topic.new(topic_params)
if #topic.save
flash[:notice]= "Topic was saved."
redirect_to #topic
else
flash.now[:alert]= "The topic could not be saved. Please try again"
render :new
end
end
def edit
#topic = Topic.find(params[:id])
end
def update
#topic = Topic.find(params[:id])
#topic.assign_attributes(topic_params)
if #topic.save
flash[:notice]= "The topic was saved sucessfully."
redirect_to #topic
else
flash.now[:alert]= "There was an error saving the topic. Please try again."
render :edit
end
end
def destroy
#topic = Topic.find(params[:id])
if #topic.destroy
flash[:notice]= "\"#{#topic.title}\" was deleted successfully."
redirect_to topics_path
else
flash.now[:alert] = "There was an error in deleting this topic."
render :show
end
end
def topic_params
params.require(:topic).permit(:title)
end
end
Update(1): New error after deleting "policy" New Error
Here is the "application policy that uses Pundit:
class ApplicationPolicy
attr_reader :user, :record
def initialize(user, record)
raise Pundit::NotAuthorizedError, "must be logged in" unless user
#user = user
#record = record
end
def index?
false
end
def show?
scope.where(:id => record.id).exists?
end
def create?
user.present?
end
def new?
create?
end
def update?
user.present? && ( record.user == user )
end
def edit?
update?
end
def destroy?
user.present? && (record.user == user)
end
def scope
Pundit.policy_scope!(user, record.class)
end
class Scope
attr_reader :user, :scope
def initialize(user, scope)
#user = user
#scope = scope
end
def resolve
scope
end
end
end
Problem is with Policy made with the Pundit gem. Can you check something called the BookmarkPolicy or something similar, or at least post that here. Did you forget to include Pundit in your controller ?
You're using the Pundit gem but I don't see the authorize method in your controller. From Pundit's documentation:
Supposing that you have an instance of class Post, Pundit now lets you
do this in your controller:
def update
#post = Post.find(params[:id])
authorize #post
if #post.update(post_params)
redirect_to #post
else
render :edit
end
end
The authorize method automatically infers that Post will have a
matching PostPolicy class, and instantiates this class, handing in the
current user and the given record.
The source code in the screenshot and the code you posted is not the same. Your code:
<% if policy(Bookmark.new).create? %>
Screenshot:
<% if (Bookmark.new).create? %>
Rails is correctly reporting that Bookmark.new does not have a create? method, because it's missing the policy() method call.
Is your file saved? Are you sure you're changing the correct file?

'nil' is not an ActiveModel-compatible object. It must implement :to_partial_path while creating comments Rails

Alright guys, as one of my projects at Bloc I am creating a reddit clone. For this particular assignment, I am to create a feature where users can comment on each post (witch is already nested within each topic). However, I am coming across this error when I try to view a post that is associated with a topic.
ArgumentError in Posts#show
'nil' is not an ActiveModel-compatible object. It must implement :to_partial_path.
So far I have created comments controller, made the foreign key connection between comments and users, made the comments routes nested inside of the posts routes, created both a form partial for comment submission and a comment partial which is in post/show, and created a CommentPolicy in order to authorize users to create new comments.
I have checked my database, and comments do exist because I added them to my seed file. I suspect my error lies in my forms and my references to my partials, but I am a bit stumped. Any help here would be greatly appreciated. Thanks!
My code:
user.rb
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable, :confirmable
has_many :posts
has_many :comments
mount_uploader :avatar, AvatarUploader
def admin?
role == 'admin'
end
def moderator?
role == 'moderator'
end
end
comment.rb
class Comment < ActiveRecord::Base
belongs_to :post
belongs_to :user
end
respective routes within routes.rb
resources :topics do
resources :posts, except: [:index] do
resources :comments, only: [:create]
end
end
comments_controller:
class CommentsController < ApplicationController
def index
end
def create
#topic = Topic.find(params[:topic_id])
#post = Post.find(params[:post_id])
#comments = #post.comments
#comment = current_user.comments.build(params[:comment])
#comment.post = #post
#new_comment = Comment.new
authorize #comment
if #comment.save
flash[:notice] = "Comment was saved"
else
flash[:error] = "There was an error saving this comment. Please try again."
end
end
end
posts_controller:
class PostsController < ApplicationController
def show
#post = Post.find(params[:id])
#topic = Topic.find(params[:topic_id])
#comment = Comment.find(params[:id])
authorize #comment
end
def new
#topic = Topic.find(params[:topic_id])
#post = Post.new
authorize #post
end
def create
#topic = Topic.find(params[:topic_id])
#post = Post.new(post_params)
#post.user = current_user
#post.topic = #topic
authorize #post
if #post.save
flash[:notice] = "Post was saved."
redirect_to [#topic, #post]
else
flash[:error] = "There was an error saving this post. Please try again."
render :new
end
end
def edit
#topic = Topic.find(params[:topic_id])
#post = Post.find(params[:id])
authorize #post
end
def update
#topic = Topic.find(params[:topic_id])
#post = Post.find(params[:id])
authorize #post
if #post.update_attributes(post_params)
flash[:notice] = "Post was updated."
redirect_to [#topic, #post]
else
flash[:error] = "There was an error saving the post. Please try again."
render :edit
end
end
private
def post_params
params.require(:post).permit(:title, :body, :image)
end
end
comments/_form.html.erb
<%= form for [topic, post, comment] do |f| %>
<div class="form-group">
<%= f.label :body %>
<%= f.text_area :body, rows: 8, class: 'form-control', placeholder: "Enter comment here" %>
</div>
<div class="form-group">
<%= f.submit "Add comment", class: "btn btn-success" %>
</div>
<% end %>
comments/_comment.html.rb
<%= comment.body %>
posts/show.html.erb
<h1><%= #post.markdown_title %></h1>
<div class="row">
<div class="col-md-8">
<small>
<%= image_tag(#post.user.avatar.tiny.url) if #post.user.avatar? %>
submitted <%= time_ago_in_words(#post.created_at) %> ago by
<%= #post.user.name %>
</small>
<p><%= #post.markdown_body %></p>
<small>
<%= image_tag #post.image_url%>
</small>
</div>
<div class="col-md-4">
<% if policy(#post).edit? %>
<%= link_to "Edit", edit_topic_post_path(#topic, #post), class: 'btn btn-success' %>
<% end %>
</div>
<%= render #comments %>
<% if policy(#comment).create? %>
<h4>New Comment</h4>
<%= render partial: 'comments/form', locals: { topic: #topic, post: #post, comment: #comment } %>
<% end %>
comment_policy
class CommentPolicy < ApplicationPolicy
def new
user.present?
end
def create
user.present?
end
end
comments within seed file
#Create Comments
100.times do
Comment.create!(
user: users.sample, # we have not yet associated Users with Comments
post: posts.sample,
body: Faker:: Lorem.paragraph
)
end
comments = Comment.all
The believe the error is in this line
<%= render #comments %>
in your posts/show.html.erb
You don't have #comments defined in show method of posts_controller. Try putting #comments = #post.comments in show method.
def show
#post = Post.find(params[:id])
#topic = Topic.find(params[:topic_id])
#comment = Comment.find(params[:id])
authorize #comment
#comments = #post.comments
end

Different notification partials for different models?

notifications/index has <%= render partial: "notifications/notification", collection: #notifications %>, which contains:
<%= link_to "", notifications_habit_path(notification.id), method: :delete, class: "glyphicon glyphicon-remove" %>
<%= link_to Comment.find_by(notification.comment_id).user.name, user_path(Comment.find_by(notification.comment_id).user.id) %>
commented on <%= link_to "your habit", habit_path(notification) %>
which shows:
This is problematic because it should say 3x ".com commented on your habit" and 2x ".com commented on your value".
We need to create two separate partials notifications/_habits & notifications/_values.
My confusion is how to make the code know when to direct to the habit partial or the value partial based on whether it's a habit or value.
notifications_controller
def index
#habits = current_user.habits
#valuations = current_user.valuations #aka values
#notifications = current_user.notifications
#notifications.each do |notification|
notification.update_attribute(:read, true)
end
The notifications are based on if a user comments on one of your habits or values:
comment.rb
class Comment < ActiveRecord::Base
after_save :create_notification
has_many :notifications
belongs_to :commentable, polymorphic: true
belongs_to :user
validates :user, presence: true
private
def create_notification
Notification.create(
user_id: self.user_id,
comment_id: self.id,
read: false
)
end
end
I followed this tutorial but it is based on using just one model: http://evanamccullough.com/2014/11/ruby-on-rails-simple-notifications-system-tutorial/
UPDATE FOR VALADAN
class CommentsController < ApplicationController
before_action :load_commentable
before_action :set_comment, only: [:show, :edit, :update, :destroy, :like]
before_action :logged_in_user, only: [:create, :destroy]
def index
#comments = #commentable.comments
end
def new
#comment = #commentable.comments.new
end
def create
#comment = #commentable.comments.new(comment_params)
if #comment.save
redirect_to #commentable, notice: "comment created."
else
render :new
end
end
def edit
#comment = current_user.comments.find(params[:id])
end
def update
#comment = current_user.comments.find(params[:id])
if #comment.update_attributes(comment_params)
redirect_to #commentable, notice: "Comment was updated."
else
render :edit
end
end
def destroy
#comment = current_user.comments.find(params[:id])
#comment.destroy
redirect_to #commentable, notice: "comment destroyed."
end
def like
#comment = Comment.find(params[:id])
#comment_like = current_user.comment_likes.build(comment: #comment)
if #comment_like.save
#comment.increment!(:likes)
flash[:success] = 'Thanks for liking!'
else
flash[:error] = 'Two many likes'
end
redirect_to(:back)
end
private
def set_comment
#comment = Comment.find(params[:id])
end
def load_commentable
resource, id = request.path.split('/')[1, 2]
#commentable = resource.singularize.classify.constantize.find(id)
end
def comment_params
params[:comment][:user_id] = current_user.id
params.require(:comment).permit(:content, :commentable, :user_id, :like)
end
end
Your notification is associated with comment, and comment can have commentable of type Habit or Value (you havent show those two model, so lets call them Habit and Value models).
So you can check if notification is for Habit or Value by checking commentable type like this:
Comment.find_by(notification.comment_id).commentable.class == Habit
or check if its value notification:
Comment.find_by(notification.comment_id).commentable.class == Value
Similar way is checking polymorphic type on the comment, like:
Comment.find_by(notification.comment_id).commentable_type == 'Habit'
So on the end, you dont actualy need two partials just IF and two different link_to, one for value and one for habit.
<%= link_to "", notifications_habit_path(notification.id), method: :delete, class: "glyphicon glyphicon-remove" %>
<%= link_to Comment.find_by(notification.comment_id).user.name, user_path(Comment.find_by(notification.comment_id).user.id) %> commented on
<% if Comment.find_by(notification.comment_id).commentable.class == Habit %>
<%= link_to "your habit", habit_path(notification) %>
<% else %>
<%= link_to "your value", value_path(notification) %>
<% end %>
I needed
<% if notification.habit_id %>
<%= link_to "your habit", habit_path(notification) %>
<% elsif notification.valuation_id %>
<%= link_to "your value", valuation_path(notification) %>
<% elsif notification.quantified_id %>
<%= link_to "your stat", quantified_path(notification) %>
<% elsif notification.goal_id %>
<%= link_to "your goal", goal_path(notification) %>
<% end %>
and in the comment model:
def create_notification
Notification.create(
habit_id: self.habit_id,
valuation_id: self.valuation_id,
quantified_id: self.quantified_id,
goal_id: self.goal_id,
user_id: self.user_id,
comment_id: self.id,
read: false
)
end

Resources