使用 Mongoid 进行多对多关联
Mongoid 支持 MongoDB 中的 Has and Belongs to Many。
Mongoid的has_and_belongs_to_many行为
首先为多对多关系准备一个模型。
class Item
include Mongoid::Document
field :name
has_and_belongs_to_many :categories
end
class Category
include Mongoid::Document
field :name
has_and_belongs_to_many :items
end
这两个模型之间的关系被设为了 has_and_belongs_to_many。
试着创建一个项目作为示例。
irb(main):001:0> item = Item.create(name: 'apple')
=> #<Item _id: 5df1364821b6207e9250c3d4, name: "apple", category_ids: nil>
确认在Mongo Shell中创建了一个文档。
> db.items.find()
{ "_id" : ObjectId("5df1364821b6207e9250c3d4"), "name" : "apple" }
此时,categories 中尚无任何文档。
接下来,向apple的categories中添加一项。
irb(main):002:0> item.categories << Category.new(name: 'fruit')
MONGODB | [8] localhost:28001 #1 | sandbox.update | STARTED | {"update"=>"items", "ordered"=>true, "updates"=>[{"q"=>{"_id"=>BSON::ObjectId('5df1364821b6207e9250c3d4')}, "u"=>{"$addToSet"=>{"category_ids"=>{"$each"=>[BSON::ObjectId('5df1367d21b6207e9250c3d5')]}}}}], "lsid"=>{"id"=><BSON::Binary:0x70161405581900 ...
MONGODB | [8] localhost:28001 | sandbox.update | SUCCEEDED | 0.002s
MONGODB | [9] localhost:28001 #1 | sandbox.insert | STARTED | {"insert"=>"categories", "ordered"=>true, "documents"=>[{"_id"=>BSON::ObjectId('5df1367d21b6207e9250c3d5'), "name"=>"fruit", "item_ids"=>[BSON::ObjectId('5df1364821b6207e9250c3d4')]}], "lsid"=>{"id"=><BSON::Binary:0x70161405581900 type=uuid data=0x731...
MONGODB | [9] localhost:28001 | sandbox.insert | SUCCEEDED | 0.011s
=> [#<Category _id: 5df1367d21b6207e9250c3d5, name: "fruit", item_ids: [BSON::ObjectId('5df1364821b6207e9250c3d4')]>]
完成:
以上从mongo shell的视角来看,既进行了对items的更新,也进行了对categories的插入。
> db.items.find()
{ "_id" : ObjectId("5df1364821b6207e9250c3d4"), "name" : "apple", "category_ids" : [ ObjectId("5df1367d21b6207e9250c3d5") ] }
> db.categories.find()
{ "_id" : ObjectId("5df1367d21b6207e9250c3d5"), "name" : "fruit", "item_ids" : [ ObjectId("5df1364821b6207e9250c3d4") ] }
双方的文件已经被转化为具有对方集合id列表的形式。
在RDB中,通常使用中间表来实现Has and Belongs to Many,但是Mongoid似乎不创建中间集合。
考虑到MongoDB的特性,我认为在实现Has and Belongs to Many时不创建中间集合是一个合适的选择。
使用Mongoid实现多对多关系,使用中间集合。
即使是在MongoDB上,使用中间集合也可以实现多对多的关系。在Mongoid中实现的方法如下所示。
class Item
include Mongoid::Document
field :name
has_many :categories, class_name: 'ItemCategory'
end
class Category
include Mongoid::Document
field :name
has_many :items, class_name: 'ItemCategory'
end
class ItemCategory
include Mongoid::Document
belongs_to :item
belongs_to :category
end
可以基于这三个模型创建以下数据。
irb(main):001:0> item = Item.create(name: 'cucumber')
=> #<Item _id: 5df13ab121b620be0550c3d4, name: "cucumber">
irb(main):002:0> category = Category.create(name: 'vegetable')
=> #<Category _id: 5df13b1521b620be0550c3d5, name: "vegetable">
irb(main):003:0> ItemCategory.create(item_id: item.id, category_id: category.id)
MONGODB | [9] localhost:28001 #1 | sandbox.find | STARTED | {"find"=>"items", "filter"=>{"_id"=>BSON::ObjectId('5df13ab121b620be0550c3d4')}, "limit"=>1, "singleBatch"=>true, "lsid"=>{"id"=><BSON::Binary:0x70161021308820 type=uuid data=0xa3aed61d66164b2d...>}}
MONGODB | [9] localhost:28001 | sandbox.find | SUCCEEDED | 0.002s
MONGODB | [10] localhost:28001 #1 | sandbox.find | STARTED | {"find"=>"categories", "filter"=>{"_id"=>BSON::ObjectId('5df13b1521b620be0550c3d5')}, "limit"=>1, "singleBatch"=>true, "lsid"=>{"id"=><BSON::Binary:0x70161021308820 type=uuid data=0xa3aed61d66164b2d...>}}
MONGODB | [10] localhost:28001 | sandbox.find | SUCCEEDED | 0.001s
MONGODB | [11] localhost:28001 #1 | sandbox.insert | STARTED | {"insert"=>"item_categories", "ordered"=>true, "documents"=>[{"_id"=>BSON::ObjectId('5df13b7921b620be0550c3d6'), "item_id"=>BSON::ObjectId('5df13ab121b620be0550c3d4'), "category_id"=>BSON::ObjectId('5df13b1521b620be0550c3d5')}], "lsid"=>{"id"=><BSON::...
MONGODB | [11] localhost:28001 | sandbox.insert | SUCCEEDED | 0.008s
=> #<ItemCategory _id: 5df13b7921b620be0550c3d6, item_id: BSON::ObjectId('5df13ab121b620be0550c3d4'), category_id: BSON::ObjectId('5df13b1521b620be0550c3d5')>
当我们在mongo shell中查看此状态时,情况如下所示。
> db.items.find()
{ "_id" : ObjectId("5df13ab121b620be0550c3d4"), "name" : "cucumber" }
> db.categories.find()
{ "_id" : ObjectId("5df13b1521b620be0550c3d5"), "name" : "vegetable" }
> db.item_categories.find()
{ "_id" : ObjectId("5df13b7921b620be0550c3d6"), "item_id" : ObjectId("5df13ab121b620be0550c3d4"), "category_id" : ObjectId("5df13b1521b620be0550c3d5") }
在这种情况下,要从项目中引用类别,或者反过来,可以按照以下方式进行。
irb(main):004:0> item.categories.first.category
MONGODB | [12] localhost:28001 #1 | sandbox.find | STARTED | {"find"=>"categories", "filter"=>{"_id"=>BSON::ObjectId('5df13b1521b620be0550c3d5')}, "limit"=>1, "singleBatch"=>true, "lsid"=>{"id"=><BSON::Binary:0x70161021308820 type=uuid data=0xa3aed61d66164b2d...>}}
MONGODB | [12] localhost:28001 | sandbox.find | SUCCEEDED | 0.002s
=> #<Category _id: 5df13b1521b620be0550c3d5, name: "vegetable">
irb(main):005:0> category.items.first.item
MONGODB | [13] localhost:28001 #1 | sandbox.find | STARTED | {"find"=>"item_categories", "filter"=>{"category_id"=>BSON::ObjectId('5df13b1521b620be0550c3d5')}, "sort"=>{"_id"=>1}, "limit"=>1, "singleBatch"=>true, "lsid"=>{"id"=><BSON::Binary:0x70161021308820 type=uuid data=0xa3aed61d66164b2d...>}}
MONGODB | [13] localhost:28001 | sandbox.find | SUCCEEDED | 0.002s
MONGODB | [14] localhost:28001 #1 | sandbox.find | STARTED | {"find"=>"items", "filter"=>{"_id"=>BSON::ObjectId('5df13ab121b620be0550c3d4')}, "limit"=>1, "singleBatch"=>true, "lsid"=>{"id"=><BSON::Binary:0x70161021308820 type=uuid data=0xa3aed61d66164b2d...>}}
MONGODB | [14] localhost:28001 | sandbox.find | SUCCEEDED | 0.001s
=> #<Item _id: 5df13ab121b620be0550c3d4, name: "cucumber">
与 has_and_belongs_to_many 相比,如果没有中间集合,数据传递看起来更容易。
除非在确实需要中间集合的情况下,基本上可以认为使用 has_and_belongs_to_many 是好的选择。