Want to change a model ObjectID value - mongodb

I am creating a Student and Course relationship
A student may have multiple courses. A one to many relationship.
This is made in Express and I'm using MongoDB. I have shorten the models to keep it simple
Student Model
const studentSchema = new mongoose.Schema({
name: {type: String},
courses: [{
type: ObjectId,
ref: 'class'
}]})
Course Model
const classSchema = new mongoose.Schema({
ClassId: {type: String,},
Grade: {type: Number,}, })
Currently, what I have is when I update the grade, it will update the grade values for the course itself and not the course in the user courses.
router.put(....)
const{username, courseId, grade} = req.params
const existingUser = await Student.findOne({username}).populate({
path: 'courses',
select:['ClassId','Grade']
})
const findCourse = existingUser.courses.find(
x => x.ClassId == courseId
)
findCourse.Grade = parseInt(grade)
await findCourse.save()
The problem is this will change the grade for the course itself. Meaning any student that adds this course will have that grade too.
I'll explain what I want to do in Java/OOP terms if that helps.
I want the student object to have it's own course objects. At the moment, it seems like classes are static class objects.
I want to access that specific student courses and change that student grade of that specific course.
Please help, I already spent a couple of hours on this. In SQL, the student would have a reference key and be able to easily change their values, I'm having trouble in MongoDB.

Alright, I finally figured it out. In hindsight, it makes sense. Gave myself a break from coding and came back to see the problem.
Lets pretend we have two students and one course. This courses is seeded with data.
When a student A picks that course, they add it to their course array. When student B wants that course, they also get that exact course. Now they are sharing the course. Basically, they are sharing the same reference.
The solution to this is to still find the course. Now make a new course object, copy every value of the original to the copy. Save the copy to the database and now you add that course to the student. Now we can still register for courses and use the seeded data and students don't share anymore.

Related

Unsure how to model many-to-many relationship in MongoDB

I'm new to MongoDB and I'm trying to model a Many to Many relationship into at least 2 collections (I need two collections for the project). What I'm having is a collection of universities, faculties and specializations, and another collection for students and their gradebook (this was the middle entity between specializations and students in SQL, not sure if it's needed in Mongo anymore). I tried to use This as an inspiration but it limits me as I can only search students by university id (I want for example to search students from a certain specialization or a certain faculty). I could put every row from university, faculty and specialization in the student collection and vice versa but I really don't think it's ideal. Here's what I have so far:
db.students.insertOne({_id:1, firstname: 'John', lastname: 'Silas', ethnicity:'english', civilstatus:'single', residence:'London', email:'johnSilas#gmail.com', gradebook:[{ year:2018, registrationyear:2017, formofeducation:'traditional'}], universities:[1]})
db.universities.insertOne({_id:1, name:'University of London', city:'London', adress:'whatever', phone: 'whatever', email: 'whatever#gmail.com', faculty:[{name: 'Law', adress:'whatever', phone: 'whatever', email: 'whatever#gmail.com'}], specialization:[{name:'criminal rights', yearlytax:5000, duration: 3, level:'bachelordegree', language:'english'}], students: [1,2]})
I'm sorry if I don't understand basic noSQL concepts, I am new to it. Thanks in advance.
Basic patterns for many to many association between A and B:
Inline references
On A, store the list of B ids in a field like b_ids
On B, store the list of A ids in a field like a_ids
This requires two writes whenever an association is created or destroyed, but requires either zero or one joins at query time to traverse the association (if you just want the id of Bs for a given A and you have the A already no further queries are needed).
Join model
Create a model C which has two fields: a_id and b_id. Each association is represented by a single instance of C.
This requires one write whenever an association is created or destroyed, but requires joins on all queries involving association (potentially two joins per query).

API Design: Caching “partial” nested objects

