关于HABTM关系(Mongoid,RoR)的MongoDB条件聚合查询?

时间:2016-02-17 20:19:06

标签: ruby-on-rails mongodb mongoid mongodb-query aggregation-framework

Rails 4.2.5Mongoid 5.1.0

我有三个模型 - MailboxCommunicationMessage

mailbox.rb

class Mailbox
    include Mongoid::Document
    belongs_to :user
    has_many :communications
end

communication.rb

class Communication
    include Mongoid::Document
    include Mongoid::Timestamps
    include AASM

    belongs_to :mailbox
    has_and_belongs_to_many :messages, autosave: true

    field :read_at,     type: DateTime
    field :box,         type: String
    field :touched_at,  type: DateTime
    field :import_thread_id, type: Integer
    scope :inbox, -> { where(:box => 'inbox') }
end

message.rb

class Message
    include Mongoid::Document
    include Mongoid::Timestamps

    attr_accessor :communication_id

    has_and_belongs_to_many :communications, autosave: true
    belongs_to :from_user, class_name: 'User'
    belongs_to :to_user, class_name: 'User'

    field :subject, type: String
    field :body,    type: String
    field :sent_at, type: DateTime
end

我正在使用身份验证gem devise,它可以访问current_user帮助程序,该帮助程序指向当前登录的用户。

我为满足以下条件的控制器构建了一个查询: 获取current_user的{​​{1}},其mailbox已被communication字段过滤,box。 它是这样构建的(并且正在工作):

box == 'inbox'

当我尝试构建此查询时,我的问题就出现了。我希望将查询链接起来,以便我只获取current_user.mailbox.communications.where(:box => 'inbox')消息不是来自messages的{​​{1}}。我知道.last方法,它返回最新的记录。我已经提出了以下查询,但无法理解需要调整哪些才能使其正常工作:

last

此查询产生以下结果: current_user

我目前能够通过执行以下操作来完成此操作,我知道效率非常低,并希望立即更改:

current_user.mailbox.communications.where(:box => 'inbox').where(:messages.last.from_user => {'$ne' => current_user})

undefined method 'from_user' for #<Origin::Key:0x007fd2295ff6d8>

我希望将这个逻辑从ruby转移到实际的数据库查询。提前感谢任何帮助我的人,如果有更多信息对我有帮助,请告诉我。

1 个答案:

答案 0 :(得分:0)

好的,所以这里发生的事情有点混乱,并且与做关联时Mongoid实际上能够有多聪明有关。

具体说明如何在跨越&#39;在两个协会之间。

如果是您的第一个查询:

current_user.mailbox.communications.where(:box => 'inbox')

对于mongoid而言很酷,因为实际上这只是真正的2 db调用:

  1. 获取用户的当前邮箱
  2. Mongoid直接针对通信集合构建标准,并使用where语句说明:使用第1项中的邮箱ID,并过滤到box = inbox。
  3. 现在,当我们进入您的下一个查询时,

    current_user.mailbox.communications.where(:box => 'inbox').where(:messages.last.from_user => {'$ne' => current_user})
    

    当Mongoid开始混淆时。

    以下是主要问题:当您使用&#39;其中&#39;你正在查询你所在的系列。你不会过关联

    where(:messages.last.from_user =&gt; {&#39; $ ne&#39; =&gt; current_user})实际上做的是检查消息关联。 Mongoid实际上正在做的是在通信文档中搜索具有类似于JSON路径的属性:communication [&#39; messages&#39;] [&#39; last&#39;] [&#39; ; FROM_USER&#39;。]

    既然你知道为什么,你可以得到你想要的东西,但它需要比同等的ActiveRecord工作更多的汗水。

    以下是您可以获得所需内容的更多方式:

    user_id = current_user.id
    communication_ids = current_user.mailbox.communications.where(:box => 'inbox').pluck(:_id)
    # We're going to need to work around the fact there is no 'group by' in
    # Mongoid, so there's really no way to get the 'last' entry in a set
    messages_for_communications = Messages.where(:communications_ids => {"$in" => communications_ids}).pluck(
      [:_id, :communications_ids, :from_user_id, :sent_at]
    )
    # Now that we've got a hash, we need to expand it per-communication,
    # And we will throw out communications that don't involve the user
    messages_with_communication_ids = messages_for_communications.flat_map do |mesg|
      message_set = []
      mesg["communications_ids"].each do |c_id|
        if communication_ids.include?(c_id)
          message_set << ({:id => mesg["_id"],
           :communication_id => c_id,
           :from_user => mesg["from_user_id"],
           :sent_at => mesg["sent_at"]})
        end
      message_set
    end
    # Group by communication_id
    grouped_messages = messages_with_communication_ids.group_by { |msg| mesg[:communication_id] }
    communications_and_message_ids = {}
    grouped_messages.each_pair do |k,v|
      sorted_messages = v.sort_by { |msg| msg[:sent_at] }
      if sorted_messages.last[:from_user] != user_id
        communications_and_message_ids[k] = sorted_messages.last[:id]
      end
    end
    # This is now a hash of {:communication_id => :last_message_id}
    communications_and_message_ids
    

    我不确定我的代码是100%(您可能需要检查文档中的字段名称以确保我正在搜索正确的代码),但我认为您获得了一般模式