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.