【问题标题】:Ruby on Rails - ActiveRecord::Relation count method is wrong?Ruby on Rails - ActiveRecord::Relation 计数方法错误?
【发布时间】:2013-09-25 04:31:18
【问题描述】:

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

我想我可以节省一些工作并使用 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_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 绑定时,我可以检查发生了什么......现在让我猜一下:

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

EDIT 2 - 在 pry 中显示第一次和第二次迭代

还有……

Conversation.all.count 返回 1!

和:

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

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

如果数组相等怎么办?我不知道这里发生了什么,现在已经花了好几个小时了。认为这是一个错误?!

编辑 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 并编写我自己的。

【问题讨论】:

  • 您的self.mailbox.conversations 是否类似于[conversation_instance, nil]
  • 不,里面没有 nil 对象
  • 我错过了什么吗?我看到2.times,但期望count == 1
  • 我有一个非常相似的问题。计数更新程序直接进入数据库而不更改活动记录。在包含计数的记录上使用 reload 修复它。
  • reply_to_conversation 是做什么的?我很想看看代码。是否有可能在数据库中创建另一个对话?

标签: ruby-on-rails ruby activerecord rspec mailboxer


【解决方案1】:

看到这个Rails 3: Difference between Relation.count and Relation.all.count

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

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

来自Mailboxcode

 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 表,本质上是计算receipts 的数量,其中有重复的conversations

另一方面,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

【讨论】:

    【解决方案2】:

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

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

    这个here有一个博客。

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

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

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

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

    post.cmets.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, ....)
    

    【讨论】:

      【解决方案3】:

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

      【讨论】:

        【解决方案4】:

        试试

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

        或许

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

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

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

        【讨论】:

        • 该死,我真的很想让这个工作,但它没有解决它。不过还是谢谢。
        【解决方案5】:

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

        【讨论】:

          猜你喜欢
          • 2023-03-31
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-11-01
          • 2019-02-23
          • 1970-01-01
          • 2011-01-21
          相关资源
          最近更新 更多