Фильтрация дочерних объектов в отношении has_many: through в Rails 3 - PullRequest
15 голосов
/ 20 сентября 2010

Привет,

У меня есть приложение, в котором 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, et al. Однако я пытаюсь получить список всех пользователей в компании, которые являются администраторами этой компании (а также проверить, является ли пользователь администратором данной компании). Моим первым решением было следующее в 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 умнее этого?), И я не уверен, есть ли лучший способ сделать это (возможно, используя области или новые фантастические Relation объекты, которые использует Rails 3?).

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

Ответы [ 4 ]

17 голосов
/ 21 сентября 2010

Я полагаю, что поступил неправильно, указав условия для 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:

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

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

7 голосов
/ 28 сентября 2010

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

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

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

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

3 голосов
/ 21 сентября 2010

Как насчет этого:

Company.find(:id).company_memberships.where(:admin => true).joins(:user)
0 голосов
/ 14 февраля 2019

Я наткнулся на этот ответ и считаю, что в настоящее время есть более хороший способ использования has_many association scopes ( has_many документация ):

has_many :admins, -> { where(admin: true) }, through: :company_memberships, class_name: :user

Вторым параметром ассоциации has_many может быть proc или lambda, содержащий ваш фильтр.

...