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

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.

Related

Search multiple collections with mongoosastic

In a Node.js application, i am using Mongoosastic to retrieve data from ElasticSearch :
Article.search({
"match_all": {}
}, function (err, results) {
console.log(results.hits.hits);
Post.search({
"match_all": {}
}, function (err, results) {
console.log(results.hits.hits);
return next();
});
});
Here i am making two requests to retrieve data from two different collections. I would like to know if it was the good way to do it ?
Is it possible to search data from the two collections (or multiple collections) in one single request ? (with the multi search API for example, but with Mongoosastic).
Thank you.
Looks like we can't do it with Mongoosastic.
In fact, the project define itself in the GitHub page as :
a mongoose plugin that can automatically index your models into
elasticsearch
So i use it to define my indexes, but to do search queries it works fine with the official javascript client.
The same question here : https://github.com/mongoosastic/mongoosastic/issues/41

Question about Model in MVC architecture in web app using NodeJs and MySQL

I am new to NodeJs and I am trying to create a web application using express framework and MySql. I get that in MVC architecture the views are for example the *.ejs files. The controllers are supposed to have the logic and the models should focus on the database.
But still I am not quite sure what is supposed to be inside the model. I have the following code in my controller (probably wrong, not following mvc design):
const mysql = require('mysql');
const db = mysql.createConnection(config);
db.query(query, (err, result) => {
if (err) {
return res.redirect('/');
}
res.render('index.ejs', {
users: result
});
});
Now from what I've read the controller should ask the model to execute the query to the database, get the results and render the view (index.ejs').
My question is this: What should be inside the model.js file? Can I make something like this?
controller.js
const db = require('./models/model.js');
db.connect();
const results = db.query(query);
if(results != null) {
res.render('index.ejs'){
users: result
});
}
model.js will make a query to mysql handle any errors and return the result.
From what I've read I have two options. Option1: pass callback function to model and let the model render the view (I think that's wrong, model should not communicate with view, or not?) Option2: possible use of async/await and wait for model to return the results but I am not sure if this is possible or how.
The model is the programmatic representation of the data stored in the database. Say I have an employees table with the following schema:
name: string
age: number
company: company_foreign_key
And another table called companies
name: string
address: string
I therefore have two models: Company and Employee.
The model's purpose is to load database data and provide a convenient programmatic interface to access and act upon this data
So, my controller might look like this:
var db = require('mongo');
var employeeName = "bob";
db.connect(function(err, connection){
const Employee = require('./models/Employee.js'); // get model class
let employeeModel = new Employee(connection); // instantiate object of model class
employee.getByName(employeeName, function(err, result){ // convenience method getByName
employee.getEmployeeCompany(result, function(err, companyResult){ // convenience method getEmployeeCompany
if(companyResultl) { // Controller now uses the results from model and passes those results to a view
res.render('index.ejs')
company: companyResult
});
})
})
}
})
Basically, the model provides a convenient interface to the raw data in the database. The model executes the queries underneath, and provides convenient methods as a public interface for the controller to access. E.g., the employee model, given an employee object, can find the employee's company by executing that query.
The above is just an example and, given more thought, a better interface could be thought up. In fact, Mongoose provides a great example of how to set up model interfaces. If you look at how Mongoose works, you can use the same principles and apply them to your custom model implementation.

Mongoose best practices for CRUD with a Schema reference

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.

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

Mongo check if a document already exists

In the MEAN app I'm currently building, the client-side makes a $http POST request to my API with a JSON array of soundcloud track data specific to that user. What I now want to achieve is for those tracks to be saved to my app database under a 'tracks' table. That way I'm then able to load tracks for that user from the database and also have the ability to create unique client URLs (/tracks/:track)
Some example data:
{
artist: "Nicole Moudaber"
artwork: "https://i1.sndcdn.com/artworks-000087731284-gevxfm-large.jpg?e76cf77"
source: "soundcloud"
stream: "https://api.soundcloud.com/tracks/162626499/stream.mp3?client_id=7d7e31b7e9ae5dc73586fcd143574550"
title: "In The MOOD - Episode 14"
}
This data is then passed to the API like so:
app.post('/tracks/add/new', function (req, res) {
var newTrack;
for (var i = 0; i < req.body.length; i++) {
newTrack = new tracksTable({
for_user: req.user._id,
title: req.body[i].title,
artist: req.body[i].artist,
artwork: req.body[i].artwork,
source: req.body[i].source,
stream: req.body[i].stream
});
tracksTable.find({'for_user': req.user._id, stream: req.body[i].stream}, function (err, trackTableData) {
if (err)
console.log('MongoDB Error: ' + err);
// stuck here - read below
});
}
});
The point at which I'm stuck, as marked above is this: I need to check if that track already exists in the database for that user, if it doesn't then save it. Then, once the loop has finished and all tracks have either been saved or ignored, a 200 response needs to be sent back to my client.
I've tried several methods so far and nothing seems to work, I've really hit a wall and so help/advice on this would be greatly appreciated.
Create a compound index and make it unique.
Using the index mentioned above will ensure that there are no documents which have the same for_user and stream.
trackSchema.ensureIndex( {for_user:1, stream:1}, {unique, true} )
Now use the mongoDB batch operation to insert multiple documents.
//docs is the array of tracks you are going to insert.
trackTable.collection.insert(docs, options, function(err,savedDocs){
//savedDocs is the array of docs saved.
//By checking savedDocs you can see how many tracks were actually inserted
})
Make sure to validate your objects as by using .collection we are bypassing mongoose.
Make a unique _id based on user and track. In mongo you can pass in the _id that you want to use.
Example {_id : "NicoleMoudaber InTheMOODEpisode14",
artist: "Nicole Moudaber"
artwork: "https://i1.sndcdn.com/artworks-000087731284-gevxfm-large.jpg?e76cf77"
source: "soundcloud"
stream: "https://api.soundcloud.com/tracks/162626499/stream.mp3? client_id=7d7e31b7e9ae5dc73586fcd143574550"
title: "In The MOOD - Episode 14"}
_id must be unique and won't let you insert another document with the same _id. You could also use this to find the record later db.collection.find({_id : NicoleMoudaber InTheMOODEpisode14})
or you could find all tracks for db.collection.find({_id : /^NicoleMoudaber/}) and it will still use the index.
There is another method to this that I can explain if you dont' like this one.
Both options will work in a sharded environment as well as a single replica set. "Unique" indexes do not work in a sharded environment.
Soundcloud API provides a track id, just use it.
then before inserting datas you make a
tracks.find({id_soundcloud : 25645456}).exec(function(err,track){
if(track.length){ console.log("do nothing")}else {//insert}
});

Categories

Resources