How to allow users delete their own comments and not others - ruby-on-rails

In my Rails App, I have a comment model ,a devise user model and a tales model. For each tales post , I have comments posted by signed-in users.The problem here is every other logged in user is able to delete the comments of other user.I want a functionality such that only the user who created the comments can delete it.
My user.rb is here
class User < ActiveRecord::Base
has_one :profile, dependent: :destroy
has_many :tales, dependent: :destroy
end
My comment.rb is here
class Comment < ActiveRecord::Base
belongs_to :tale
end
My tale.rb is here
class Tale < ActiveRecord::Base
belongs_to :user
has_many :comments, dependent: :destroy
belongs_to :category
end
My routes.rb is as follows
Rails.application.routes.draw do
get 'tales/index'
devise_for :users, controllers: { registrations: "registrations" }
resources :profiles
resources :tales do
resources :comments
end
resources :categories
authenticated :user do
root "tales#index"
end
unauthenticated :user do
get "/" => "tales#index"
end
end
My comment controller is here:
class CommentsController < ApplicationController
before_action :authenticate_user!
def create
#tale = Tale.find(params[:tale_id])
#comment = #tale.comments.create(comment_params)
redirect_to tale_path(#tale)
end
def destroy
#tale = Tale.find(params[:tale_id])
#comment = #tale.comments.find(params[:id])
#comment.destroy
end
private
def comment_params
params.require(:comment).permit(:name, :body, :tale_id)
end
end
The excerpt from my tales/show page to add comments is here:
<div id="comments">
<h2><%= #tale.comments.count %> Comments</h2>
<%= render #tale.comments %>
<h3>Add a comment:</h3>
<%= render "comments/form" %>
</div>
</div>
My _comment.html.erb is here
<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.tale, comment], method: :delete, data:
{ confirm: 'Are you sure?' } %></p>
<% end %>
</div>
I see no connection between user and comments and I dont the right way to do it here.Can someone guide me through this such that I can do so without using any gems .

You don't appear to have a relationship between Comment and User. You would need something like this in your Comment class assuming you are storing the user_id for each comment:
belongs_to :user
Then in your CommentsController your destroy method should be something like this:
def destroy
# Only the comments posted by that user will be returned
#comment = #user.comments.find(params[:id])
#comment.destroy
end

Add use_id in comments table if don't have
add_column :comments, :user_id, :integer
in your view file put following condition. Delete link will only visible to user who added comment.
<% if user_signed_in? && current_user.id == comment.user_id %>
<p><%= link_to 'Delete', [comment.tale, comment], method: :delete, data:
{ confirm: 'Are you sure?' } %></p>
<% end %>

Related

Ruby on rails. Form for a nested model in a different view

