2009-09-15 104 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(狀態:: WAITING_USERS,:條件=>「game.users.doesnt_contain('用戶名=玩家)

但我不知道怎麼辦?

Update:

使用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 

(上面的代碼也引出兩個問題.... - 我怎麼了一場比賽,而不是一個數組的結果,以及如何我得到一個非只讀版本它,這就是爲什麼我做的第二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

看起來很有前途,但該語法對於這個新的rails用戶沒有太多的sql經驗。當我嘗試它時,我從mysql獲得以下可怕的消息: 遊戲加載(0.0ms)Mysql ::錯誤:您的SQL語法有錯誤; ((''games'.'status' = 2)AND(gu.game_id爲null或gu.user_id!= 2))''檢查與您的MySQL服務器版本對應的手冊,在第1行:選擇'遊戲'。*從'遊戲'離開外部連接games_users gu gu.game_id ='games'.id其中((''games'.'status' = 2)AND(gu.game_id爲null或gu.user_id!= 2)) – cmaughan 2009-09-15 17:28:01

+0

..和 異常:Mysql ::錯誤:您的SQL語法有錯誤; ((''games'.'status' = 2)AND(gu.game_id爲null或gu.user_id!= 2))''檢查與您的MySQL服務器版本對應的手冊,在第1行:選擇'遊戲'。*從'遊戲'離開外部連接games_users gu gu.game_id ='games'.id其中((''games'.'status' = 2)AND(gu.game_id爲null或gu.user_id!= 2)) – cmaughan 2009-09-15 17:28:32

+0

奇怪。這是用sqlite測試的代碼,但也許MySQL做了一些奇怪的事情。我會把它放在那裏看看。 – jdl 2009-09-15 18:00:34

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 2009-09-15 15:11:34

+0

嗨克里斯,我是新來的Stackoverflow和不太確定如何確保你看到我的編輯。所以希望這個評論會通知你,你可以檢查出來:) – Bitterzoet 2009-09-15 15:17:27

+0

@Bitterzoet#{user_id}應該是@ user.id在我的情況下,對不對?我嘗試過,但MySQL抱怨'id not in 2'不是有效的語法 – cmaughan 2009-09-15 15:28:06

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 2009-09-16 10:03:22