Ruby on Rails – ActiveRecord ::关系计数方法错了?

我正在编写一个应用程序,允许用户发送有关“优惠”的消息。

我以为我会节省一些工作并使用Mailboxer gem。

我正在遵循RSpec的测试驱动开发方法。 我正在编写一个测试,确保每个优惠只允许一次Conversation 。 优惠belongs_to两个不同的用户(提出优惠的用户和收到优惠的用户)。

这是我的失败测试:

 describe "after a message is sent to the same user twice" do before do 2.times { sending_user.message_user_regarding_offer! offer, receiving_user, random_string } end specify { sending_user.mailbox.conversations.count.should == 1 } end 

因此,在测试运行之前,用户sending_usersending_user发送两次消息。 message_user_regarding_offer! 看起来像这样:

 def message_user_regarding_offer! offer, receiver, body conversation = offer.conversation if conversation.nil? self.send_message(receiver, body, offer.conversation_subject) else self.reply_to_conversation(conversation, body) # I put a binding.pry here to examine in console end offer.create_activity key: PublicActivityKeys.message_received, owner: self, recipient: receiver end 

在测试的第一次迭代中(当发送第一条消息时), conversation变量nil因此发送消息并在两个用户之间创建会话。

在第二次迭代中,返回在第一次迭代中创建的对话,并且用户回复该对话,但不创建新对话。

这一切都有效,但测试失败了,我无法理解为什么!

当我在上面指定的位置的代码中放置一个pry绑定时,我可以检查发生了什么…现在谜语我:

wtf红宝石

self.mailbox.conversations[0]返回一个Conversation实例

self.mailbox.conversations[1]返回nil

self.mailbox.conversations清楚地显示包含ONE对象的集合。

self.mailbox.conversations.count返回2 ?!

那里发生了什么? count方法不正确,我的测试失败了……

我错过了什么? 或者这是一个错误?!

编辑

offer.conversation看起来像这样:

  def conversation Conversation.where({subject: conversation_subject}).last end 

offer.conversation_subject

  def conversation_subject "offer-#{self.id}" end 

编辑2 – 显示撬的第一次和第二次迭代

wtf红宝石2

也…

Conversation.all.count返回1!

和:

Conversation.all == self.mailbox.conversations返回true

Conversation.all.count == self.mailbox.conversations.count返回false

如果arrays相等,怎么会这样? 我不知道这里发生了什么,现在已经开始了。 认为这是一个错误?!

编辑3

从Mailboxer gem的来源……

 def conversations(options = {}) conv = Conversation.participant(@messageable) if options[:mailbox_type].present? case options[:mailbox_type] when 'inbox' conv = Conversation.inbox(@messageable) when 'sentbox' conv = Conversation.sentbox(@messageable) when 'trash' conv = Conversation.trash(@messageable) when 'not_trash' conv = Conversation.not_trash(@messageable) end end if (options.has_key?(:read) && options[:read]==false) || (options.has_key?(:unread) && options[:unread]==true) conv = conv.unread(@messageable) end conv end 

reply_to_convesation代码可在此处获取 – > http://rubydoc.info/gems/mailboxer/frames 。

只是看不出我做错了什么! 可能会修改我的测试以解决这个问题。 或抛弃gem并写下我自己的gem。

看到这个Rails 3:Relation.count和Relation.all.count之间的区别

简而言之,当您将count应用于查询时,Rails会忽略select列(如果不止一个)。 这是因为

SQL的COUNT只允许一列或更少的列作为参数。

来自Mailbox 代码

  scope :participant, lambda {|participant| select('DISTINCT conversations.*'). where('notifications.type'=> Message.name). order("conversations.updated_at DESC"). joins(:receipts).merge(Receipt.recipient(participant)) } 

self.mailbox.conversations.count忽略select('DISTINCT conversations.*')并使用receipts计算join表,主要计算其中包含重复conversationsreceipts数。

另一方面, self.mailbox.conversations.all.count首先获取应用select的记录,该记录获得唯一的conversations ,然后对其进行计数。

self.mailbox.conversations.all == self.mailbox.conversations因为它们都使用select查询数据库。

要解决您的问题,您可以使用sending_user.mailbox.conversations.all.countsending_user.mailbox.conversations.group('conversations.id').length

我倾向于在我的代码中使用size方法。 根据ActiveRecord代码,size将使用缓存计数(如果可用),并且还会在通过关系创建模型时返回正确的数字,但尚未保存。

 # File activerecord/lib/active_record/relation.rb, line 228 def size loaded? ? @records.length : count end 

这里有一个博客。

在Ruby中,#length和#size是同义词,它们都做同样的事情:它们告诉你有多少元素在数组或散列中。 从技术上讲,#length是方法,#size是它的别名。

在ActiveRecord中,有几种方法可以找出关联中有多少记录,并且它们的工作方式存在一些细微差别。

post.comments.count – 使用SQL COUNT查询确定元素的数量。 您还可以指定条件以仅计算关联元素的子集(例如:conditions => {:author_name =>“josh”})。 如果在关联上设置计数器缓存,#count将返回该缓存值,而不是执行新查询。

post.comments.length – 这总是将关联的内容加载到内存中,然后返回加载的元素数。 请注意,如果先前已加载关联,然后通过其他方式创建新注释(例如,Comment.create(…)而不是post.comments.create(…)),则不会强制更新。

post.comments.size – 这是前两个选项的组合。 如果集合已经加载,它将返回其长度,就像调用#length一样。 如果还没有加载,就像调用#count一样。

如果您不是通过关联创建模型,那么值得一提的是,相关模型不一定在其关联代理/集合中具有这些实例。

 # do this mailbox.conversations.build(attrs) # or this mailbox.conversations << Conversation.new(attrs) # or this mailbox.conversations.create(attrs) # or this mailbox.conversations.create!(attrs) # NOT this Conversation.new(mailbox_id: some_id, ....) 

我不知道这是否解释了发生了什么,但ActiveRecord count方法在数据库中查询存储的记录数。 Relationlength可能不同,如http://archive.railsforum.com/viewtopic.php?id=6255中所述 ,尽管在该示例中,数据库中的记录数小于 Rails数据结构。

尝试

 self.mailbox.conversations.reload; self.mailbox.conversations.count 

也许

 self.mailbox.reload; self.mailbox.conversations.count 

或者,如果这些都不起作用,只需尝试reload尽可能多的对象,看看是否可以让它工作( selfmailboxconversations等)。

我的猜测是内存和数据库之间有些混乱。 这绝对是一个非常奇怪的错误,可能想在Rails上提出一个问题,看看为什么会出现这种情况。

第一次调用后,将缓存mailbox.conversations的结果。 要重新加载它,请写入mailbox.conversations(true)