I'm writing an app in meteor and trying to get familiar in mongodb, I am trying to update a user with the following schema.
user = {
... SOME DATA....,
"profile": {
"firstName": "HELLO",
"lastName": "MIKE",
"phoneNumber": "0432456524",
"userRole": "General Practitioner",
"practice": {
"name": "Hello koramaiku",
"address": "222 Hello St Helloville",
"state": "NSW",
"postcode": "2000"
},
"AHPRANumber": "4586546545",
"providerNumber": "4565498751321"
}, ..... SOME MORE DATA
}
I have a settings form, which will modify the some details in the profile object of the user.
I have a form which allows you to edit the firstName, lastName and phoneNumber in the profile object, without replacing some of the existing values.
var userData = {
firstName: 'Hello',
lastName: 'Kora',
phoneNumber: '0422222222'
};
Meteor.users.update({'_id': Meteor.userId() }, {$set : userData}, function(error){
........
});
However, if I perform the update, it overwrites the entire profile object with the new values instead of replacing just the values that I need. From I what I understand the $set modifier would replace the data at a certain field if they already exist and adds to set if they don't exist.
Is there a way to update a document's data as the above, without overwriting data that I don't want replaced?
Thanks.
Use the .
userData = {
"profile.firstName": 'Hello',
"profile.lastName": 'Kora',
"profile.phoneNumber": '0422222222'
};
These will ensure only those particular keys are altered.
Related
I have an array of data that I want to insert in my database:
[
{
"id":"816307",
"email": "john.doe#test.com",
"firstName": "John",
"lastName": "Doe"
},
{
"id":"391650",
"email": "mary#williams",
"firstName": "Mary",
"lastName": "Williams"
},
{
"id":"183751",
"email": "paul.smith#test.com",
"firstName": "Paul",
"lastName": "Smith"
},
]
I know that I can use the .create method from mongoose to insert all the data.
However, in the data, I need to be able to have a combination of new values and existing values. For example, if a user with id 816307 (John Doe) already exists but have a different email it should update the value and at the same time if a user with id 391650 (Mary Williams) does not exist it should create it.
The id field cannot be updated.
It seems like the best method to use is currently updateMany and to use the upsert option to insert new elements at the same time .updateMany().
But, how to do to reference the id? I cannot use $eq and hardcode anything here, I would like that the document automatically finds the proper item by its id and update it if it exists, otherwise, create it.
Since each object has it's own filter & update, then you might not be able to do it using .updateMany(), instead you can use MongoDB's .bulkWrite(), with which you could do things in one DB call, internally MongoDB would do multiple write ops.
Try below code :
let input = [
{
id: "816307",
email: "john.doe#test.com",
firstName: "John",
lastName: "Doe",
},
{
id: "391650",
email: "mary#williams",
firstName: "Mary",
lastName: "Williams",
},
{
id: "183751",
email: "paul.smith#test.com",
firstName: "Paul",
lastName: "Smith",
},
];
let bulkArr = [];
input.forEach((each) => {
bulkArr.push({
updateOne: {
filter: each.id,
update: {
$set: {
email: each.email,
firstName: each.firstName,
lastName: each.lastName,
},
},
upsert: true,
},
});
});
db.collection.bulkWrite(bulkArr)
I'm trying to update a field in a collection with data from the same collection, but from a sub-sub field in it, and either can't get the syntax right, or I'm just doing it wrong.
I've spent quite some time now digging around here, but can't seem to get anywhere.
Here's the example structure of the users collection:
{
"_id": "12345qwerty",
"services": {
"oauth": {
"CharacterID": 12345678,
"CharacterName": "Official Username",
},
},
"name": "what I want to change",
"username": "OfficialUsername"
}
What I'm trying to do would be pretty trivial with SQL, ie: update all the display names to match a trusted source...
update users
set name = services.oauth.CharacterName;
...but I'm having trouble getting in MongoDB, and I have a feeling im doing it wrong.
Here's what i have so far, but it doesn't work as expected.
db.users.find().snapshot().forEach(
function (elem) {
db.users.update(
{ _id: elem._id },
{ $set: { name: elem.services.oauth.CharacterName } }
);
}
);
I can set the name to be anything at the base level, but can't set it to be something from the sublevel, as it doesn't recognise the sub-fields.
Any help would be greatly appreciated!
db.users.update({"services.oauth.CharacterName": {$exists: true}},{$set: {"name": "services.oauth.CharacterName"}},{multi:true})
I am setting name at the root of your document to be equal to the value in services.oauth.CharacterName in the sub sub document. multi = true will update multiple document, I am only updating documents that have the services.oauth.CharacterName value.
I've been wrestling with the asynchronous nature of MongoDB, Mongoose and JavaScript and how to best make multiple updates to a collection.
I have an Excel sheet of client and contact data. There are some clients with multiple contacts, one per line, and the client data is the same (so the client name can be used as a unique key - in fact in the schema it's defined with unique: true).
The logic I want to achieve is:
Search the Client collection for the client with clientName as the key
If a matching clientName isn't found then create a new document for that client (not an upsert, I don't want to change anything if the client document is already in the database)
Check to see if the contact is already present in the array of contacts within the client document using firstName and lastName as the keys
If the contact isn't found then $push that contact onto the array
Of course, we could easily have a situation where the client doesn't exists (and so is created) and then immediately, the very next row of the sheet, is another contact for the same client so then I'd want to find that existing (just created) client and $push that 2nd new contact into the array.
I've tried this but it's not working:
Client.findOneAndUpdate(
{clientName: obj.client.clientname},
{$set: obj.client, $push: {contacts: obj.contact}},
{upsert: true, new: true},
function(err, client){
console.log(client)
}
)
and I've had a good look at other questions, e.g.:
create mongodb document with subdocuments atomically?
https://stackoverflow.com/questions/28026197/upserting-complex-collections-through-mongoose-via-express
but can't get a solution... I'm coming to the conclusion that maybe I have to use some app logic to do the find, then decisions in my code, then writes, rather than use a single Mongoose/Mongo statement, but then the issues of asynchronicity rear their ugly head.
Any suggestions?
The approach to handling this is not a simple one, as mixing "upserts" with adding items to "arrays" can easily lead to undesired results. It also depends on if you want logic to set other fields such as a "counter" indicating how many contacts there are within an array, which you only want to increment/decrement as items are added or removed respectively.
In the most simple case however, if the "contacts" only contained a singular value such as an ObjectId linking to another collection, then the $addToSet modifier works well, as long as there no "counters" involved:
Client.findOneAndUpdate(
{ "clientName": clientName },
{ "$addToSet": { "contacts": contact } },
{ "upsert": true, "new": true },
function(err,client) {
// handle here
}
);
And that is all fine as you are only testing to see if a doucment matches on the "clientName", if not upsert it. Whether there is a match or not, the $addToSet operator will take care of unique "singular" values, being any "object" that is truly unique.
The difficulties come in where you have something like:
{ "firstName": "John", "lastName": "Smith", "age": 37 }
Already in the contacts array, and then you want to do something like this:
{ "firstName": "John", "lastName": "Smith", "age": 38 }
Where your actual intention is that this is the "same" John Smith, and it's just that the "age" is not different. Ideally you want to just "update" that array entry end neiter create a new array or a new document.
Working this with .findOneAndUpdate() where you want the updated document to return can be difficult. So if you don't really want the modified document in response, then the Bulk Operations API of MongoDB and the core driver are of most help here.
Considering the statements:
var bulk = Client.collection.initializeOrderedBulkOP();
// First try the upsert and set the array
bulk.find({ "clientName": clientName }).upsert().updateOne({
"$setOnInsert": {
// other valid client info in here
"contacts": [contact]
}
});
// Try to set the array where it exists
bulk.find({
"clientName": clientName,
"contacts": {
"$elemMatch": {
"firstName": contact.firstName,
"lastName": contact.lastName
}
}
}).updateOne({
"$set": { "contacts.$": contact }
});
// Try to "push" the array where it does not exist
bulk.find({
"clientName": clientName,
"contacts": {
"$not": { "$elemMatch": {
"firstName": contact.firstName,
"lastName": contact.lastName
}}
}
}).updateOne({
"$push": { "contacts": contact }
});
bulk.execute(function(err,response) {
// handle in here
});
This is nice since the Bulk Operations here mean that all statements here are sent to the server at once and there is only one response. Also note here that the logic means here that at most only two operations will actually modify anything.
In the first instance, the $setOnInsert modifier makes sure that nothing is changed when the document is just a match. As the only modifications here are within that block, this only affects a document where an "upsert" occurs.
Also note on the next two statements you do not try to "upsert" again. This considers that the first statement was possibly successful where it had to be, or otherwise did not matter.
The other reason for no "upsert" there is because the condtions needed to test the presence of the element in the array would lead to the "upsert" of a new document when they were not met. That is not desired, therefore no "upsert".
What they do in fact is respectively check whether the array element is present or not, and either update the existing element or create a new one. Therefore in total, all operations mean you either modify "once" or at most "twice" in the case where an upsert occurred. The possible "twice" creates very little overhead and no real problem.
Also in the third statement the $not operator reverses the logic of the $elemMatch to determine that no array element with the query condition exists.
Translating this with .findOneAndUpdate() becomes a bit more of an issue. Not only is it the "success" that matters now, it also determines how the eventual content is returned.
So the best idea here is to run the events in "series", and then work a little magic with the result in order to return the end "updated" form.
The help we will use here is both with async.waterfall and the lodash library:
var _ = require('lodash'); // letting you know where _ is coming from
async.waterfall(
[
function(callback) {
Client.findOneAndUpdate(
{ "clientName": clientName },
{
"$setOnInsert": {
// other valid client info in here
"contacts": [contact]
}
},
{ "upsert": true, "new": true },
callback
);
},
function(client,callback) {
Client.findOneAndUpdate(
{
"clientName": clientName,
"contacts": {
"$elemMatch": {
"firstName": contact.firstName,
"lastName": contact.lastName
}
}
},
{ "$set": { "contacts.$": contact } },
{ "new": true },
function(err,newClient) {
client = client || {};
newClient = newClient || {};
client = _.merge(client,newClient);
callback(err,client);
}
);
},
function(client,callback) {
Client.findOneAndUpdate(
{
"clientName": clientName,
"contacts": {
"$not": { "$elemMatch": {
"firstName": contact.firstName,
"lastName": contact.lastName
}}
}
},
{ "$push": { "contacts": contact } },
{ "new": true },
function(err,newClient) {
newClient = newClient || {};
client = _.merge(client,newClient);
callback(err,client);
}
);
}
],
function(err,client) {
if (err) throw err;
console.log(client);
}
);
That follows the same logic as before in that only two or one of those statements is actually going to do anything with the possibility that the "new" document returned is going to be null. The "waterfall" here passes a result from each stage onto the next, including the end where also any error will immediately branch to.
In this case the null would be swapped for an empty object {} and the _.merge() method will combine the two objects into one, at each later stage. This gives you the final result which is the modified object, no matter which preceeding operations actually did anything.
Of course, there would be a differnt manipulation required for $pull, and also your question has input data as an object form in itself. But those are actually answers in themselves.
This should at least get you started on how to approach your update pattern.
I don't use mongoose so I'll post a mongo shell update; sorry for that. I think the following would do:
db.clients.update({$and:[{'clientName':'apple'},{'contacts.firstName': {$ne: 'nick'}},{'contacts.lastName': {$ne: 'white'}}]},
{$set:{'clientName':'apple'}, $push: {contacts: {'firstName': 'nick', 'lastName':'white'}}},
{upsert: true });
So:
if the client "apple" does not exists, it is created, with a contact with given first and last name. If it exists, and does not have the given contact, it has it pushed. If it exists, and already has the given contact, nothing happens.
Here below is a hypothetical Users collection where more than one address is allowed:
{
"firstName": "Joe",
"lastName": "Grey",
...
"addresses":
[
{
"name": "Default",
"street": "..."
...
},
{
"name": "Home",
"street": "..."
...
},
{
"name": "Office",
"street": "..."
...
}
]
}
Every address has a name... which should be unique – e.g. there couldn't be two addresses named Default. If I want to update let's say the address at index 1 (Home), how do I ensure the update data does not contain names Default and Office?
I guess a two-steps approach (i.e. find and then update) wouldn't be very correct since data might be updated between the find and the subsequent update operation, isn't?
var renamed = 'Office'; // from user input
var users = getUserMongoCollection();
users.update({_id:userId, 'addresses.name': { $ne : renamed } },
{ $set : { 'addresses.1.name' : renamed } }, function(err){
//all done!
});
Find the record by ID, and only update it if the array doesn't contain the new name.
I have a problem regarding Ember data and Mongodb embedded objects. Here's my model :
App.Contact = App.Entity.extend({
name : DS.attr('string'),
firstname : DS.attr('string'),
additional_names : DS.attr('string'),
civility : DS.attr('string'),
birthday : DS.attr('date'),
organization : DS.belongsTo('App.Organization'),
role : DS.attr('string'),
photo_source : DS.attr('string'),
photo_uri : DS.attr('string'),
gravatar_mail : DS.attr('string'),
addresses : DS.hasMany('App.Address', { embedded: true }),
emails : DS.hasMany('App.Email', { embedded: true }),
phones : DS.hasMany('App.Phone', { embedded: true })
});
Now I'm fetching a contact through the API: (GET /app/api/v1/contact/4f86c4774ab63c2417000001/) here's what I get :
{
"additional_names": null,
"addresses": [],
"birthday": null,
"civility": null,
"emails": [
{
"email": "alexandre#test.com",
"label": null,
"resource_uri": "/app/api/v1/contact/4f86c4774ab63c2417000001/emails/0/",
"type": "HOME"
}
],
"firstname": "Alexandre",
"gravatar_mail": null,
"groups": [],
"id": "4f86c4774ab63c2417000001",
"name": "Simoui",
"organization": null,
"phones": [],
"photo_source": null,
"photo_uri": "/static/img/nophoto.png",
"resource_uri": "/app/api/v1/contact/4f86c4774ab63c2417000001/",
"role": null
}
My "root" object has an id but the embedded object "emails" hasn't. Because in mongodb, id is not set on subdocuments, but only on root document.
This way ember-data see that "email" object hasn't id and then it try to get the full object through the API. For example : GET /app/api/v1/email/set// 404 (NOT FOUND)
To be sure it was the wright problem I tried to return Mongodb subdocuments with a fake ID field. Like : (see difference on email object)
{
"additional_names": null,
"addresses": [],
"birthday": null,
"civility": null,
"emails": [
{
"id": 431,
"email": "alexandre#test.com",
"label": null,
"resource_uri": "/app/api/v1/contact/4f86c4774ab63c2417000001/emails/0/",
"type": "HOME"
}
],
"firstname": "Alexandre",
"gravatar_mail": null,
"groups": [],
"id": "4f86c4774ab63c2417000001",
"name": "Simoui",
"organization": null,
"phones": [],
"photo_source": null,
"photo_uri": "/static/img/nophoto.png",
"resource_uri": "/app/api/v1/contact/4f86c4774ab63c2417000001/",
"role": null
}
Then I got no problem everything is fine. So my question is: Is there a way to fix it?
I have begun experimenting with a workaround. I only use embedded objects without IDs on a read-only basis, so I haven't tested saving, creation, and proper model state management (isDirty, etc.)...but so far this has solved my similar problem.
Unfortunately I needed to modify the ember-data source. See my fork of ember-data.
The extent of my change involved modifying the hasAssociation() function of DS.hasMany. What I did was inject fake IDs the first time the computed property that accesses the association returns. I keep a client-side ID counter, injectedIdCounter in the closure that defines hasAssociation() as well as a function to fetch an ID and update the counter. I then define a new option, trueEmbedded which is an embedded sub-document with no ID. Each time get() is called on the association, the elements are checked for IDs and if the id doesn't exist one is injected. If IDs have been added, then set() needs to be called so the modified association is stored back on the parent object. This solved my problem at least. Here's my code.
var injectedIdCounter = 1;
var getInjectedId = function() {
return injectedIdCounter++;
};
var hasAssociation = function(type, options) {
options = options || {};
var embedded = options.embedded,
findRecord = embedded ? embeddedFindRecord : referencedFindRecord;
var meta = { type: type, isAssociation: true, options: options, kind: 'hasMany' };
return Ember.computed(function(key, value) {
var data = get(this, 'data'),
store = get(this, 'store'),
ids, id, association;
if (typeof type === 'string') {
type = get(this, type, false) || get(window, type);
}
key = options.key || get(this, 'namingConvention').keyToJSONKey(key);
if (options.trueEmbedded) {
association = get(data, key);
var injectedIdCount = 0;
association.forEach(function(item) {
if (Ember.none(item.id)) {
item.id = getInjectedId();
injectedIdCount++;
}
});
if (injectedIdCount > 0) {
set(data, key, association);
}
ids = embeddedFindRecord(store, type, data, key);
} else {
ids = findRecord(store, type, data, key);
}
association = store.findMany(type, ids || []);
set(association, 'parentRecord', this);
return association;
}).property().cacheable().meta(meta);
};
You would think that an embedded document doesn't need an ID, but the way ember-data first fetches all the IDs of the objects and then the objects themselves, even for an embedded association, means that some messy solution like this is required.
Hopefully this will be fixed in a future release of Ember.
Cheers,
Kevin