Let's say we have schools with some data including a name and a list of students, and students with some data including courses they're enrolled in and a reference to their school. On the client:
I'd like to show a screen that shows information about a school, which includes a list of all of its students by name.
I'd like to show a screen that shows information about a student, including the name of their school and the names of courses they're taking.
I'd like to cache this information so that I can show the same screen without waiting on a new fetch. I should be able to go from school to student and back to school without fetching the school again.
I'd like to show each screen with only one fetch. Going from the school page to the student page can take a separate fetch, but I should be able to show a school with the full list of student names in one fetch.
I'd like to avoid duplicating data, so that if the school name changes, one fetch to update the school will lead to the correct name being shown both on the school page and the student pages.
Is there a good way to do all of this, or will some of the constraints have to be lifted?
A first approach would be to have an API that does something like this:
GET /school/1
{
id: 1,
name: "Jefferson High",
students: [
{
id: 1
name: "Joel Kim"
},
{
id: 2,
name: "Chris Green"
}
...
]
}
GET /student/1
{
id: 1,
name: "Joel Kim",
school: {
id: 1,
name: "Jefferson High"
}
courses: [
{
id: 3
name: "Algebra 1"
},
{
id: 5,
name: "World History"
}
...
]
}
An advantage of this approach is that, for each screen, we can just do a single fetch. On the client side, we could normalize schools and students so that they reference eachother with IDs, and then store the objects in different data stores. However, the student object nested inside of school isn't a full object -- it doesn't include the nested courses, or a reference back to the school. Likewise, the school object inside of student doesn't have a list of all attending students. Storing partial representations of objects in data stores would lead to a bunch of complicated logic on the client side.
Instead of normalizing these objects, we could store schools and students with their nested partial objects. However, this means data duplication -- each student at Jefferson High would have the name of the school nested. If the school name changed just before doing a fetch for a specific student, then we'd show the right school name for that student but the wrong name everywhere else, including on the "school details" page.
Another approach could be to design the API to just return the ids of nested objects:
GET /school/1
{
id: 1,
name: "Jefferson High",
students: [1, 2]
}
GET /student/1
{
id: 1,
name: "Joel Kim",
school: 1,
courses: [3, 5]
}
We'd always have "complete" representations of objects with all of their references, so it's pretty easy to store this information in data-stores client side. However, this would require multiple fetches to show each screen. To show information about a student, we'd have to fetch the student and then fetch their school, as well as their courses.
Is there a smarter approach that would allow us to cache just one copy of each object, and to prevent multiple fetches to show basic screens?
You might be mixing two concepts: Storage and Representations. You can give back a non-normalized representation (the first option you suggested) without also storing those "partial" object in your database.
So I would suggest to try to return non-normalized representations, but storing them normalized (if you are using a relational DB).
Also, an improvement suggestion: You may want to use proper URIs instead of Ids in your representations. You probably want the clients to know "where" to get that object from, it's easier therefore to just supply the URI. Otherwise the client needs to figure out how to produce a URI out of an Id, and that usually ends up being hard-coded in the client, which is a no-no in REST.

Understanding Mongoose Schema better

I am relatively new to the MongoDb world, coming from a MS Sql / Entity framework environment.
I am excited about Mongo, because of:
MongoDb's ability to dynamically change the shape of the class/table/collection at run time.
Entity framework does not offer me that.
Why is that so important?
Because I would like to create a generic inventory app and have the product class/collection/table be dynamic for clients to add fields pertinent to their business that cannot be used by everyone, eg. Vin Number, ISBN number, etc.
Now I have come to learn about Mongoose and how it offers a schema, which to me detracts from the flexibility of MongoDb described above.
I have read in a few sections that there is such an animal as mixed-schema, but that appears to be dynamic relative to the data type and not the collection of properties for the given class/collection/table.
So this is my question:
If I am looking at developing a generic class/collection /table that affords clients to shape it to include whatever fields/properties they want that pertain to their business, dynamically, should I abandon the whole notion of mongoose?
I found a benefit today as to where a Schema may be warranted:
Allow me to preface though and say I still thoroughly am excited about the whole idea that Mongo allows a collection to be reshaped at run time in circumstances where I may need ti to be. As mentioned above, a perfect example would be an Inventory app where I would want each client to add respective fields that pertain to their business as opposed to other clients, such as a Car dealership needing a VIN Number field, or a Book store needing a ISBN Number field.
Mongo will allow me to create one generic table and let the client shape it according to his own wishes for his own database at run time - SWEET!
But I discovered today where a schema would be appropo:
If in another table that will not be 're-shapeable', say a user table, I can create a Schema for pre-determined fields and make them required, as such:
var dbUserSchema = mongoose.Schema({
title: {type:String, required:'{PATH} is required!'},
FullName: {
FirstName: {type: String, required: '{PATH} is required!'},
LastName: {type: String, required: '{PATH} is required!'}
}
});
By having the respective first-name and last-name required from the schema, the database will not add any records for a user if they are not both included in the insert.
So, I guess one gets the best of both worlds: Tables that can be re-shaped and thru a schema, tables that can be rigid.

