rails сложный запрос «многие ко многим»

#ruby-on-rails-3 #scope #many-to-many

#ruby-on-rails-3 #область применения #»многие ко многим»

Вопрос:

У меня есть 3 модели: пользователь, команда и членство —

 class Team < ActiveRecord::Base
  has_many :memberships
  has_many :members, :through => :memberships, :source => :user
end

class User < ActiveRecord::Base
  has_many :memberships, :dependent => :destroy
  has_many :teams, :through => :memberships

  def team_mates
    teams = Team.where(:members => id)
    team_mates = teams.members
  end
end

class Membership < ActiveRecord::Base
  belongs_to :user
  belongs_to :team

  validates :user_id, :presence => true
  validates :team_id, :presence => true
end
  

И я не совсем понимаю, как написать метод team_mates в пользовательской модели. Он должен возвращать массив других пользователей, которые находятся в команде с current_user . Я думаю, что мне следует использовать область действия, чтобы ограничить команду включением только команд, членом которых является текущий пользователь, но я не совсем понимаю синтаксис. Любая помощь по этому вопросу была бы высоко оценена.

Спасибо!

Ответ №1:

Используйте таблицу членства, чтобы найти пользователей, которые разделяют какую-либо команду с пользователем, для которого вы вызываете метод. Затем, чтобы отфильтровать повторяющихся пользователей, которые используют более 1 команды с текущим пользователем, используйте distinct.

Я не тестировал приведенный ниже код, но, надеюсь, он выведет вас на правильный путь:

 def team_mates
  m = Membership.scoped.table
  Users.join(m).where(m[:team_id].in(team_ids)).project('distinct users.*')
end
  

Обновить

Похоже, что некоторые из методов Arel в этом ответе не имеют простого отображения обратно в ActiveRecord land. (Если кто-то знает как, я хотел бы знать!) А также возвращает «текущего пользователя». Попробуйте это вместо:

 def team_mates
  User.joins(:memberships).where('memberships.team_id' => team_ids).where(['users.id != ?', self.id]).select('distinct users.*')
end
  

Комментарии:

1. Спасибо! Похоже, мы приближаемся к решению. Мне также пришлось использовать User в качестве socoped.table, чтобы заставить соединение работать. Но теперь функция возвращает Arel::SelectManager вместо массива пользователей. Есть идеи, как извлечь пользовательский массив из Arel::SelectManager? Вот мой код (извините за ужасное форматирование): def team_mates m = Membership.scoped.table u = User.scoped.table u.join(m).where(m[:team_id].in(team_ids)).project('distinct users.*') end

2. Я ищу в Google, как преобразовать это обратно в объект отношения ActiveRecord, который является тем, что вы хотели бы. Вероятно, вы можете взять результат этого последнего оператора и сделать, .to_sql затем передать его в User.find_by_sql : def team_mates; m = Membership.scoped.table; u = User.scoped.table; sql = u.join(m).where(m[:team_id].in(team_ids)).project('distinct users.*').to_sql; User.find_by_sql(sql); end

3. Идеально! Большое вам спасибо. Это потрясающе, и вы сэкономили мне столько времени, что теперь я могу прочитать кучу материала по Arel 🙂 Я в раю для ботаников.

Ответ №2:

Как насчет?

 User.joins(:memberships).joins(:teams).where("teams.id" => id).uniq
  

Ответ №3:

Может быть, что-то вроде этого

 scope team_mates, lambda {|user_id, teams| joins(:memberships).where(:team_id => teams, :user_id => user_id)}

def team_mates
  User.team_mates(self.id, self.teams.collect {|t| t.id})
end
  

Вот дополнительная информация:

http://api.rubyonrails.org/classes/ActiveRecord/NamedScope/ClassMethods.html