使用has_many关联的Ruby ActiveRecord查询

时间:2015-02-05 21:13:35

标签: mysql sql ruby-on-rails ruby activerecord

我的系统有User,Message和MessageToken模型。用户可以创建消息。但是当任何用户读取其他人的消息时,会创建一个将读者(用户)与消息相关联的MessageToken。 MessageTokens是收据,用于跟踪用户和特定邮件的状态。模型中的所有关联都是正确设置的,一切正常,除了构造一个我无法正常工作的非常具体的查询。


User.rb

has_many :messages

Message.rb

belongs_to :user
has_many :message_tokens

MessageToken.rb

  belongs_to :user
  belongs_to :message


<小时/>

我正在尝试构建一个查询以返回以下消息:不属于该用户; AND {用户有一个令牌,其读取值设置为false或用户根本没有令牌}

声明的后半部分是造成问题的原因。我能够成功获得非用户的消息的结果,用户拥有令牌的消息,带有read =&gt;假。但是当我尝试查询没有为用户提供MessageToken的消息时,我无法获得预期的结果。此查询不会出错,它只是不返回预期的结果。你会如何构建这样的查询?

以下是我成功查询的结果和预期结果。

130 --> # Messages
Message.count


78  --> # Messages that are not mine
Message.where.not(:user_id => @user.id)


19  --> # Messages that are not mine and that I do not have a token for


59  --> # Messages that are not mine, and I have a token for
Message.where.not(:user_id => @user.id).includes(:message_tokens).where(message_tokens: {:user_id => @user.id}).count
Message.where.not(:user_id => @user.id).includes(:message_tokens).where(["message_tokens.user_id = ?", @user.id]).count

33  --> # Messages that are not mine, and I have a token for, and the token is not read
Message.where.not(:user_id => @user.id).includes(:message_tokens).where(message_tokens: {:user_id => @user.id, :read => false}).count
Message.where.not(:user_id => @user.id).includes(:message_tokens).where(["message_tokens.user_id = ? AND message_tokens.read = false", @user.id]).references(:message_tokens).count


<小时/>

最终预期结果

52 --> # Messages that are not mine and: I have a token for that is not read OR I do not have a token for


<小时/> 我最好尝试查询以实现目标

64  --> # Wrong number returned, expected 52
Message.where.not(:user_id => @user.id).includes(:message_tokens).where(["(message_tokens.user_id = ? AND message_tokens.read = false) OR message_tokens.user_id <> ?", @user.id, @user.id]).references(:message_tokens).count



问题在于查询尝试查找非用户的邮件以及用户没有的标记

63 --> #This does not yield the expected result, it should == 19 (the number of Messages that are not mine and that I do not have a token for)
Message.where.not(:user_id => @user.id).includes(:message_tokens).where.not(message_tokens: {:user_id => @user.id}).count
Message.where.not(:user_id => @user.id).includes(:message_tokens).where(["message_tokens.user_id <> ?", @user.id]).references(:message_tokens).count


我该如何解决这个问题?

3 个答案:

答案 0 :(得分:1)

如果您不介意使用2个查询,可能的解决方案是:

messages_not_written_by_user =  Message.where.not(:user_id => @user.id)
messages_already_read_by_user = Message.where.not(:user_id => @user.id).includes(:message_tokens).where(message_tokens: {:user_id => @user.id, :read => true})
messages_not_read_by_user_yet = messages_not_written_by_user - messages_already_read_by_user

我个人觉得这种语法更具可读性:

messages_not_written_by_user =  Message.where.not(:user => @user).count
messages_already_read_by_user = Message.where.not(:user => @user).includes(:message_tokens).where(message_tokens: {:user => @user, :read => true}).count

对此查询的一点评论:

63 --> #This does not yield the expected result, it should == 19 (the number of Messages that are not mine and that I do not have a token for)
Message.where.not(:user_id => @user.id).includes(:message_tokens).where.not(message_tokens: {:user_id => @user.id}).count

此查询将搜索具有任意其他用户的令牌的所有邮件。 (如果msg1有一个带@user的令牌,并且它还有一个带有@another_user的令牌,则此查询将找到它。)

答案 1 :(得分:0)

完全披露 - 我不确定我是如何做到的,因为你现在已经设置好了。但是:你反对安装宝石来帮忙吗?如果您不这样做,我建议您查看Squeel gem(https://github.com/activerecord-hackery/squeel)。

Squeel使这些关联变得更加容易,并允许使用普通的旧|运营商。它建立在Arel之上,不应该影响您在ActiveRecord中编写的任何内容(至少根据我的经验)。希望有所帮助!

答案 2 :(得分:0)

好的,感谢R11 Runner的帮助,我能够提出一个解决方案,需要使用纯SQL。我无法使用Squeel gem或ActiveRecord,因为没有相当于SQL的NOT EXISTS运算符,这是缺少的关键组件。

这是有效的原因是因为与其他解决方案不同,NOT EXISTS运算符将返回Messages表中的所有记录,其中MessageTokens表中没有给定user_id的记录,而使用where.not将查找第一个匹配而不是确保所需的不存在。

Message.find_by_sql ["SELECT * FROM messages where messages.user_id <> ? 
                      AND (
                          (EXISTS (SELECT * FROM message_tokens WHERE message_id = messages.id AND user_id = ? AND read = FALSE)) 
                          OR
                          (NOT EXISTS (SELECT * FROM message_tokens WHERE message_id = messages.id AND user_id = ?))
                          )",@user.id, @user.id, @user.id]