Mongoose best practices for CRUD with a Schema reference - javascript

I'd like to know the best practices for adding CRUD endpoints for a Schema which references another Schema. I have a Company Schema and an Address Schema, where a Company will have an Array of Address IDs defined in its Model like this:
CountrySchema = new Schema({
addresses: [{
type: Schema.Types.ObjectId,
ref: 'Address'
}]
});
When creating a new Company, my first thoughts are to loop through the Addresses and create them all in the database before proceeding to save the Company. One very annoying issue with this could be that if 3 Addresses save, and 1 does not, I will need to go back and remove those 3 Addresses.
Creating the Address before creating the Company could be something like this:
function CreateModel1WithStuff(data, cb) {
if (data.child) { // Save child model first
data.child = Model2(data.child);
data.child.save(function(err) {
cb(err, err ? null : Model1(data));
});
} else { // Proceed without dealing with child
cb(null, Model1(data));
}
}
CreateModel1WithStuff({
foo: 'abc',
child: {
bar: 'cba'
}
}, function(err, doc) {
doc.save();
});
Another issue is with updating a Company's Address, is it better to update the Address separately using an Address endpoint? Or is it better to send the new Address to the Company endpoint and let that endpoint check if the Address should be updated. This would be kind of similar to the previous point.

Related

Meteor mongo: sync fields across collections

I have a meeting document like this:
{
"name":"Meeting Name",
"uuid":"NYoc2aL6",
"participants":[
{
"id":"JLKGZnfFkGvX9DHgz",
"status":"joined",
"name":"Guest 03"
},
{
// newly invited user, user hasn't logged in with invite url yet
"id":"",
"status":"invited",
"name":"email#email.com"
}
]
}
and I need to synchronize the 'name' field with the name in the Users collection. Is there an automatic way to do this, like at the database level, or am I stuck with manually updating in every place that the name is changed?
This is a pretty common pattern in Meteor. You can use the matb33:collection-hooks package to "hook" the collection update to synchronize the shared value(s). This should be done server-side of course so you don't have to worry about some of the related documents not being available to you.
Example:
Meteor.users.after.update((userId, doc, fieldNames, modifier, options)=>{
if ( fieldNames.indexOf('profile.name') > -1 ){ // the name was changed
Meetings.update({ 'participants.id': doc._id },
{ $set: { 'participants.name': doc.profile.name }},
{ multi: true });
}
});
There is no "automatic" way to do this that I know of in Mongo or Meteor. However, why not take the common fields out of your document and just link the ID? This is known as "Database Normalization", which is a process by which you remove redundant data from your tables (collections in Mongo) to prevent these sorts of problems.
This could be done 'automatically' by observing changes on the users db:
var usersCursor = Meteor.users.find();
usersCursor.observeChanges({"changed":function(id, fields){
if(fields.profile.name){
... do whatever needs to be done ...
}
}});

Saving document and its object id for referencing in other documents

