Mongoose updateOne vs save methods - javascript

There are few ways to update the document record in MongoDB database.
With the User model defined I can find the user first, then modify its attribute and save it with the save() method:
let user = await User.findOne({"email": "user#email.com"});
user["email"] = "another#email.com";
await user.save();
Another option would be to use updateOne method instead of save:
await user.updateOne({"email": "another#email.com"});
Why would I choose to use the updateOne method over the save? What are the pros and cons?

I believe in your first example it requires 2 separate queries to your database, so less performant. One to find the document, and then another to save (update it). Though the benefit of doing it this way is if you have validation setup in your schema, then it would check the validation before updating.
The second one you only do one query and it's done. This however won't check your validations. Though you can opt-in by passing runValidators:true:
await user.updateOne({"email": "another#email.com"}, {"email": "another#email.com"}, { runValidators: true });

Related

Sequelize .update() not using the correct where and updates all rows

I'm trying to create an approval command.
Everything about it, should be working, but it never works correctly.
I've changed my Database to have "id" first, but it still prefers to update on "user" instead.
I've tried having my code just be; but that never worked out. Even though I use it everywhere else.
await ticketApproval.update({status: 1});
I've checked documentation and other StackOverflow questions and answers.
Nothing has worked yet, but from what I can tell, nothing in my code should be telling it to update on the "user" id. Since I only ever tell it to search on the ticket "id" and "server" id
It feels like it's just outright ignoring the Where.
Code
Console
Test Sample
You clearly confuse the update method of a record instance with the static update of a model.
Only static update has the where option because it updates several records in DB using passed conditions in contrast to the instance update method that uses a primary key value of a certain record to update it with passed values.
Compare:
// here we have a model instance for a concrete record
const ticketApproval = await Ticket.findOne({
where: {
id: ticketToApprove
}
})
// by calling the instance `update` we always update this concrete record only
// using a primary key value condition, see a primary key field defined in a model
await ticketApproval.update({ status: 1 });
with
// by calling the static `update` we update all records that satisfy the indicated conditions
await Ticket.update({ status: 1 }, {
where: {
id: ticketToApprove
}
});

Firestore transaction sequential creation of collections and updating of documents

Enviroment: nodejs, firebase-admin, firestore.
Database scructure (space):
Database scructure (user):
Creating new space (example):
// init data
const userId = "someUserId";
// Create new space
const spaceRef = await db.collection("spaces").add({ name: "SomeName" });
// Get spaceId
spaceId = spaceRef.id;
// Get user Doc for upate their spaces
const userRef = await db.collection("users").doc(userId);
// Add "spaceId" to user spaces list
userRef.collection("spaces").doc(spaceId).set({ some: "data" });
// Create collection "members" in new space with "userId"
spaceRef.collection("members").doc(userId).set({role: "OWNER"})
Question: I want to execute this code inside single runTransaction, but as I see transactions support only one-time read and multiple update, this does not suit me, since I get the spaceId I need during the execution of the code.
Why I want to use transaction: In my data structure, the relationship between the created space and the presence of the ID of this space on the user is required. If we assume that an error occurred during the execution of this code, for example, the space was created, but this space was not added inside the user profile, then this will be a fatal problem in my database structure.
Similar to other databases, transactions solve this problem, but I can't figure out how to do it with firestore.
Maybe you know a better way to protect yourself from consistent data in this case?
Actually, you don't need a Transaction for that, since you are not reading documents.
With db.collection("users").doc(userId); you are actually not reading a document, just calling "locally" the doc() method to create a DocumentReference. This method is not asynchronous, therefore you don't need to use await. To read the doc, you would use the asynchronous get() method.
So, using a batched write, which atomically commits all pending write operations to the database, will do the trick:
const userId = 'someUserId';
const userRef = db.collection('users').doc(userId);
const spaceRef = firestore.collection('spaces').doc();
const spaceId = spaceRef.id;
const writeBatch = firestore.batch();
writeBatch.set(spaceRef, { name: "SomeName" });
writeBatch.set(userRef.collection("spaces").doc(spaceId), { some: "data" });
writeBatch.set(spaceRef.collection("members").doc(userId), {role: "OWNER"});
await writeBatch.commit();
You should include this code in a try/catch block and if the batch commit fails you will be able to handle this situation in the catch block, knowing that none of the write were committed.

Sequelize, reading the id of inserted row right after inserting it without commiting the transaction

