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
Related
Right now I have a like button that allows you to like foods. When you try to unlike the food, I get this error:
The action 'destroy' could not be found for UsersController
I'm not sure why it is looking for the destroy action in the users controller. My only guess is because the button is on the user show page, so I assume it defaults to that controller, but how would I access the delete method from my votes controller?
Shared like form
<% unless current_user.votes.empty? || current_user.votes.pluck(:food_id).include?(food.id) %>
<%= form_for #vote do |f| %>
<%= f.hidden_field 'food_id', food.id %>
<%= f.hidden_field 'user_id', food.user.id %>
<%= f.submit "Vote", :class => "like_button" %>
<% end %>
<% else %>
<% vote = food.votes.where(user_id: current_user.id).first %>
<div class="unlike_button">
<%= button_to "Unlike", vote, method: :delete %>
</div>
<% end %>
class VotesController < ApplicationController
def index
#votes = Vote.all
end
def new
#vote = Vote.new
end
def create
#vote = Vote.new(vote_params)
if #vote.save
puts #vote
flash[:notice] = "Thanks for voting!"
redirect_back(fallback_location: root_path)
else
puts "No"
flash[:notice] = "Something went wrong"
redirect_back(fallback_location: root_path)
end
end
def destroy
#vote = Vote.find(params[:id])
if #vote.destroy!
flash[:notice] = "Unvoted!"
redirect_back(fallback_location: root_path)
end
end
private
def vote_params
params.require(:vote).permit(:food_id, :user_id)
end
end
class Vote < ApplicationRecord
belongs_to :user
belongs_to :food
end
I'm trying to create a comment on a video show page. When I submit the form, rails gives me a flash notice: "User must exist, Video must exist". Not sure why my strong params aren't going through to the create method.
comments_controller.rb
def create
#user = current_user
#video = Video.find(params[:video_id])
#comment = Comment.new(comment_params)
#post = #video.post
if #comment.save
flash[:notice] = "Comment successfully created"
redirect_to post_video_path(#post, #video)
else
#errors = #comment.errors.full_messages.join(', ')
flash[:notice] = #errors
render :'videos/show'
end
private
def comment_params
params.require(:comment).permit(
:body,
:user,
:video
)
end
models/comment.rb
class Comment < ActiveRecord::Base
belongs_to :user
belongs_to :video
validates :body, presence: true
end
models/video.rb
class Video < ActiveRecord::Base
belongs_to :user
belongs_to :post
has_many :comments
end
views/videos/show.html.erb
<%= #video.title %>
<%= content_tag(:iframe, nil, src: "//www.youtube.com/embed/#{#video.embed_id}") %>
<%= link_to "Delete Video", post_video_path(#post, #video), method: :delete %>
<%= link_to('Back', user_post_path(#user, #post)) %>
<h3>Comments</h3>
<%= form_for [#video, #comment] do |f| %>
<%= f.label(:body, "Comment") %>
<%= f.text_area(:body) %>
<%= f.submit("Submit Comment") %>
<% end %>
<% unless #comments.nil? %>
<% #comments.each do |comment| %>
<%= comment.body %>
<%= comment.user %>
<% end %>
<% end %>
I tried adding this to the create method...
#comment.user = current_user
#comment.video = #video
That allowed the comment to save but instead of displaying the comment.body, it displayed the comment object. It still doesn't explain why the strong params aren't being passed.
This is probably a nested params issue and how you have the strong params defined. Check this answer for more information.
If you need to see what is in the params, insert a pry statement into the controller and inspect it there.
Good luck!
There are multiple things you should look at. I have made multiple changes to your code. Go through them.
In your videos/show.html.erb
<%= form_for Comment.new do |f| %>
<%= f.label(:body, "Comment") %>
<%= f.text_area(:body) %>
<%= f.hidden_field :video_id, :value => #video.id %>
<%= f.submit("Submit Comment") %>
<% end %>
Send video_id using hidden_field. Do not send current user id for security reasons. If you take current user id from form end user can easily edit your form in html and pass someone else's user id and this will be one of the easiest and major vulnerability.
In comments_controller.rb
def create
#comment = Comment.new(comment_params)
#comment.user = current_user # we are making sure that current_user is set to comment.
if #comment.save
flash[:notice] = "Comment successfully created"
redirect_to post_video_path(#comment.video.post, #comment.video)
else
#errors = #comment.errors.full_messages.join(', ')
flash[:notice] = #errors
render :'videos/show'
end
end
private
def comment_params
params.require(:comment).permit(:body, :user, :video_id)
# We are permitting video_id instead of video
end
I got this to work but I'm not sure if this addresses the security concerns that #Dinesh raised.
comments_controller.rb
def create
#user = current_user
#video = Video.find(params[:video_id])
#comment = Comment.new(comment_params)
#comment.user = #user
#comment.video = #video
#post = #video.post
if current_user == #video.user || current_user.admin
if #comment.save
flash[:notice] = "Comment successfully created"
redirect_to post_video_path(#post, #video)
else
#errors = #comment.errors.full_messages.join(", ")
flash[:notice] = #errors
render :"videos/show"
end
else
flash[:notice] = "Only OP or admin may comment"
render :"videos/show"
end
end
and
private
def comment_params
params.require(:comment).permit(
:body,
)
end
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
EDIT: I managed to delete! i had to define teh instance variable #movies = Movie.find(params[:id]) to the delete method in the controller.
I still can't update though. I get "param is missing or the value is empty: movie"
I forgot to add my contrller! sorry!
I'm trying to replicate one of my in class exercises into another app, for practice.
The idea is to be able to add new movies into a database, and get the option to update their info, and delete them as well. I can add new content, but I can't update them or delete them.
Appreciate any inputs I can get.
Routes.rb
Rails.application.routes.draw do
root "movies#index"
get "/movies", to: "movies#index"
get "/movies/new", to: "movies#new", as: "new_movie"
get '/movies/:id', to: "movies#movie_page", as: "movie_page"
post "/movies", to: "movies#add"
get "/movies/:id/edit", to: "movies#edit", as: "movie_edit"
put "/movies/:id", to: "movies#update"
patch "/movies/:id", to: "movies#update", as: "update_movie"
delete "/movies/:id", to: "movies#delete", as: "delete_movie"
end
Controller
class MoviesController < ApplicationController
def index
#movies = Movie.all
end
def movie_page
#movies = Movie.find(params[:id])
end
def new
#movies = Movie.new
end
def add
#movies = Movie.create(movie_params)
redirect_to movies_path
end
def edit
#movies = Movie.find(params[:id])
end
def update
#movies.update(movie_params)
redirect_to #movies, notice: "Shirt was updated."
end
def delete
#movies = Movie.find(params[:id])
#movies.destroy
# flash[:notice] = "Shirt was deleted."
redirect_to root_path, notice: "Shirt was deleted."
end
def movie_params
params.require(:movie).permit(:title, :description, :year_released)
end
# def set_movie
# #movies = Movie.find(params[:id])
# end
end
Form partial
<%= form_for #movies do |m| %>
<p>
<%= m.label :title %><br>
<%= m.text_field :title %>
</p>
<p>
<%= m.label :description %><br>
<%= m.text_field :description %>
</p>
<p>
<%= m.label :year_released %><br>
<%= m.text_field :year_released %>
</p>
<p>
<%= m.submit %>
</p>
<% end %>
Movie page html (individual movies, labeled by IDs)**I can't update or Delete, no route matches Delete.
When I press Update - I get param is missing or the value is empty: movie
<h1><%= #movies.title %></h1>
<h2>Released on : <%= #movies.year_released %> </h2>
<p> <%= #movies.description %> </p>
<%= link_to "Update", movie_edit_path(#movies) %>
<%= link_to "Delete", movies_path, method: :delete %
Edit page *I cant access this link. the form is the problem
<h1>Edit <%= #movies.title %> Info </h1>
<%= render "form" %>
<%= link_to "Cancel Edit", movie_edit_path(#movies) %>
Many thanks guys
def update
#movie = Move.find(params[:id])
#movie.update(movie_params)
redirect_to movie_path(#movie)
end
on your routes. all you need is resources :movies
you are getting param is empty because you have to pass in the id of the movie to update.
The major issue is that you do not load the variable #movies from the DB before you use it.
def update
#movies.update(movie_params)
redirect_to #movies, notice: "Shirt was updated."
end
def update
#movies.find(params[:id])
#movie.update(movie_params)
redirect_to #movies, notice: "Shirt was updated."
end
Aside from that you have tons of duplication and quite a few idiosyncrasies.
Rails uses these naming conventions for actions:
index
show (not movie_page)
new
create (not add)
edit
update
destroy (not delete)
You should follow them unless you have a damn good reason not to.
class MoviesController < ApplicationController
# cuts the duplication
before_filter :set_movie, except: [:new, :index]
def index
#movies = Movie.all
end
# GET /movies/:id
def show
end
# GET /movies/new
def new
#movie = Movie.new
end
# POST /movies
def create
#movie = Movie.create(movie_params)
redirect_to movies_path
end
# GET /movies/edit
def edit
end
# PUT|PATCH /movies/:id
def update
#movie.update(movie_params)
redirect_to #movie, notice: "Shirt was updated."
end
# DELETE /movies/:id
def destroy
#movie.destroy
redirect_to action: :index
end
private
def movie_params
params.require(:movie).permit(:title, :description, :year_released)
end
def set_movie
# Use the singular form when naming a variable with a single record
# failure to do so may result in tarring and feathering
#movie = Movie.find(params[:id])
end
end
I've read other SO articles relating to UrlGenerationError's which seem to point to singularization or plurization of a word, but I don't think that's the issue here.
It works when I remove from valuations/_form.html.erb:
<%= render "comments/comments" %>
<%= render "comments/form" %>
Submit the _form with :name & :tag_list, readd
<%= render "comments/comments" %>
<%= render "comments/form" %>
and then refresh. What's the deal when nil?
routes
resources :valuations do
resources :comments
end
comments_controller
class CommentsController < ApplicationController
before_action :load_commentable
before_action :set_comment, only: [:show, :edit, :update, :destroy]
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
#comment.create_activity :create, owner: current_user
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
#comment.create_activity :destroy, owner: current_user
redirect_to #commentable, notice: "comment destroyed."
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.require(:comment).permit(:content, :commentable)
end
end
valuations_controller
class ValuationsController < ApplicationController
before_action :set_valuation, only: [:show, :edit, :update, :destroy]
before_action :logged_in_user, only: [:create, :destroy]
def index
if params[:tag]
#valuations = Valuation.tagged_with(params[:tag])
else
#valuations = Valuation.order('RANDOM()')
end
end
def show
#valuation = Valuation.find(params[:id])
#commentable = #valuation
#comments = #commentable.comments
#comment = Comment.new
end
def new
#valuation = current_user.valuations.build
#commentable = #valuation
#comments = #commentable.comments
#comment = Comment.new
end
def edit
end
def create
#valuation = current_user.valuations.build(valuation_params)
if #valuation.save
redirect_to #valuation, notice: 'Value was successfully created'
else
#feed_items = []
render 'pages/home'
end
end
def update
if #valuation.update(valuation_params)
redirect_to #valuation, notice: 'Value was successfully updated'
else
render action: 'edit'
end
end
def destroy
#valuation.destroy
redirect_to valuations_url
end
private
def set_valuation
#valuation = Valuation.find(params[:id])
end
def correct_user
#valuation = current_user.valuations.find_by(id: params[:id])
redirect_to valuations_path, notice: "Not authorized to edit this valuation" if #valuation.nil?
end
def valuation_params
params.require(:valuation).permit(:name, :private_submit, :tag_list, :content, :commentable, :comment)
end
end
comments/_form.html.erb
<%= form_for [#commentable, #comment] do |f| %>
<% if #comment.errors.any? %>
<div class="error_messages">
<h2>Please correct the following errors.</h2>
<ul>
<% #comment.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="america">
<div class="form-group">
<%= f.text_area :content, rows: 4, class: 'form-control', placeholder: 'Enter Comment' %>
</div>
<div class="america2">
<%= button_tag(type: 'submit', class: "btn") do %>
<span class="glyphicon glyphicon-plus"></span> Comment
<% end %>
</div>
<% end %>
Thank you so much for your time.
When you have a nested resource like that, the url for creating a comment looks like /valuations/123/comments where 123 is the id of the valuation - without a valuation id this url cannot be generated.
On your Valuations#new page, the valuation (i.e. #commentable) is an unsaved object, so it has no id yet, hence the error about a missing valuation_id. In addition having one form nested within another is invalid html and will lead to odd behaviour. On your show page on the other hand, the valuation does have an id and so things should work as they are.
If you want to create a valuation and its initial comment(s) in one go then you should use accepts_nested_attributes_for and fields_for to add the fields for the comments to your valuation form (There are other ways, but accepts_nested_attributes_for is what is built into rails)