javascript - collection - mongoose timestamp
¿Cómo actualizo/subido un documento en Mongoose? (23)
Acabo de quemar un sólido 3 horas tratando de resolver el mismo problema. Específicamente, quería "reemplazar" todo el documento si existe, o insertarlo de otra manera. Aquí está la solución:
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{...});
Creé un problema en la página del proyecto Mongoose solicitando que se agregue información sobre esto a los documentos.
Tal vez sea el momento, tal vez me esté ahogando en documentación escasa y no poder envolver mi cabeza en torno al concepto de actualización en Mongoose :)
Aquí está el trato:
Tengo un esquema y modelo de contacto (propiedades abreviadas):
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);
Recibo una solicitud del cliente, que contiene los campos que necesito y uso mi modelo de esta manera:
mongoose.connect(connectionString);
var contact = new Contact({
phone: request.phone,
status: request.status
});
Y ahora llegamos al problema:
- Si llamo a
contact.save(function(err){...})
recibiré un error si ya existe el contacto con el mismo número de teléfono (como se esperaba - único) - No puedo llamar a
update()
al contacto, ya que ese método no existe en un documento - Si llamo actualizar en el modelo:
Contact.update({phone:request.phone}, contact, {upsert: true}, function(err{...})
Me meto en un bucle infinito de algún tipo, ya que la implementación de la actualización de Mongoose claramente no quiere un objeto como segundo parámetro. - Si hago lo mismo, pero en el segundo parámetro paso una matriz asociativa de las propiedades de la solicitud
{status: request.status, phone: request.phone ...}
funciona, pero no tengo ninguna referencia al contacto específico y no puede descubrir sus propiedadescreatedAt
yupdatedAt
.
Así que la conclusión, después de todo lo que intenté: dado un contact
documento, ¿cómo lo actualizo si existe, o lo agrego si no es así?
Gracias por tu tiempo.
Acabo de volver a este problema después de un tiempo y decidí publicar un complemento basado en la respuesta de Aaron Mast.
https://www.npmjs.com/package/mongoose-recursive-upsert
Úsalo como un plugin de mangosta. Establece un método estático que fusionará recursivamente el objeto pasado.
Model.upsert({unique: ''value''}, updateObject});
Bueno, esperé el tiempo suficiente y no hubo respuesta. Finalmente, abandoné todo el enfoque de actualización / inserción y se fue con:
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? Sí. ¿Estoy feliz con esto? Probablemente no. 2 llamadas DB en lugar de una.
Esperemos que una futura implementación de Mongoose Model.upsert
una función Model.upsert
.
Después de leer las publicaciones anteriores, decidí usar este código:
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);
}
});
Si r es nulo, creamos un nuevo elemento. De lo contrario, utilice upsert en la actualización porque la actualización no crea un nuevo elemento.
Esta es la forma más sencilla de crear / actualizar a la vez que llama al middleware y los validadores.
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);
});
})
Estabas cerca de
Contact.update({phone:request.phone}, contact, {upsert: true}, function(err){...})
pero su segundo parámetro debe ser un objeto con un operador de modificación, por ejemplo
Contact.update({phone:request.phone}, {$set: { phone: request.phone }}, {upsert: true}, function(err){...})
Este coffeescript funciona para mí con Node: el truco es que _id get se despoja de su envoltorio de ObjectID cuando se envía y devuelve desde el cliente, por lo que este debe ser reemplazado por actualizaciones (cuando no se proporciona _id, el guardado volverá a insertarse y agregarse uno).
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
Hay un error introducido en 2.6, y afecta a 2.7 también
El upsert solía funcionar correctamente en 2.4.
https://groups.google.com/forum/#!topic/mongodb-user/UcKvx4p4hnY https://jira.mongodb.org/browse/SERVER-13843
Echa un vistazo, contiene información importante.
ACTUALIZADO:
No significa que el cambio no funciona. Aquí hay un buen ejemplo de cómo usarlo:
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);
});
He creado una cuenta de para responder esta pregunta. Después de buscar infructuosamente las redes, yo mismo escribí algo. Así es como lo hice para que pueda aplicarse a cualquier modelo de mangosta. Importe esta función o agréguela directamente a su código donde está realizando la actualización.
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;
}
Luego, para subir un documento de mangosta, haga lo siguiente:
YourModel.upsert = function (id, newData, callBack) {
this.findById(id, function (err, oldData) {
if(err) {
callBack(err);
} else {
upsertObject(newData, oldData).save(callBack);
}
});
};
Esta solución puede requerir 2 llamadas a la base de datos sin embargo usted obtiene el beneficio de,
- Validación del esquema contra su modelo porque está utilizando .save ()
- Puede subir objetos profundamente anidados sin enumeración manual en su llamada de actualización, por lo que si su modelo cambia no tiene que preocuparse por actualizar su código
Solo recuerde que el objeto de destino siempre anulará el origen incluso si el origen tiene un valor existente
Además, para las matrices, si el objeto existente tiene una matriz más larga que la que lo reemplaza, los valores al final de la matriz anterior permanecerán. Una manera fácil de realzar la matriz completa es configurar la antigua matriz como una matriz vacía antes de la superiorización si eso es lo que pretende hacer.
ACTUALIZACIÓN - 01/16/2016 Agregué una condición adicional porque si hay una matriz de valores primitivos, Mongoose no se da cuenta de que la matriz se actualiza sin usar la función "establecer".
Mongoose ahora admite esto de forma nativa con findOneAndUpdate (llama a MongoDB findAndModify ).
La opción upsert = true crea el objeto si no existe. por defecto es falso .
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");
});
Edición: Mongoose no admite estos ganchos con este método:
- por defecto
- setters
- validadores
- middleware
Necesitaba actualizar / subir un documento en una colección, lo que hice fue crear un nuevo objeto literal como este:
notificationObject = {
user_id: user.user_id,
feed: {
feed_id: feed.feed_id,
channel_id: feed.channel_id,
feed_title: ''''
}
};
compuesto de datos que obtengo de algún otro lugar en mi base de datos y luego llamo a update en el Modelo
Notification.update(notificationObject, notificationObject, {upsert: true}, function(err, num, n){
if(err){
throw err;
}
console.log(num, n);
});
esta es la salida que obtengo después de ejecutar el script por primera vez:
1 { updatedExisting: false,
upserted: 5289267a861b659b6a00c638,
n: 1,
connectionId: 11,
err: null,
ok: 1 }
Y esta es la salida cuando ejecuto el script por segunda vez:
1 { updatedExisting: true, n: 1, connectionId: 18, err: null, ok: 1 }
Estoy usando la versión 3.6.16 de la mangosta
Ninguna otra solución funcionó para mí. Estoy utilizando una solicitud posterior y actualizando los datos, si los encuentra, insértelos, también se envía _id con el cuerpo de la solicitud que debe eliminarse.
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'');
});
Para cualquier persona que llegue aquí que todavía busque una buena solución para "sobresalir" con soporte de ganchos, esto es lo que he probado y funcionando. Todavía requiere 2 llamadas de DB, pero es mucho más estable que cualquier otra cosa que haya intentado en una sola llamada.
// 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);
});
});
}
Si hay generadores disponibles, se vuelve aún más 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();
Siguiendo la respuesta de Traveling Tech Guy , que ya es increíble, podemos crear un complemento y adjuntarlo a la mangosta una vez que lo .upsert()
para que .upsert()
esté disponible en todos los 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
Luego puede hacer algo como User.upsert({ _id: 1 }, { foo: ''bar'' })
o YouModel.upsert({ bar: ''foo'' }, { value: 1 })
cuando lo desee.
Simplemente puede actualizar el registro con esto y obtener los datos actualizados en respuesta
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"
})
}
});
});
Solución muy elegante que puedes lograr usando la cadena de Promesas:
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);
});
});
esto funcionó para mí.
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"});
});
});
para construir sobre lo que Martin Kuzdowicz publicó anteriormente. Uso lo siguiente para hacer una actualización usando mongoose y una combinación profunda de objetos json. Junto con la función model.save () en mongoose, esto permite que mongoose realice una validación completa, incluso una que se basa en otros valores en el json. sí requiere el paquete deepmerge https://www.npmjs.com/package/deepmerge . Pero ese es un paquete muy ligero.
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);
});
});
//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);
});
});
--
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);
}
});
}
});
User.findByIdAndUpdate(req.param(''userId''), req.body, (err, user) => {
if(err) return res.json(err);
res.json({ success: true });
});
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!'' });
});
});
});
Este es un mejor enfoque para resolver el método de actualización en mongoose, puede consultar Scotch.io para obtener más detalles. Esto definitivamente funcionó para mí!