I've been battling this for a while now and I can't figure it out. I have a user model using devise. Users can upload songs, and add youtube videos etc..
I'm trying to let users add/delete songs and videos from the devise edit registrations view.
Videos upload fine, but as songs are a nested resource of playlists, which belongs to user, I think I'm getting muddle up.
Music uploads with the same form on it's corresponding page, but not from the devise registration edit view.
routes:
devise_for :users, controllers: { registrations: "users/registrations", sessions: "users/sessions" }
resources :videos
resources :playlists do
resources :songs
end
Devise registrations controller:
def edit
#song = Song.new
#video = Video.new
end
Form in devise edit registrations:
<div id="user-music-box">
<p class="p-details-title"> Upload Music </p>
<%= simple_form_for [#user.playlist, #song] do |f| %>
<%= f.file_field :audio %>
<%= f.button :submit %>
<% end %>
</div>
<div id="user-video-box">
<p class="p-details-title"> Add videos </p>
<%= simple_form_for #video do |f| %>
<%= f.input :youtubeurl %>
<%= f.button :submit %>
<% end %>
</div>
As I said, videos (Which is a youtube url string) create and save no problem. The exact same form for songs, basically seems to just update the user registration. The song information is shown in the server logs, but no playlist_id is present and nothing gets saved.
Songs controller:
def new
if user_signed_in?
#song = Song.new
if current_user.playlist.songs.count >= 5
redirect_to user_path(current_user)
flash[:danger] = "You can only upload 5 songs."
end
else
redirect_to(root_url)
flash[:danger] = "You must sign in to upload songs"
end
end
def create
#song = current_user.playlist.songs.new song_params
#song.playlist_id = #playlist.id
if #song.save
respond_to do |format|
format.html {redirect_to user_path(current_user)}
format.js
end
else
render 'new'
end
end
Playlist.rb
class Playlist < ActiveRecord::Base
belongs_to :user
has_many :songs, :dependent => :destroy
accepts_nested_attributes_for :songs
end
song.rb
class Song < ActiveRecord::Base
belongs_to :user
belongs_to :playlist
has_attached_file :audio
validates_attachment_presence :audio
validates_attachment_content_type :audio, :content_type => ['audio/mp3','audio/mpeg']
end
Unless you're passing songs/playlists through accepts_nested_attributes_for you shouldn't be using registrations#edit. I'll detail both ways to achieve what you want below:
Nested Attributes
#app/models/user.rb
class User < ActiveRecord::Base
has_many :videos
has_many :playlists
has_many :songs, through: :playlists
accepts_nested_attributes_for :videos
end
#app/models/playlist.rb
class PlayList < ActiveRecord::Base
belongs_to :user
has_and_belongs_to_many :songs
end
#app/models/song.rb
class Song < ActiveRecord::Base
has_and_belongs_to_many :playlists
end
The importance of this is that to use it properly, you're able to edit the #user object directly, passing the nested attributes through the fields_for helper:
#config/routes.rb
devise_for :users, controllers: { registrations: "users/registrations", sessions: "users/sessions" }
#app/controllers/users/registrations_controller.rb
class Users::RegistrationsController < ApplicationController
before_action :authenticate_user!, only: [:edit, :update]
def edit
#user = current_user
#user.playlists.build.build_song
#user.videos.build
end
def update
#user = current_user.update user_params
end
private
def user_params
params.require(:user).permit(:user, :attributes, videos_attributes: [:youtubeurl], playlists_attributes: [song_ids: [], song_attributes: [:title, :artist, :etc]])
end
end
This will allow you to use:
#app/views/users/registrations/edit.html.erb
<%= form_for #user do |f| %>
<%= f.fields_for :videos do |v| %>
<%= v.text_field :youtubeurl %>
<% end %>
<%= f.fields_for :playlists do |p| %>
<%= p.collection_select :song_ids, Song.all, :id, :name %>
<%= p.fields_for :song do |s| %>
<%= f.text_field :title %>
<% end %>
<% end %>
<%= f.submit %>
<% end %>
This will give you a single form, from which you'll be able to create videos, playlists and songs for the #user.
Separate
The other option is to create the object separately.
There is no technical reason for preferring this way over nested attributes; you'd do it to make sure you have the routes in the correct order etc.
As a note, you need to remember that routes != model structure. You can have any routes you want, so long as they define a good pattern for your models:
# config/routes.rb
authenticated :user do #-> user has to be logged in
resources :videos, :playlists, :songs #-> url.com/videos/new
end
# app/controllers/videos_controller.rb
class VideosController < ApplicationController
def new
#video = current_user.videos.new
end
def create
#video = current_user.videos.new video_params
#video.save
end
private
def video_params
params.require(:video).permit(:youtubeurl)
end
end
# app/views/videos/new.html.erb
<%= form_for #video do |f| %>
<%= f.text_field :youtubeurl %>
<%= f.submit %>
<% end %>
The above will require the duplication of the VideosController for Playlists and Songs

Voting for nested objects using the Acts As Votable gem

I have a Restaurant model that has_many :dishes, through: dish_categories. I found a post that shows how to write the view code necessary to get things going for the Acts As Votable gem. My situation differs being that the dish model is the nested resource that's being voted upon.
I tried translating the provided code but to no avail. At this point should I create a new controller for dishes and place the votable actions there? If so how would I setup my route so I can accomplish this on my restaurant's show page?
Models
class Restaurant < ActiveRecord::Base
has_many :dish_categories, dependent: :destroy
has_many :dishes, through: :dish_categories
end
class DishCategory < ActiveRecord::Base
belongs_to :restaurant
has_many :dishes, dependent: :destroy
delegate :name, to: :dish_category, prefix: "category"
delegate :restaurant, to: :dish_category
end
class Dish < ActiveRecord::Base
belongs_to :dish_category
end
Restaurants Controller
...
def upvote
#restaurant = Restaurant.find(params[:id])
#dish = Dish.find(params[:id])
#dish.liked_by current_user
redirect_to #restaurant
end
def downvote
#restaurant = Restaurant.find(params[:id])
#dish = Dish.find(params[:id])
#dish.disliked_by current_user
redirect_to #restaurant
end
...
Routes
resources :restaurants do
member do
put "upvote", to: "restaurants#upvote"
put "downvote", to: "restaurants#downvote"
end
end
Restaurants - Show View
...
<% #restaurant.dishes.each do |dish| %>
<div>
<h2><%= dish.category_name %></h2>
<b><%= dish.name %></b>
<%= link_to "Upvote", like_restaurant_path(dish), method: :put %>
<%= link_to "Downvote", dislike_restaurant_path(dish), method: :put %>
</div>
<% end %>
A number of things needed to be done to get this to work. The first order of business was moving my controller action to my dishes controller. I also added two more actions: unlike and undislike for toggle functionailty.
NOTE: Logic for authenticating non-registered for users to liking/disliking dishes would still need to be written but this should help get you started.
Dishes Controller
class DishesController < ApplicationController
before_action :load_restaurant_and_dish, only: [:like, :unlike, :dislike, :undislike]
def like
#dish.liked_by current_user
redirect_to #restaurant
end
def unlike
#dish.unliked_by current_user
redirect_to #restaurant
end
def dislike
#dish.disliked_by current_user
redirect_to #restaurant
end
def undislike
#dish.undisliked_by current_user
redirect_to #restaurant
end
private
def load_restaurant_and_dish
#dish = Dish.find(params[:id])
#restaurant = #dish.restaurant
end
end
Next was configuring my routes to correspond with my restaurant and dish models:
Routes
resources :restaurants do
resources :dishes, only: [:like, :unlike, :dislike, :undislike] do
member do
put "like", to: "dishes#like"
put "unlike", to: "dishes#unlike"
put "dislike", to: "dishes#dislike"
put "undislike", to: "dishes#undislike"
end
end
end
I ended up refactoring my show view and created a few partials to reduce clutter now that there's a little bit of logic involved:
Restaurants - Show View
...
<%= render "restaurants/dish_partials/dishes" %>
...
Dishes Partial
<% #dishes.each do |dish| %>
<div>
<h2><%= dish.category_name %></h2>
<span><b><%= dish.name %></b></span>
<%= render "restaurants/dish_partials/like_toggle", dish: dish %>
</div>
<% end %>
Like Toggle Partial
<% if current_user.liked? dish %>
<%= link_to "Unlike", unlike_restaurant_dish_path(#restaurant, dish), method: :put %>
<% else %>
<%= link_to "Like", like_restaurant_dish_path(#restaurant, dish), method: :put %>
<% end %>
<% if current_user.disliked? dish %>
<%= link_to "Undislike", undislike_restaurant_dish_path(#restaurant, dish), method: :put %>
<% else %>
<%= link_to "Dislike", dislike_restaurant_dish_path(#restaurant, dish), method: :put %>
<% end %>

Add Comment to User and Post models Rails 4

I have microposts that belong to artists, and everything with that works perfectly.
Now I'm trying to let artists comment on microposts. However, this isn't working how I want it. The comments need to belong to both a specific artist and a specific micropost.
Right now I have a form to create a comment, but it only saves under the most recent micropost id.
### controllers/artists/comments_controller.rb ###
class Artists::CommentsController < ApplicationController
def create
#micropost = ArtistMicropost.find_by(params[:micropost_id])
#comment = #micropost.artist_micropost_comments.build(comment_params)
#comment.artist_id = current_artist.id
end
private
def comment_params
params.require(:artist_micropost_comment).permit(:artist_micropost_id, :artist_id, :content)
end
end
### controllers/artists/artists_controller.rb ###
class Artists::ArtistsController < ApplicationController
def show
#artist = Artist.find(params[:id])
#micropost = ArtistMicropost.new
#micro = ArtistMicropost.find_by(params[:micropost_id])
#comment = ArtistMicropostComment.new
end
end
### views/artists/show.html.erb ###
<% #artist.artist_microposts.each do |micropost| %>
...
<%= micropost.content %>
...
<% #micro.artist_micropost_comments.each do |comment| %>
<%= comment.content %>
<% end %>
<%= form_for(#comment) do |f| %>
<%= f.text_area :content %>
<%= f.submit "post comment" %>
<% end %>
<% end %>
### models/artist.rb ###
class Artist < ActiveRecord::Base
has_many :artist_microposts, dependent: :destroy
has_many :artist_micropost_comments, dependent: :destroy
end
### models/artist_micropost.rb ###
class ArtistMicropost < ActiveRecord::Base
belongs_to :artist
has_many :artist_micropost_comments, dependent: :destroy
end
### models/artist_micropost_comment.rb ###
class ArtistMicropostComment < ActiveRecord::Base
belongs_to :artist_micropost
belongs_to :artist
end
I want it to display each micropost by the artist and underneath each micropost to display the comments that belong to the micropost. I the want the from to display under the comments to add new comments. Basically, I want it to look something like Facebook.
Right now, all the comments are displaying under each micropost no matter what the micropost_id and the create method won't create under any micropost_id, except the most recent one.
So my two problems are:
I can't get the comments to save under the correct micropost_id
I can't get the comments to loop for their micropost.
Any ideas?
Short names are easier to read and understand so I will rename your models in my example to Artist, Micropost and Comment
class Artist < ActiveRecord::Base
has_many :microposts, dependent: :destroy
has_many :comments, through: :microposts, dependent: :destroy
end
class Micropost < ActiveRecord::Base
belongs_to :artist
has_many :comments, dependent: :destroy
end
class Comment < ActiveRecord::Base
# I renamed artist to commenter to make it clear that is not the same artist as the one that created the micropost,
# this implies that instead of author_id you will have commented_id in comments table
belongs_to :commenter, :class_name => Artist
belongs_to :micropost
end
### views/artists/show.html.erb ###
<% #artist.microposts.each do |micropost| %>
...
<%= micropost.content %>
...
<% micropost.comments.each do |comment| %>
# here you display comments for each micropost
<%= comment.content %>
# pay attention at the way I builded the comment
<%= form_for(micropost.comments.build) do |f| %>
<%= f.hidden_field :micropost_id %> # this will make the link to your micropost
<%= f.text_area :content %>
<%= f.submit "post comment" %>
<% end %>
<% end %>
<% end %>
In your comments_controller you must assign current logged in artist (the commenter) to your comment.
class CommentsController < ApplicationController
def create
#comment = Comment.new(comment_params)
#comment.commenter = current_artist
if #comment.save
...
end
end
private
def comment_params
params.require(:comment).permit(:micropost_id, :content)
end
end
To avoid N+1 when you load artists, microposts and commenters do something like this:
class ArtistsController < ApplicationController
def show
#artist = Artist.includes(:microposts, :comments => :commenter).find(params[:id])
end
end
For Your requirement. For creation of microposts.
do something like this.
artist=Artist who is logged in
artist.artist_micro_posts.build(attributes)
artist.save
For creating comments to microposts
micro_posts= Micropost Id
micro_post.artist_micropost_comments.build(:artist_id=logged in person)
micro_post.save

Deleting a HABTM object

I have the following code which gives me an ActionView::Template::Error (undefined methodroster_path' for #<#:0x007fe34005c208>):`
While in the background it deletes the association player_roster (Has and belongs to many), but i want to delete it when i press the link.
The roster path is nested within a team, but the issue is regarding roster and players.
<%= form_for [#team, #roster] do |f| %>
<% #players.each do |player| %>
<%= player.gamertag %>
<%= link_to "Delete", player.rosters.delete(#roster) %>
<% end %>
<% end %>
:Update
Player.rb
class Player < ActiveRecord::Base
has_and_belongs_to_many :rosters
belongs_to :country
mount_uploader :picture, PictureUploader
end
Roster.rb
class Roster < ActiveRecord::Base
has_and_belongs_to_many :players
has_many :placements
belongs_to :team, touch: true
end
The way you are doing it now will call your delete when the page loads. You can't link to arbitrary Ruby code, you need to link to a route and controller action which will perform your logic.
<%= form_for [#team, #roster] do |f| %>
<% #players.each do |player| %>
<%= player.gamertag %>
<%= link_to "Delete", player_roster_path(player, #roster), method: :delete %>
<% end %>
<% end %>
This link will route to players/:id/rosters/:id with the DELETE HTTP action, which Rails will route to the destroy method.
class RostersController < ApplicationController
def destroy
#player = Player.find(params[:player_id])
#roster = Roster.find(params[:id])
#player.rosters.destroy(#roster)
# redirect/render
end
end
You also will need to setup player_roster_path as a route in config/routes.rb
resources :players do
resources :rosters, only: [:destroy] # you may have other routes here as well
end

Rails Link_to is linking to undesired view

My link_to in my view is going to a completely different "show.html.erb" than I'd like it to. I'm basically trying to understand why the "link_to #exhibit is linking to an "Investigation" profile. I think it may have to do with my routes file or the fact that its a "belong to" relationship...but can't seem to get it workin...what should that link_to be?
UPDATE: (AS PER BROIS QUESTION)
The missing misbehaving link_to is in the <%= link_to #exhibit do %> in show.html.erb
MY EXHIBIT.RB (MODEL)
class Exhibit < ActiveRecord::Base
attr_accessible :content, :investigation_id, :name, :user_id, :media, :media_html
belongs_to :investigation
has_many :categorizations
has_many :categories, :through => :categorizations
validates :name, presence: true, length: { maximum: 140 }
validates :content, presence: true
default_scope -> { order('created_at DESC') }
auto_html_for :media do
html_escape
image
youtube(:width => 400, :height => 250)
link :target => "_blank", :rel => "nofollow"
simple_format
end
MY EXHIBIT CONTROLLER:
class ExhibitsController < ApplicationController
include AutoHtml
def new
#exhibit = Exhibit.new
end
def show
#exhibit = Exhibit.find(params[:id])
end
def index
#exhibits = Exhibit.paginate(page: params[:page])
end
def create
#investigation = Investigation.find(params[:investigation_id])
#exhibit = #investigation.exhibits.create(params[:exhibit])
if #exhibit.save
flash[:success] = "You've successfully added etc etc..."
redirect_to investigation_path(#investigation)
else
render 'new'
end
end
end
MY ROUTES.RB
resources :sessions, only: [:new, :create, :destroy]
resources :investigations do
resources :players
end
resources :investigations do
resources :exhibits
end
LASTLY MY SHOW.HTML.ERB (INVESTIGATION PROFILE)
<% #investigation.exhibits.each do |exhibit| %>
<div class="row-fluid services_circles">
<%= link_to #exhibit do %>
<div class="media">
<div class="pull-left">
<%= exhibit.media_html %>
</div>
<div class="media-body">
<h4 class="media-heading"><%= exhibit.name %></h4>
<p><%= exhibit.content %></p>
</div>
</div>
<% end %>
<% end %>
ADDED THE INVESTIGATIONS CONTROLLER
class InvestigationsController < ApplicationController
def new
#investigation = Investigation.new
end
def show
#investigation = Investigation.find(params[:id])
end
def index
#investigations = Investigation.paginate(page: params[:page])
end
def create
#investigation = Investigation.new(params[:investigation])
if #investigation.save
flash[:success] = "You've successfully created..."
redirect_to #investigation
else
render 'new'
end
end
end
ADDED THE INVESTIGATION MODEL
class Investigation < ActiveRecord::Base
# belongs_to :user
has_many :players, dependent: :destroy
has_many :exhibits, dependent: :destroy
default_scope -> { order('created_at DESC') }
end
I appreciate the help...if i need to post any more info just let me know
IN YOUR : app/contorollers/exhibits_controller.rb
def show
#investigation = Investigation.find(params[:investigation_id])
#exhibit = Exhibit.find(params[:id])
end
IN YOUR : app/views/exhibits/show.html.erb
<%= link_to investigation_exhibit_path(#investigation, #exhibit) do %>
Maybe, I think.

Resources