Как создать элемент, если он не существует, и вернуть ошибку, если существует - PullRequest
0 голосов
/ 25 апреля 2018

Я пишу навык alexa и хотел бы проверить, существует ли пользователь в MongoDB. Мой код работает, но я не знаю, как определить ситуацию, если пользователь уже находится в базе данных: (

Каждый раз, когда я выполняю код, я получаю: «Привет, Анна, ты здесь новенький»

Мой пользователь Анна сохраняется в MongoDB

Но я бы хотел различить, когда мой пользователь уже находится в базе данных, и отреагировать на это.

Кто-нибудь умный есть решение для моей проблемы?

    var myName = "Anan1";
    var userID = this.event.session.user.userId;
    console.log(userID);

    self = this;
    User.findOneAndUpdate(
        {userId:  userID},
        {$set:{name:myName}},
        {upsert: true, new: false, runValidators: true},
        function(err, doc){
            if(err){
                console.log("eeoror");
            }

            console.log(doc);
            if (doc==null){
                self.emit(':ask',
                    "Hello "+ myName +"you are new here")
            }else {
                self.emit(':ask',
                    "Hello "+ myName +"you are not new here")
            }

        });

Ответы [ 2 ]

0 голосов
/ 25 апреля 2018

Как отмечалось в комментарии ранее, у вас есть два основных подхода, чтобы выяснить, было ли что-то «создано» или нет. Это либо:

  • Верните rawResult в ответе и проверьте свойство updatedExisting, которое сообщает вам, является ли это "upsert" или нет

  • Установите new: false так, чтобы "no document" фактически возвращался в результате, когда это фактически "upsert"

В качестве списка для демонстрации:

const { Schema } = mongoose = require('mongoose');

const uri = 'mongodb://localhost/thereornot';

mongoose.set('debug', true);
mongoose.Promise = global.Promise;

const userSchema = new Schema({
  username: { type: String, unique: true },   // Just to prove a point really
  password: String
});

const User = mongoose.model('User', userSchema);

const log = data => console.log(JSON.stringify(data, undefined, 2));

(async function() {

  try {

    const conn = await mongoose.connect(uri);

    await Promise.all(Object.entries(conn.models).map(([k,m]) => m.remove()));

    // Shows updatedExisting as false - Therefore "created"

    let bill1 = await User.findOneAndUpdate(
      { username: 'Bill' },
      { $setOnInsert: { password: 'password' } },
      { upsert: true, new: true, rawResult: true }
    );
    log(bill1);

    // Shows updatedExisting as true - Therefore "existing"

    let bill2 = await User.findOneAndUpdate(
      { username: 'Bill' },
      { $setOnInsert: { password: 'password' } },
      { upsert: true, new: true, rawResult: true }
    );
    log(bill2);

    // Test with something like:
    // if ( bill2.lastErrorObject.updatedExisting ) throw new Error("already there");


    // Return will be null on "created"
    let ted1 = await User.findOneAndUpdate(
      { username: 'Ted' },
      { $setOnInsert: { password: 'password' } },
      { upsert: true, new: false }
    );
    log(ted1);

    // Return will be an object where "existing" and found
    let ted2 = await User.findOneAndUpdate(
      { username: 'Ted' },
      { $setOnInsert: { password: 'password' } },
      { upsert: true, new: false }
    );
    log(ted2);

    // Test with something like:
    // if (ted2 !== null) throw new Error("already there");

    // Demonstrating "why" we reserve the "Duplicate" error
    let fred1 = await User.findOneAndUpdate(
      { username: 'Fred', password: 'password' },
      { $setOnInsert: { } },
      { upsert: true, new: false }
    );
    log(fred1);       // null - so okay

    let fred2 = await User.findOneAndUpdate(
      { username: 'Fred', password: 'badpassword' }, // <-- dup key for wrong password
      { $setOnInsert: { } },
      { upsert: true, new: false }
    );

    mongoose.disconnect();

  } catch(e) {
    console.error(e)
  } finally {
    process.exit()
  }


})()

А на выходе:

Mongoose: users.remove({}, {})
Mongoose: users.findAndModify({ username: 'Bill' }, [], { '$setOnInsert': { password: 'password', __v: 0 } }, { upsert: true, new: true, rawResult: true, remove: false, fields: {} })
{
  "lastErrorObject": {
    "n": 1,
    "updatedExisting": false,
    "upserted": "5adfc8696878cfc4992e7634"
  },
  "value": {
    "_id": "5adfc8696878cfc4992e7634",
    "username": "Bill",
    "__v": 0,
    "password": "password"
  },
  "ok": 1,
  "operationTime": "6548172736517111811",
  "$clusterTime": {
    "clusterTime": "6548172736517111811",
    "signature": {
      "hash": "AAAAAAAAAAAAAAAAAAAAAAAAAAA=",
      "keyId": 0
    }
  }
}
Mongoose: users.findAndModify({ username: 'Bill' }, [], { '$setOnInsert': { password: 'password', __v: 0 } }, { upsert: true, new: true, rawResult: true, remove: false, fields: {} })
{
  "lastErrorObject": {
    "n": 1,
    "updatedExisting": true
  },
  "value": {
    "_id": "5adfc8696878cfc4992e7634",
    "username": "Bill",
    "__v": 0,
    "password": "password"
  },
  "ok": 1,
  "operationTime": "6548172736517111811",
  "$clusterTime": {
    "clusterTime": "6548172736517111811",
    "signature": {
      "hash": "AAAAAAAAAAAAAAAAAAAAAAAAAAA=",
      "keyId": 0
    }
  }
}
Mongoose: users.findAndModify({ username: 'Ted' }, [], { '$setOnInsert': { password: 'password', __v: 0 } }, { upsert: true, new: false, remove: false, fields: {} })
null
Mongoose: users.findAndModify({ username: 'Ted' }, [], { '$setOnInsert': { password: 'password', __v: 0 } }, { upsert: true, new: false, remove: false, fields: {} })
{
  "_id": "5adfc8696878cfc4992e7639",
  "username": "Ted",
  "__v": 0,
  "password": "password"
}

Таким образом, первый случай фактически рассматривает этот код:

User.findOneAndUpdate(
  { username: 'Bill' },
  { $setOnInsert: { password: 'password' } },
  { upsert: true, new: true, rawResult: true }
)

Большинство опций здесь стандартно, так как «все» "upsert" действия приведут к тому, что содержимое поля, используемое для «соответствия» (т. Е. username), будет «всегда» создан в новом документе, поэтому вам не нужно $set это поле. Чтобы не «модифицировать» другие поля в последующих запросах, вы можете использовать $setOnInsert, который добавляет эти свойства только во время действия "upsert", когда совпадение не найдено.

Здесь стандарт new: true используется для возврата «измененного» документа из действия, но разница в rawResult, как показано в возвращенном ответе:

{
  "lastErrorObject": {
    "n": 1,
    "updatedExisting": false,
    "upserted": "5adfc8696878cfc4992e7634"
  },
  "value": {
    "_id": "5adfc8696878cfc4992e7634",
    "username": "Bill",
    "__v": 0,
    "password": "password"
  },
  "ok": 1,
  "operationTime": "6548172736517111811",
  "$clusterTime": {
    "clusterTime": "6548172736517111811",
    "signature": {
      "hash": "AAAAAAAAAAAAAAAAAAAAAAAAAAA=",
      "keyId": 0
    }
  }
}

Вместо «мангуста-документа» вы получаете реальный «сырой» ответ от водителя. Фактическое содержимое документа находится в свойстве "value", но это интересующий нас "lastErrorObject".

Здесь мы видим свойство updatedExisting: false. Это указывает на то, что «совпадение не найдено», поэтому новый документ «создан». Таким образом, вы можете использовать это, чтобы определить, что творение действительно произошло.

Когда вы снова введете те же параметры запроса, результат будет другим:

{
  "lastErrorObject": {
    "n": 1,
    "updatedExisting": true             // <--- Now I'm true
  },
  "value": {
    "_id": "5adfc8696878cfc4992e7634",
    "username": "Bill",
    "__v": 0,
    "password": "password"
  },
  "ok": 1,
  "operationTime": "6548172736517111811",
  "$clusterTime": {
    "clusterTime": "6548172736517111811",
    "signature": {
      "hash": "AAAAAAAAAAAAAAAAAAAAAAAAAAA=",
      "keyId": 0
    }
  }
}

Значение updatedExisting теперь равно true, и это потому, что уже был документ, который соответствует username: 'Bill' в операторе запроса. Это говорит о том, что документ уже был там, так что вы можете затем развернуть свою логику, чтобы вернуть «Ошибка» или любой другой ответ, который вы хотите.

В другом случае может быть желательно не возвращать «сырой» ответ и использовать вместо него возвращенный «документ Мангуста». В этом случае мы изменяем значение на new: false без опции rawResult.

User.findOneAndUpdate(
  { username: 'Ted' },
  { $setOnInsert: { password: 'password' } },
  { upsert: true, new: false }
)

В большинстве случаев применимо то же самое, за исключением того, что теперь действие представляет собой оригинальное состояние документа, возвращаемое в противоположность «измененному» состоянию документа «после» действия. Поэтому, когда нет документа, который действительно соответствует выражению «запрос», возвращаемый результат будет null:

Mongoose: users.findAndModify({ username: 'Ted' }, [], { '$setOnInsert': { password: 'password', __v: 0 } }, { upsert: true, new: false, remove: false, fields: {} })
null           // <-- Got null in response :(

Это говорит о том, что документ был "создан", и можно утверждать, что вы уже знаете, каким должно быть содержимое документа, поскольку вы отправили эти данные с заявлением (в идеале в $setOnInsert). В сущности, вы уже знаете, что для возврата «если» вам необходимо вернуть содержимое документа.

Напротив, «найденный» документ возвращает «исходное состояние», показывающее документ «до того», как он был изменен:

{
  "_id": "5adfc8696878cfc4992e7639",
  "username": "Ted",
  "__v": 0,
  "password": "password"
}

Поэтому любой ответ, который "не null", следовательно, является указанием на то, что документ уже присутствовал, и снова вы можете использовать свою логику в зависимости от того, что было фактически получено в ответе.

Так что это два основных подхода к тому, что вы просите, и они, безусловно, «работают»! И так же, как это продемонстрировано и воспроизводимо с теми же утверждениями здесь.


Приложение - Резервный Дубликат Ключа для плохих паролей

Существует еще один действительный подход, на который намекают и в полном листинге, который заключается в простом .insert() (или .create() в моделях mongoose) новых данных и выдаче ошибки «дублирующий ключ», когда «уникальное» свойство по индексу фактически встречается. Это правильный подход, но в «валидации пользователя» есть один конкретный вариант использования, который представляет собой удобную часть логической обработки, а именно «проверка паролей».

Так что это довольно распространенный способ получения информации о пользователях с помощью комбинации username и password.В случае «upsert» эта комбинация оправдывается как «уникальная», и поэтому предпринимается попытка «вставить», если совпадение не найдено.Это именно то, что делает сопоставление пароля полезной реализацией.

Рассмотрим следующее:

    // Demonstrating "why" we reserve the "Duplicate" error
    let fred1 = await User.findOneAndUpdate(
      { username: 'Fred', password: 'password' },
      { $setOnInsert: { } },
      { upsert: true, new: false }
    );
    log(fred1);       // null - so okay

    let fred2 = await User.findOneAndUpdate(
      { username: 'Fred', password: 'badpassword' }, // <-- dup key for wrong password
      { $setOnInsert: { } },
      { upsert: true, new: false }
    );

С первой попытки у нас фактически нет username для "Fred"так что произойдет «upsert», и все другие вещи, как уже было описано выше, будут определять, было ли это создание или найденный документ.

В приведенном ниже утверждении используется то же значение username, но он предоставляетпароль отличается от того, что записано.Здесь MongoDB пытается «создать» новый документ, поскольку он не совпадает по комбинации, а поскольку ожидается, что username будет "unique", вы получите «Ошибка повторяющегося ключа»:

{ MongoError: E11000 duplicate key error collection: thereornot.users index: username_1 dup key: { : "Fred" }

Итак, вы должны понимать, что теперь у вас есть три условия для оценки "бесплатно".Быть:

  • "upsert" был записан либо результатом updatedExisting: false, либо null в зависимости от метода.
  • Вы знаете, что документ (по комбинации) "существует"либо через updatedExisting: true, либо там, где возвращаемый документ был "not null".
  • Если предоставленный password не соответствует тому, что уже существовало для username, то вы получите«ошибка дублированного ключа», которую вы можете перехватить и отреагировать соответствующим образом, сообщая пользователю в ответ, что «неверный пароль».

Все это из одного запроса.

Это основная причина использования «upserts», а не просто броска вставок в коллекцию, поскольку вы можете получить различное ветвление логики без дополнительных запросов к базе данных, чтобы определить, «какое» из этих условий должно бытьфактический ответ.

0 голосов
/ 25 апреля 2018

Похоже, что вы действительно хотите, это ограничение уникального ключа , а не упадок.

Уникальный ключ можно установить в [mongoose] с помощью одного из параметров поля схемы:

const s = new Schema({ name: { type: String, unique: true }});

или index метод:

Schema.path('name').index({ unique: true });

Если предпринята попытка создать документ, в котором уже есть запись для этого ключа, будет выдано сообщение об ошибке:

ПРИМЕЧАНИЕ: нарушение ограничения возвращает ошибку E11000 из MongoDB при сохранении, а не ошибку проверки Mongoose.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...