Reputation: 51
This is strange. I'm using Rolify + CanCan + Devise in my rails 3.2 app. My use case is simple. I want a user to have only one role at a time, thus to change a role, I do something like this:
user.remove_role "admin"
user.add_role "associate"
The strange thing to me is that when I do this, the role "admin" gets deleted from the Roles table. Why would this be? I don't want to eliminate the role entirely, just a given role from the user. What am I doing wrong?
Here's the SQL. Notice the last delete from roles statement:
3] pry(main)> u.remove_role "sub_admin"
Role Load (0.1ms) SELECT "roles".* FROM "roles" INNER JOIN "users_roles" ON "roles"."id" = "users_roles"."role_id" WHERE "users_roles"."user_id" = 2 AND "roles"."name" = 'sub_admin'
(0.0ms) begin transaction
(0.3ms) DELETE FROM "users_roles" WHERE "users_roles"."user_id" = 2 AND "users_roles"."role_id" IN (2)
(1.9ms) commit transaction
User Load (0.1ms) SELECT "users".* FROM "users" INNER JOIN "users_roles" ON "users"."id" = "users_roles"."user_id" WHERE "users_roles"."role_id" = 2
(0.0ms) begin transaction
SQL (2.1ms) DELETE FROM "roles" WHERE "roles"."id" = ? [["id", 2]]
(0.6ms) commit transaction
Upvotes: 5
Views: 3251
Reputation: 3011
Go to config/initializers/rolify.rb
uncomment this line
config.remove_role_if_empty = false
so the file will look like this now:
Rolify.configure do |config|
# Configuration to remove roles from database once the last resource is removed. Default is: true
config.remove_role_if_empty = false
end
By default, remove_role_if_empty is set to true
and rolify will delete role if it's not assigned to any other user.
Upvotes: -1
Reputation: 403
This is how I resolved this issue:
def delete_roles
roles.delete(roles.where(:id => self.roles.ids))
end
def add_role
@user.delete_roles
role = params[:user][:roles]
@user.add_role Role.find(role).name if not role.blank?
@user.role_id = Role.find(role).id if not role.blank?
end
Every time a user is created or updated, delete the user's roles.
Upvotes: 1
Reputation: 440
I know this question is more than two years old, but I found better and more "rolify like" answer.
You can do it really easy in User model:
def remove_only_role_relation(role_name)
roles.delete(roles.where(:name => role_name))
end
and use it like:
@user = User.find_by_id(params[:id])
role_name = params[:role_name]
# or:
# role_name = Role.find_by_id(params[:role_id]).name rescue nil
if @user and role_name
@user.remove_only_role_relation(role_name)
end
I found similar code in their sources:
rolyfi: role.rb - here method remove_role cals method "remove" in adapter file: rolyfi: role_adapter.rb
Upvotes: 2
Reputation: 584
The basic problem is that each combination if role-name, resource_type, and resource_id is stored only once in the roles
table. If you delete this row, it is deleted for everyone.
The solution then is to delete only the rows from the join table
of rolify connecting the User
and the Role
models. For ease of access i will make the join table a model to use some rails magic to generate the SQL. Since this is really a kind of service object, i will make it a class singleton.
Here is my hack:
class UsersRoles < ActiveRecord::Base
def self.delete_role(subject,role_symbol, obj=nil)
res_name = obj.nil? ? nil : obj.class.name
res_id = obj.id rescue nil
role_row = subject.roles.where(name: role_symbol.to_s, resource_type: res_name , resource_id: res_id).first
if role_row.nil?
raise "cannot delete nonexisting role on subject"
end
role_id = role_row.id
self.delete_all(user_id: subject.id,role_id: role_id)
end
private_class_method :new
end
This code is not optimized, but should give you idea what to do: for example you can now add a convenience method to the User
model:
def delete_role(role_symbol,target=nil)
UsersRoles.delete_role self,role_symbol,target
end
then you can say:
user.delete_role :admin
and it will only remove what you want.
Note that this will not remove the table row with the role, which I would retain for future use.
Upvotes: 4