2009-09-15 81 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