Hello Im trying to do associaton with many to many in node using node-orm2.
I have this tables:
project --> id, name
user --> id, name
user_project --> user_fk, project_fk (this is many to many association table)
The controller returns me a list of users and a list of projects, but i need to return in a both another property with projects of every user and users of every prject.
Models:
module.exports = function (orm, db) {
var Project = db.define('project', {
name : { type: 'text', required: true }
},
{
methods: {
serialize: function () {
return {
id : this.id,
name : this.name
};
}
}
});
};
module.exports = function (orm, db) {
var User = db.define('user', {
name : { type: 'text', required: true },
email : { type: 'text', required: true }
},
{
methods: {
serialize: function () {
return {
id : this.id,
name : this.name,
email : this.email
};
}
}
});
};
Controller:
module.exports = {
list: function (req, res, next) {
req.models.project.find().limit(4).order('-created').all(function (err, messages) {
if (err) return next(err);
var projects = messages.map(function (m) {
return m.serialize();
});
console.log(projects);
});
req.models.user.find().limit(4).order('-created').all(function (err, messages) {
if (err) return next(err);
var users = messages.map(function (m) {
return m.serialize();
});
console.log(users);
});
res.sendfile(settings.path + '/public/index2.html');
}
How can I do this I'm confused, I readed the documentation but i don't understand this.
Had some issues with node-orm also...
You can do it with light-orm or bookshelf.js.
In light-orm you can simply extend model with complex request like this:
var SiteCollection = new ORM.Collection({
connector: ORM.driver,
tableName: 'site',
modelExtension: {
getAds: function(callback) {
var query = "SELECT DISTINCT `ads`.* FROM `ads` INNER JOIN `siteads` " +
"ON `ads`.`id` = `siteads`.`ads_id` " +
"WHERE site_id = " + this.get('id');
ORM.Collections.AdsCollection.find(query, callback);
},
renderAds: function(callback) {
var that = this;
this.getAds(function(err, ads) {
var html = ejs.render(adsTemplate, {
content: new ORM.Collection(ads).toJSON()
});
fs.writeFileSync(writeDir + that.get('id') + ".html", html, {
encoding: 'utf8'
});
callback();
});
}
}
});
Try it: https://npmjs.org/package/light-orm
Related
I am new to node/express js, and trying to execute the following. The control executes the lines after function call "var nextVersion =getNextContractVersion(cid)", even before the function returns a response. As a result the value for newVersion is not updated to Contract object. Also, function getNextContractVersion(cid) returns undefined, unlike the updated nextVersion.
How do i fix this behavior, please suggest. Also, is the right way of invoking function?
// Package imports
const express = require('express');
var router = express.Router();
const mongoose = require('mongoose');
//Local imports
var { Customer } = require('../models/customer');
var { Contract } = require('../models/contract');
router.put('/:cid/contracts', (req, res) => {
var cid = req.params.cid;
var nextVersion =getNextContractVersion(cid);
var contract;
if (validateCustomerId(cid)) {
req.body.contract.forEach((item) => {
contract = new Contract({
customerID: cid,
startDate: item.startDate,
endDate: item.endDate,
conditions: item.conditions,
price: item.price,
author: item.author,
version: nextVersion
});
});
contract.save((err, docs) => {
if (!err) {
Customer.findOneAndUpdate({ customerID: cid }, { $push: { contract: contract } },
{ safe: true, upsert: true, new: true }).populate({ path: 'contract' }).exec((err1, docs1) => {
if (!err1) {
res.send(docs1).status(200);
} else {
console.log('Error is adding a new contract:' + JSON.stringify(err1, undefined, 2));
}
});
} else {
console.log('Error is updating a new customer:' + JSON.stringify(err, undefined, 2));
}
});
} else {
res.status(400).send('Bad Request - Invalid input!')
}
});
function getNextContractVersion(cid) {
var nextVersion=1;
Contract.findOne({ customerID: cid }).sort({version: 'descending'}).exec((err, doc) => {
if (!err && doc != null) {
var currentVersion = parseInt(doc.version);
nextVersion = currentVersion + 1;
}
});
return nextVersion;
}
You are mixing synchronous and asynchronous code.
Contract.findOne({ customerID: cid }).sort({version: 'descending'}).exec((err, doc) => {
if (!err && doc != null) {
var currentVersion = parseInt(doc.version);
nextVersion = currentVersion + 1;
}
});
The above code effectively says "Go to the database, find one of these objects and whenever in the future that is done, run this code that's in the exec block."
One of the ways to reason about asynchronous code from a synchronous mindset is that of promises.
Here's a semi pseudo implementation:
router.put('/:cid/contracts', (req, res) => {
var cid = req.params.cid;
return getTheMostRecentContract(cid)
.then(function(oldContract){
var nextVersion = oldContract.version +1;
if(!validateCustomerId(cid)){
return res.status(400).send('Bad Request - Invalid input!');
}
var contract;
var savePromises = [];
req.body.contract.forEach((item) => {
contract = new Contract({
customerID: cid,
startDate: item.startDate,
endDate: item.endDate,
conditions: item.conditions,
price: item.price,
author: item.author,
version: nextVersion
});
savePromises.push(contract.save());
});
return Promise.all(savePromises);
})
.then(function(resultOfAllSavePromises){
//rest of code here
}).catch(function(error){
console.log('Error is updating a new customer:' + JSON.stringify(err, undefined, 2));
return res.status(400);
})
});
function getTheMostRecentContract(cid) {
return Contract.findOne({ customerID: cid }).sort({version: 'descending'});
}
As a matter of practice though, have the database control your auto-increment values. This code won't work in a high traffic environment.
I have an action where I need to update MongoDB entry including _id field, which requires deleting old entry and making a new one, here is server side:
exports.update = function(req, res, next){
var outcome = [];
outcome.previousId = req.params.id;
outcome.newId = req.body.name;
var getPreviousRecord = function(callback) {
req.app.db.models.AccountGroup
.findOne({ _id: req.params.id })
.lean()
.exec(function(err, accountGroups) {
if (err) {
return callback(err, null);
}
outcome.accountGroups = accountGroups;
return callback(null, 'done');
});
};
var makeNewRecord = function(callback) {
var permissions = outcome.accountGroups.permissions;
var fieldsToSet = {
_id: outcome.newId.toLowerCase(),
name: outcome.newId,
permissions: permissions
};
req.app.db.models.AccountGroup
.create(fieldsToSet, function(err, record) {
if (err) {
return callback(err, null);
}
outcome.record = record;
return callback(null, 'done');
});
};
var deletePreviousRecord = function() {
req.app.db.models.AccountGroup
.findByIdAndRemove(outcome.previousId)
.exec(function(err) {
if (err) {
return next(err);
}
res.redirect('admin/account-groups/' + outcome.newId + '/');
});
};
var asyncFinally = function(err) {
if (err) {
return next(err);
}
};
require('async').series([getPreviousRecord, makeNewRecord, deletePreviousRecord], asyncFinally);
};
It works fine, but I can't make this work normally on the front-end, it returns me both old route and a new route, for example:
PUT /admin/account-groups/customers22/admin/account-groups/Customers2233/ 404 213.749 ms - 31
where customers22 is old _id and customers2233 is new _id. If I navigate from another page to new entry it gets route normally.
On client side:
(function() {
'use strict';
app = app || {};
app.Details = Backbone.Model.extend({
idAttribute: '_id',
defaults: {
success: false,
errors: [],
errfor: {},
name: ''
},
url: function() {
return '/admin/account-groups/'+ app.mainView.model.id +'/';
},
parse: function(response) {
if (response.accountGroup) {
app.mainView.model.set(response.accountGroup);
delete response.accountGroup;
}
return response;
}
});
app.DetailsView = Backbone.View.extend({
el: '#details',
events: {
'click .btn-update': 'update'
},
template: Handlebars.compile( $('#tmpl-details').html() ),
initialize: function() {
this.model = new app.Details();
this.syncUp();
this.listenTo(app.mainView.model, 'change', this.syncUp);
this.listenTo(this.model, 'sync', this.render);
this.render();
},
syncUp: function() {
this.model.set({
_id: app.mainView.model.id,
name: app.mainView.model.get('name')
});
},
render: function() {
this.$el.html(this.template( this.model.attributes ));
for (var key in this.model.attributes) {
if (this.model.attributes.hasOwnProperty(key)) {
this.$el.find('[name="'+ key +'"]').val(this.model.attributes[key]);
}
}
},
update: function() {
this.model.save({
name: this.$el.find('[name="name"]').val()
});
}
});
app.MainView = Backbone.View.extend({
el: '.page .container',
initialize: function() {
app.mainView = this;
this.model = new app.AccountGroup( JSON.parse( unescape($('#data-record').html()) ) );
// ...
app.detailsView = new app.DetailsView();
}
});
$(document).ready(function() {
app.mainView = new app.MainView();
});
}());
It probably requires to trigger both model.save and model.destroy or prevent URL being used. Any advice on how to do it is appreciated, thank you.
Edit
Just a typo mistake here that is not related to the question, recklessly checking routes, see as cancelled
I believe the problem is here:
res.redirect('admin/account-groups/' + outcome.newId + '/');
That's a relative path so it'll be appended onto the current URL. I suspect you want something like this:
res.redirect('/admin/account-groups/' + outcome.newId + '/');
I know this questions has almost the same title but the issue is different.
I'm using Jade template engine v.1.11.0 built into latest Keystone.js release. In a controller, I query the data with two view.on('init') callbacks. First callback only queries one record and always passes. The second sometimes don't.
category.js
var keystone = require('keystone');
var async = require('async');
exports = module.exports = function (req, res) {
var view = new keystone.View(req, res);
var locals = res.locals;
// Init locals
locals.section = 'category';
locals.filters = {
category: req.params.category
};
locals.data = {
sections: [],
category: {}
};
// Load current category
view.on('init', function (next) {
var q = keystone.list('Category').model.findOne({
key: locals.filters.category
});
q.exec(function (err, result) {
locals.data.category = result;
locals.section = locals.data.category.name.toLowerCase();
next(err);
});
});
// Load sections
view.on('init', function (next) {
var q = keystone.list('Section').model.find().where('category').in([locals.data.category]).sort('sortOrder').exec(function(err, results) {
if (err || !results.length) {
return next(err);
}
async.each(results, function(section, next) {
keystone.list('Article').model.find().where('section').in([section.id]).sort('sortOrder').exec(function(err, articles){
var s = section;
if (articles.length) {
s.articles = articles;
locals.data.sections.push(s);
} else {
locals.data.sections.push(s);
}
});
}, function(err) {
console.log(locals.data.sections);
});
next(err);
});
});
view.render('category');
};
In my view, I should always get this passed:
sections: { _id: 574b909b43ff68163ed86bf2, publicTitle: 'Title 1', key: 'name-1', sortOrder: 3, name: 'Name 1', __v: 0, category: 574b8960947f45f034ac89b4, text: '', image: {} }
category: { _id: 574b8960947f45f034ac89b4, key: 'blabla', sortOrder: 1, name: 'Blabla', __v: 0, image: {} }
But 60% of the time, I get this:
sections:
category: { _id: 574b8960947f45f034ac89b4, key: 'johndoe', sortOrder: 1, name: 'JohnDoe', __v: 0, image: {} }
Strange thing is, If I go to another category, which has more sections and like 30 articles, I get sections 90% of the time, but still missing them 10%. This persists in both development and production.
Is some "next()" firing too early? I can't see where I messed up.
Alright. After some reading on async I managed to get it to work properly using async.parallel.
var keystone = require('keystone');
var async = require('async');
exports = module.exports = function (req, res) {
var view = new keystone.View(req, res);
var locals = res.locals;
// Init locals
locals.section = 'category';
locals.filters = {
category: req.params.category
};
locals.data = {
sections: [],
category: {}
};
// Load current category
view.on('init', function (next) {
var q = keystone.list('Category').model.findOne({
key: locals.filters.category
});
q.exec(function (err, result) {
locals.data.category = result;
locals.section = locals.data.category.name.toLowerCase();
getChildrenRecords(locals.data.category, next);
});
});
function getChildrenRecords(category, next){
var q = keystone.list('Section').model.find().where('category').in([category]).sort('sortOrder').exec(function(err, results) {
if (err || !results.length) {
return next(err);
}
callItems = [];
for(var i = 0; i < results.length; i++) {
var data = results[i];
callItems.push(makeCallbackFunction(data));
}
function makeCallbackFunction(section) {
return function (callback) {
keystone.list('Article').model.find().where('section').in([section.id]).sort('sortOrder').exec(function(err, articles){
if (err) return callback(err);
if (articles.length) { section.articles = articles; }
locals.data.sections.push(section);
callback();
});
};
}
async.parallel(callItems, function(err, results){
next(err);
});
});
}
// Render the view
view.render('category');
};
Each of my schemas have a method, called toItem() which converts the doc to a more verbose / human-readable form. How can I create a toItems() method to do the same thing for an array of documents?
My example schema:
var mongoose = require('mongoose');
var membershipSchema = new mongoose.Schema({
m : { type: mongoose.Schema.ObjectId, ref: 'member' },
b : { type: Date, required: true },
e : { type: Date },
a : { type: Boolean, required: true }
});
var accountSchema = new mongoose.Schema({
n : { type: String, trim: true },
m : [ membershipSchema ]
});
accountSchema.methods.toItem = function (callback) {
var item = {
id : this._id.toString(),
name : this.n,
members : []
};
(this.m || []).forEach(function(obj){
item.members.push({
id : obj.m.toString(),
dateBegin : obj.b,
dateEnd : obj.e,
isAdmin : obj.a
});
});
return callback(null, item);
};
var accountModel = mongoose.model('account', accountSchema);
module.exports = accountModel;
I've tried using statics, methods, and third-party libraries, but nothing clean works. I would like to keep this as simple / clean as possible and have the toItems() function contained within my model file.
Thank you, in advance.
Your toItem() method is specific to the schema / model. Your toItems() method sounds more like a utility method which can / will be used by all of your models. If so, I would move create the toItems() method inside a utility file. You would simply pass in the array of documents and the utility method would call the individual toItem() method on each document.
For example:
var async = require('async');
var toItems = function (models, callback) {
models = models || [];
if (models.length < 1) { return callback(); }
var count = -1,
items = [],
errors = [];
async.forEach(models, function (model, next) {
count++;
model.toItem(function (err, item) {
if (err) {
errors.push(new Error('Error on item #' + count + ': ' + err.message));
}
else {
items.push(item);
}
next();
});
}, function (err) {
if (err) {
return callback(err);
}
if (errors.length > 0) {
return callback(errors[0]);
}
return callback(null, items);
});
};
module.exports.toItems = toItems;
I have the following code:
var method = PushLoop.prototype;
var agent = require('./_header')
var request = require('request');
var User = require('../models/user_model.js');
var Message = require('../models/message_model.js');
var async = require('async')
function PushLoop() {};
method.startPushLoop = function() {
getUserList()
function getUserList() {
User.find({}, function(err, users) {
if (err) throw err;
if (users.length > 0) {
getUserMessages(users)
} else {
setTimeout(getUserList, 3000)
}
});
}
function getUserMessages(users) {
// console.log("getUserMessages")
async.eachSeries(users, function (user, callback) {
var params = {
email: user.email,
pwd: user.password,
token: user.device_token
}
messageRequest(params)
callback();
}, function (err) {
if (err) {
console.log(err)
setTimeout(getUserList, 3000)
}
});
}
function messageRequest(params) {
var url = "https://voip.ms/api/v1/rest.php?api_username="+ params.email +"&api_password="+ params.pwd +"&method=getSMS&type=1&limit=5"
request(url, function(err, response, body){
if (!err) {
var responseObject = JSON.parse(body);
var messages = responseObject.sms
if (responseObject["status"] == "success") {
async.eachSeries(messages, function(message, callback){
console.log(params.token)
saveMessage(message, params.token)
callback();
}, function(err) {
if (err) {
console.log(err)
}
// setTimeout(getUserList, 3000)
})
} else {
// setTimeout(getUserList, 3000)
}
} else {
console.log(err)
// setTimeout(getUserList, 3000)
}
});
setTimeout(getUserList, 3000)
}
function saveMessage(message, token) {
// { $and: [ { price: { $ne: 1.99 } }, { price: { $exists: true } }
// Message.find({ $and: [{ message_id: message.id}, {device_token: token}]}, function (err, doc){
Message.findOne({message_id: message.id}, function (err, doc){
if (!doc) {
console.log('emtpy today')
var m = new Message({
message_id: message.id,
did: message.did,
contact: message.contact,
message: message.message,
date: message.date,
created_at: new Date().toLocaleString(),
updated_at: new Date().toLocaleString(),
device_token: token
});
m.save(function(e) {
if (e) {
console.log(e)
} else {
agent.createMessage()
.device(token)
.alert(message.message)
.set('contact', message.contact)
.set('did', message.did)
.set('id', message.id)
.set('date', message.date)
.set('message', message.message)
.send();
}
});
}
}) //.limit(1);
}
};
module.exports = PushLoop;
Which actually works perfectly fine in my development environment - However in production (i'm using Openshift) the mongo documents get saved in an endless loop so it looks like the (if (!doc)) condition always return true therefore the document gets created each time. Not sure if this could be a mongoose issue - I also tried the "find" method instead of "findOne". My dev env has node 0.12.7 and Openshift has 0.10.x - this could be the issue, and i'm still investigating - but if anybody can spot an error I cannot see in my logic/code please let me know
thanks!
I solved this issue by using a "series" like pattern and using the shift method on the users array. The mongoose upsert findOneOrCreate is good however if there is a found document, the document is returned, if one isn't found and therefore created, it's also returned. Therefore I could not distinguish between the newly insert doc vs. a found doc, so used the same findOne function which returns null if no doc is found I just create it and send the push notification. Still abit ugly, and I know I could have used promises or the async lib, might refactor in the future. This works for now
function PushLoop() {};
var results = [];
method.go = function() {
var userArr = [];
startLoop()
function startLoop() {
User.find({},function(err, users) {
if (err) throw err;
users.forEach(function(u) {
userArr.push(u)
})
function async(arg, callback) {
var url = "https://voip.ms/api/v1/rest.php?api_username="+ arg.email +"&api_password="+ arg.password +"&method=getSMS&type=1&limit=5"
request.get(url, {timeout: 30000}, function(err, response, body){
if (!err) {
var responseObject = JSON.parse(body);
var messages = responseObject.sms
var status = responseObject.status
if (status === "success") {
messages.forEach(function(m) {
var message = new Message({
message_id: m.id,
did: m.did,
contact: m.contact,
message: m.message,
date: m.date,
created_at: new Date().toLocaleString(),
updated_at: new Date().toLocaleString(),
device_token: arg.device_token
});
var query = { $and : [{message_id: m.id}, {device_token: arg.device_token}] }
var query1 = { message_id: m.id }
Message.findOne(query).lean().exec(function (err, doc){
if (!doc || doc == null) {
message.save(function(e) {
console.log("message saved")
if (e) {
console.log("there is an error")
console.log(e)
} else {
console.log(message.device_token)
var messageStringCleaned = message.message.toString().replace(/\\/g,"");
var payload = {
"contact" : message.contact,
"did" : message.did,
"id" : message.message_id,
"date" : message.date,
"message" : messageStringCleaned
}
var note = new apns.Notification();
var myDevice = new apns.Device(message.device_token);
note.expiry = Math.floor(Date.now() / 1000) + 3600; // Expires 1 hour from now.
note.badge = 3;
note.alert = messageStringCleaned;
note.payload = payload;
apnsConnection.pushNotification(note, myDevice);
}
})
}
});
});
}
else {
console.log(err)
}
}
});
setTimeout(function() {
callback(arg + "testing 12");
}, 1000);
}
// Final task (same in all the examples)
function series(item) {
if(item) {
async( item, function(result) {
results.push(result);
return series(userArr.shift());
});
} else {
return final();
}
}
function final() {
console.log('Done');
startLoop();
}
series(userArr.shift())
});
}
}
module.exports = PushLoop;