rails habtm: возвращать связанные записи, но с эксклюзивным соответствием - PullRequest
0 голосов
/ 28 марта 2012

Поддержка существующего приложения 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-запросов.

Есть предложения?


Обновление: 29 марта 2012 г.
Это было мое решение, чтобы в основном определить кучу таких методов (хорошо используя некоторое динамическое программирование вместе с 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

Однако я обнаружил, что существует проблема со старыми рельсами 2.3.x. Например, вызов size () или count () для Member.vips_exclusively приведет к неверным результатам. Вызов length () даст правильный результат, но рекомендуется использовать size () везде, где это возможно.

После просмотра кода Rails выглядит, что такие параметры, как :group и :having, не передаются в count () при установке в scoped (). Замена вызовов функции scoped () на named_scopes ( обновление: НЕ ) решает проблему подсчета.

Поэтому я включил предложение Криса вместе с некоторыми правками для правильности / краткости. Спасибо!


Еще одно обновление.
На самом деле проблема: group и: не пройдя также находится в реализации named_scoped.

И, конечно же, вот устаревший тикет, без каких-либо исправлений, когда-либо вносимых в дерево исходных текстов Rails (по крайней мере, в ветке 2.3.x). https://rails.lighthouseapp.com/projects/8994/tickets/1349-named-scope-with-group-by-bug

Отлично ...

1 Ответ

0 голосов
/ 28 марта 2012

Я не думаю, что вам нужно будет писать запросы SQL напрямую, но я думаю, что вам понадобится named_scope с некоторой группой SQL и с предложениями для выполнения того, что вы ищете:

В приложении / 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  }
} 

В приложении / 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
...