简体   繁体   English

重新思考MongoDB的关系多对多关系

[英]Rethinking relational many-to-many relationships for MongoDB

I am just starting a new Rails 3 project using Mongoid ORM for MongoDB. 我刚刚使用Mongoid ORM为MongoDB启动了一个新的Rails 3项目。 There is just one thing I can not get my head around, and that is how to effectively have a many-to-many relationship. 只有一件事我无法理解,那就是如何有效地建立多对多的关系。 Now there is a good chance that I may be approaching this problem wrong, but as far as I know, there is at least two containers in my project that need a many-to-many relationship. 现在我很可能错误地解决了这个问题,但据我所知,我的项目中至少有两个容器需要多对多的关系。 I would prefer to treat both models as "first class" models and allocate each with its own container. 我更愿意将这两个模型视为“第一类”模型,并为每个模型分配自己的容器。

This is the simplest way I can think to structure my many-to-many relationship: 这是构建我的多对多关系的最简单方法:

// Javascript pseudo modeling
// -------------------- Apps
{ 
  app: {
    _id: "app1",
    name: "A",
    event_ids: ["event1","event2"]
  }
}

{ 
  app: {
    _id: "app2",
    name: "B",
    event_ids: ["event1"]
  }
}

// -------------------- Events

{
  event: {
    _id: "event1",
    name: "Event 1",
  }
}

{
  event: {
    _id: "event2",
    name: "Event 2",
  }
}

As far as I can tell this is the minimum amount of information need to infer a many-to-many relationship. 据我所知,这是推断多对多关系所需的最少量信息。 My assumption is that I might have to have a map reduce procedure to determine what apps belong to an event. 我的假设是我可能必须有一个map reduce过程来确定哪些应用程序属于某个事件。 I would also have to write post commit/save hooks on Event to update App.event_ids if an app is added to or removed from an event model. 如果在事件模型中添加或删除应用程序,我还必须在Event上编写post commit / save hooks来更新App.event_ids。

Am I on the right track here? 我在这里走在正确的轨道上吗? If someone has any Mongoid or Mongomapper code examples of a many-to-many relationship working, could you please share. 如果某人有任何关于多对多关系的Mongoid或Mongomapper代码示例,请与您分享。

Your structure can work and you don't need a mapreduce function to determine what apps belong to an event. 您的结构可以工作,您不需要mapreduce函数来确定哪些应用程序属于某个事件。 You can query the app collection on an eventid. 您可以在eventid上查询应用程序集合。 You can index field collection.event_ids. 您可以索引字段collection.event_ids。

If you don't want to search apps on an eventid but on a event name, you will need to add that event name to the app collection (denormalization). 如果您不想在偶数上搜索应用但在事件名称上搜索应用,则需要将该事件名称添加到应用集合(非规范化)。 That means that you also have to update the app collection when the name of an event changes. 这意味着您还必须在事件名称更改时更新应用程序集合。 I don't know if that happens very often? 我不知道这种情况经常发生吗?

You often have to denormalize when you use MongoDB, so you don't store the minimal amount of information but you store some things "twice". 当您使用MongoDB时,您经常需要进行非规范化,因此您不会存储最少量的信息,而是将某些内容存储为“两次”。

I was able to implement this design using Mongoid. 我能够使用Mongoid实现这个设计。 I wrote extensive tests and I was able to get my solution working; 我写了大量的测试,我能够使我的解决方案正常运行; however, I am not satisfied with my implementation. 但是,我对我的实施不满意。 I believe that my implementation would be a difficult to maintain. 我相信我的实施将难以维持。

I'm posting my non-elegant solution here. 我在这里发布我的非优雅解决方案。 Hopefully, this will help someone with the start of a better implementation. 希望这可以帮助一些人开始更好的实施。

class App
  include Mongoid::Document
  field :name

  references_one :account
  references_many :events, :stored_as => :array, :inverse_of => :apps

  validates_presence_of :name
end

class Event
  include Mongoid::Document
  field :name, :type => String

  references_one :account

  validates_presence_of :name, :account

  before_destroy :remove_app_associations

  def apps
    App.where(:event_ids => self.id).to_a
  end

  def apps= app_array
    unless app_array.kind_of?(Array)
      app_array = [app_array]
    end
    # disassociate existing apps that are not included in app_array
    disassociate_apps App.where(:event_ids => self.id).excludes(:id => app_array.map(&:id)).to_a
    # find existing app relationship ids
    existing_relationship_ids = App.where(:event_ids => self.id, :only => [:id]).map(&:id)
    # filter out existing relationship ids before making the new relationship to app
    push_apps app_array.reject { |app| existing_relationship_ids.include?(app.id) }
  end

  def push_app app
    unless app.event_ids.include?(self.id)
      app.event_ids << self.id
      app.save!
    end
  end

  def disassociate_app app
    if app.event_ids.include?(self.id)
      app.event_ids -= [self.id]
      app.save!
    end
  end

  def push_apps app_array
    app_array.each { |app| push_app(app) }
  end

  def disassociate_apps app_array
    app_array.each { |app| disassociate_app(app) }
  end

  def remove_app_associations
    disassociate_apps apps
  end

end

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM