维护现有的Rails 2.3.x应用程序,该应用程序具有基于角色的自定义授权系统。
代码有这样的内容:
class Role << AR:Base
# has an int attribute called "level" with higher values indicating more powerful role
habtm: members
end
class Member << AR:Base
habtm: roles
end
角色表有类似的东西
(id, name, level)
1, admin, 1000
2, VIP, 500
3, regular, 100
4, some_other_role, 50
我有以下成员,具有明确的角色
member1(角色:admin
,VIP
,regular
)
member2(角色:VIP
,regular
)
member3(角色:regular
)
我有时需要根据最高指定的角色提升会员资格:
Role.admins_exclusively # should return member1
Role.vips_exclusively # should return just member2
Role.regulars_exclusively # should be just member3
无法在Rails中解决如何执行此操作,而无需编写原始SQL查询。
有什么建议吗?
更新:2012年3月29日
这是我的解决方案,基本上为每个角色定义了一堆这样的方法(很好地使用一些动态编程和define_method())。
class Member < AR:Base
define_method :vips_exclusively do
scoped :joins => :roles,
:group => 'members.id',
:having => ["max(roles.level) = ?", Role.find_by_name('vip').level]
end
end
但是,我发现旧的rails 2.3.x存在问题。例如,在Member.vips_exclusive上调用size()或count()会产生不正确的总数。调用length()会产生正确的结果,但建议尽可能使用size()。
在查看Rails代码之后,看起来像:group
和:having
这样的选项在scoped()中设置时不会传递给count()。用named_scopes替换对scoped()的调用( update:DOES NOT )解决了计数问题。
因此,我将Chris的提案与一些正确/简洁的编辑结合在一起。谢谢!
另一个更新。
实际上:group和:没有被传递的问题也在named_scoped实现中。
当然这里有一张陈旧的票,没有修复到Rails源码树(至少不在2.3.x分支中)。
https://rails.lighthouseapp.com/projects/8994/tickets/1349-named-scope-with-group-by-bug
那很棒......
答案 0 :(得分:0)
我认为你不需要直接编写SQL查询,但我认为你需要一个带有一些SQL组的named_scope,并且有条款来做你想要的:
在app / models / member.rb
named_scope :maximum_level, lambda { |level| {
:having => [ 'MAX(roles.level) = ?', level ],
:group => 'members.id', # edited to need quotes
:joins => :roles # dont need the whole join statement }
}
在app / models / role.rb
def exclusive_members
Member.maximum_level(self.level).all
end
def self.members_by_role_name(role_name)
role = self.find(:conditions => ['name = ?', role_name]).first
role.exclusive_members
end
示例强>
>> r = Role.find(1)
=> <Role id:1, name:"admin", level:1000>
>> r.exclusive_members
=> [ list of members with a highest role of "admin"]
>> r.exclusive_members.map { |m| m.name }
=> [ "member1" ]
>> Role.members_by_role_name("admin")
=> # the same list as you'd get by calling r.exclusive_members