In a Node.js App with Mongodb/Mongoose. i have two collections. Users and Books. My question is: when a user saves a book i have to save it in the Books collection and its object id inside Users collection for referencing. Two save operation for one user input.
Is this correct way? is this Mongodb standard? in a relation based database system its a wrong architecture but in the lack of Join in Mongodb what should i do? if i have a large database should i save current books id inside each of related collections?
I know that i can has books embedded into each user document but it has own problems.
Im confused. what should i do?
You can create a MySQL "join" in MongoDB with Mongoose. It is not the MongoDB standard but it makes developing in MongoDB sometimes a lot easier. Below are two simple example schema's.
var BookSchema = new Schema({
title: {
type String
},
author: {
type: String
}
});
module.exports = mongoose.model('Book', BookSchema);
var UserSchema = new Schema({
username: {
type: String,
required: true
},
books: [{
type: String,
ref: 'Book'
}]
});
module.exports = mongoose.model('User', UserSchema);
In the last schema there is a "books" property with a reference to books. You can store Strings of Object id's inside the array (not as an object). If you want, you can also store Object id's instead of Strings. Below is an example schema for a user:
{
username: "Fcoder",
books: ["550adf3899fbe92a168d3051", "550adf3899fbe92a168d3052"]
}
When querying your MongoDB database, you can populate the data. Your query will look like this:
User.find({}).populate('books').exec(function(err, data) {
// callback
});
Inside data, you will find something like this:
{
username: "Fcoder",
books: [{
_id: ObjectId("550adf3899fbe92a168d3051"),
title 'Some title 1',
author: 'Some author 1'
}, {
_id: ObjectId("550adf3899fbe92a168d3052"),
title 'Some title 2',
author: 'Some author 2'
}
}

Mongoose: Adding an element to array

I'm using Drywall to create a website.
I'm trying to add a dashboard element to the accounts section of the admin site. The dashboard element is to store an array of dashboards (strings) that the user has access to.
I've managed to successfully add the "dashboards" into the schema and store data in it.
Here's the problem:
I need to be able to add elements to the array. The way the code stands currently replaces the contents of dashboards in the database.
I know I can use $addToSet, but I'm not sure how I'd do that since the fieldsToSet variable is sent to the findByIdAndUpdate() method as a single object.
Here's the snippet of my code:
workflow.on('patchAccount', function() {
var fieldsToSet = {
name: {
first: req.body.first,
middle: req.body.middle,
last: req.body.last,
full: req.body.first +' '+ req.body.last
},
company: req.body.company,
phone: req.body.phone,
zip: req.body.zip,
search: [
req.body.dashboards,
req.body.first,
req.body.middle,
req.body.last,
req.body.company,
req.body.phone,
req.body.zip,
]
};
req.app.db.models.Account.findByIdAndUpdate(req.params.id, fieldsToSet, function(err, account) {
if (err) {
return workflow.emit('exception', err);
}
workflow.outcome.account = account;
return workflow.emit('response');
});
});
Here's a link to the original file: (lines 184-203)
Thanks!
fieldsToSet is a bad name (at least misleading in this case), the parameter is actually update which can take $actions like $addToSet
I don't think you want to set (only) the search field with dashboards. I'm guessing that field is used to index users for a search. So you'll probably wind up doing something like this:
fieldsToSet = {
....all the regular stuff,
$addToSet: {dashboard: req.body.dashboardToAdd}
//I'm not sure that you can add multiple values at once
}
Since this is setting all of the values each time I'm not sure you actually will want to add single dashboard items. Instead you might want to get the full set of dashboards the user has and set the whole array again anyway (what if they removed one?)
fieldsToSet = {
....all the regular stuff,
dashboards: req.body.dashboards
//In this case you'd want to make sure dashboards is an appropriate array
}

Chaining models relations in node.js (node-orm) like in Rails

I'm building a relatively big NodeJS application, and I'm currently trying to figure out how to fetch the data I need from the DB. Here is a part of my models :
One user has one role, which has access to many modules (where there's a table role_modules to link roles and modules).
In Rails, I would do something like user.role.modules to retrieve the list of the modules he has access to. In NodeJS it's a bit more complicated. I'm using node-orm2 along with PostgreSQL. Here is what I have so far:
req.models.user.find({email: req.body.user}, function(err, user) {
user[0].getRole(function(err, role) {
role.getModules(function(err, modules) {
var list_modules = Array();
modules.forEach(function(item) {
console.log(item);
list_modules.push(item.name);
})
But I can't do this, because item only contains role_id and module_id. If I want to have the name, I would have to do item.getModule(function() {...}), but the results would be asynchronous ... so I don't really see how I could end up with an array containing the names of the modules a user has access to ... have any idea?
Also, isn't that much slower than actually running a single SQL query with many JOIN? Because as I see it, the ORM makes multiple queries to get the data I want here...
Thank you!
I wrote an ORM called bookshelf.js that aims to simplify associations and eager loading relations between models in SQL. This is what your query would probably look like to load the role & modules on a user given your description:
var Module = Bookshelf.Model.extend({
tableName: 'modules'
});
var Role = Bookshelf.Model.extend({
tableName: 'roles',
modules: function() {
return this.belongsToMany(Module);
}
});
var User = Bookshelf.Model.extend({
tableName: 'users'
role: function() {
return this.hasOne(Role);
}
});
User.forge({email: req.body.user})
.fetch({
require: true,
withRelated: ['role.modules']
})
.then(function(user) {
// user, now has the role and associated modules eager loaded
console.log(user.related('role'));
console.log(user.related('role').related('modules'))
}, function(err) {
// gets here if no user was found.
});
Might be worth taking a look at.

Modelling blogs and ratings in mongodb and nodejs

I have a blogs collection that contains title, body and agrregate rating that the users have given to them. Another collection 'Ratings' whose schema has reference to the blog, user who rated(if at all he rates them) it in the form of their ObjectIds and the rating they have given ie., +1 or -1.
When a particular user browses through blogs in the 'latest first' order (say 40 of them per page. Call them an array of blogs[0] to blogs[39]) I have to retrieve the rating documents related to this particular user and those 40 blogs if at all the user rated them and notify him of what ratings he has given those blogs.
I tried to extract all rating documents of a particular user in which blog reference objectIds lie between blogs[0]._id and blogs[39]._id which returns empty list in my case. May be objectIds cant be compared using $lt and $gt queries. In that case how should I go about it? Should I redesign my schemas to fit to this scenario?
I am using mongoosejs driver for this case. Here are the relevant parts of the code which differ a bit in execution but youu get the idea.
Schemas:
Client= new mongoose.Schema({
ip:String
})
Rates = new mongoose.Schema({
client:ObjectId,
newsid:ObjectId,
rate:Number
})
News = new mongoose.Schema({
title: String,
body: String,
likes:{type:Number,default:0},
dislikes:{type:Number,default:0},
created:Date,
// tag:String,
client:ObjectId,
tag:String,
ff:{type:Number,default:20}
});
models:
var newsm=mongoose.model('News', News);
var clientm=mongoose.model('Client', Client);
var ratesm=mongoose.model('Rates', Rates);
Logic:
newsm.find({tag:tag[req.params.tag_id]},[],{ sort:{created:-1},limit: buffer+1 },function(err,news){
ratesm.find({client:client._id,newsid:{$lte:news[0]._id,$gte:news.slice(-1)[0]._id}},function(err,ratings){
})
})
Edit:
While implementing the below said schema, I had to do this query in mongoose.js
> db.blogposts.findOne()
{ title : "My First Post", author: "Jane",
comments : [{ by: "Abe", text: "First" },
{ by : "Ada", text : "Good post" } ]
}
> db.blogposts.find( { "comments.by" : "Ada" } )
How do I do this query in mongoose?
A good practice with MongoDB (and other non-relational data stores) is to model your data so it is easy to use/query in your application. In your case, you might consider denormalizing the structure a bit and store the rating right in the blog collection, so a blog might look something like this:
{
title: "My New Post",
body: "Here's my new post. It is great. ...",
likes: 20,
dislikes: 5,
...
rates: [
{ client_id: (id of client), rate: 5 },
{ client_id: (id of another client), rate: 3 },
{ client_id: (id of a third client), rate: 10 }
]
}
The idea being that the objects in the rates array contains all the data you'll need to display the blog entry, complete with ratings, right in the single document. If you also need to query the rates in another way (e.g. find all the ratings made by user X), and the site is read-heavy, you may consider also storing the data in a Rates collection as you're doing now. Sure, the data is in two places, and it's harder to update, but it may be an overall win after you analyze your app and how it accesses your data.
Note that you can apply indexes deep into a document's structure, so for example you can index News.rates.client_id, and then you can quickly find any documents in the News collection that a particular user has rated.

Categories

Resources