How to manage drafts and multiple versions in Mongo?

This is a follow-up to my previous question.
Suppose there is a product catalog stored as a collection in Mongo. User Alice is a catalog manager and may update, remove and add products to the catalog. User Bob is a customer and may view the catalog.
Currently when Alice changes the catalog Bob sees the changes immediately. Now we want the changes to be visible only if Alice explicitly publish them. For example:
There is a catalog which consists of Product A, Product B, and Product C. Both Alice and Bob see the same products.
Alice changed the catalog. She modified Product A, removed Product C, and added Product D but did not publish the changes.
Now Alice sees Product A' (modified), Product B, and Product D but Bob still sees the previous version: Product A, Product B, and Product C.
Alice published the catalog. Now both Alice and Bob see the same products: Product A' (modified), Product B, and Product D
My questions are:
how to implement it with Mongo
how to manage versions/revisions of the catalog, so Alice will be able to undo/redo the changes she made in the catalog.
Ahh temporal data, the bane of database developers everywhere.
Fortunately this is arguably easier in mongodb than other relational dbs.
If you can make the assumption that you'll have at most ONE unpublished version this problem is much simpler than if you can have different users editing unpublished versions.
Assuming you've got some standard things in your schema:
{
_id: ObjectId
name: String,
CreatedDate: Date,
Price: Number
}
You need to add a sub-document with a duplicate of any field editable by the user. It also will contain a flag for deletion.
{
_id: ObjectId
name: String,
createdDate: Date,
price: Number,
revised: {
name: String,
createdDate: Date,
price: Number,
deleted: Boolean,
}
}
When a user goes to edit the product, you'll copy over the existing props into the 'revised' object. All edits go to that object. When you publish, you copy those items back to the base layer, and delete the 'revised' property.
If you have multiple users editing the document, and they can't see each other's edits you could make your revised document a bit more complicated
{ revised: { U1234: { name : ... }, U2345 : { name: ... } } }
Where each user has a separate copy. Of course when one user publishes it could delete the entity entirely. I would of course recommend adding a 'deleted' flag to the root item instead of actually deleting it from the db, unless these objects are HUGE. (Index the deleted flag of course.)

Can I use NoSQL instead of a Relational Database?

For a library, I need to keep track of users and books. Basically I need to be able to know:
the list of books currently borrowed by a user
the current borrower of a given book
The app is done with node.js and mongoDB (with moogoose). I have the following schema:
BookSchema = new Schema({
title : String,
author : String,
current_borrower_email: String,
});
mongoose.model('Book', BookSchema);
// Define User model
UserSchema = new Schema({
lastname : String,
firstname : String,
email : String,
books : [BookSchema] // Books the user is borrowing
});
mongoose.model('User', UserSchema);
I guess this would be simplier to set this up in a relational DB where I could easily use many to many relation ships with foreign keys but I wanted to give a try to MongoDB.
Do you think this solution could work ? Also, if I delete a Book object, it seems I will have to remove it manually from the array of the user who borrowed it, it that right ?
In general mongodb will be good replacement of relational database for above task.
So some basics:
1.Once some one take a book you just need to copy book into the nested collection of user and user to the Book.
2.Once user has updated his profile you need aslo update information about user within Book.
3.Once book data was changed you also need update info about book within user.
4.If you trying to delete some book and current borrower exists you should say that book was borrowed by 'User' and not delete it.
I just suggest to add into your schema instead of current_borrower_email entire User object -> current_borrower: UserSchema.
So with such denormalized schema you will able easy show(within one request to mongodb):
the list of books currently borrowed by a user.
the current borrower of a given book
It is an old question but it came first in google so...
It's not too complicated but it is too long to summarize.
Read this:
http://blog.mongodb.org/post/87200945828/6-rules-of-thumb-for-mongodb-schema-design-part-1