Rails User Association with comments - ruby-on-rails

I have a User scaffold (DEVISE), a Comments scaffold and a movies scaffold
Currently, Comments are posted on the movie show page.
What i'm having trouble with is having the comment be created by User. So that a comment is create by a User.
So if i display the comment in the movies/:show
I could do
Body: <%= comment.body %>
Author: <%= comment.user.first_name %>
How would I make a comment belong to a user, and only editable and destroy-able BY that user only?
Please dont tell to use before_filter :authenticate_user!, only: [:create,:destroy]
or follow the Michael Hartl Tutorial with Microposts because I have already done both of these and they dont work
Anyways, does anyone know how I could do this?
MAny Thanks

first I would show the edit and destroy link only to owner with:
<% if comment.user == current_user %>
<%= link_to "edit", ... %>
<%= link_to "delete", ... %>
<% end %>
and then just in case for smart guys who knows how to use inspect element in chrome, I would do a controller level check for comment owner:
def edit
#comment = Comment.find(params[:id])
if #comment.user == current_user
#comment.update_attributes(....)
#message = "Comment updated or created or deleted, depends on method"
else
#message = "It's not your comment wise guy :)"
end
redirect_to :back, notice: #message
end
the same for destroy and update method.
!not a copy/paste ready code.
this is what I did once and it worked quite nice, other method you can use gem cancan https://github.com/ryanb/cancan and set abilities for users.
can :edit, Comment, :user_id => user.id
can :destroy, Comment, :user_id => user.id
with setting abilities this way only owner will be able to access edit page and the update, destroy actions.

what's about devise helper 'current_user'? something like this:
class Comment < ActiveRecord::Base
belongs_to :user
end
class CommentsController < ApplicationController
def edit
comment = current_user.comments.where(id: params[:id]).first
if comment.nil?
...
401 error or something else (current user is not creator of this comment)
else
...
end
end
end
And also you can check permissions in view:
<% if comment.user == current_user %>
<%= link_to "edit comment" ... %>
<%= link_to "delete comment" ... %>
<% end %>

To make the comment belongs to user, in your create action:
comment = current_user.comments.new(params[:comment])
To make it editable/destroyable only for owner
before_filter :find_comment, only: [:show, :edit, :update, :destroy]
before_filter :authorize_user!, only: [:edit, :update, :destroy]
#...
private
def find_comment
#comment = Comment.find params[:id]
end
def authorize_user!
deny_access if #comment.user != current_user # example
end

Making sure that a user is signed in with :authenticate_user! is a good thing, but you have to associate the comment with th user too.
Devise gives you a current_user. So if your Comment belongs_to :user and your User has_many :comments write in your CommentsController:
def new
#comment= current_user.comments.new
end
def create
#comment= current_user.comments.new(params[:comment])
if #comment.save
...
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(params[:comment])
...
end
end

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.

Undefined method link_to_edit using Draper decorator

