How to manage drafts and multiple versions in Mongo? - mongodb

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.)

Related

Want to change a model ObjectID value

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.

Implementing 3 way relationship in Prisma

I also asked this question on Prisma forum.
** EDIT **: The forum has since been locked as read-only just 1 day after my question. It's sure getting scary because there is no official announcement of whether they'll stop developing Prisma 1 after implementing promised features or not. Surely they could have said something. And TBH, the unstable Prisma site does add to my shaking confidence in Prisma despite the number of stars in their repo.
I'm new to Prisma. So I have a 3-way relationship between User, Event and Role I would like to define. For each association of a User and an Event, there exists a Role for that association. If I were to design a database table for another ORM, I would have created a event_user table with user_id, event_id and role_id as columns to relate them.
Practically, a row of these 3 columns must be unique. Obviously, it would be good if Prisma can do the safeguarding of these constraints, but the obvious solution I see might not even come to Prisma 1.
My current design consists of the following:
type User {
// ...
eventUsers: [EventUser!]!
}
type Event {
// ...
eventUsers: [EventUser!]!
}
type EventUser {
role: Role!
event: Event!
user: User!
}
This design will make render all xxWhereUniquexx unusable, which is definitely a hassle to maintain relationships. upserts will certainly be unusable for maintaining the relationships.
How would one create a relationship like this in Prisma?
For some context, each user would have a list of global roles as well, so there would already be an association between User and Role. The question concerns the "local roles" for each event.
If each user already have a value for their respective roles, there would be no need for a third table (unless there is more information you'd like to be stored in your modal layer in which case the role type should be in the Role table).
Relationships are set with the #relation directive. You can apply it to either one of two tables in a relation but for clarity I'm apply them to the Event table. The example assumes users can be part of several events.
Tables:
enum ROLE_TYPE {
TYPE_ONE
TYPE_TWO
}
User {
user_id: ID! #id
events: [Event!]!
}
Event {
event_id: ID! #id
users: [User!]! #relation(link: INLINE)
}
Role {
role_id: ID! #id
type: ROLE_TYPE
event: Event! #relation(link: INLINE)
user: User! #relation(link: INLINE)
}
You can read more about relations on the Prisma site

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.

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