我的系统有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 =>假。但是当我尝试查询没有为用户提供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
我该如何解决这个问题?
答案 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]