Как я могу получить доступ к has_many: через соответствующую модель соединения объекта ассоциации без дополнительных запросов? - PullRequest
4 голосов
/ 16 июня 2011

Это то, с чем я сталкивался уже несколько раз, и я хотел бы либо выяснить, как сделать то, что я хочу, либо создать, и отправить патч в Rails, который это делает. Много раз в моих приложениях у меня было несколько моделей, которые выглядят примерно так:

class User < ActiveRecord::Base
  has_many :memberships
  has_many :groups, through: :memberships
end

class Membership
  belongs_to :user
  belongs_to :group

  def foo
    # something that I want to know
  end
end

class Group
  has_many :memberships
  has_many :users, through: :memberships
end

То, что я хочу иметь, - это получить доступ к соответствующему членству от звонка в ассоциацию без выполнения дополнительных запросов. Например, я хочу сделать что-то вроде этого:

@group = Group.first
@group.users.each do |user|
  membership = user.membership # this would be the membership for user in @group
end

Есть ли в Rails что-нибудь, что позволяет это? Потому что единственные известные мне методы достижения результата, о котором я говорю, ужасно уродливы и неэффективны, что-то вроде этого:

@group.users.each do |user|
  membership = Membership.where(group_id: @group.id, user_id:user.id).first
end

Есть ли в ActiveRecord какие-то тайные встроенные средства для достижения этой цели? Кажется, что это не будет слишком сложно, ему уже нужно выбрать модель соединения, чтобы в любом случае должным образом получить ассоциацию, поэтому, если эта функциональность не существует, мне кажется, она должна. Я сталкивался с этим несколько раз и готов закатать рукава и решить это навсегда. Что я могу сделать?

Обновление: Другой шаблон для этого, который я мог бы использовать, который в основном получает то, что я хочу, выглядит примерно так:

@group.memberships.includes(:user).each do |membership|
  user = membership.user
end

Но эстетически мне не нравится это решение, потому что я на самом деле не заинтересован в членстве настолько, насколько я являюсь пользователем, и кажется неправильным перебирать модель соединения вместо цели ассоциации. Но это лучше, чем другой способ, который я указал выше (спасибо Ян Ян из Intridea за напоминание мне об этом).

Ответы [ 4 ]

2 голосов
/ 16 июня 2011

Если вы просто хотите получить доступ к некоторым атрибутам в членстве, есть ужасный трюк

group.users.select('*, memberships.some_attr as membership_some_attr')

Это работает, потому что членство включено в JOIN неявно.

Обновление

Более того, если вы добавите еще один уродливый трюк в User

class User < ActiveRecord::Base
  has_many :memberships
  has_many :groups, through: :memberships
  belongs_to :membership #trick, cheat that we have membership_id
end

Сейчас:

group.users.select('*, memeberships.id as membership_id').includes(:membership).each do |user|
  membership = user.membership
end
0 голосов
/ 04 мая 2014
   users = Group.first.users.select("*, memberships.data as memberships_data, users.*")

Это даст вам доступ ко всему

0 голосов
/ 21 января 2013

Вы можете сделать это так:

Получить всех пользователей в определенных группах:

Где group_array - массив идентификаторов для групп, в которые вы хотите, чтобы @user входил.

@user = User.all(
  include: :groups, conditions: ["memberships.group_id in (?)", group_array]
).first

Переверните его с помощью:

@group = Group.all(
  include: :users, conditions: ["memberships.user_id in (?)", user_array]
).first
0 голосов
/ 16 июня 2011

Если данные, к которым вы хотите получить доступ, являются атрибутом таблицы присоединения, то включить это довольно простой способ сделать это.

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

  1. Вывести информацию о пользователе (именно поэтому вы итерируете пользовательский объект в первую очередь, если вы просто хотели членство, выпросто итерируем по ним).
  2. Выведите некоторую интеллектуальную и обработанную информацию для вашего объекта членства.

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

Я обычно определяю это чувство как потребность в другом уровне абстракции.Подумайте о создании новой модели с именем MembershipUsers (это ужасное название, но вы можете придумать другое).

Ниже приведена моя попытка специального кодирования, которая не проверялась, но должна дать вам представление о решении.:

class MembershipUser < User
  def self.for_group(group)
    select('memberships.*, users.*').
    joins('join memberships on memberships.user_id = users.id').
    where('memberships.group_id = ?', group.id)
  end
  def foo
    # now you have access to the user attributes and membership attributes
    # and you are free to use both sets of data for your processing
  end
end

Создав класс, который представляет пользователя и его членство в указанной группе, вы создали контекст, в котором метод foo кажется подходящим.Я предполагаю, что foo мало что значил, не будучи в контексте определенного пользователя, и что вы ссылаетесь на ассоциированного пользователя в методе foo.

-Nick (@ngauthier)

РЕДАКТИРОВАТЬ: забыл привести этот полный круг:

class Group
  def membership_users
    MembershipUser.for_group(self)
  end
end

# then iterate
group.membership_users.each do |membership_user|
  membership_user.user_name # a pretend method on user model
  membership_user.foo # the foo method that's only on MembershipUser
end
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...