rails habtm:返回相关记录但具有独占匹配

时间:2012-03-28 11:38:51

标签: ruby-on-rails join has-and-belongs-to-many

维护现有的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(角色:adminVIPregular
member2(角色:VIPregular
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

那很棒......

1 个答案:

答案 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