The perfect solution for me would be to insert a parent Model with some child associations and create on cascade the children associations. However, as I am starting with Sequelize and I did manage to get that working, I need to insert the data and set up the associations manually so far to get it working.
Basically, after inserting the parent row, I need to be able to get the autoincremented id (sequence managed by mysql, not sequelize) in order to assign that id as a foreign key to the child entity.
Ideally I would like to execute everything atomically inside a transaction so if something goes wrong, we rollback everything.
That being said, unless I commit the transaction manually after the insertion, I am unable to get the id of the parent entity. Is there a way to read the value prior the transaction has been commited, some dirty read?
this is the code so far:
const ipiInterestedParty = {
ip_base_nr: '118',
};
const transaction = await session.transaction();
const instance = await IpiInterestedParty.create(ipiInterestedParty, {transaction});
await transaction.commit();
const createdWithId = await IpiInterestedParty.findOne({
where: {ip_base_nr: '118'}
});
This is the only way I was able to get the parent id, however, as the transaction has been already commited, in case that after that there is any error after that, the transaction has been commited and cannot be undone, leaving the system inconsistent.
Any help please?
Usually you get instance with a primary key value after executing create method of a model, if you indicated in a model's PK autoIncrement: true.
So that way you already have an id of a parent record to create child records. Don't forget to pass a transaction object to all methods that make CRUD inside a transaction.

Fetch associations on create or save with Sequelize

Fairly simple problem, just cant find the good/clean way to do this without making a call to another find
I've got my node app rigged up with Angular-Resource, and I'm just making some round-trip like data calls on new or changed data.
So ngResource making the $save() call to my /api/users/:id and such. And Node reacts to this call by creating or finding the user, making the updates, and saving them.
Whether through create() or save(), it returns the created record, and for right now, I use res.json(user) to spill the created/returned record for my Angular to handle populating my view with the updated information
Now, I know with Sequelizes find() and findAll() methods, I can use findAll({ include: [{ all: true }]}) or specify my models individually.
What I want to know is, what is the best way to get my records associations on save/create
and unfortunately, this just doesn't work:
models.User.create(newuser, {include:[{ all: true }]}).then(function(user) {
res.json(user);
});
Do I really have to perform another find() just to get my managed models associations?
To better illustrate the opted solution from RedactedProfile's comment, here's the code.
models.User
.create(newuser, {include:[{ all: true }]})
.then(user => {
user.reload().then(user => { res.json(user); })
});

Cast plain object to mongoose document

UPDATE 1: 5 votes have been received, so I have submitted a feature request: https://github.com/LearnBoost/mongoose/issues/2637
Please cast your +1 votes there to let the core team know you want this feature.
UPDATE 2: See answer below...
ORIGINAL POST:
Lets say I do a "lean" query on a collection OR receive some data from a REST service and I get an array of objects (not mongoose documents).
These objects already exist in the database, but I need to convert some/all of those objects to mongoose documents for individual editing/saving.
I have read through the source and there is a lot going on once mongoose has data from the database (populating, casting, initializing, etc), but there doesn't seem to be a method for 'exposing' this to the outside world.
I am using the following, but it just seems hacky ($data is a plain object):
// What other properties am I not setting? Is this enough?
var doc = new MyModel( $data );
doc.isNew = false;
// mimicking mongoose internals
// "init" is called internally after a document is loaded from the database
// This method is not documented, but seems like the most "proper" way to do this.
var doc = new MyModel( undefined );
doc.init( $data );
UPDATE: After more searching I don't think there is a way to do this yet, and the first method above is your best bet (mongoose v3.8.8). If anybody else is interested in this, I will make a feature request for something like this (leave a comment or upvote please):
var doc = MyModel.hydrate( $data );
Posting my own answer so this doesn't stay open:
Version 4 models (stable released on 2015-03-25) now exposes a hydrate() method. None of the fields will be marked as dirty initially, meaning a call to save() will do nothing until a field is mutated.
https://github.com/LearnBoost/mongoose/blob/41ea6010c4a84716aec7a5798c7c35ef21aa294f/lib/model.js#L1639-1657
It is very important to note that this is intended to be used to convert a plain JS object loaded from the database into a mongoose document. If you are receiving a document from a REST service or something like that, you should use findById() and update().
For those who live dangerously:
If you really want to update an existing document without touching the database, I suppose you could call hydrate(), mark fields as dirty, and then call save(). This is not too different than the method of setting doc.isNew = false; as I suggested in my original question. However, Valeri (from the mongoose team) suggested not doing this. It could cause validation errors and other edge case issues and generally isn't good practice. findById is really fast and will not be your bottleneck.
If you are getting a response from REST service and say you have a User mongoose model
var User = mongoose.model('User');
var fields = res.body; //Response JSON
var newUser = new User(fields);
newUser.save(function(err,resource){
console.log(resource);
});
In other case say you have an array of user JSON objects from User.find() that you want to query or populate
var query = User.find({});
query.exec(function(users){
//mongoose deep-populate ref docs
User.deeppopulate users 'email_id phone_number'.exec({
//query through populated users objects
});
});
MongoDB doesn't support Joins and Transfers. So for now you can't cast values to an object directly. Although you can work around it with forEach.

Categories