How would you prevent other users from editing a object, say a profile object that does - not - belong to themselves?
Most online examples are complexes with multiple user roles, i haven't been able to get this working, must be simple though:
def initialize(user)
can :update, Profile do |profile|
profile.try(:user) == current_user
end
end
And inside my ProfilesController#edit
authorize! :update, @profile
First question is, have you made your roles for the User
?
app/models/user.rb
class User < ActiveRecord::Base
attr_accessible :email, :password, :remember_me
devise :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable, # regular devise stuff
before_create :setup_default_role_for_new_users
ROLES = %w[admin default banned]
private
def setup_default_role_for_new_users
if self.role.blank?
self.role = "default"
end
end
end
As you can see I have 3 different roles here and when a new user is created they are always default
users. Now with CanCan set up, lets say you wanted to have the admin
be able to do everything, the default
users be able to do everything with their own profiles, banned
users cannot do anything and guest users be able to see profiles:
class Ability
include CanCan::Ability
# Remember that CanCan is for a resource, meaning it must have a class(model).
def initialize(user)
user ||= User.new # guest user (not logged in)
if user.role == "admin"
can :manage, :all
elsif user.role == "default"
can :manage, Profile, :user_id => user.id
elsif user.role == "banned"
cannot :manage, :all
else
can :read, Profile # guest user
end
end
end
So that's how you let users edit only their own profiles and nobody elses.
Some other handy notes: Make sure you have a user_id
column in your Profile
table. Also if you may need to let guess users see profiles like this:
class ProfileController < ApplicationController
before_filter :authenticate_user!, :except => :show
load_and_authorize_resource
end
They won't be able to use any other action and CanCan still checks authentication on everything else except show
.
Good luck!
UPDATE: Making :role attribute for Users
What I did was run a migration that would add the role
column to the Devise users
table:
rails generate migration add_role_to_users role:string
And then rake db:migrate
. The new migration file should look like this and also check your db/schema.rb file to make sure its apart of the users table correctly. If it isn't then rake db:drop
, then rake db:create
and then rake db:migrate
again.
class AddRoleToUsers < ActiveRecord::Migration
def self.up
add_column :users, :role, :string
end
def self.down
remove_column :users, :role
end
end
This is how you successfully make the user.role
work.
Note: Make sure you leave the line: can :manage, Profile, :user_id => user.id
as is with no changes. It should work after adding the role
column to user
.
IMPORTANT! If you use Rails 3, DO NOT MAKE role
attr_accessible
or everyone can edit their roles! Rails 4 uses Strong Parameters by default and is not affected by this issue as you can choose the allowed parameters.
Give something like this a try....
can :update, Profile, :user_id => user.id
UPDATE
Seems like the above code examples where correct. After reading all of the docs of cancan rtfm ;p I found out about the role column you need to add.
Because of the way I have my profile update action organized it seems CanCan does not work! I solved like below:
def edit
@profile = Profile.find params[:id]
what = params[:what]
if can? :update, @profile
if ["basics", "location", "details", "photos", "interests"].member?(what)
render :action => "edit_#{what}"
else
render :action => "edit_basics"
end
else
raise CanCan::AccessDenied.new("Not authorized!", :update, Profile)
end
end
Maybe not the cleanest way but the only way to get it to work. Any suggestions on improvements are welcome, I did have the
load_and_authorize_resource
Inside profiles controller though! Perhaps a bug