I have a Rails app with the following models:
class User < ActiveRecord::Base
has_many :administrations
has_many :calendars, through: :administrations
end
class Calendar < ActiveRecord::Base
has_many :administrations
has_many :users, through: :administrations
end
class Administration < ActiveRecord::Base
belongs_to :user
belongs_to :calendar
end
For a given calendar
, a user
has a role
, which is define in the administration
join model.
For each calendar, a user can have only one of the following three roles: Owner
, Editor
or Viewer
.
These roles are currently not stored in dictionary or a constant, and are only assigned to an administration as strings ("Ower", "Editor", "Viewer") through different methods.
Authentication on the User
model is handled through Devise, and the current_user
method is working.
In order to only allow logged-in users to access in-app resources, I have already add the before_action :authenticate_user!
method in the calendars
and administrations
controllers.
Now, I need to implement a role-based authorization system, so I just installed the CanCanCan
gem.
Here is what I want to achieve:
- All (logged-in)
user
s can create newcalendar
s. - If a
user
is theowner
of acalendar
, then he canmanage
thecalendar
and all theadministration
s that belong to thiscalendar
, including his ownadministration
. - If a user is
editor
of acalendar
, then he canread
andupdate
this calendar, and destroy hisadministration
. - If a
user
isviewer
of acalendar
, then he canread
thiscalendar
, anddestroy
hisadministration
.
To implement the above, I have come up with the following ability.rb
file:
class Ability
include CanCan::Ability
def initialize(user, calendar)
user ||= User.new
calendar = Calendar.find(params[:id])
user can :create, :calendar
if user.role?(:owner)
can :manage, :calendar, :user_id => user.id
can :manage, :administration, :user_id => user.id
can :manage, :administration, :calendar_id => calendar.id
elsif user.role?(:editor)
can [:read, :update], :calendar, :user_id => user.id
can :destroy, :administration, :user_id => user.id
elsif user.role?(:viewer)
can [:read], :calendar, :user_id => user.id
can :destroy, :administration, :user_id => user.id
end
end
end
Since I am not very experimented with Rails and it is the first time I am working with CanCanCan
, I am not very confident with my code and would like some validation or advice for improvement.
So, would this code work, and would it allow me to achieve what I need?
UPDATE: with the current code, when I log in as a user, and visit the calendars#show page of another user's calendar, I can actually access the calendar, which I should not.
So, obviously, my code is not working.
Any idea of what I am doing wrong?
UPDATE 2: I figured there were errors in my code, since I was using :model
instead of Model
to allow user
s to perform actions on a given model
.
However, the code is still not working.
Any idea of what could be wrong here?
UPDATE 3: could the issue be caused by the fact that I use if user.role?(:owner)
to check if a user's role is set to owner, while in the database the role is actually defined as "Owner" (as a string)?
UPDATE 4: I kept on doing some research and I realized I had done two mistakes.
I had not added
load_and_authorize_resource
to thecalendars
andadministrations
controllers.I had defined two attributes two parameters —
initialize(user, calendar)
— instead of one in myinitialize
method.
So, updated both controllers, as well as the ability.rb file as follows:
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new
if user.role?(:owner)
can :manage, Calendar, :user_id => user.id
can :manage, Administration, :user_id => user.id
can :manage, Administration, :calendar_id => calendar.id
elsif user.role?(:editor)
can [:read, :update], Calendar, :user_id => user.id
can :destroy, Administration, :user_id => user.id
elsif user.role?(:viewer)
can [:read], Calendar, :user_id => user.id
can :destroy, Administration, :user_id => user.id
end
end
end
Now, when I try to visit a calendar that does not belong to the current_user
, I get the following error:
NoMethodError in CalendarsController#show
undefined method `role?' for #<User:0x007fd003dff860>
def initialize(user)
user ||= User.new
if user.role?(:owner)
can :manage, Calendar, :user_id => user.id
can :manage, Administration, :user_id => user.id
can :manage, Administration, :calendar_id => calendar.id
How I can fix this?