Como atualizo/upsert um documento em Mangusto?
tenho um esquema e modelo de contacto (propriedades encurtadas):
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var mongooseTypes = require("mongoose-types"),
useTimestamps = mongooseTypes.useTimestamps;
var ContactSchema = new Schema({
phone: {
type: String,
index: {
unique: true,
dropDups: true
}
},
status: {
type: String,
lowercase: true,
trim: true,
default: 'on'
}
});
ContactSchema.plugin(useTimestamps);
mongoose.model('Contact', ContactSchema); //is this line superflous??
var Contact = mongoose.model('Contact', ContactSchema);
recebo um pedido do cliente, contendo os campos de que preciso e usando assim o meu modelo:
mongoose.connect(connectionString);
var contact = new Contact({
phone: request.phone,
status: request.status
});
E agora chegamos ao problema.
- se eu ligar
contact.save(function(err){...})
vou receber um erro se o o contacto com o mesmo número de telefone já existe (como esperado - único) - não posso ligar
update()
em contacto, uma vez que esse método não existe num documento - Se eu ligar para o update sobre o modelo:
Contact.update({phone:request.phone}, contact, {upsert: true}, function(err{...})
Eu entro em um loop infinito de alguns tipos, uma vez que a implementação Mongoose update claramente não quer um objeto como o segundo parâmetro. - Se eu fizer o mesmo, mas no segundo parâmetro eu passo uma matriz associativa das propriedades do pedido {[[5]} ele funciona-mas então não tenho nenhuma referência ao contato específico e não consigo descobrir suas propriedades
createdAt
eupdatedAt
.
então a conclusão, depois de tudo o que eu tentei: dado um documento contact
, Como eu atualizá-lo se ele existe, ou adicioná-lo se ele não existe?
23 answers
A Mangusto agora suporta isto nativamente com findOneAndUpdate (chamadas MongoDB findAndModify ).
A opção upsert = true cria o objecto se este não existir. o valor por omissão é false .
var query = {'username':req.user.username};
req.newData.username = req.user.username;
MyModel.findOneAndUpdate(query, req.newData, {upsert:true}, function(err, doc){
if (err) return res.send(500, { error: err });
return res.send("succesfully saved");
});
Editar: a Mangusto não suporta estes ganchos com este método:
- defaults
- setters
- validadores Middleware
var contact = new Contact({
phone: request.phone,
status: request.status
});
// Convert the Model instance to a simple object using Model's 'toObject' function
// to prevent weirdness like infinite looping...
var upsertData = contact.toObject();
// Delete the _id property, otherwise Mongo will return a "Mod on _id not allowed" error
delete upsertData._id;
// Do the upsert, which works like this: If no Contact document exists with
// _id = contact.id, then create a new doc using upsertData.
// Otherwise, update the existing doc with upsertData
Contact.update({_id: contact.id}, upsertData, {upsert: true}, function(err{...});
Eu criei um problema na página do projecto Mangusto pedindo que essa informação sobre isto fosse adicionada aos documentos.
Eras próximo de
Contact.update({phone:request.phone}, contact, {upsert: true}, function(err){...})
Mas o seu segundo parâmetro deve ser um objecto com um operador de modificação, por exemplo
Contact.update({phone:request.phone}, {$set: { phone: request.phone }}, {upsert: true}, function(err){...})
ContactSchema.findOne({phone: request.phone}, function(err, contact) {
if(!err) {
if(!contact) {
contact = new ContactSchema();
contact.phone = request.phone;
}
contact.status = request.status;
contact.save(function(err) {
if(!err) {
console.log("contact " + contact.phone + " created at " + contact.createdAt + " updated at " + contact.updatedAt);
}
else {
console.log("Error: could not save contact " + contact.phone);
}
});
}
});
Funciona? Sim. Estou feliz com isto? Provavelmente não. 2 DB chama em vez de um.Esperemos que uma futura implementação do mangusto venha a surgir com uma função
Model.upsert
.
Uma solução muito elegante que pode conseguir usando uma cadeia de promessas:
app.put('url', (req, res) => {
const modelId = req.body.model_id;
const newName = req.body.name;
MyModel.findById(modelId).then((model) => {
return Object.assign(model, {name: newName});
}).then((model) => {
return model.save();
}).then((updatedModel) => {
res.json({
msg: 'model updated',
updatedModel
});
}).catch((err) => {
res.send(err);
});
});
function upsertObject (src, dest) {
function recursiveFunc (src, dest) {
_.forOwn(src, function (value, key) {
if(_.isObject(value) && _.keys(value).length !== 0) {
dest[key] = dest[key] || {};
recursiveFunc(src[key], dest[key])
} else if (_.isArray(src) && !_.isObject(src[key])) {
dest.set(key, value);
} else {
dest[key] = value;
}
});
}
recursiveFunc(src, dest);
return dest;
}
Então para upsert um documento mangusto faça o seguinte,
YourModel.upsert = function (id, newData, callBack) {
this.findById(id, function (err, oldData) {
if(err) {
callBack(err);
} else {
upsertObject(newData, oldData).save(callBack);
}
});
};
Esta solução pode necessitar de 2 DB chamadas no entanto, você tem o benefício de,
- esquema validação contra o seu modelo porque você está usando .save ()
- pode upsert objectos profundamente aninhados sem enumeração manual na sua chamada de actualização, por isso, se o seu modelo mudar, não terá de se preocupar com a actualização do seu código
lembre-se apenas que o objeto de destino irá sempre sobrepor a fonte, mesmo que a fonte tenha um valor existente
Além disso, para arrays, se o objeto existente tem um array mais longo do que o que o substitui, então os valores em o fim da antiga Matriz permanecerá. Uma maneira fácil de uptert o array inteiro é definir o array antigo para ser um array vazio antes do uptert, se é isso que você pretende fazer.
UPDATE-01/16/2016 Eu adicionei uma condição extra para que se houver um array de valores primitivos, Mangusto não percebe que o array se torna atualizado sem usar a função "set".
Eu precisava atualizar / upsert um documento em uma única coleção, o que eu fiz foi criar um novo objeto literal como este:
notificationObject = {
user_id: user.user_id,
feed: {
feed_id: feed.feed_id,
channel_id: feed.channel_id,
feed_title: ''
}
};
Composed from data that I get from somewhere another in my database and then call update on the Model
Notification.update(notificationObject, notificationObject, {upsert: true}, function(err, num, n){
if(err){
throw err;
}
console.log(num, n);
});
Este é o uput que eu recebo depois de executar o script pela primeira vez:
1 { updatedExisting: false,
upserted: 5289267a861b659b6a00c638,
n: 1,
connectionId: 11,
err: null,
ok: 1 }
E esta é a saída quando eu executar o script pela segunda vez:
1 { updatedExisting: true, n: 1, connectionId: 18, err: null, ok: 1 }
Estou a usar a versão mangusto 3.6.16
app.put('url', function(req, res) {
// use our bear model to find the bear we want
Bear.findById(req.params.bear_id, function(err, bear) {
if (err)
res.send(err);
bear.name = req.body.name; // update the bears info
// save the bear
bear.save(function(err) {
if (err)
res.send(err);
res.json({ message: 'Bear updated!' });
});
});
});
Aqui está uma melhor abordagem para resolver o método de atualização em mangusto, você pode verificar Scotch.io para mais detalhes. Isto funcionou comigo!!!
Há um bug introduzido em 2.6, e afeta a 2.7 também
O upsert funcionava correctamente em 2.4
Https://groups.google.com/forum/#! topic/mongodb-user / UcKvx4p4hnY https://jira.mongodb.org/browse/SERVER-13843
Dê uma olhada, ele contém algumas informações importantes
Actualizado:
Não significa que a upert não funcione. Aqui está um bom exemplo de como usá-lo:User.findByIdAndUpdate(userId, {online: true, $setOnInsert: {username: username, friends: []}}, {upsert: true})
.populate('friends')
.exec(function (err, user) {
if (err) throw err;
console.log(user);
// Emit load event
socket.emit('load', user);
});
app.put('/student/:id', (req, res) => {
Student.findByIdAndUpdate(req.params.id, req.body, (err, user) => {
if (err) {
return res
.status(500)
.send({error: "unsuccessful"})
};
res.send({success: "success"});
});
});
ContactSchema.connection.findOne({phone: request.phone}, function(err, contact) {
if(!err) {
if(!contact) {
contact = new ContactSchema();
contact.phone = request.phone;
}
contact.status = request.status;
contact.save(function(err) {
if(!err) {
console.log("contact " + contact.phone + " created at " + contact.createdAt + " updated at " + contact.updatedAt);
}
else {
console.log("Error: could not save contact " + contact.phone);
}
});
}
});
// Create or update a Person by unique email.
// @param person - a new or existing Person
function savePerson(person, done) {
var fieldsToUpdate = ['name', 'phone', 'address'];
Person.findOne({
email: person.email
}, function(err, toUpdate) {
if (err) {
done(err);
}
if (toUpdate) {
// Mongoose object have extra properties, we can either omit those props
// or specify which ones we want to update. I chose to update the ones I know exist
// to avoid breaking things if Mongoose objects change in the future.
_.merge(toUpdate, _.pick(person, fieldsToUpdate));
} else {
toUpdate = person;
}
toUpdate.save(function(err, updated, numberAffected) {
if (err) {
done(err);
}
done(null, updated, numberAffected);
});
});
}
//Here is my code to it... work like ninj
router.param('contractor', function(req, res, next, id) {
var query = Contractors.findById(id);
query.exec(function (err, contractor){
if (err) { return next(err); }
if (!contractor) { return next(new Error("can't find contractor")); }
req.contractor = contractor;
return next();
});
});
router.get('/contractors/:contractor/save', function(req, res, next) {
contractor = req.contractor ;
contractor.update({'_id':contractor._id},{upsert: true},function(err,contractor){
if(err){
res.json(err);
return next();
}
return res.json(contractor);
});
});
--
Se os geradores estiverem disponíveis, torna-se ainda mais fácil:
var query = {'username':this.req.user.username};
this.req.newData.username = this.req.user.username;
this.body = yield MyModel.findOneAndUpdate(query, this.req.newData).exec();
Pode simplesmente udpate o registo com isto e obter os dados actualizados em resposta
router.patch('/:id', (req,res,next)=>{
const id = req.params.id;
Product.findByIdAndUpdate(id, req.body, {new: true},
function(err,model) {
if(!err){
res.status(201).json({
data : model
});
}
else{
res.status(500).json({
message: "not found any relative data"
})
}
});
});
User.findByIdAndUpdate(req.param('userId'), req.body, (err, user) => {
if(err) return res.json(err);
res.json({ success: true });
});
router.post('/user/createOrUpdate', function(req,res){
var request_data = req.body;
var userModel = new User(request_data);
var upsertData = userModel.toObject();
delete upsertData._id;
var currentUserId;
if (request_data._id || request_data._id !== '') {
currentUserId = new mongoose.mongo.ObjectId(request_data._id);
} else {
currentUserId = new mongoose.mongo.ObjectId();
}
User.update({_id: currentUserId}, upsertData, {upsert: true},
function (err) {
if (err) throw err;
}
);
res.redirect('/home');
});
Https://www.npmjs.com/package/mongoose-recursive-upsert
Usa-o como um 'plugin' de mangusto. Ele configura um método estático que irá fundir recursivamente o objeto passado.
Model.upsert({unique: 'value'}, updateObject});
Contact.findOne({ phone: request.phone }, (err, doc) => {
const contact = (doc) ? doc.set(request) : new Contact(request);
contact.save((saveErr, savedContact) => {
if (saveErr) throw saveErr;
console.log(savedContact);
});
})
Este coffeescript funciona para mim com o Node - o truque é que o _id get foi despojado da sua capa ObjectID quando enviado e devolvido do cliente e, por isso, isto precisa ser substituído para actualizações (quando nenhum _id é fornecido, save irá reverter para inserir e adicionar um).
app.post '/new', (req, res) ->
# post data becomes .query
data = req.query
coll = db.collection 'restos'
data._id = ObjectID(data._id) if data._id
coll.save data, {safe:true}, (err, result) ->
console.log("error: "+err) if err
return res.send 500, err if err
console.log(result)
return res.send 200, JSON.stringify result
var merge = require('deepmerge');
app.put('url', (req, res) => {
const modelId = req.body.model_id;
MyModel.findById(modelId).then((model) => {
return Object.assign(model, merge(model.toObject(), req.body));
}).then((model) => {
return model.save();
}).then((updatedModel) => {
res.json({
msg: 'model updated',
updatedModel
});
}).catch((err) => {
res.send(err);
});
});
Seguindo a resposta de Traveling Tech Guy , que já é incrível, podemos criar um plugin e anexá-lo ao mangusto uma vez que inicializá-lo para que .upsert()
estará disponível em todos os modelos.
Plugins.js
export default (schema, options) => {
schema.statics.upsert = async function(query, data) {
let record = await this.findOne(query)
if (!record) {
record = new this(data)
} else {
Object.keys(data).forEach(k => {
record[k] = data[k]
})
}
return await record.save()
}
}
Db.js
import mongoose from 'mongoose'
import Plugins from './plugins'
mongoose.connect({ ... })
mongoose.plugin(Plugins)
export default mongoose
Então podes fazer algo como User.upsert({ _id: 1 }, { foo: 'bar' })
ou YouModel.upsert({ bar: 'foo' }, { value: 1 })
quando quiseres.
itemModel.findOne({'pid':obj.pid},function(e,r){
if(r!=null)
{
itemModel.update({'pid':obj.pid},obj,{upsert:true},cb);
}
else
{
var item=new itemModel(obj);
item.save(cb);
}
});
Se o r é nulo, criamos um novo item. Caso contrário, use o upsert em update porque o update não cria um novo item.