【问题标题】:Schema Error, 'has no method save'架构错误,'没有方法保存'
【发布时间】:2015-03-21 23:51:30
【问题描述】:

目前我想做的是,一旦用户登录,他可以转到任何用户的网址并关注他们。我遇到了这个问题

TypeError: Object { _id: 54bd6b90b7d4cc8c10b40cbd,
  name: 'Johnny',
  username: 'batman',
  __v: 0,
  following: [],
  followers: [] } has no method 'save'

schema.js

var UserSchema = new Schema({
    name: String,
    username: { type: String, required: true, index: { unique: true }},
    password: { type: String, required: true, select: false },
    level: String,
    followers: [{ type: Schema.Types.ObjectId, ref: 'User'}],
    following: [{ type: Schema.Types.ObjectId, ref: 'User'}]
});

module.exports = mongoose.model('User', UserSchema);

api.js

        // this is where I'm Stuck ( Should I use put or post? for best practices)

        .post(function(req, res) {

            // find a current user that has logged in
            User.find({ _id: req.decoded.id }, function(err, user) {

                // simply follow the user by matching the id
                user.following = req.params.user_id;

                // save to the database ( The problem )
                user.save(function(err) {
                    if(err) res.send(err);

                    res.json({ message: "Successfully followed!"})
                })
            });
        })

问题出在 .post 中,因为我无法保存。我应该怎么做才能让它保存到数据库中?

【问题讨论】:

  • User .findOne() as .find() 返回一个数组,而一个数组没有.save() 方法。你真的应该使用.update()。您的代码存在并发问题。
  • 也许你应该使用.findOne()
  • @NeilLunn 能否解释一下并发问题?
  • @NeilLunn 这是最佳实践吗?
  • 当您.find() 某物时,您会从数据库中检索它。如果您进行更改然后.save() 文档,其他人可能已经更改了数据库中的文档,并且这些更改将丢失。 .update() 和类似的方法可以避免这种情况。

标签: javascript node.js mongodb mongoose mongodb-query


【解决方案1】:

您的代码中的问题是 .find() 不返回单个 mongoose 文档,而是返回一个文档数组,即使该数组仅包含一个项目。你可以通过调用.findOne() 甚至.findById 来纠正这个问题,这允许更短的形式。

如前所述,这仍然存在问题,因为无法保证在您发出 .save() after making modifications. For this reason MongoDB provides an.update()` 方法之前服务器上的文档尚未更改,该方法只会通过以下方式对文档进行额外更改指定的运算符。

要添加新的追随者,您需要将$push 元素放到数组中:

.post(function(req, res) {

  User.update(
    { 
        "_id": req.decoded.id, 
        "following": { "$ne": req.params.user_id }
    }, 
    { "$push": { "following": req.params.user_id }}, 
    function(err, user) {
      if (err) return res.send(err);

      res.json({ message: "Successfully followed!"})
    }
  );
})  

我实际上会检查该元素是否存在,然后仅在出现这种情况时更新。有一个 $addToSet 运算符可以执行此操作,但我还建议修改您的架构以包含 "followingCount": 和类似字段。这些对于查询很有用,在不读取整个文档的情况下返回数组的“长度”并不是一件简单的事情:

var UserSchema = new Schema({
    name: String,
    username: { type: String, required: true, index: { unique: true }},
    password: { type: String, required: true, select: false },
    level: String,
    followers: [{ type: Schema.Types.ObjectId, ref: 'User'}],
    following: [{ type: Schema.Types.ObjectId, ref: 'User'}],
    folloersCount: Number,
    followingCount: Number
});

// And in later code

  User.update(
    { 
        "_id": req.decoded.id, 
        "following": { "$ne": req.params.user_id }
    }, 
    { 
        "$push": { "following": req.params.user_id }},
        "$inc": { "followingCount": 1 }
    },
    function(err, user) {
      if (err) return res.send(err);

      res.json({ message: "Successfully followed!"})
    }
  );
})  

这通过使用$inc 运算符来增加计数器值。要执行相反的操作并删除关注者,请使用 $pull

  User.update(
    { 
        "_id": req.decoded.id, 
        "following": req.params.user_id 
    }, 
    { 
        "$pull": { "following": req.params.user_id }},
        "$inc": { "followingCount": -1 }
    },
    function(err, user) {
      if (err) return res.send(err);

      res.json({ message: "Successfully Un-followed :("})
    }
  );
})  

那是$addToSet在不满足查询条件时不能也不会触碰文档的。

如果您希望在响应中包含修改后的文档,请改用.findOneAndUpdate()

所以,仅仅因为您使用的是 mongoose,请不要忘记您基本上仍在使用 MongoDB。最好使用数据库运算符来修改文档,而不是在代码中进行这些修改。

阅读文档中的 queryupdate 运算符。事实上,请阅读 all of them,因为它值得学习。

【讨论】:

  • 这是一个了不起的答案!我从您的解决方案中学到了很多东西。我只有一个问题,$ne 是什么?您是否故意离开 following: {"$ne": req.params.userId} ?还是应该是 user_id?再次感谢您
  • @NaufalYahaya 肥大的手指和急切的复制和粘贴可能无法说明这一点。 $ne 表示“不等于”,因此您使用请求中的该参数(一旦更正错字)以确保该“跟随者”不在数组中,并且您不会复制它,或增加计数当他们已经在关注时。编辑了我的错误并添加了更多链接供您阅读。高代表的好处是我可以添加很多链接:)
  • 哈哈再次感谢您的建议!我喜欢“胖手指和急切的复制和粘贴可能没有说清楚”部分事实上我正在再次这样做:)。无论如何,非常感谢您提供的所有链接和建议!对于像我这样的新手程序员来说,这意味着很多。
  • 嗨 Neil Lunn,我需要你的帮助,我有一个新问题 stackoverflow.com/questions/28474558/…,我不能再在这个帐户上提问。它基于您帮助我的代码
【解决方案2】:

您应该使用 update 方法,因为这将执行原子操作并且不容易出现任何并发问题。这是因为它会同时执行查找查询和保存。

.post(function(req, res) {

  User.update({ _id: req.decoded.id }, { $push: {following: req.params.user_id }}, function(err, user) {
    if (err) return res.send(err);

    res.json({ message: "Successfully followed!"})
  });
})  

【讨论】:

  • 我可以详细说明我自己的 cmets 并将它们称为答案。这是不正确的,因为它会覆盖现有文档的全部内容。
  • @yzarubin 我应该遵循这种方法,因为 NeilLunn 说这是不正确的?
  • @yzarubin 它正在工作,但它会覆盖现有的。尼尔伦说的是对的
  • @NeilLunn 您能否详细说明您的答案,因为我很困惑哪种是最佳做法。
  • 我忘了添加 $set,对此感到抱歉,我已经修改了答案。 @Neil Lunn 我什至没有阅读您的消息,这是一个非常标准的答案。
猜你喜欢
  • 1970-01-01
  • 2018-05-13
  • 2015-11-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-01-05
  • 2017-11-21
  • 1970-01-01
相关资源
最近更新 更多