【问题标题】:How to implement has_many :through relationships with Mongoid and mongodb?如何实现has_many:通过与Mongoid和mongodb的关系?
【发布时间】:2011-10-23 10:58:13
【问题描述】:

使用the Rails guides 中的这个修改示例,如何使用 mongoid 对关系“has_many :through”关联进行建模?

挑战在于 mongoid 不像 ActiveRecord 那样支持 has_many :through。

# doctor checking out patient
class Physician < ActiveRecord::Base
  has_many :appointments
  has_many :patients, :through => :appointments
  has_many :meeting_notes, :through => :appointments
end

# notes taken during the appointment
class MeetingNote < ActiveRecord::Base
  has_many :appointments
  has_many :patients, :through => :appointments
  has_many :physicians, :through => :appointments
end

# the patient
class Patient < ActiveRecord::Base
  has_many :appointments
  has_many :physicians, :through => :appointments
  has_many :meeting_notes, :through => :appointments
end

# the appointment
class Appointment < ActiveRecord::Base
  belongs_to :physician
  belongs_to :patient
  belongs_to :meeting_note
  # has timestamp attribute
end

【问题讨论】:

    标签: ruby-on-rails activerecord mongodb data-modeling mongoid


    【解决方案1】:

    Mongoid 没有 has_many :through 或等效功能。它对 MongoDB 没有那么有用,因为它不支持连接查询,因此即使您可以通过另一个集合引用相关集合,它仍然需要多个查询。

    https://github.com/mongoid/mongoid/issues/544

    通常,如果您在 RDBMS 中存在多对多关系,您将在 MongoDB 中使用包含“外”键数组的字段以不同方式进行建模。例如:

    class Physician
      include Mongoid::Document
      has_and_belongs_to_many :patients
    end
    
    class Patient
      include Mongoid::Document
      has_and_belongs_to_many :physicians
    end
    

    换句话说,您将消除连接表,它会产生与 has_many 类似的效果:在访问“另一边”方面。但在您的情况下,这可能不合适,因为您的联接表是一个 Appointment 类,其中包含一些额外信息,而不仅仅是关联。

    如何对此进行建模在某种程度上取决于您需要运行的查询,但您似乎需要添加 Appointment 模型并定义与 Patient 和 Physician 的关联,如下所示:

    class Physician
      include Mongoid::Document
      has_many :appointments
    end
    
    class Appointment
      include Mongoid::Document
      belongs_to :physician
      belongs_to :patient
    end
    
    class Patient
      include Mongoid::Document
      has_many :appointments
    end
    

    对于 MongoDB 中的关系,您始终必须在嵌入文档或关联文档之间做出选择。在您的模型中,我猜 MeetingNotes 是嵌入关系的良好候选者。

    class Appointment
      include Mongoid::Document
      embeds_many :meeting_notes
    end
    
    class MeetingNote
      include Mongoid::Document
      embedded_in :appointment
    end
    

    这意味着您可以一起检索笔记和约会,而如果这是一个关联,则需要多个查询。您只需要记住单个文档的 16MB 大小限制,如果您有大量会议记录,这可能会发挥作用。

    【讨论】:

    • +1 非常好的答案,仅供参考,mongodb 大小限制已增加到 16 MB。
    • 出于好奇(抱歉查询迟了),我也是 Mongoid 的新手,我想知道当它是使用单独的集合存储关联的 nn 关系时,您将如何查询数据,它和 ActiveRecord 一样吗?
    【解决方案2】:

    只是为了对此进行扩展,这里的模型扩展了与 has_many 非常相似的方法:从 ActiveRecord 通过返回查询代理而不是记录数组:

    class Physician
      include Mongoid::Document
      has_many :appointments
    
      def patients
        Patient.in(id: appointments.pluck(:patient_id))
      end
    end
    
    class Appointment
      include Mongoid::Document
      belongs_to :physician
      belongs_to :patient
    end
    
    class Patient
      include Mongoid::Document
      has_many :appointments
    
      def physicians
        Physician.in(id: appointments.pluck(:physician_id))
      end
    end
    

    【讨论】:

    • 这肯定有助于导致我的检索方法返回一个搞乱分页的数组。
    • 没有魔法。 @CyrilDD,你指的是什么? map(&:physician_id) 是 map{|appointment| 的简写约会.physician.id}
    • 我想知道,考虑到文档不是嵌入的而是使用外部模型关联的,这种方法是否会减少对 16MB 文档大小限制的潜在挫败感? (对不起,如果这是一个菜鸟问题!)
    • 正如弗朗西斯解释的那样,使用.pluck() 而不是.map 要快得多。你能为未来的读者更新你的答案吗?
    • 我收到undefined method 'pluck' for #&lt;Array:...&gt;
    【解决方案3】:

    Steven Soroka 的解决方案真的很棒!我没有评论答案的声誉(这就是我添加新答案的原因:P)但我认为使用 map 来建立关系是昂贵的(特别是如果你的 has_many 关系有数百条|数千条记录),因为它得到从数据库中获取数据,构建每条记录,生成原始数组,然后迭代原始数组以使用给定块中的值构建一个新数组。

    使用 pluck 更快,也许是最快的选择。

    class Physician
      include Mongoid::Document
      has_many :appointments
    
      def patients
        Patient.in(id: appointments.pluck(:patient_id))
      end
    end
    
    class Appointment
      include Mongoid::Document
      belongs_to :physician
      belongs_to :patient 
    end
    
    class Patient
      include Mongoid::Document
      has_many :appointments 
    
      def physicians
        Physician.in(id: appointments.pluck(:physician_id))
      end
    end
    

    这里有一些使用 Benchmark.measure 的统计数据:

    > Benchmark.measure { physician.appointments.map(&:patient_id) }
     => #<Benchmark::Tms:0xb671654 @label="", @real=0.114643818, @cstime=0.0, @cutime=0.0, @stime=0.010000000000000009, @utime=0.06999999999999984, @total=0.07999999999999985> 
    
    > Benchmark.measure { physician.appointments.pluck(:patient_id) }
     => #<Benchmark::Tms:0xb6f4054 @label="", @real=0.033517774, @cstime=0.0, @cutime=0.0, @stime=0.0, @utime=0.0, @total=0.0> 
    

    我只使用了 250 个约会。 不要忘记在约会文档中为 :patient_id 和 :physician_id 添加索引!

    希望对你有帮助 感谢阅读!

    【讨论】:

    • 我收到undefined method 'pluck' for #&lt;Array:...&gt;
    【解决方案4】:

    我想从自引用关联的角度来回答这个问题,而不仅仅是 has_many :through 的角度。

    假设我们有一个包含联系人的 CRM。联系人将与其他联系人有关系,但不是在两个不同模型之间创建关系,而是在同一模型的两个实例之间创建关系。一个联系人可以有很多朋友,并且可以与很多其他联系人成为朋友,因此我们将不得不创建多对多的关系。

    如果我们使用 RDBMS 和 ActiveRecord,我们将使用 has_many :through。因此我们需要创建一个连接模型,比如 Friendship。该模型将有两个字段,一个表示正在添加朋友的当前联系人的contact_id,一个表示正在成为好友的用户的friend_id。

    但我们使用的是 MongoDB 和 Mongoid。如上所述,Mongoid 没有 has_many :through 或等效功能。它对 MongoDB 没有那么有用,因为它不支持连接查询。因此,为了在像 MongoDB 这样的非 RDBMS 数据库中建模多对多关系,您需要使用一个字段,该字段在任一侧都包含一个“外”键数组。

    class Contact
      include Mongoid::Document
      has_and_belongs_to_many :practices
    end
    
    class Practice
      include Mongoid::Document
      has_and_belongs_to_many :contacts
    end
    

    如文档所述:

    多对多关系,其中反向文档存储在一个 使用 Mongoid 定义与基础文档分开的集合 has_and_belongs_to_many 宏。这表现出与 Active Record,但不需要加入集合, 外键 ID 以数组形式存储在 关系。

    在定义这种性质的关系时,每个文档都存储在 它各自的集合,每个文档都包含一个“外键” 以数组的形式引用另一个。

    # the contact document
    {
      "_id" : ObjectId("4d3ed089fb60ab534684b7e9"),
      "practice_ids" : [ ObjectId("4d3ed089fb60ab534684b7f2") ]
    }
    
    # the practice document
    {
      "_id" : ObjectId("4d3ed089fb60ab534684b7e9"),
      "contact_ids" : [ ObjectId("4d3ed089fb60ab534684b7f2") ]
    }
    

    现在对于 MongoDB 中的自引用关联,您有几个选择。

    has_many :related_contacts, :class_name => 'Contact', :inverse_of => :parent_contact
    belongs_to :parent_contact, :class_name => 'Contact', :inverse_of => :related_contacts
    

    相关联系人与联系人多且属于多实践有什么区别?巨大的差异!一种是两个实体之间的关系。其他是自引用。

    【讨论】:

    • 示例文档好像是一样的?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-11
    • 1970-01-01
    • 2023-03-16
    • 1970-01-01
    • 2012-08-03
    • 1970-01-01
    相关资源
    最近更新 更多