Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 7 years ago.
Improve this question
I am building a chat application using socket.io.
I need to create an array to manage the users connected to each chat room.
To start with I need to store the chat room name, and then in that chat room each user and their status.
chat1
nickname: user1
status: admin
nickname: user2
status: none
nickname: user3
status: none
I need it to sort the nicknames in alphabetical order when it is created or when a user is added or deleted. Is this possible?
What you've described isn't a multidimensional array, it's an array of Objects or Maps. Assuming Objects:
chat1 = [
{ nickname: 'ethelred',
status: 'admin'},
{ nickname: 'alfred',
status: 'none' },
{ nickname: 'canute',
status: 'none' }
]
// This is all that's needed for a very simple sort:
chat1.sort((a,b) => a.nickname > b.nickname)
This will sort and give you [{nickname: 'alfred', status: 'none' }, {nickname: 'canute', status: 'none' }, { nickname: 'ethelred', status: 'admin'}]
// You can continue to transform the data,
// for example, just pull the user nicknames:
chat1.sort((a,b) => a.nickname - b.nickname)
.map(user => user.nickname)
That will sort and then map across the resulting sorted array to give you a list of users in alphabetical order - ['alfred','canute','ethelred'].
Note, this is naïve, you will need better logic in the sort function (what happens with capital letters, numbers, numbers mixed in with letters, &c?). But that function is completely isolated. You just write a separate (dumb) function that knows nothing about your chatroom object, and pass that to sort() with a.nickname and b.nickname as the arguments - chat1.sort(myFunkySortFunction(a.nickname, b.nickname)) (or you inline the logic and don't abstract it, whatever's simplest).
You'll start getting issues if you have a lot of users, as you'll need to run this on every join/leave event. At that point you'll need something a lot more robust.
Edit1:
So when a new person joins, they either get a. .push()-ed onto the original array, or b. you create a new array with the new person's object. When a person leaves, they either a. get removed from the original array, or b. a new array is created that doesn't include them.
When either of those events happen, you could run the sort function. You need to look at the Array.prototype.sort() method. That should literally be the only thing you need.
Note, what I'm suggesting is not very efficient, it will just do the job, and is very simple.
Re the function passed to sort, a.nickname > b.nickname is not going to work very well. A-Z will come before a-z. 'Xerxes' will come before 'albert'. You could do:
chat1.sort(function(a,b) {
return a.nickname.toLowerCase() > b.nickname.toLowerCase();
})
So that would sort that out. But you've still an issue with more complex names: it's worth looking at something robust like described here: Javascript : natural sort of alphanumerical strings
Basically, what you're doing isn't hard: you just need to make sure that in that callback function, you specify that you're grabbing the nickname property, otherwise it'll try to sort based on the whole object, and return weird incorrect results.
[very late] Edit2
I just realised that you asked how to add and remove. Staying on the same (functional) theme as the rest of the above: to add you can use Array.prototype.concat() to create a new array that includes the extra user. To remove, Array.prototype.filter() will return a new array with a specific user excised.
So add:
current = [
{ nickname: 'ethelred',
status: 'admin'},
{ nickname: 'alfred',
status: 'none' },
{ nickname: 'canute',
status: 'none' }
]
current.concat({ nickname: 'offa', status: 'none' })
// [{ nickname: 'ethelred', status: 'admin'}, { nickname: 'alfred', status: 'none'}, { nickname: 'canute', status: 'none'}, { nickname: 'offa', status: 'none'}]
To remove:
current = [
{ nickname: 'ethelred',
status: 'admin'},
{ nickname: 'alfred',
status: 'none' },
{ nickname: 'canute',
status: 'none' }
]
current.filter({nickname} => nickname !== 'canute')
// [{ nickname: 'ethelred', status: 'admin'}, { nickname: 'alfred', status: 'none'}]
Related
I have the following code which is finding and updating an employee-store record (if it exists, otherwise it creates one).
I have lots of employees in different stores, and they can choose to change store at any point (as long as the location is in America)
Below is my code so far:
employee = await this.employeeStoreModel.findOneAndUpdate(
{ employee: employeeRecord._id, location: "America" },
{
employee: employeeRecord._id,
store: employeeRecord.store,
location: "America",
},
{ new: true, upsert: true }
);
This works correctly, however I am trying to return some messages from this based on what is being updated. It could be any of the following messages:
If it's a completely new employee-store record being added, then return "{{StoreID}} has a new employee - {{EmployeeID}}"
If it's a change of store on an existing employee-store record, then return "{{EmployeeID}} has changed from {{old StoreID}} to {{new StoreID}}"
Is this possible to do? Can anyone guide me on how I could start this?
In the Options field,
rawResult: true
You must add the parameter.
The result will be as in the example below.
{ response:
{ n: 1,
updatedExisting: false,
upserted: 5e6a9e5ec6e44398ae2ac16a },
value:
{ _id: 5e6a9e5ec6e44398ae2ac16a,
name: 'Will Riker',
__v: 0,
age: 29 },
ok: 1 }
Depending on whether there is an update or insert in the updatedExisting field, you can return any message you want.
Consider I have this document in my MongoDB collection, Workout:
{
_id: ObjectId("60383b491e2a11272c845749") <--- Workout ID
user: ObjectId("5fc7d6b9bbd9473a24d3ab3e") <--- User ID
exercises: [
{
_id: ObjectId("...") <--- Exercise ID
exerciseName: "Bench Press",
sets: [
{
_id: ObjectId("...") <--- Set ID
},
{
_id: ObjectId("...") <--- Set ID
}
]
}
]
}
The Workout object can include many exercise objects in the exercises array and each exercise object can have many set objects in the sets array. I am trying to implement a delete functionality for a certain set. I need to retrieve the workout that the set I want to delete is stored in. I have access to the user's ID (stored in a context), exercise ID and the set ID that I want to delete as parameters for the .findOne() function. However, I'm not sure whether I can traverse through the different levels of arrays and objects within the workout object. This is what I have tried:
const user = checkAuth(context) // Gets logged in user details (id, username)
const exerciseID, setID // Both of these are passed in already and are set to the appropriate values
const workoutLog = Workout.findOne({
user: user.id,
exercises: { _id: exerciseID }
});
This returns an empty array but I am expecting the whole Workout object that contains the set that I want to delete. I would like to omit the exerciseID from this function's parameters and just use the setID but I'm not sure how to traverse through the array of objects to access it's value. Is this possible or should I be going about this another way? Thanks.
When matching against an array, if you specify the query like this:
{ exercises: { _id: exerciseID } }
MongoDB tries to do an exact match on the document. So in this case, MongoDB would only match documents in the exercises array of the exact form { _id: ObjectId("...") }. Because documents in the exercises have other fields, this will never produce a match, even if the _ids are the same.
What you want to do instead is query a field of the documents in the array. The complete query document would then look like this:
{
user: user.id,
"exercises._id": exerciseID
}
You can perform both find and update in one step. Try this:
db.Workout.updateOne(
{
"user": ObjectId("5fc7d6b9bbd9473a24d3ab3e"),
},
{
$pull: {
"exercises.$[exercise].sets": {
"_id": ObjectId("6039709fe0c7d52970d3fa30") // <--- Set ID
}
}
},
{
arrayFilters: [
{
"exercise._id" : ObjectId("6039709fe0c7d52970d3fa2e") // <--- Exercise ID
}
]
}
);
I would like to know how to keep track of the values of a document in MongoDB.
It's a MongoDB Database with a Node and Express backend.
Say I have a document, which is part of the Patients collection.
{
"_id": "4k2lK49938d82kL",
"firstName": "John",
"objective": "Burn fat"
}
Then I edit the "objective" property, so the document results like this:
{
"_id": "4k2lK49938d82kL",
"firstName": "John",
"objective": "Gain muscle"
}
What's the best/most efficient way to keep track of that change? In other words, I would like to know that the "objective" property had the value "Burn fat" in the past, and access it in the future.
Thanks a lot!
Maintaining/tracking history in the same document is not all recommended. As the document size will keep on increasing leading to
probably if there are too many updates, 16mb document size limit
Performance degrades
Instead, you should maintain a separate collection for history. You might have use hibernates' Javers or envers for auditing for your relational databases. if not you can check how they work. A separate table (xyz_AUD) is maintained for each table (xyz). For each row (with primary key abc) in xyz table, there exist multiple rows in xyz_AUD table, where each row is version of that row.
Moreover, Javers also support MongoDB auditing. If you are using java you can directly use it. No need to write your own logic.
Refer - https://nullbeans.com/auditing-using-spring-boot-mongodb-and-javers/
One more thing, Javers Envers Hibernate are java libraries. But I'm sure for other programming languages also, similar libraries will be present.
There is a mongoose plugin as well -
https://www.npmjs.com/package/mongoose-audit (quite oudated 4 years)
https://github.com/nassor/mongoose-history#readme (better)
Maybe you can change the type of "objective" to array and track the changes in it. the last one of the array is the latest value.
Maintain it as a sub-document like below
{
"_id": "4k2lK49938d82kL",
"firstName": "John",
"objective": {
obj1: "Gain muscle",
obj2: "Burn fat"
}
}
You can also maintain it as an array field but remember, mongodb doesn't allow you to maintain uniqueness in an array field and if you plan to index the "objective" field, you'll have to create a multi key index
I think the simplest solution would be to use and update an array:
const patientSchema = new Schema({
firstName: { type: String, required: true },
lastName: { type: String, required: true },
objective: { type: String, required: true }
notes: [{
date: { type: Date, default: Date.now() },
note: { type: String, required: true }
}],
});
Then when you want to update the objective...
const updatePatientObjective = async (req, res) => {
try {
// check if _id and new objective exist in req.body
const { _id, objective, date } = req.body;
if (!_id || !objective) throw "Unable to update patient's objective.";
// make sure provided _id is valid
const existingPatient = await Patient.findOne({ _id });
if (!existingPatient) throw "Unable to locate that patient.";
// pull out objective as previousObjective
const { objective: previousObjective } = existingPatient;
// update patient's objective while pushing
// the previous objective into the notes sub document
await existingPatient.updateOne({
// update current objective
$set { objective },
// push an object with a date and note (previouseObjective)
// into a notes array
$push: {
notes: {
date,
note: previousObjective
},
},
}),
);
// send back response
res
.status(201)
.json({ message: "Successfully updated your objective!" });
} catch (err) {
return res.status(400).json({ err: err.toString() });
}
};
Document will look like:
firstName: "John",
lastName: "Smith",
objective: "Lose body fat.",
notes: [
{
date: 2019-07-19T17:45:43-07:00,
note: "Gain muscle".
},
{
date: 2019-08-09T12:00:38-07:00,
note: "Work on cardio."
}
{
date: 2019-08-29T19:00:38-07:00,
note: "Become a fullstack web developer."
}
...etc
]
Alternatively, if you're worried about document size, then create a separate schema for patient history and reference the user's id (or just store the patient's _id as a string instead of referencing an ObjectId, whichever you prefer):
const patientHistorySchema = new Schema({
_id: { type: Schema.Types.ObjectId, ref: "Patient", required: true },
objective: { type: String, required: true }
});
Then create a new patient history document when the objective is updated...
PatientHistory.create({ _id, objective: previousObjective });
And if you need to access to the patient history documents...
PatientHistory.find({ _id });
I know that this question might be beginner level but I haven't find anything yet.
I would like to update an array of objects with mongoose. I am interested in updating one object from the users array according to the index.
Usually one user is getting changed at a time.
Here is my schema:
_id: Schema.Types.ObjectId,
name: { type: String, required: true },
gm: {
type: Schema.Types.ObjectId,
ref: 'User',
required: true
},
users: [],
I want to update an object in the users array which is like this:
{
id:"5bcb7c7ff9c5c01b9482d244",
gm:"5bcb7c7ff9c5c01b9482d246",
name:"room 1"
users: [
{
id:"5bcb7c7ff9c5c01b9482d243",
stats:{
power:10,
mobility: 5,
vitality: 20
},
bag:{itemSlot1: "Knife",itemSlot2:"Sword" }
},
{
id:"5bcb7c7ff9c5c01b9482d241",
stats:{
power:10,
mobility: 5,
vitality: 20
},
bag:{itemSlot1: "Knife",itemSlot2:"Sword" }
]
}
I want to perform a patch or a post request to update one user each time from the user array. i am getting the id of the user from req.body to match it with my db.
My request is like this:
I would like to update based on a request like this:
data = {
stats={
power:"10",
vitality:"20"
}
}
Thanks in advance,
Cheers
You can do an update like this:
YourSchema.update({
'users.id': '5bcb7c7ff9c5c01b9482d243'
}, {
$set: {
'users.$.stats': data.stats
}
})
Which would update the first user with id 5bcb7c7ff9c5c01b9482d243 power stats to 20
This is using the update with the $ positional operator to update the element in the array.
Just have it set up in your post/patch request.
Sorry if this was answered elsewhere, I tried to search but I'm not even sure what I'm looking for.
Let say I have this object to work with:
userRequest: {
id: number,
subject: string,
...
orderIds: number[]
...
}
order: {
id: number,
...
clientId: number,
productIds: number[]
}
client: {
id: number,
name: string,
...
}
product: {
id: number,
name: string,
price: number
}
Now, at some point the user will fill a form using that composite object and send it for analysis. But before sending it, it has first to be validated. And I cannot validate in the form because the user is simply entering the data received on paper. If the data is "invalid", a request for more information will be sent.
So, I need to validate the request, but also the order, the products and the client. I am requested to show a "Validating Request" screen and after each element was checked, a "Valid" or "Invalid" screen. Simple enough.
But now, I'm sending http requests and get Observables to deal with. I'm trying to learn more about them and all the available operators and how to mix them, but at the moment, I'm completely lost.
So, I first get an Observable<userRequest> from server. Then, once I get a userRequest, I need to get all the orders from their id's, and when I get an "order", I have to get the client & his products.
All this is done asynchronously, but I cannot get the client or the products until I receive the order, and I need the userRequest to provide the orders. In addition, when I get an order, I need to get both the client AND the products at the "same time" since they both need the same order...? For the grand finale, for every element I get (request, order, client, product) I need to validate it and wait for every element to say "the request is valid" or not.
So to resume:
I need to get an Observable<userRequest> and validate
Now, I have to get an Observable<order[]> and validate each order
For each order, I have to 1) get an Observable<Client> and validate PLUS 2) get an Observable<Product[]> and validate each one
Wait for every observables to complete and check if it's valid or not
Steps 1 and 2 needs to be executed sequentially, but when step 2 completes, I need to execute steps 3.1 and 3.2 for each result of step 2. And wait.
I'm sure it's far from clear, I just hope it clear enough so you guys gets want I want to achieve. If you have any hints for me, please do share!!! ; )
Edit
I do know somehow what needs to be done. But where I lose my cool, is when I need to chain the Observables sequentially (as each one depends on the one before), at various point I need to call a validation method and when it comes to the Client and the Products, both need the Order for it's Id. I did try many, many ways but I just don't grasp the concept completely.
bygrace - No, I don't want the validation to block. It should validate everything as it will result in a request for all the missing or invalid parts, and it should be showed at the end. That why I need a way to know when everything is done so I can check if errors were found.
The request, orders, client and products each comes from their respective services. The service makes an http resquest and returns an Observable. So I need to chain the calls (and when it comes to the Order, I need to get TWO Observables for the same Order Id).
QuietOran - Here's something I tried. It's horrible I know, but I'm so lost right now...
onValidateRequest(requestId: number) {
this.requestService.getUserRequest$(this.requestId)
.do(request => {
this.validateRequest(request);
})
.concatMap(request => this.orderService.getOrdersForRequest$(request.id))
.do(orders => {
this.validateOrders(orders);
})
.concatMap(orders => {
// Now, this is were I'm completely lost
// I manage to get the request and the orders, but in this block, I need to get the client AND the products
// and validate each one as I receive it
// Then return something
})
.do(() => {
// when I validate an element, if there's an error, I simple add it in an array.
// So when ALL the Observable above are completed, this function simply checks
// if there's something in it
this.checkForErrors();
})
.subscribe();
}
I'm going to give you something rough that you can refine with feedback because I'm not clear on the final shape of the data you want back and all. Hopefully this points you in the right direction.
Basically if you want the data from one observable to feed another then you can use switchmap or one of its cousins. If you need the value fed in as well as the result then just lump them together with a combineLatest or something similar.
console.clear();
function getUserRequest(requestId) {
return Rx.Observable.of({ id: 1, subject: 'a', orderIds: [10, 20] })
.delay(500).take(1);
}
function getOrdersForRequest(requestId) {
return Rx.Observable.of([
{ id: 10, clientId: 100, productIds: [ 1000 ] },
{ id: 20, clientId: 200, productIds: [ 1001, 1002 ] }
]).delay(200).take(1);
}
function getClientForOrder(orderId) {
let client;
switch(orderId) {
case 10:
client = { id: 100, name: 'Bob' };
break;
case 20:
client = { id: 200, name: 'Alice' };
break;
}
return Rx.Observable.of(client).delay(200).take(1);
}
function getProductsForOrder(orderId) {
let products;
switch(orderId) {
case 10:
products = [{ id: 1000, name: 'p1', price: 1 }];
break;
case 20:
products = [
{ id: 1001, name: 'p1', price: 2 },
{ id: 1002, name: 'p1', price: 3 }
];
break;
}
return Rx.Observable.of(products).delay(200).take(1);
}
Rx.Observable.of(1)
.switchMap(id => Rx.Observable.combineLatest(
getUserRequest(id),
getOrdersForRequest(id)
.switchMap(orders => Rx.Observable.combineLatest(
Rx.Observable.of(orders),
Rx.Observable.combineLatest(...orders.map(o => getClientForOrder(o.id))),
Rx.Observable.combineLatest(...orders.map(o => getProductsForOrder(o.id)))
)
),
(userRequest, [orders, clients, products]) =>
({ userRequest, orders, clients, products })
)
).subscribe(x => { console.dir(x); });
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.5.6/Rx.min.js"></script>
Right now I flattened the results by category. You may want them nested or something like that. This is just a rough pass so provide feedback as needed.