ActiveRecord найти с деталями ассоциации - PullRequest
1 голос
/ 15 сентября 2009

Предположим, я занимаюсь подбором пользователей и игр. У меня есть модели, содержащие пользователей и игры.

class Game < ActiveRecord::Base
  has_and_belongs_to_many :users 

class User < ActiveRecord::Base
  has_and_belongs_to_many :games

Игры могут иметь много пользователей, пользователи могут играть во многие игры. Из-за HASBM у меня тоже есть таблица с именем games_users.

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

Я хочу что-то вроде этого:

@ game = Game.find_by_status (Status :: WAITING_USERS,: condition => "game.users.doesnt_contain ('username = player')

Но я не уверен, как это сделать?

Обновление:

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

logger.debug "Excluding user: #{@user.id}"
games = Game.excluding_user(@user)
if (games != nil && games.count > 0)
  @game = Game.find(games[0].id)
  games[0].users.each {
   |u|
   logger.debug "returned game user: #{u.id}"
  }
end

(вышеприведенный код также напрашивается на 2 вопроса .... - как получить результат только одной игры вместо массива и как получить версию, не предназначенную только для чтения; поэтому я делаю второй Game.find ...)

А вот вывод в журнале:

Excluding user: 2
  Game Load (0.3ms)   SELECT `games`.* FROM `games` left outer join games_users gu on gu.game_id = games.id WHERE (gu.game_id is null or gu.user_id != 2) 
  Game Columns (1.0ms)   SHOW FIELDS FROM `games`
  SQL (0.2ms)   SELECT count(*) AS count_all FROM `games` left outer join games_users gu on gu.game_id = games.id WHERE (gu.game_id is null or gu.user_id != 2) 
  Game Load (0.1ms)   SELECT * FROM `games` WHERE (`games`.`id` = 3) 
  games_users Columns (6.8ms)   SHOW FIELDS FROM `games_users`
  User Load (0.9ms)   SELECT * FROM `users` INNER JOIN `games_users` ON `users`.id = `games_users`.user_id WHERE (`games_users`.game_id = 3 ) 
returned game user: 1
returned game user: 2

Ответы [ 3 ]

2 голосов
/ 16 сентября 2009

Это может быть проще в два этапа.

Шаг 1 - получить список игр, в которых участвует пользователь:

games_playing = user.games.for_status('playing')

Шаг 2 - получить список открытых игр для игрока:

open_games = Game.for_status('waiting').not_including(games_playing)

Где у вас есть дополнительная именованная область в классе Game:

named_scope :not_including, lambda {|g| { :conditions => ["id not in (?) ", g] }}
1 голос
/ 15 сентября 2009

Именованные области - ваш друг здесь.

Например:

class Game < ActiveRecord::Base
  has_and_belongs_to_many :users

  named_scope :for_status, lambda {|s| {:conditions => {:status => s}}}
  named_scope :excluding_user, lambda {|u| {:conditions => ["gu.game_id is null or gu.game_id not in (select game_id from games_users where user_id = ?) ", u.id], :joins => "left outer join games_users gu on gu.game_id = games.id", :group => "games.id" }}
end

Это позволит вам сделать следующее:

user = User.first  # Or whoever.
games_in_progress = Game.for_status("playing")
games_in_progress_for_others = Game.excluding_user(user).for_status("playing")
# etc...

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

user = User.first
users_games_in_waiting = user.games.for_status("waiting")
0 голосов
/ 15 сентября 2009

Вы можете выполнить свой запрос так, как вы хотите, и использовать метод слайса, чтобы удалить пользователя из результатов (но это не очень хорошая производительность, если пользователь уже добавлен во многие игры)

вроде как

   a = [ "a", "b", "c" ]
   a.slice!(1)     #=> "b"
   a               #=> ["a", "c"]

Или написать собственный SQL-запрос (используя find_by_sql и используйте! = user_id, чтобы исключить это из запроса.

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

edit:

Вы можете сделать что-то вроде этого, довольно "Railsy",

@game = Game.find_by_status(Status::WAITING_USERS, :conditions => ["id NOT IN #{user_id}"])

Или для нескольких пользователей, массив

@game = Game.find_by_status(Status::WAITING_USERS, :conditions => ["id NOT IN (?)", [1,2,3]])

Дайте мне знать, если это сработает для вас: -)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...