来自具有复杂条件的数据库的动态康康舞
dynamic cancan from database with complex conditions
我正在尝试从权限列表中定义具有 select 权限的用户创建角色。
我想要这样的权限:
def initialize(user)
user.projects_users.each do |project_user|
project_user.role.privileges do |privilege|
can :create, ProjectsUser, :project_id => project_user.project_id
end
end
end
但我正在尝试将权限保存在数据库中,这样我就可以这样做以获得上述结果
def initialize(user)
user.projects_users.each do |project_user|
project_user.role.privileges do |privilege|
can privilege.action.to_sym, privilege.subject_class.constantize, privilege.conditions
end
end
end
问题出在'privilege.conditions'部分。我无法将必须只执行的条件存储在 Ability.rb 文件中。如果我尝试存储:
{ :project_id => project_user.project_id }
它会说没有名为'project_user'的变量。我可以将它保存为字符串并在我的能力文件中执行 eval(privilege.condition),但是我只需要对值执行此操作。我试过这样的事情:
def initialize(user)
user.projects_users.each do |project_user|
project_user.role.privileges do |privilege|
can privilege.action.to_sym, privilege.subject_class.constantize, privilege.conditions.each do |subject, id|
subject => eval(id)
end
end
end
end
我得到的错误是 'syntax error, unexpected =>, expecting keyword_end' 'subject =>' 部分。
不知道具体怎么做...
我正在使用这行命令对其进行测试:
@user_id = 4
@role = Role.create(name: "Tester", project_id: 4)
@priv = Privilege.create(:action => :create, :subject_class => 'ProjectsUser', :conditions => { :project_id => 'project_user.project_id' })
@role.privileges << @priv
@project_user = ProjectsUser.create(:user_id => @user_id, :role_id => @role.id, :project_id => @role.project_id)
@a = Ability.new(User.find(@user_id))
@a.can?(:create, ProjectsUser.new(:user_id => @user_id + 1, :role_id => @role.id, :project_id => @role.project_id))
有什么建议吗?
我肯定会说你的做法有些倒退。由于测试套件中到处都需要大量设置,而且过于复杂,因此从数据库记录动态创建一堆身份验证规则将非常难以测试。
而是使用块来声明复杂的关系和角色来存储权限。
使用 awesome Rolify gem 和作用域为资源的角色的示例:
can :moderate, Forum do |forum|
user.has_role?(:moderator, forum)
end
您还可以使用角色为群组分配权限:
if user.has_role? :admin
can :crud, User
# ...
end
我会说,如果您真的需要一个复杂的完全动态的解决方案,管理员可以从 Web 界面创建授权规则、角色定义等,那么您肯定已经超越了 CanCan。但你真的需要它吗?
好的,所以我找到了一个非常简单的解决方法。本质上,条件上的 do 块没有被正确评估。这是工作代码:
user.projects_users.each do |project_user|
project_user.role.privileges.each do |privilege|
can privilege.action.to_sym, privilege.subject_class.constantize, Hash[privilege.conditions.map {|subject, condition| [subject, eval(condition)] }]
end
end
注意 Hash[privilege.conditions.map {|subject, condition| [subject, eval(condition)] }]
这样做是将一个符号作为条件中的键,例如 :subject_id 并将其映射到评估的条件,该条件评估为特定的 id。
在我的模型中我有
class Privilege < ActiveRecord::Base
has_and_belongs_to_many :roles
serialize :conditions, Hash
end
示例模型是:
Privilege.create(
:action => :create,
:subject_class => 'ProjectsUser',
:conditions => { :project_id => 'project_user.project_id' }
)
更新
此方法仅适用于一层深度的条件。像这样的条件是行不通的。你会得到一个:TypeError: no implicit conversion of Hash into String
:conditions => {
:project => {
:location_id => 'project_user.project.location_id'
}
}
这不是最佳解决方案,但可以解决此问题
:conditions => {
:project => "{
:location_id => eval(\"project_user.project.location_id'\")
}"
}
我正在尝试从权限列表中定义具有 select 权限的用户创建角色。 我想要这样的权限:
def initialize(user)
user.projects_users.each do |project_user|
project_user.role.privileges do |privilege|
can :create, ProjectsUser, :project_id => project_user.project_id
end
end
end
但我正在尝试将权限保存在数据库中,这样我就可以这样做以获得上述结果
def initialize(user)
user.projects_users.each do |project_user|
project_user.role.privileges do |privilege|
can privilege.action.to_sym, privilege.subject_class.constantize, privilege.conditions
end
end
end
问题出在'privilege.conditions'部分。我无法将必须只执行的条件存储在 Ability.rb 文件中。如果我尝试存储:
{ :project_id => project_user.project_id }
它会说没有名为'project_user'的变量。我可以将它保存为字符串并在我的能力文件中执行 eval(privilege.condition),但是我只需要对值执行此操作。我试过这样的事情:
def initialize(user)
user.projects_users.each do |project_user|
project_user.role.privileges do |privilege|
can privilege.action.to_sym, privilege.subject_class.constantize, privilege.conditions.each do |subject, id|
subject => eval(id)
end
end
end
end
我得到的错误是 'syntax error, unexpected =>, expecting keyword_end' 'subject =>' 部分。
不知道具体怎么做...
我正在使用这行命令对其进行测试:
@user_id = 4
@role = Role.create(name: "Tester", project_id: 4)
@priv = Privilege.create(:action => :create, :subject_class => 'ProjectsUser', :conditions => { :project_id => 'project_user.project_id' })
@role.privileges << @priv
@project_user = ProjectsUser.create(:user_id => @user_id, :role_id => @role.id, :project_id => @role.project_id)
@a = Ability.new(User.find(@user_id))
@a.can?(:create, ProjectsUser.new(:user_id => @user_id + 1, :role_id => @role.id, :project_id => @role.project_id))
有什么建议吗?
我肯定会说你的做法有些倒退。由于测试套件中到处都需要大量设置,而且过于复杂,因此从数据库记录动态创建一堆身份验证规则将非常难以测试。
而是使用块来声明复杂的关系和角色来存储权限。
使用 awesome Rolify gem 和作用域为资源的角色的示例:
can :moderate, Forum do |forum|
user.has_role?(:moderator, forum)
end
您还可以使用角色为群组分配权限:
if user.has_role? :admin
can :crud, User
# ...
end
我会说,如果您真的需要一个复杂的完全动态的解决方案,管理员可以从 Web 界面创建授权规则、角色定义等,那么您肯定已经超越了 CanCan。但你真的需要它吗?
好的,所以我找到了一个非常简单的解决方法。本质上,条件上的 do 块没有被正确评估。这是工作代码:
user.projects_users.each do |project_user|
project_user.role.privileges.each do |privilege|
can privilege.action.to_sym, privilege.subject_class.constantize, Hash[privilege.conditions.map {|subject, condition| [subject, eval(condition)] }]
end
end
注意 Hash[privilege.conditions.map {|subject, condition| [subject, eval(condition)] }]
这样做是将一个符号作为条件中的键,例如 :subject_id 并将其映射到评估的条件,该条件评估为特定的 id。
在我的模型中我有
class Privilege < ActiveRecord::Base
has_and_belongs_to_many :roles
serialize :conditions, Hash
end
示例模型是:
Privilege.create(
:action => :create,
:subject_class => 'ProjectsUser',
:conditions => { :project_id => 'project_user.project_id' }
)
更新
此方法仅适用于一层深度的条件。像这样的条件是行不通的。你会得到一个:TypeError: no implicit conversion of Hash into String
:conditions => {
:project => {
:location_id => 'project_user.project.location_id'
}
}
这不是最佳解决方案,但可以解决此问题
:conditions => {
:project => "{
:location_id => eval(\"project_user.project.location_id'\")
}"
}