I've got a User and Post models, which are related to each other in a classical way -- User has_many :posts and Post belongs_to :user. In my users#show, where I display a user's profile, I also have a list of all posts he has made. Also, I wanted to have links to edit and delete each post respectfully. So, I made up with this:
<% #user.posts.each do |post| %>
<h1><%= link_to post.title, post_path(post) %></h1>
<% if #user == current_user %>
<%= link_to 'Edit', edit_post_path(post) %>
<%= link_to 'Delete', post_path(post), method: :delete %>
<% end %>
<% end %>
But surely placing this logic into view results in a mess, so I decided to use Draper and write decorators for that. As we are going to check rights for posts#edit and posts#delete methods, I came up with a decorator for Post model and tried to use it in PostsController. Here it goes:
class PostDecorator << Draper::Decorator
delegate_all
def link_to_edit
if object.user == current_user
h.link_to 'Edit', h.edit_post_path(object)
end
end
def link_to_delete
if object.user == current.user
h.link_to 'Delete', h.post_path(object), method: :delete
end
end
end
Then, in my PostsController:
# ... class definition
before_action :set_post, only: [:show, :edit, :update, :destroy]
# ... other controller methods
def edit; end
def update
if #post.update(post_params)
#post.save
redirect_to post_path(#post)
else
render 'edit'
end
end
def destroy
#post.destroy
redirect_to feed_path
end
private
# Using FriendlyId gem to have neat slugs
def set_post
#post = Post.friendly.find(params[:id]).decorate
end
But every time I try to render my User profile with list of his posts, with the use of my new helpers <%= post.link_to_delete %> and <%= post.link_to_edit %> instead of that conditional mess, it just returns me the following error:
What am I doing wrong?
You probably figured this out in the meantime but here's an answer for others: You were calling #post = ....decorate in your controller but you are using #user.posts.each { |post| ... } in your view. The objects fed to that block are not decorated. Only #post is.
In your view you should have done something like #user.posts.each { |raw_post| post = raw_post.decorate } and so on. Obviously, with ERB syntax. Or #user.decorated_posts.each ... where
class User < ActiveRecord::Base
...
def decorated_posts
# this will load every post associated with the user.
# if there are a lot of them you might want to only load a limited scope of them
posts.map(&:decorate)
end
...
end

Only allow user to create one feedback for each movies

I have three models, User, Movie, and Review. Here is the relation:
# User.rb
has_many :movies
has_many :reviews
# Movie.rb
belongs_to :user
has_many :reviews
# Review.rb
belongs_to :movies
belongs_to :users
Here is the routes:
# routes.rb
resources :movies do
resources :reviews
end
Here is the controller:
# reviews_controller.rb
class ReviewsController < ApplicationController
before_action authenticate_user!
before_action :find_movie
before_action :find_review, only: [:edit, :update, :destroy]
def new
#review = Review.new
end
def create
#review = Review.new(review_params)
if #review.save
redirect_to movie_path(#movie)
else
render 'new'
end
end
def edit
end
def update
if #review.update(review_params)
redirect_to movie_path(#movie)
end
end
private
def find_movie
#movie = Movie.find(params[:movie_id])
end
def find_review
#review = Review.find(params[:id])
end
def review_params
params.require(:review).permit(:rating, :comment)
end
end
I created a new and partial form and then in the show page of the movie, I create this line of code to show the button of creating new review for a particular movie:
# views/movies/show.html.erb
<%= link_to 'Give review', new_movie_review_path(#movie) %>
I don't want the user to create another review after they submit a review for the same movie. That's why I want to hide the "Give review" button if the user is already gave the feedback. How do I do that?
Something like:
<% unless current_user.reviews.select{|review| review.movie_id == #movie.id}.count > 0 %>
<%= link_to 'Give review', new_movie_review_path(#movie) %>
<% end %>
Could also use where instead:
Review.where(user_id: current_user.id, movie_id: #movie.id).count > 0
You should add a custom validation in the review model which checks for a preexisting review from the same user for the same movie.
If you have the current_user available to views then you can have something like the following to hide Give Review link:
# views/movies/show.html.erb
<%= link_to 'Give review', new_movie_review_path(#movie) unless current_user.movies.where(id: #movie.id).first.comments.any? %>

Rails 4: Redirect not working after a POST request made

So we have two models, Articles and Likes. An article has_many :likes, a like belongs_to :user and belongs_to :article. The resources are nested as below:
resources :articles do
resources :likes
end
We have logic in our view that renders a "like" or "unlike" button as follows:
<% unless signed_in? and current_user.likes? #article %>
<%= link_to "Like this article!", article_likes_path(#article), method: :post, remote: true %>
<% else %>
<%= link_to "Unlike this article!", article_like_path(#article, current_user.article_like(#article)), method: :delete, remote: true %>
<% end %>
Here is our LikesController:
class LikesController < ApplicationController
before_action :set_article
before_action :set_like, only: [:destroy]
before_action :authenticate_user!
after_action :redirect_to_article, only: [:create, :destroy]
respond_to :html
def create
like = Like.new
current_user.likes << like
#article.likes << like
redirect_to #article
end
def destroy
#like.destroy
redirect_to #article
end
private
def set_article
#article = Article.find(params[:article_id])
end
def set_like
#like = Like.find(params[:id])
end
def like_params
params[:like]
end
def redirect_to_article
redirect_to #article
end
end
In the view, the likes count is rendered with:
<%= #article.likes.size %>
The problem is, after we hit "like" or "unlike", the like (or unlike) goes through the backend, but we have to manually refresh the page to see the like count refresh. In other words, the two calls to redirect_to #article in the LikesController do not actually refresh the page.
Any ideas? Thanks.
Your buttons are using remote: true which is telling Rails to use AJAX, but your controller is set to only respond to HTML. From you question, it sounds like you're cool with the page being refreshed, so just delete remote: true from the buttons and it should work.

Rails - before_filter undefined local variable

I have this form where a user input a review. A user must be signed with Facebook to save a review.
I use a before_filter to check if the user is signed in or not.
But I get this error: undefined local variable or method signed_in_user'
.
The other thing is, how do I logged the user in with facebook and the save its review? Without losing and making the user input the same review again.
Review form:
<%= form_for [#school, Review.new] do |f| %>
<%= f.text_area :content %>
<% if current_user %>
<%= f.submit 'Save my review', :class => "btn" %>
<% else %>
<%= f.submit 'Save my review and sign me into facebook', :class => "btn" %>
<% end %>
<%end %>
ReviewsController
class ReviewsController < ApplicationController
before_filter :signed_in_user, only: [:create, :destroy]
def create
#school = School.find(params[:school_id])
#review = #school.reviews.new(params[:review])
#review.user_id = current_user.id
if #review.save
redirect_to #review.school, notice: "Review has been created."
else
render :new
end
end
def new
#school = School.find_by_id(params[:school_id])
#review = Review.new
end
end
ReviewsHelper
module ReviewsHelper
def signed_in?
!current_user.nil?
end
def signed_in_user
unless signed_in?
redirect_to "/auth/facebook"
end
end
end
I am using omniauth to authenticate users from facebook.
include your ReviewsHelper in controller:
class ReviewsController < ApplicationController
include ReviewsHelper #or helper :reviews
before_filter :signed_in_user, only: [:create, :destroy]
def create
#school = School.find(params[:school_id])
#review = #school.reviews.new(params[:review])
#review.user_id = current_user.id
if #review.save
redirect_to #review.school, notice: "Review has been created."
else
render :new
end
end
def new
#school = School.find_by_id(params[:school_id])
#review = Review.new
end
end
Your helper is not included in the controller by default.
You can include it as codeit suggested.
Most people put their before filters in ApplicationController as a private method.
EDIT:
To persist the log in, save it to the session data. Look up sessions in the Rails Guides.
I have meet the same problem before. The helper is to help edit the view layer. The before_filter method cannot be written in the helper by default, unless you write 'include BlbalblaHelper' in the controller.
You Can just write the before_filter method in the application_controller.rb as a private method, or in lib/ folder. I think both of them are better approach for DRY.

Resources