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

时间:2010-07-15 23:18:40

标签: mongodb ruby-on-rails-3 mongomapper mongoid

我刚刚使用Mongoid ORM为MongoDB启动了一个新的Rails 3项目。只有一件事我无法理解,那就是如何有效地建立多对多的关系。现在我很可能错误地解决了这个问题,但据我所知,我的项目中至少有两个容器需要多对多的关系。我更愿意将这两个模型视为“第一类”模型,并为每个模型分配自己的容器。

这是构建多对多关系的最简单方法:

// 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",
  }
}

据我所知,这是推断多对多关系所需的最少量信息。我的假设是我可能必须有一个map reduce过程来确定哪些应用程序属于某个事件。如果在事件模型中添加或删除应用程序,我还必须在Event上编写post commit / save hooks来更新App.event_ids。

我在这里走在正确的轨道上吗?如果某人有任何关于多对多关系的Mongoid或Mongomapper代码示例,请与您分享。

2 个答案:

答案 0 :(得分:1)

您的结构可以工作,您不需要mapreduce函数来确定哪些应用程序属于某个事件。您可以在eventid上查询应用程序集合。您可以索引字段collection.event_ids。

如果您不想在偶数日但在事件名称上搜索应用,则需要将该事件名称添加到应用集合(非规范化)。这意味着您还必须在事件名称更改时更新应用程序集合。我不知道这种情况经常发生吗?

当您使用MongoDB时,您经常需要进行非规范化,因此您不会存储最少量的信息,但会存储一些“两次”的内容。

答案 1 :(得分:1)

我能够使用Mongoid实现这个设计。我写了大量的测试,我能够使我的解决方案正常运行;但是,我对我的实施不满意。我相信我的实施将难以维持。

我在这里发布了我非优雅的解决方案。希望这有助于有人开始更好的实施。

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