#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