Authorization with cancan - ruby-on-rails

Im trying to authorize each user so that they can create, read, update and destroy ONLY their own logg. I created the logg using a scaffold. I have used devise and admin as a boolean in users. I have a LoggsController, User model, logg model and ability.rb I tried following the rails cast video so that I can allow all users to do this. So far, admins can do everything. But users cant do what I want them to.
class Ability
include CanCan::Ability
def initialize(user)
user||= User.new
if user.admin?
can :manage, :all
else
can :read, :all
can :create, Logg
can :update, Logg do |logg|
logg.try(:user) == user
end
end
end
end
My models and controller
class User < ActiveRecord::Base
ROLES = %w[admin moderator author banned]
has_many :loggs
def role?(role)
roles.include? role.to_s
end
end
class Logg < ActiveRecord::Base
belongs_to :users
end
class LoggsController < ApplicationController
load_and_authorize_resource
before_action :set_logg, only: [:show, :edit, :update, :destroy]
respond_to :html
def index
#loggs = Logg.all
respond_with(#loggs)
end
def show
respond_with(#logg)
end
def new
respond_with(#logg)
end
def edit
end
def create
#logg.save
respond_with(#logg)
end
def update
#logg.update(logg_params)
respond_with(#logg)
end
def destroy
#logg.destroy
respond_with(#logg)
end
private
def set_logg
#logg = Logg.find(params[:id])
end
def logg_params
params.require(:logg).permit(:name, :date, :time, :whats_gone_well_this_week, :whats_not_gone_well_this_week, :learnt_anything_new, :what_would_you_like_to_improve, :anything_else)
end
end
View
<% if can? :show, #logg %>
<%= link_to 'Show', logg %>
<% end %>
<% if can? :update, #logg %>
| <%= link_to 'Edit', edit_logg_path(logg) %>
<% end %>
<% if can? :destroy, #logg %>
| <%= link_to 'Destroy', logg, method: :delete, data: { confirm: 'Are you sure?' } %></p>
<% end %>

It doesn't look like you're setting the Logg's owner (user) anywhere. In your create, you'll need something like:
def create
#logg = current_user.loggs.create(logg_params)
respond_with(#logg)
end

Related

Rails - routing to a parent show page with a child_id

I would like to route to the parent (journal) show page after deleting the child (habit) using the child_id.
In my app, a User can create journals, which then have multiple habits. I would like to be able to delete (and edit) a habit and then return to the journal show page, which displays all of the habits.
Getting the following and similar errors:
ActiveRecord::RecordNotFound in HabitsController#destroy
journal.rb
class Journal < ApplicationRecord
has_many :entries
has_many :habits
belongs_to :user
end
habit.rb
class Habit < ApplicationRecord
belongs_to :journal
has_many :completed_dates
end
show.html.erb
<h3>Habits</h3>
<% #journal.habits.each do |habit| %>
<div iv class="habit-list">
<div class="habit-name"><%= habit.name %></div>
<div class="habit-status">
<%= simple_form_for [#journal, #habit] do |f| %>
<%= f.input :status, label: false, :inline_label => true %>
<% end %>
</div>
<div>
<%= link_to habit_path(habit), method: :delete do %>
<i class="fas fa-trash btn-edit"></i>
<% end %>
</div>
</div>
<% end %>
habits_controller.rb
class HabitsController < ApplicationController
before_action :set_journal, only: [:new, :create, :edit, :update, :destroy]
before_action :set_habit, only: [:show, :edit, :update, :destroy]
def index
#habits = Habit.all.sort_by &:name
end
def new
#habit = Habit.new
end
def show
end
def create
#habit = #journal.habits.new(habit_params)
if #habit.save
redirect_to journal_path(#journal)
else
render :new
end
end
def edit
end
def update
if #journal.habits.update(habit_params)
redirect_to journals_path(#journal)
else
render :edit
end
end
def destroy
#habit.destroy
redirect_to journal_path(#habit, #journal)
end
private
def habit_params
params.require(:habit).permit(:name, :status, :user_id, :journal_id)
end
def set_journal
#journal = Journal.find(params[:journal_id])
end
def set_habit
#habit = Habit.find(params[:id])
end
end
Your trying to create URL to a deleted resource (#habit).
Change
def destroy
#habit.destroy
redirect_to journal_path(#habit, #journal)
end
to
def destroy
#habit.destroy
redirect_to journal_path(#journal)
end

link_to causing "Stack level to deep"

I'm trying to make a link_to for accepting friend requests using the update method in the friends_requests controller. When I click the link it acts like a recursive method with no base case and I end up with a "stack level to deep" error. In the logs I get a bunch of app/models/friendship.rb:11:in `create_inverse_relationship'
edit# For what it's worth I'm using devise for authentication.
The view: users/index
<ul>
New friend requests
<% #incoming.each do |user| %>
<% #users.each do |f| %>
<% if f.id == user.user_id %>
<li>
<%= f.name %>
###### id:1 or id: user.id both lead to the same error.
<%= link_to "Accept request", friend_request_path(id: 1), :method => :put %>
</li>
<% end %>
<% end %>
</ul>
<% end %>
friend_requests controller:
class FriendRequestsController < ApplicationController
before_action :set_friend_request, except: [:index, :create]
def index
#incoming = FriendRequest.where(friend: current_user)
#outgoing = current_user.friend_requests
end
def create
friend = User.find(params[:friend_id])
#friend_request = current_user.friend_requests.new(friend: friend)
if #friend_request.save
flash[:notice]="Friend request sent."
redirect_to root_path
else
flash[:alert]="Friend request not sent."
redirect_to root_path
end
end
def update
#friend_request.accept
head :no_content
flash[:notice]="Friend added!"
redirect_to root_path
end
def destroy
#friend_request.destroy
head :no_content
end
private
def set_friend_request
#friend_request = FriendRequest.find(params[:id])
end
end
users controller:
class UsersController < ApplicationController
def index
#users = User.all
#incoming = FriendRequest.where(friend: current_user)
end
def show
#user = current_user
end
end
friend_request model:
class FriendRequest < ApplicationRecord
belongs_to :user
belongs_to :friend, class_name: 'User'
# This method will build the actual association and destroy the request
def accept
user.friends << friend
destroy
end
end
friendship model:
class Friendship < ActiveRecord::Base
after_create :create_inverse_relationship
after_destroy :destroy_inverse_relationship
belongs_to :user
belongs_to :friend, class_name: 'User'
private
def create_inverse_relationship
friend.friendships.create(friend: user)
end
def destroy_inverse_relationship
friendship = friend.friendships.find_by(friend: user)
friendship.destroy if friendship
end
end
In your Friendship, you're calling #create in an after_create callback, which would invoke the callback again. Depending on your setup, you could probably prevent this by making sure that you only call it when the friendship does not already exist:
def create_inverse_relationship
if friend.friendships.where(friend: user).blank?
friend.friendships.create(friend: user)
end
end

Ruby on Rails - Problems with controller method show in views

Right now i am finishing my small learning project but I am sturggling with one thing. The funcionalities of the project are: register, login, logout for the Author; creating, editing, removing of the Events; attend, un-attend event.
Last thing what I want to implement is, to be able to see who is attending certain event. The functionality of attending / un-attending is already done (made a table Eventattendences with references to events and author).
The only thing which is missing is showing WHO is attending different event (that means to show Authors_key from the eventattendances table where events_id is equal in my show.html.erb). When i am trying to do that i always get an empty object from the SHOW method of eventattendacnes_controller.
Adding here all the code that should be related to this (if anything is missing just let me know and I will add it here). THANK YOU!
Versions: Rails(4.0.0), Ruby(2.3.3p222)
eventattendances_controller
class EventattendancesController < ApplicationController
before_action only: [:show, :edit, :update, :destroy]
before_filter :require_login
def new
if Eventattendance.exists?(events_id: params[:event_id].to_i, authors_id: current_user.id)
redirect_to event_path(params[:event_id])
else
#eventattendance = Eventattendance.new(events_id: params[:event_id].to_i, authors_id: current_user.id)
#eventattendance.save
redirect_to event_path(params[:event_id])
end
end
def destroy
if Eventattendance.exists?(events_id: params[:event_id].to_i, authors_id: current_user.id)
Eventattendance.where(events_id: params[:event_id].to_i, authors_id: current_user.id).first.destroy
redirect_to event_path(params[:event_id])
else
redirect_to event_path(params[:event_id])
end
end
def show
#eventattendances = Eventattendance.find(events_id= params[:event_id].to_i)
end
private
def author_params
params.require(:author).permit(:username, :email, :password,:password_confirmation)
end
def event_params
params.require(:event).permit(:title, :body)
end
end
eventattendances.rb (Model)
class Eventattendance < ActiveRecord::Base
belongs_to :author
belongs_to :event
end
routes.rb
Events::Application.routes.draw do
root to: 'events#index'
resources :events
resources :authors
resources :author_sessions, only: [ :new, :create, :destroy ]
resources :eventattendances, only: [:destroy, :new, :show]
get 'login' => 'author_sessions#new'
get 'logout' => 'author_sessions#destroy'
end
views/events/show.html.erb (here i want to set the show function from eventattendances to show the authors_id of the authors that are attending the event)
`<h1><%= #event.title %></h1>
<p><%= #event.body %></p>
<%= #at_list.blank? %>
<%= #at_list_att.blank? %>
<%=sql = "Select * from eventattendances"
at_list = ActiveRecord::Base.connection.execute(sql) %>
<% #event.eventattendances.map do |eventattendance| %>
<tr>
<td><%= eventattendance.author_id %></td>
</tr>
<% end %>
<%= link_to "<< Back to Event List", events_path %>
<p> </p>
<br>
<% if logged_in? %>
<%= link_to "edit", edit_event_path(#event) %>
<%= link_to "delete", event_path(#event), method: :delete, data: { confirm: "Are you sure?" } %>
<% end %>
<%= link_to "Cancel atendance", eventattendance_path(event_id: #event.id), method: :delete, class: "btn btn-primary" %>
<%= link_to "Attend", new_eventattendance_path(event_id: #event.id), class: "btn btn-success"%>
`
events_controller
class EventsController < ApplicationController
include EventsHelper
before_filter :require_login, except: [:index]
def index
#events = Event.all
end
def show
#event = Event.find(params[:id])
end
def new
#event = Event.new
end
def create
#event = Event.new(event_params)
#event.save
redirect_to event_path(#event)
end
def destroy
#event = Event.find(params[:id])
#event.destroy
redirect_to events_path
end
def edit
#event = Event.find(params[:id])
end
def update
#event = Event.find(params[:id])
#event.update(event_params)
redirect_to event_path(#event)
end
private
def event_params
params.require(:event).permit(:title, :body)
end
end
author.rb
`class Author < ActiveRecord::Base
authenticates_with_sorcery!
validates_confirmation_of :password, message: "should match confirmation", if: :password
has_and_belongs_to_many :events
end`
event.rb
class Event < ApplicationRecord
has_and_belongs_to_many :authors
end
In first place, if you are rendering the views/events/show you could do
<% #event.eventattendances.map do |eventattendance| %>
<tr>
<td><%= eventattendance.author_id %></td>
</tr>
<% end %>
to get the author_id.
But thinking better, maybe you are having a problem of relations and you need a
has and belongs to many association
The solution was to put it into the show method of events_controller.rb (instead of eventattendances_controller)
#eventattendances = Eventattendance.find_by(events_id: params[:id])
+
to change the arguments of the find:
from (events_id= params[:event_id]) to (events_id: params[:id])
(maybe it is going to help someone in the future)

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 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.

Resources