The title sounds complicated. I have a users table, and each user can have multiple interests. These interests are linked to the user via a lookup table. In PHP I queried the users table, then for each one did a query to find interests. How can I do this in Node.js/Sequelize? How can I set up some sort of promises too? For example:
sequelize.query("SELECT * FROM users").success(function(users) {
for (var u in users) {
sequelize.query("SELECT interests.id, interests.title FROM interests, user_interests WHERE interests.id = user_interests.interest_id AND user_interests.user_id = " + users[u].id).success(function(interests) {
if (interests.length > 0) {
users[u].interests = interests;
}
});
}
return users;
});
From the return statement in the bottom of your code, it seems you have not totally grasped the asynchronous nature of node.js. The return statement in your code will be executed directly after the first call to sequelize.query, that is, before the query returns. This means that users will be undefined.
If you wanted to actually "return" the users and their interest, I would suggest something like this:
sequelize.query("SELECT * FROM users").success(function(users) {
done = _.after(users.length, function () {
callback(users)
})
for (var u in users) {
sequelize.query("SELECT interests.id, interests.title FROM interests, user_interests WHERE interests.id = user_interests.interest_id AND user_interests.user_id = " + users[u].id).success(function(interests) {
if (interests.length > 0) {
users[u].interests = interests;
}
done();
});
}
});
In the code above _ refers to a utility lib. that executes the callback function after the function has been called users.length times. Callback is a function that is passed to your piece of code, and should process the return result, for example returning the users to the client in the context of a webserver.
Another comment - if you are only doing raw SQL queries, Sequelize might not be the best choice for you. Any reason why you are not using the SQL driver directly? If you want to use sequelize, you should take advantage of its features. Try to the define a model for users and interests, set up an association and load up users and interests in one go using JOINs / eager loading
update: An example using promises
sequelize.query("SELECT * FROM users").then(function(users) {
return sequelize.Promise.map(users, function (u) {
return sequelize.query("SELECT interests.id, interests.title FROM interests, user_interests WHERE interests.id = user_interests.interest_id AND user_interests.user_id = " + users[u].id).then(function(interests) {
if (interests.length > 0) {
user.interests = interests;
}
});
});
});
Related
I am trying to get data from another database before reading data in the table. However, I can't seem to find a way to access it properly.
The best I've got so far is based on some other examples both on Microsoft's documentation and on StackOverflow but they all seem to fail.
table.read(function (context) {
var results = context.tables("table2").read();
var text = results[0].column;
context.query.where({ columnName: text });
return context.execute();
});
I get an error when doing this saying that column doesn't exist.
As per your description, if I do not misunderstand, you want to query table2 in table1 operations in EasyTables scripts.
we can leverage "use()" to custom middleware to specify middleware to be executed for every request against the table as the description on the document of azure-mobile-apps sdk at
E.G.
var queries = require('azure-mobile-apps/src/query');
var insertMiddleware = function(req,res,next){
var table = req.azureMobile.tables('table2'),
query = queries.create('table2')
.where({ TestProperty : req.body.testproperty });
table.read(query).then(function(results) {
if(results){
req.someStoreData = somehander(results); //some hander operations here to get what you want to store and will use in next step
next();
}else{
res.send("no data");
}
});
};
table.insert.use(insertMiddleware, table.operation);
table.insert(function (context) {
console.log(context.req.someStoreData);
return context.execute();
});
More example:
async function filterByAllowedDomain(context) {
var domains = await context.tables('domains')
.where({ allowed: true })
.read();
var categories = await context.tables('categories')
.where(function (ids) {
return this.domainId in ids;
}, domains.map(d => d.id))
.read();
context.query.where(function (ids) {
return this.categoryId in ids;
}, categories.map(c => c.id));
return context.execute(); }
The tables module in azure-mobile-apps-node sdk contains functionality for adding tables to an Azure Mobile App. It returns a router that can be attached to an express app with some additional functions for registering tables. Which actually leverage Azure SQL (SQL Server database service on Azure).
Hope it helps.
So here's the problem. I have a REST API that handles a booking creation, however, before saving the booking inside mongo it validates if there is a clash with another booking.
exports.create = function(req, res) {
var new_type = new Model(req.body);
var newBooking = new_type._doc;
//check if the new booking clashes with existing bookings
validateBooking.bookingClash(newBooking, function(clash){
if(clash == null) // no clashes, therefore save new booking
{
new_type.save(function(err, type) {
if (err)
{
res.send(err); //error saving
}
else{
res.json(type); //return saved new booking
}
});
}
else //clash with booking
{
//respond with "clashDate"
}
});
};
Here you have the validation function to check if there is a clash with bookings on the same day:
exports.bookingClash = function (booking, clash) {
//find the bookings for the same court on the same day
var courtId = (booking.courtId).toString();
Model.find({courtId: courtId, date: booking.date}, function(err, bookings) {
if(err == null && bookings == null)
{
//no bookings found so no clashes
clash(null);
}
else //bookings found
{
//for each booking found, check if the booking start hour falls between other booking hours
for(var i = 0; i<bookings.length ; i++)
{
//here is where I check if the new booking clashes with bookings that are already in the DB
{
//the new booking clashes
//return booking date of the clash
clash(clashDate); //return the clashDate in order to tell the front-end
return;
}
}
//if no clashes with bookings, return null
clash(null);
}
});
};
So, ALL of this works with one single new booking. However, now I want to be able to handle a recursive booking (booking that is made weekly). I have recreated the "create" function and call the validateBooking.bookingClash function inside a for loop.
Unfortunately, when I run this, it calls the bookingClash function perfectly, but when it reaches the line making the search in the database:
Model.find({courtId: courtId, date: booking.date}, function(err, bookings)
It does not wait for the callback and before handling the response "clash", makes i++ and continues.
How can I make it work and wait for the callback?
var array = req.body;
var clashes = [];
for(var i = 0; i<array.length;i++)
{
validateBooking.bookingClash(array[i], function(clash)
{
if(clash)
{
clashes.push(clash);
}
else{
console.log("no clash");
}
}
}
Seems like a basic async call problem, for loops do not wait for callbacks to be called.
You could use async 'series' function for exmaple instead of the for loop. This way each find will get called after the previous one.
Mongoose also has a promise based syntax which can help you : http://mongoosejs.com/docs/promises.html
You Can use async eachSeries
async.eachSeries(users, function iterator(user, callback) {
if(something) {
//thing you want to do
callback();
} else {
callback();
}
}
Since you are using callback functions there are two ways you could try to solve this:
1) use some external library that allows you to perform an asynchronous map operation and run all the checks for each clash. Once they are done check the combined results for a clash and proceed accordingly
I would suggest using the async library
your code would look something like:
async.map(array,(entry,callback) => validateBooking.bookingClash(entry,callback),(error,mappingResults)=>{...})
2) you could try to change this function to a recursive one
`function recursiveValidation(arrayToCheck,mainCallback){
if(arrayToCheck.length === 0) {
return cb(null} // end of array without errors
}
validateBooking.bookingClash(_.head(arrayToCheck), function(clash)
{
if(clash)
{
return mainCallback(clash);
}
return recursiveValidation(_.tail(arrayToCheck),mainCallback);
}
}`
The above code is just a mockup but it should show the point.
The _ is lodash
No need to changing anything in your code except the declaration use let instead of var and your loop should work.
var array = req.body;
var clashes = [];
`
for(**let** i = 0; i<array.length;i++)
{
validateBooking.bookingClash(array[i], function(clash)
{
if(clash)
{
clashes.push(clash);
}
else{
console.log("no clash");
}
}
}`
You have to understand the difference between let and var. Also why var cannot be used for running async code inside a loop.
Learn about let: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let
I found the way to get this done after trying all of your answers.
What I had to do was this:
validateBooking.singleBooking(new_type._doc, newBookingClubId, function (clash) {
if (clash == null) // no clash
{
validatorArray.push(0);
if(validatorArray.length == array.length) //has received everything from mongo
{
console.log("Clashes: " + clashes.toString());
if(validatorArray.indexOf(1) > -1) //contains a clash
{
var error = {
code: 409,
message: "409 Conflict",
clashes: clashes
};
errorsHandler.handleError(error, res);
}
This way, I created an array called "validatorArray" that was called every time I received something back from Mongo.
This way I could easily compare the length of the array of bookings and the validatorArray length. When they were equal, it meant that it had received everything back from mongo and could send back the response.
Thanks for the help!
I have the following many-to-many relationship in my Parse.com application:
Bit ↔ Message
The following fetches related messages from a 'Bit' object:
const query = new Parse.Query('Bit');
query.include('participants');
query.include('user');
query.equalTo('objectId', req.query.bitId);
const promise = query.first().then(function(bit) {
return new Promise(function(resolve) {
const relation = bit.relation('messages');
relation.query().find().then(function(messages) {
resolve(messages);
});
});
});
*Note: messages is a relation column type not a pointer type.
The result inside messages is an array that contains all of the related messages for that bit. Each message contains a pointer to a User called sender. Is it possible to include this User object in the query result? It would be nice if I could use the include() method on a relational query like so:
relation.include('sender');
You can qualify a relation's query as you would any other query. (Also, you've got some superfluous promise code in the OP. I cleaned that up a little, but the point of the answer is on the commented line)...
var query = new Parse.Query('Bit');
query.include('participants');
query.include('user');
query.equalTo('objectId', req.query.bitId);
var promise = query.first().then(function(bit) {
var relationalQuery = bit.relation('messages').query();
relationalQuery.include("sender"); // <-- the point
return relationalQuery().find();
}).then(function(messages) {
return resolve(messages);
});
I'm learning FRP using Bacon.js, and would like to assemble data from a paginated API in a stream.
The module that uses the data has a consumption API like this:
// UI module, displays unicorns as they arrive
beautifulUnicorns.property.onValue(function(allUnicorns){
console.log("Got "+ allUnicorns.length +" Unicorns");
// ... some real display work
});
The module that assembles the data requests sequential pages from an API and pushes onto the stream every time it gets a new data set:
// beautifulUnicorns module
var curPage = 1
var stream = new Bacon.Bus()
var property = stream.toProperty()
var property.onValue(function(){}) # You have to add an empty subscriber, otherwise future onValues will not receive the initial value. https://github.com/baconjs/bacon.js/wiki/FAQ#why-isnt-my-property-updated
var allUnicorns = [] // !!! stateful list of all unicorns ever received. Is this idiomatic for FRP?
var getNextPage = function(){
/* get data for subsequent pages.
Skipping for clarity */
}
var gotNextPage = function (resp) {
Array.prototype.push.apply(allUnicorns, resp) // just adds the responses to the existing array reference
stream.push(allUnicorns)
curPage++
if (curPage <= pageLimit) { getNextPage() }
}
How do I subscribe to the stream in a way that provides me a full list of all unicorns ever received? Is this flatMap or similar? I don't think I need a new stream out of it, but I don't know. I'm sorry, I'm new to the FRP way of thinking. To be clear, assembling the array works, it just feels like I'm not doing the idiomatic thing.
I'm not using jQuery or another ajax library for this, so that's why I'm not using Bacon.fromPromise
You also may wonder why my consuming module wants the whole set instead of just the incremental update. If it were just appending rows that could be ok, but in my case it's an infinite scroll and it should draw data if both: 1. data is available and 2. area is on screen.
This can be done with the .scan() method. And also you will need a stream that emits items of one page, you can create it with .repeat().
Here is a draft code (sorry not tested):
var itemsPerPage = Bacon.repeat(function(index) {
var pageNumber = index + 1;
if (pageNumber < PAGE_LIMIT) {
return Bacon.fromCallback(function(callback) {
// your method that talks to the server
getDataForAPage(pageNumber, callback);
});
} else {
return false;
}
});
var allItems = itemsPerPage.scan([], function(allItems, itemsFromAPage) {
return allItems.concat(itemsFromAPage);
});
// Here you go
allItems.onValue(function(allUnicorns){
console.log("Got "+ allUnicorns.length +" Unicorns");
// ... some real display work
});
As you noticed, you also won't need .onValue(function(){}) hack, and curPage external state.
Here is a solution using flatMap and fold. When dealing with network you have to remember that the data can come back in a different order than you sent the requests - that's why the combination of fold and map.
var pages = Bacon.fromArray([1,2,3,4,5])
var requests = pages.flatMap(function(page) {
return doAjax(page)
.map(function(value) {
return {
page: page,
value: value
}
})
}).log("Data received")
var allData = requests.fold([], function(arr, data) {
return arr.concat([data])
}).map(function(arr) {
// I would normally write this as a oneliner
var sorted = _.sortBy(arr, "page")
var onlyValues = _.pluck(sorted, "value")
var inOneArray = _.flatten(onlyValues)
return inOneArray
})
allData.log("All data")
function doAjax(page) {
// This would actually be Bacon.fromPromise($.ajax...)
// Math random to simulate the fact that requests can return out
// of order
return Bacon.later(Math.random() * 3000, [
"Page"+page+"Item1",
"Page"+page+"Item2"])
}
http://jsbin.com/damevu/4/edit
I'm trying to write a ORM in Node.js. I want to declare a class named Model which will be used to declare a data object, like:
Users = new Model(someModelRules);
newUser = new Users(userInfomation);
the data model User have a function named find(). Now, I want to make find() chained, like:
Users.find(" name = 'John' ")
.orderedBy("age").desc()
.limit(0,10)
or maybe just a simply find:
Users.find(" name = 'John' ")
to code this find function, I believe I must build the SQL first,and do the SQL query at the end of this find chain.
I don't know how to do this, all I can think of is to add a function like: doQuery(), so I will know it's time to do the SQL query when the doQuery() function was called, like:
Users.find(" name = 'John' ")
.orderedBy("age").desc()
.limit(0,10)
.doQuery();
I know this is a simply solution, but I don't want the extra doQuery() function. :(
So, how should I design this? It would so nice of you if you can show me some example code with comments.
Thx! (sorry for my poor English)
ps. I know the ORM2 has a find function I just want, but I wanna know how to code it and I can barely understand the code in ORM2 as there are no comments. (I'm not gonna use orm2.)
================================= SOLUTION ==============================
Inspired by #bfavaretto :
function User() {
this.find = function(id, condition) {
return new findChain(id, condition);
}
}
function findChain(id, condition) {
this._id = id
this._condition = condition
this.queryTimerSet = false;
this.scheduleQuery = function () {
var self = this;
if(!self.queryTimerSet) {
console.log('[TEST CASE: ' + self._id + '] Insert query into eventLoop');
setTimeout(function(){
console.log('[TEST CASE: ' + self._id + '] Start query: '+self._condition);
}, 0);
self.queryTimerSet = true;
} else {
console.log('[TEST CASE: ' + self._id + '] No need to insert another query');
}
}
this.orderedBy = function(column) {
console.log('[TEST CASE: ' + this._id + '] orderedBy was called');
this._condition = this._condition + ' ORDER BY ' + column
this.scheduleQuery();
return this;
}
this.desc = function() {
// simply add DESC to the end of sql
this._condition = this._condition + ' DESC'
}
this.scheduleQuery();
}
var user = new User();
user.find(1,'SELECT * FROM test').orderedBy('NAME1').desc();
user.find(2,'SELECT * FROM test').orderedBy('NAME2');
user.find(3,'SELECT * FROM test');
runnning this code, you will get the result:
[TEST CASE: 1] Insert query into eventLoop
[TEST CASE: 1] orderedBy was called
[TEST CASE: 1] No need to insert another query
[TEST CASE: 2] Insert query into eventLoop
[TEST CASE: 2] orderedBy was called
[TEST CASE: 2] No need to insert another query
[TEST CASE: 3] Insert query into eventLoop
[TEST CASE: 1] Start query: SELECT * FROM test ORDER BY NAME1 DESC
[TEST CASE: 2] Start query: SELECT * FROM test ORDER BY NAME2
[TEST CASE: 3] Start query: SELECT * FROM test
I believe there must be a better way to achieve this, but this is the best I can get for now.
Any comments?
It is possible to achieve that if you schedule the doQuery logic to run asynchronously (but as soon as possible). I am thinking on something like this:
function User() {
// Flag to control whether a timer was already setup
var queryTimerSet = false;
// This will schedule the query execution to the next tick of the
// event loop, if it wasn't already scheduled.
// This function is available to your model methods via closure.
function scheduleQuery() {
if(!queryTimerSet) {
setTimeout(function(){
// execute sql
// from the query callback, set queryTimerSet back to false
}, 0);
queryTimerSet = true;
}
}
this.find = function() {
// ... logic that builds the sql
scheduleQuery();
return this;
}
this.orderedBy = function() {
// ... logic that appends to the sql
scheduleQuery();
return this;
}
// etc.
}
One totally different approach is to have a single method for building the SQL, and passing the ORDER BY and LIMIT parameters in an options object. Then your call would look like this:
user.find({
what : " name = 'John' ",
orderedBy : "age DESC",
limit : "0,10"
});
This is more suited for SQL queries than what you're trying to do. What you have looks like noSQL stuff like MongoDB, where fetching the records and sorting are separate operations (I think).
You will always have to have a execute/doQuery function at the end of the chain.
This is because all the other functions before the doQuery help build the query that needs to be executed at the end.