I am experimenting with the MEAN stack, specifically with MEAN.js.
While everything is pretty well explained in the documentation, it seems that the simple task to associate an entity (or Model) with another isn't explained in the documentation or the examples.
For example, it is easy to generate a crud for Ideas and one for Polls. But what if I have to link "polls" to an "idea", in a one-to-many relation?
I assume I would do something that resembles to this in polls.client.controller.js:
// Create new Poll
$scope.create = function() {
// Create new Poll object
var poll = new Polls ({
ideaId: this.idea.ideaId,//here I associate a poll with an Idea
vote1: this.vote1,
vote2: this.vote2,
vote3: this.vote3,
vote4: this.vote4,
vote5: this.vote5
});
// Redirect after save
poll.$save(function(response) {
$location.path('polls/' + response._id);
// Clear form fields
$scope.name = '';
}, function(errorResponse) {
$scope.error = errorResponse.data.message;
});
};
But when the angular model is pushed to Express.js backend, I don't see any trace in the request about the Idea, the only thing I get is the Poll.
/**
* Create a Poll
*/
exports.create = function(req, res) {
var poll = new Poll(req.body);
poll.user = req.user;
//poll.ideaId = req.ideaId;//undefined
poll.save(function(err) {
if (err) {
return res.status(400).send({
message: errorHandler.getErrorMessage(err)
});
} else {
res.jsonp(poll);
}
});
};
Here is my Mongoose Model:
'use strict';
/**
* Module dependencies.
*/
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
/**
* Poll Schema
*/
var PollSchema = new Schema({
vote1: {
type: Number
},
vote2: {
type: Number
},
vote3: {
type: Number
},
vote4: {
type: Number
},
vote5: {
type: Number
},
created: {
type: Date,
default: Date.now
},
user: {
type: Schema.ObjectId,
ref: 'User'
},
idea: {
type: Schema.ObjectId,
ref: 'Idea'
}
});
mongoose.model('Poll', PollSchema);
I am sure that there is something I did wrong, but any explanation (or link) on how to perform this task that goes beyond this particular error or setup of mine would be appreciated.
The solution I found (I am not sure if it is the correct solution or a workaround) is to populate the .idea field of a poll with its corresponding ._id:
var poll = new Polls ({
idea: this.idea._id,
vote1: 5,
vote2: 3,
vote3: 3,
vote4: 1,
vote5: 2
});
At this point, when I get to express, poll.idea has the correct association.
Related
I am trying to pre-load array of objects to MongoDB as below:
the below code works if I do one object at a time. that is,
if I set:
tmp_obj = {
id:1,
name: 'Tmp 1'
}
model file
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var TmpSchema = new Schema({
id: Number,
name: String
});
var Tmp= mongoose.model('Tmp', TmpSchema);
module.exports = Tmp;
routes file
var express = require('express');
var router = express.Router();
var Tmp = require('../models/tmp');
var tmp_obj = [
{
id:1,
name: 'Tmp 1'
},
{
id:2,
name: 'Tmp 2'
},
{
id:3,
name: 'Tmp 3'
}
];
var tmp = new Tmp(tmp_obj);
tmp.save(function (err) {
if (err) return console.log(err);
console.log('tmp saved to the database');
return res.redirect('/login');
})
how do I push an array of objects to the mongo? and also I have multiple collections to add. so, do I do something like:
tmp1.save(function (err) {
if (err) return console.log(err);
console.log('tmp1 saved to the database');
tmp2.save(function (err) {
if (err) return console.log(err);
console.log('tmp2 saved to the database');
return res.redirect('/login');
})
})
Another alternative is to use .create() method, it could accept an array of objects or a single object, and you don't need to create a model instance (i.e var tmp = new Tmp(tmp_obj);), here is an example:
var Tmp = require('../models/tmp');
var tmp_obj = [
{ id:1, name: 'Tmp 1' },
{ id:2, name: 'Tmp 2' },
{ id:3, name: 'Tmp 3' }
];
Tmp.create(tmp_obj, function (err, temps) {
if (err) {
console.log(err);
// terminate request/response cycle
return res.send('Error saving');
}
res.redirect('/login');
});
One last thing, don't forget to terminate the request/response cycle if an error has been occurred, otherwise the page will hangs
You can use the method insertMany from mongoose to insert multiple document at once.
From the documentation of mongoose v5.0.4
var arr = [{ name: 'Star Wars' }, { name: 'The Empire Strikes Back' }];
Movies.insertMany(arr, function(error, docs) {});
An alternative using .save() would be
// Create all objects
const objects = tmp_obj.map(x => new Tmp(x));
try {
// Saves objects
const docs = await Promise.all(objects.map(x => x.save()));
} catch(e) {
// An error happened
}
But you should not use it since insertMany is way better
I would like to ask if there is anyone getting the same response on JSON format:
Objectdata: "User is not authorized"headers: (name) {status: 403statusText: "Forbidden"
Scenario:
User A post a product and add comment on the product.
Result: Successful.
User B comment on the same product:
Result: User is not authorized.
The code I'm using to update the product comment is here:
applicationname/`
// Add comment to Product
$scope.comment = function(){
// console.log("name: ",$scope.user);
// console.log("textarea: ",this.commentarea);
var comment = {
name: $scope.product.user.displayName,
text: this.commentarea
};
$scope.product.comments.push(comment);
$scope.product.$update(function() {
console.log('success update');
}, function(errorResponse) {
console.log('success error', errorResponse);
});
};
This is the server side.
'use strict';
/**
* Module dependencies.
*/
var init = require('./config/init')(),
config = require('./config/config'),
mongoose = require('mongoose'),
chalk = require('chalk');
/**
* Main application entry file.
* Please note that the order of loading is important.
*/
// Bootstrap db connection
var db = mongoose.connect(config.db, function(err) {
if (err) {
console.error(chalk.red('Could not connect to MongoDB!'));
console.log(chalk.red(err));
}
});
// Init the express application
var app = require('./config/express')(db);
// Bootstrap passport config
require('./config/passport')();
// Start the app by listening on <port>
app.listen(config.port);
// Expose app
exports = module.exports = app;
// Logging initialization
console.log('MEAN.JS application started on port ' + config.port);
If your Products schema looks like this:
var ProductSchema = new Schema({
created: {
type: Date,
default: Date.now
},
title: {
type: String,
default: '',
trim: true,
required: 'Title cannot be blank'
},
comments: [{
type: String,
default: '',
trim: true
}]
});
And you have restricted your products route in your app/routes/products.server.routes.js file like so:
app.route('/products/:productId')
.get(products.read)
.put(users.requiresLogin, products.hasAuthorization, products.update)
.delete(users.requiresLogin, products.hasAuthorization, products.delete);
Then a non-authorized user cannot add a comment because they can't update the Product record.
You probably want to create a separate CommentsSchema and use the Mongoose ObjectId type to create a one-to-many relationship with the product:
var CommentSchema = new Schema({
product: ObjectId,
content: {
type: String,
default: '',
trim: true,
required: 'Content cannot be blank'
},
})
That will preserve the security of your product and allow non-authorized users to comment, but would require you to do slightly more complex queries to get your comments in your product view.
I have been googleing this for a few weeks with no real resolution.
I am sure someone will mark this a duplicate, but I am not sure it really is, maybe I am just being too specific, anyway here goes.
I am using angular in a node-webkit app that I am building. I have an api built in express and I am using MongoDB (#mongolab) with Mongoose for the DB.
I had this working fine as long as all of the data types were simple strings and numbers. but I had to restructure the data to use arrays and complex objects. After restructuring the data I was able to get post API calls to work fine, but I cannot get my PUT calls to work at all.
The data looks like this:
itemRoles was an array, but I thought it was throwing the error I am getting now, so I converted it back to a string.
itemStats is causing the problem. Angular is looking for an object, but itemStats is an array (I think anyway). itemStats used to be a string as well, but its easier to work with in my view if it is an array of objects with key:value pairs, which is why I altered it.
I should note I am new to MongoDB as well, first time using it.
{
"_id": {
"$oid": "55a10b9c7bb9ac5832d88bd8"
},
"itemRoles": "healer,dps",
"itemRating": 192,
"itemName": "Advanced Resolve Armoring 37",
"itemClass": "consular",
"itemLevel": 69,
"itemStats": [
{
"name": "Endurance",
"value": 104,
"_id": {
"$oid": "55a10b9c7bb9ac5832d88bda"
}
},
{
"name": "Willpower",
"value": 124,
"_id": {
"$oid": "55a10b9c7bb9ac5832d88bd9"
}
}
],
"__v": 0
}
The Mongoose Schema looks like this:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
//var stats = new Schema({
//name: String,
//value: Number
//});
var armoringSchema = new Schema({
itemType: String,
itemClass: String,
itemRoles: String,
itemLevel: Number,
itemName: String,
itemRating: Number,
itemStats: [{ name:String, value:Number}]
});
module.exports = mongoose.model('Armor', armoringSchema);
Express API Route:
/ on routes that end in /armors/:id
// ----------------------------------------------------
router.route('/armors/:id')
// get method omitted
// update the armoring with specified id (accessed at PUT http://localhost:8080/api/armors/:id)
.put(function(req, res) {
// use our armor model to find the armor we want
Armoring.findById({_id: req.params.id}, function(err, armor) {
if (err) {
return res.send(err);
}
for(prop in req.body) {
armor[prop] = req.body[prop];
}
// save the armor
armor.save(function(err) {
if (err) {
return res.send(err);
}
res.json({success:true, message: 'Armor updated!' });
});
});
})
Resource Factory:
swtorGear.factory('armoringFactory', ['$resource', function ($resource) {
return $resource('http://localhost:8080/api/armors/:id', {}, {
update: { method: 'PUT', params: {id: '#_id'}},
delete: { method: 'DELETE', headers: {'Content-type': 'application/json'}, params: {id: '#_id'}}
});
}]);
Route for editing:
.when('/edit/armor/id/:id', {
templateUrl: 'views/modelViews/newArmor.html',
controller: 'editArmorCtrl',
resolve: {
armoring: ['$route', 'armoringFactory', function($route, armoringFactory){
return armoringFactory.get({ id: $route.current.params.id}).$promise;
}]
}
})
Contoller (just the save method, the first part of the controller populates the form with existing data):
$scope.save = function(id) {
$scope.armor.itemStats = [
$scope.armor.stats1,
$scope.armor.stats2
];
$scope.armor.itemRoles = '';
if($scope.armor.role.tank) {
$scope.armor.itemRoles += 'tank';
}
if($scope.armor.role.healer) {
if($scope.armor.itemRoles != '') {
$scope.armor.itemRoles += ',healer';
} else {
$scope.armor.itemRoles += 'healer';
}
}
if($scope.armor.role.dps) {
if($scope.armor.itemRoles != '') {
$scope.armor.itemRoles += ',dps';
} else {
$scope.armor.itemRoles += 'dps';
}
}
console.log($scope.armor);
$scope.armor.$update(id)
.then(function(resp) {
if(resp.success) {
var message = resp.message;
Flash.create('success', message, 'item-success');
$scope.armors = armoringFactory.query();
} else {
var message = resp.message;
Flash.create('success', message, 'item-success');
}
});
}
Formatted data being sent via PUT method (from console.log($scope.armor) ):
Error on save:
I haven't seen nesting schemas in the way that you're doing it. Here's something to try (hard to say if this is it for sure, there's a lot going on):
var armoringSchema = new Schema({
itemType: String,
itemClass: String,
itemRoles: String,
itemLevel: Number,
itemName: String,
itemRating: Number,
itemStats: [{
name: String,
value: Number
}]
});
Also we need to pass in an object to $update instead of just a number. Change $scope.armor.$update(id) to $scope.armor.$update({id: id}).
I have two mongoose schemas as follow:
var playerSchema = new mongoose.Schema({
name: String,
team_id: mongoose.Schema.Types.ObjectId
});
Players = mongoose.model('Players', playerSchema);
var teamSchema = new mongoose.Schema({
name: String
});
Teams = mongoose.model('Teams', teamSchema);
When I query Teams I would to get also the virtual generated squad:
Teams.find({}, function(err, teams) {
JSON.stringify(teams); /* => [{
name: 'team-1',
squad: [{ name: 'player-1' } , ...]
}, ...] */
});
but I can't get this using virtuals, because I need an async call:
teamSchema.virtual('squad').get(function() {
Players.find({ team_id: this._id }, function(err, players) {
return players;
});
}); // => undefined
What is the best way to achieve this result?
Thanks!
This is probably best handled as an instance method you add to teamSchema so that the caller can provide a callback to receive the async result:
teamSchema.methods.getSquad = function(callback) {
Players.find({ team_id: this._id }, callback);
});
I have a Node.js/Express app that queries a MySQL db within the route and displays the result to the user. My problem is how do I run the queries and block until both queries are done before redirecting the user to the page they requested?
In my example I have 2 queries that need to finish before I render the page. I can get the queries to run synchronously if i nest query 2 inside the 'result' callback of query 1. This however will become very convoluted when the number of queries increase.
How do I go about running multiple (in this case 2) database queries synchronously without nesting the subsequent query in the prior query's 'result' callback?
I've looked at the 'Flow control / Async goodies' in the Node modules and tried flow-js but I can't get it to work with the async queries.
Listed below are the 2 queries that I'm attempting to execute from the '/home' route. Can the Node experts explain the 'right' way to do this.
app.get('/home', function (req,res) {
var user_array = [];
var title_array = [];
// first query
var sql = 'select user_name from users';
db.execute(sql)
.addListener('row', function(r) {
user_array.push( { user_name: r.user_name } );
})
.addListener('result', function(r) {
req.session.user_array = user_array;
});
// second query
var sql = 'select title from code_samples';
db.execute(sql)
.addListener('row', function(r) {
title_array.push( { title: r.title } );
})
.addListener('result', function(r) {
req.session.title_array = title_array;
});
// because the queries are async no data is returned to the user
res.render('home.ejs', {layout: false, locals: { user_name: user_array, title: title_array }});
});
The goal with node is not to care what order things happen in. This can complicate some scenarios. There is no shame in nesting callbacks. Once you are used to how it looks, you may find that you actually prefer that style. I do; it is very clear what order callbacks will fire. You can forgo the anonymous functions to make it less verbose if you have to.
If you are willing to restructure your code a bit, you can use the "typical" nested callback method. If you want to avoid callbacks, there are numerous async frameworks that will try and help you do this. One that you might want to check out is async.js (https://github.com/fjakobs/async.js). Example of each:
app.get('/home', function (req,res) {
var lock = 2;
var result = {};
result.user_array = [];
result.title_array = [];
var finishRequest = function(result) {
req.session.title_array = result.title_array;
req.session.user_array = result.user_array;
res.render('home.ejs', {layout: false, locals: { user_name: result.user_array, title: result.title_array }});
};
// first query
var q1 = function(fn) {
var sql = 'select user_name from users';
db.execute(sql)
.addListener('row', function(r) {
result.user_array.push( { user_name: r.user_name } );
})
.addListener('result', function(r) {
return fn && fn(null, result);
});
};
// second query
var q2 = function(fn) {
var sql = 'select title from code_samples';
db.execute(sql)
.addListener('row', function(r) {
result.title_array.push( { title: r.title } );
})
.addListener('result', function(r) {
return fn && fn(null, result);
});
}
//Standard nested callbacks
q1(function (err, result) {
if (err) { return; //do something}
q2(function (err, result) {
if (err) { return; //do something}
finishRequest(result);
});
});
//Using async.js
async.list([
q1,
q2,
]).call().end(function(err, result) {
finishRequest(result);
});
});
For a one-off, I would probably just use a reference counting type approach. Simply keep track of how many queries you want to execute and render the response when they have all finished.
app.get('/home', function (req,res) {
var lock = 2;
var user_array = [];
var title_array = [];
var finishRequest = function() {
res.render('home.ejs', {layout: false, locals: { user_name: user_array, title: title_array }});
}
// first query
var sql = 'select user_name from users';
db.execute(sql)
.addListener('row', function(r) {
user_array.push( { user_name: r.user_name } );
})
.addListener('result', function(r) {
req.session.user_array = user_array;
lock -= 1;
if (lock === 0) {
finishRequest();
}
});
// second query
var sql = 'select title from code_samples';
db.execute(sql)
.addListener('row', function(r) {
title_array.push( { title: r.title } );
})
.addListener('result', function(r) {
req.session.title_array = title_array;
lock -= 1;
if (lock === 0) {
finishRequest();
}
});
});
An even nicer approach would be to simply call finishRequest() in each 'result' callback an check for non-empty arrays before you render the response. Whether that will work in your case depends on your requirements.
Here's a really easy trick to handle multiple callbacks.
var after = function _after(count, f) {
var c = 0, results = [];
return function _callback() {
switch (arguments.length) {
case 0: results.push(null); break;
case 1: results.push(arguments[0]); break;
default: results.push(Array.prototype.slice.call(arguments)); break;
}
if (++c === count) {
f.apply(this, results);
}
};
};
Example
Usage:
var handleDatabase = after(2, function (res1, res2) {
res.render('home.ejs', { locals: { r1: res1, r2: res2 }):
})
db.execute(sql1).on('result', handleDatabase);
db.execute(sql2).on('result', handleDatabase);
So basically you need reference counting. This is the standard approach in these situations. I actually use this small utility function instead of flow control.
If you want a full blown flow control solution I would recommend futuresJS
I find that the async library is the best for things like this. https://github.com/caolan/async#parallel
I can't test this or anything, so forgive me if there are some typos. I refactored your query function to be reusable. So, calling queryRows will return a function that matches the format of the async module's parallel callback functions. After both queries are complete, it will call the last function and pass the result of the two queries as an argument, which you can read to pass to your template.
function queryRows(col, table) {
return function(cb) {
var rows = [];
db.execute('SELECT ' + col + ' FROM ' + table)
.on('row', function(r) {
rows.push(r)
})
.on('result', function() {
cb(rows);
});
}
}
app.get('/home', function(req, res) {
async.parallel({
users: queryRow('user_name', 'users'),
titles: queryRow('title', 'code_samples')
},
function(result) {
res.render('home.ejs', {
layout: false,
locals: {user_name: result.users, title: result.titles}
});
});
});
There are some solutions here, but in my opinion the best solution is to make the code synchronously in a very easy way.
You could use the "synchonize" package.
Just
npm install synchronize
Then var sync = require(synchronize);
Put logic which should be synchronous into a fiber by using
sync.fiber(function() {
//put your logic here
}
An example for two mysql queries:
var express = require('express');
var bodyParser = require('body-parser');
var mysql = require('mysql');
var sync = require('synchronize');
var db = mysql.createConnection({
host : 'localhost',
user : 'user',
password : 'password',
database : 'database'
});
db.connect(function(err) {
if (err) {
console.error('error connecting: ' + err.stack);
return;
}
});
function saveSomething() {
var post = {id: newId};
//no callback here; the result is in "query"
var query = sync.await(db.query('INSERT INTO mainTable SET ?', post, sync.defer()));
var newId = query.insertId;
post = {foreignKey: newId};
//this query can be async, because it doesn't matter in this case
db.query('INSERT INTO subTable SET ?', post, function(err, result) {
if (err) throw err;
});
}
When "saveSomething()" is called, it inserts a row in a main table and receives the last inserted id. After that the code below will be executed. No need for nesting promises or stuff like that.
option one: if all your queries related to each other, create stored procedure, put all your data logic into it and have a single db.execute
option two: if your db uses one connection then commands a guaranteed to be executed serially and you can use this as async helper
db.execute(sql1).on('row', function(r) {
req.session.user_array.push(r.user);
});
db.execute(sql2)
.on('row', function(r) {
req.session.title_array.push(r.title);
})
.on('end'), function() {
// render data from req.session
});
You can use fibers to write pseudo-synchronous code with Node.JS take a look at these tests for DB https://github.com/alexeypetrushin/mongo-lite/blob/master/test/collection.coffee
they are asynchronous but looks like synchronous, more details http://alexeypetrushin.github.com/synchronize