Фильтрация дочерних объектов в has_many: через отношения в Rails 3

Привет,

У меня есть приложение, в котором Companies и Users должны принадлежать друг другу с помощью модели CompanyMembership, которая содержит дополнительную информацию о членстве (в частности, является ли пользователь администратором компании, через логическое значение admin). Простая версия кода:

class CompanyMembership < ActiveRecord::Base
  belongs_to :company
  belongs_to :user
end

class Company < ActiveRecord::Base
  has_many :company_memberships
  has_many :users, :through => :company_memberships
end

class User < ActiveRecord::Base
  has_many :company_memberships
  has_many :companies, :through => :company_memberships
end

Конечно, это упрощает получение всех членов компании через company.users.all и др. Тем не менее, я пытаюсь получить список всех пользователей в компании, которые являются администраторами этой компании (а также проверить, является ли пользователь администратором данной компании). Моим первым решением было следующее в company.rb:

def admins
  company_memberships.where(:admin => true).collect do |membership|
    membership.user
  end
end

def is_admin?(user)
    admins.include? user
end

В то время как это работает, что-то чувствует себя неэффективным (он перебирает каждое членство, каждый раз выполняет SQL, или это отношение более умное, чем это?), и я не уверен, есть ли лучший способ сделать это (возможно, с использованием областей или новых объектов Relation, которые использует Rails 3?).

Приветствуются любые советы по наилучшему методу (предпочтительно, используя лучшие практики Rails 3)!

Ответы

Ответ 1

Я считаю, что я ошибался, указав условия company_memberships вместо users, что я и хотел (список users, а не список CompanyMemberships). Решение, которое, я думаю, я искал, это:

users.where(:company_memberships => {:admin => true})

который генерирует следующий SQL (для компании с идентификатором 1):

SELECT "users".* FROM "users"
  INNER JOIN "company_memberships"
    ON "users".id = "company_memberships".user_id
  WHERE (("company_memberships".company_id = 1))
    AND ("company_memberships"."admin" = 't')

Я еще не уверен, если мне это понадобится, но метод includes() будет выполнять загружаемую загрузку, чтобы при необходимости уменьшить количество SQL-запросов:

Активная запись позволяет указать продвигать все ассоциации, которые будет загружен. Это возможно указав метод includesвызов Model.find. С включением, Активная запись гарантирует, что все заданные ассоциации загружаются используя минимально возможное количество queries.queries. RoR Guides: запрос ActiveRecord

(Я по-прежнему открыт для любых предложений от тех, кто считает, что это не лучший/самый эффективный/правильный способ сделать это.)

Ответ 2

Еще более чистым способом было бы добавить связь с вашей моделью компании, примерно так:

has_many :admins, :through => :company_memberships, :class_name => :user, :conditions => {:admin => true}

Вам может понадобиться вставить rails doc, чтобы получить точный синтаксис.

Вам не нужно: включать, если у вас нет других классов, связанных с: пользователем, который вы можете ссылаться в своем представлении.

Ответ 3

Как насчет чего-то вроде этого:

Company.find(:id).company_memberships.where(:admin => true).joins(:user)