【问题标题】:What is a good way to `update_or_initialize_with` in Mongoid?在 Mongoid 中 `update_or_initialize_with` 的好方法是什么?
【发布时间】:2016-09-07 19:57:39
【问题描述】:

每个用户都有一个地址。

class User
  include Mongoid::Document

  has_one :address
end

class Address
  include Mongoid::Document

  belongs_to :user

  field :street_name, type:String
end

u = User.find(...)
u.address.update(street_name: 'Main St')

如果我们有一个没有地址的用户,这将失败。

那么,u.address.update_or_initialize_with 有什么好的(内置)方法吗?

Mongoid 5

【问题讨论】:

    标签: ruby-on-rails ruby mongodb mongoid mongoid5


    【解决方案1】:

    我不熟悉红宝石。但我想我理解这个问题。您的架构可能如下所示。

    user = {
       _id : user1234,
       address: address789
    } 
    
    address = {
       _id: address789,
       street_name: ""
       user: user1234
    }
    
    //in mongodb(javascript), you can get/update address of user this way
    u = User.find({_id: user1234})
    u.address //address789
    db.address.update({user: u.address}, {street_name: "new_street name"})
    
    //but since the address has not been created, the variable u does not even have property address.
    u.address = undefined
    

    也许您可以尝试像这样手动创建和附加它:

     #create an address document, to get _id of this address
     address = address.insert({street_name: "something"}); 
    
     #link or attached it to u.address
     u.update({address: address._id})
    

    【讨论】:

      【解决方案2】:

      我最近遇到了这个问题。有一个内置方法,但它不同于活动记录的 #find_or_initialize_by#find_or_create_by 方法。

      在我的例子中,我需要批量插入记录并更新或创建如果没有找到,但我相信即使你不是批量插入也可以使用相同的技术。

      # returns an array of query hashes:
      def update_command(users)
        updates = []
        users.each do |user|
          updates << { 'q' => {'user_id' => user._id},
                       'u' => {'address' => 'address'},
                       'multi' => false,
                       'upsert' => true }
        end
        { update: Address.collection_name.to_s, updates: updates, ordered: false }
      end
      
      def bulk_update(users)
        client = Mongoid.default_client
        command = bulk_command(users)
        client.command command
        client.close
      end
      

      由于您不是批量更新,假设您的地址集合中有一个名为 user_id 的外键字段。您或许可以:

      Address.collection.update({ 'q' => {'user_id' => user._id},
                                  'u' => {'address' => 'address'},
                                  'multi' => false,
                                  'upsert' => true }
      

      将匹配user_id,在找到时更新给定字段(在本例中为address)或在未找到时创建一个新字段。

      为此,还有最后一个关键步骤。 您必须使用特殊标志向您的地址集合添加索引。

      您正在查询的字段(在本例中为user_id) 必须使用{ unique: true } 的标志进行索引 或{ sparse: true }unique 标志将引发错误 如果您有 2 个或更多 nil user_id 字段。 sparse 选项不会。 如果您认为您可能有 nil 值,请使用它。

      通过终端访问您的 mongo 数据库

      1. show dbs
      2. use your_db_name 检查地址集合是否已有您要查找的索引
      3. db.addresses.getIndexes() 如果它在 user_id 上已有索引,您可能需要删除它 db.addresses.dropIndex( { user_id: 1} ) 并使用以下标志再次创建它:
      4. db.addresses.createIndex( { user_id: 1}, { sparse: true } )

      https://docs.mongodb.com/manual/reference/method/db.collection.update/

      编辑#1

      Mongoid 5 似乎发生了变化。你可以使用 User.collection.update 而不是 User.collection.update_one

      https://docs.mongodb.com/manual/reference/method/db.collection.updateOne/

      文档显示您需要 filter 而不是 query 作为第一个参数,但它们似乎是相同的..

      Address.collection.update_one( { user_id: user_id },
        '$set' => { "address": 'the_address', upsert: true} )
      

      PS: 如果您只写 { "address": 'the_address' } 作为更新子句而不包括 update operator,例如 $set,整个文档将被覆盖,而不是仅更新 address 字段。

      编辑#2

      关于您可能希望使用uniquesparse 编制索引的原因

      如果您查看下面链接中的upsert 部分,您会看到:

      为避免多次更新插入,请确保过滤器字段是唯一的 索引。

      https://docs.mongodb.com/manual/reference/method/db.collection.updateOne/

      【讨论】:

      • 因为如果找到该密钥,您将匹配到 update 或如果未找到 insert。如果您没有唯一或稀疏索引,您仍然可以更新第一个匹配文档或使用 { multi: true } 标志更新所有匹配文档。请参阅编辑#2。
      猜你喜欢
      • 2014-05-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-11-25
      • 2019-07-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多