2009-09-15 6 views
1

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

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,: условие => «game.users.doesnt_contain ('имя пользователя = игрок')

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

Обновление:

Используя решение 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 

ответ

1

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

Например:

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

Выглядит многообещающе, но этот синтаксис довольно непрозрачен этот новый пользователь рельсов без особого опыта работы с sql. Я получаю следующее ужасное сообщение из mysql, когда я его пытаюсь: Загрузка игры (0.0ms) Mysql :: Ошибка: у вас возникла ошибка в синтаксисе SQL; проверьте руководство, соответствующее версии вашего сервера MySQL, для правильного синтаксиса для использования рядом с .id WHERE (('games'.'status' = 2) AND (gu.game_id имеет значение null или gu.user_id! = 2)) ' в строке 1: SELECT 'games'. * FROM' games' left external join games_users gu on gu.game_id = 'games'.id WHERE (('games'.'status' = 2) AND (gu.game_id имеет значение null или gu.user_id! = 2)) – cmaughan

+0

.. и Исключение: Mysql :: Ошибка: у вас возникла ошибка в синтаксисе SQL; проверьте руководство, соответствующее версии вашего сервера MySQL, для правильного синтаксиса для использования рядом с .id WHERE (('games'.'status' = 2) AND (gu.game_id имеет значение null или gu.user_id! = 2)) ' в строке 1: SELECT 'games'. * FROM' games' left external join games_users gu on gu.game_id = 'games'.id WHERE (('games'.'status' = 2) AND (gu.game_id имеет значение null или gu.user_id! = 2)) – cmaughan

+0

Странно. Это был протестированный код с sqlite, но, возможно, MySQL делает с ним что-то странное. Я выстрелю туда и посмотрю. – jdl

0

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

вроде этого

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

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

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

редактировать:

Вы могли бы сделать что-то вроде этого, довольно "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]]) 

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

+0

Я боялся этого. Не уверен, как построить sql, но будет изучать его. – cmaughan

+0

Привет Крис, я новичок в Stackoverflow и не совсем уверен, как убедиться, что вы видите мое редактирование. Так что, надеюсь, этот комментарий уведомит вас, и вы можете проверить его :) – Bitterzoet

+0

@Bitterzoet # {user_id} должен быть @ user.id в моем случае, правильно? Я пробовал это, но MySQL жалуется, что «id NOT IN 2» не является допустимым синтаксисом – cmaughan

2

Это может быть проще в двухэтапном процессе.

Шаг 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] }} 
+0

Этот метод работал и на меня; и немного легче следовать, чем jdl .. Я подозреваю, что это медленнее, хотя. – cmaughan

Смежные вопросы