PageRenderTime 58ms CodeModel.GetById 29ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/orm.js

https://github.com/radzikowski/node-orm
JavaScript | 527 lines | 433 code | 81 blank | 13 comment | 122 complexity | 5f62c3ce08b0292fcb09600d5f765242 MD5 | raw file
  1. var crypto = require("crypto"),
  2. ORM = function (db) {
  3. this._db = db;
  4. };
  5. ORM.prototype.define = function (model, fields, colParams) {
  6. var orm = this,
  7. associations = [];
  8. var addOneAssociationMethods = function (model, association, associationModel) {
  9. var camelCaseAssociation = association.substr(0, 1).toUpperCase() + association.substr(1);
  10. model.prototype["get" + camelCaseAssociation] = function (cb) {
  11. var self = this;
  12. if (self[association + "_id"] > 0) {
  13. if (self[association]) return cb(self[association]);
  14. var data = {};
  15. data[model + "_id"] = this.id;
  16. orm._db.selectRecords(model, {
  17. "conditions": { "id": self[association + "_id"] },
  18. "callback" : function (err, data) {
  19. if (err || !data || data.length == 0) return cb(null);
  20. cb(new Model(data[0]));
  21. }
  22. });
  23. return;
  24. }
  25. cb(null);
  26. };
  27. model.prototype["unset" + camelCaseAssociation] = function (cb) {
  28. this["set" + camelCaseAssociation](null, cb);
  29. };
  30. model.prototype["set" + camelCaseAssociation] = function (instance, cb) {
  31. var self = this;
  32. if (instance === null) {
  33. self[association + "_id"] = 0;
  34. delete self[association];
  35. return cb();
  36. }
  37. if (!instance.saved()) {
  38. instance.save(function (err, savedInstance) {
  39. if (err) return cb(err);
  40. self[association + "_id"] = savedInstance.id;
  41. self[association] = savedInstance;
  42. cb();
  43. });
  44. return;
  45. }
  46. self[association + "_id"] = instance.id;
  47. self[association] = instance;
  48. cb();
  49. };
  50. };
  51. var addManyAssociationMethods = function (self, association, field) {
  52. var camelCaseAssociation = association.substr(0, 1).toUpperCase() + association.substr(1);
  53. var collection = model + "_" + association;
  54. self.prototype["add" + camelCaseAssociation] = function () {
  55. var instances = [], cb = null;
  56. var data = {};
  57. data[model + "_id"] = this.id;
  58. for (var i = 0; i < arguments.length; i++) {
  59. if (typeof arguments[i] == "function") {
  60. cb = arguments[i];
  61. } else {
  62. instances.push(arguments[i]);
  63. }
  64. }
  65. if (instances.length == 0) {
  66. throw { "message": "No " + camelCaseAssociation + " were given" };
  67. }
  68. var missingInstances = instances.length;
  69. instances.forEach(function (instance) {
  70. if (!instance.saved()) {
  71. instance.save(function (err, savedInstance) {
  72. if (err) return cb(err);
  73. data[field + "_id"] = savedInstance.id;
  74. orm._db.saveRecord(collection, data, function (err) {
  75. if (--missingInstances == 0) cb(null);
  76. });
  77. });
  78. return;
  79. }
  80. data[field + "_id"] = instance.id;
  81. orm._db.saveRecord(collection, data, function (err) {
  82. if (--missingInstances == 0) cb(null);
  83. });
  84. });
  85. };
  86. self.prototype["remove" + camelCaseAssociation] = function () {
  87. var instances = [],
  88. cb = null,
  89. data = {};
  90. data[model + "_id"] = this.id;
  91. for (var i = 0; i < arguments.length; i++) {
  92. if (typeof arguments[i] == "function") {
  93. cb = arguments[i];
  94. } else {
  95. instances.push(arguments[i]);
  96. }
  97. }
  98. if (instances.length == 0) {
  99. orm._db.clearRecords(collection, {
  100. "conditions" : data,
  101. "callback" : function () {
  102. cb(null);
  103. }
  104. });
  105. return;
  106. }
  107. var missingInstances = instances.length;
  108. instances.forEach(function (instance) {
  109. if (typeof instance.id == "undefined" || instance.id == 0) {
  110. if (--missingInstances == 0) cb(null);
  111. return;
  112. }
  113. data[field + "_id"] = instance.id;
  114. orm._db.clearRecords(collection, {
  115. "conditions" : data,
  116. "callback" : function () {
  117. if (--missingInstances == 0) cb(null);
  118. }
  119. });
  120. });
  121. };
  122. self.prototype["set" + camelCaseAssociation] = function () {
  123. var instances = [],
  124. cb = null,
  125. data = {};
  126. data[model + "_id"] = this.id;
  127. for (var i = 0; i < arguments.length; i++) {
  128. if (typeof arguments[i] == "function") {
  129. cb = arguments[i];
  130. } else {
  131. instances.push(arguments[i]);
  132. }
  133. }
  134. orm._db.clearRecords(collection, {
  135. "conditions" : data,
  136. "callback" : function () {
  137. if (instances.length == 0) return cb(null);
  138. var missingInstances = instances.length;
  139. instances.forEach(function (instance) {
  140. if (!instance.saved()) {
  141. instance.save(function (err, savedInstance) {
  142. if (err) return cb(err);
  143. data[field + "_id"] = savedInstance.id;
  144. orm._db.saveRecord(collection, data, function (err) {
  145. if (--missingInstances == 0) cb(null);
  146. });
  147. });
  148. return;
  149. }
  150. data[field + "_id"] = instance.id;
  151. orm._db.saveRecord(collection, data, function (err) {
  152. if (--missingInstances == 0) cb(null);
  153. });
  154. });
  155. }
  156. });
  157. };
  158. self.prototype["get" + camelCaseAssociation] = function (cb) {
  159. var items = [],
  160. conditions = {};
  161. conditions[model + "_id"] = this.id;
  162. orm._db.selectRecords(collection, {
  163. "conditions": conditions,
  164. "callback" : function (err, data) {
  165. if (err) return cb(null);
  166. var ids = [];
  167. for (var i = 0; i < data.length; i++) {
  168. ids.push(data[i][field + "_id"]);
  169. }
  170. orm._db.selectRecords(model, {
  171. "conditions": { "id": ids },
  172. "callback" : function (err, data) {
  173. if (err) return cb(null);
  174. for (var i = 0; i < data.length; i++) {
  175. data[i] = new Model(data[i]);
  176. }
  177. cb(data);
  178. }
  179. });
  180. }
  181. });
  182. };
  183. };
  184. var Model = function (data) {
  185. if (data) {
  186. for (k in data) {
  187. if (!data.hasOwnProperty(k)) continue;
  188. if (fields.hasOwnProperty(k)) {
  189. switch (fields[k].type) {
  190. case "bool":
  191. case "boolean":
  192. data[k] = (data[k] == 1);
  193. break;
  194. case "struct":
  195. if (typeof data[k] == "string") {
  196. data[k] = (data[k].length > 0 ? JSON.parse(data[k]) : {});
  197. }
  198. }
  199. }
  200. this[k] = data[k];
  201. }
  202. }
  203. for (k in fields) {
  204. if (!fields.hasOwnProperty(k)) continue;
  205. if (!data.hasOwnProperty(k) && fields[k].def) this[k] = fields[k].def;
  206. }
  207. for (var i = 0; i < associations.length; i++) {
  208. switch (associations[i].type) {
  209. case "one":
  210. if (!this.hasOwnProperty(associations[i].field + "_id")) {
  211. this[associations[i].field + "_id"] = data[associations[i].field + "_id"] = 0;
  212. }
  213. break;
  214. }
  215. }
  216. if (colParams && colParams.methods) {
  217. for (k in colParams.methods) {
  218. this[k] = data[k] = colParams.methods[k];
  219. }
  220. }
  221. var h = crypto.createHash("md5");
  222. h.update(JSON.stringify(data));
  223. this._dataHash = h.digest("hex");
  224. };
  225. Model.prototype._getData = function () {
  226. var data = {};
  227. if (typeof this.id == "number" && this.id > 0) {
  228. data.id = this.id;
  229. }
  230. for (k in fields) {
  231. if (!fields.hasOwnProperty(k)) continue;
  232. switch (fields[k].type) {
  233. case "bool":
  234. case "boolean":
  235. data[k] = (this[k] == 1);
  236. break;
  237. case "date":
  238. if (this[k]) {
  239. data[k] = (this[k].toJSON ? this[k].toJSON() : null);
  240. } else {
  241. data[k] = "0000-00-00";
  242. }
  243. break;
  244. case "struct":
  245. if (this[k]) {
  246. data[k] = (typeof this[k] == "object" ? JSON.stringify(this[k]) : this[k]);
  247. } else {
  248. data[k] = "";
  249. }
  250. break;
  251. default:
  252. data[k] = this[k];
  253. }
  254. }
  255. for (var i = 0; i < associations.length; i++) {
  256. switch (associations[i].type) {
  257. case "one":
  258. if (this.hasOwnProperty(associations[i].field + "_id")) {
  259. data[associations[i].field + "_id"] = this[associations[i].field + "_id"];
  260. }
  261. }
  262. }
  263. return data;
  264. };
  265. Model.prototype.saved = function () {
  266. var h = crypto.createHash("md5");
  267. h.update(JSON.stringify(this._getData()));
  268. return (this._dataHash == h.digest("hex"));
  269. };
  270. Model.prototype.save = function (callback) {
  271. var data = this._getData(), self = this;
  272. orm._db.saveRecord(model, data, function (err, id) {
  273. if (err) {
  274. if (typeof callback == "function") callback(err);
  275. return;
  276. }
  277. if (!self.id) self.id = id;
  278. if (typeof callback == "function") callback(null, self);
  279. });
  280. };
  281. Model.hasOne = function (association, otherModel) {
  282. var model = !otherModel ? this : otherModel;
  283. associations.push({
  284. "field" : association,
  285. "type" : "one",
  286. "model" : model // this = circular reference
  287. });
  288. addOneAssociationMethods(this, association, model);
  289. };
  290. Model.hasMany = function (association, otherModel, field) {
  291. var model = !otherModel ? this : otherModel;
  292. associations.push({
  293. "field" : association,
  294. "name" : field,
  295. "type" : "many",
  296. "model" : model // this = circular reference
  297. });
  298. addManyAssociationMethods(this, association, field);
  299. };
  300. Model.sync = function () {
  301. orm._db.createCollection(model, fields, associations);
  302. };
  303. Model.clear = function (callback) {
  304. orm._db.clearRecords(model, {
  305. "callback": function (err, info) {
  306. callback(!err);
  307. }
  308. });
  309. };
  310. Model.prototype.remove = function (callback) {
  311. if (typeof this.id == "number" && this.id > 0) {
  312. var self = this;
  313. orm._db.clearRecords(model, {
  314. "conditions": { "id": this.id },
  315. "callback": function (err, info) {
  316. /*
  317. The object will still have all properties and you can save() later
  318. if you want (a new ID should be assigned)
  319. */
  320. delete self.id;
  321. callback(!err);
  322. }
  323. });
  324. return;
  325. }
  326. // no id so nothing to "unsave"
  327. callback(true);
  328. };
  329. Model.get = function (id, callback) {
  330. orm._db.selectRecords(model, {
  331. "conditions": { "id": id },
  332. "callback" : function (err, data) {
  333. if (err || data.length == 0) return callback(null);
  334. callback(new Model(data[0]));
  335. }
  336. });
  337. };
  338. Model.find = function () {
  339. var args = arguments,
  340. callback = null,
  341. config = {},
  342. last_arg = arguments.length - 1;
  343. if (last_arg >= 0) {
  344. callback = arguments[last_arg];
  345. last_arg--;
  346. }
  347. //.find(callback);
  348. //.find(conditions, callback);
  349. //.find(conditions, limit, callback);
  350. //.find(conditions, order, callback);
  351. //.find(conditions, order, limit, callback);
  352. for (var i = 0; i <= last_arg; i++) {
  353. switch (typeof arguments[i]) {
  354. case "object": // conditions
  355. config.conditions = arguments[i];
  356. break;
  357. case "number": // limit
  358. config.limit = arguments[i];
  359. break;
  360. case "string": // order
  361. config.order = arguments[i];
  362. break;
  363. }
  364. }
  365. if (callback !== null) {
  366. config.callback = function (err, data) {
  367. if (err || data.length == 0) return callback(null);
  368. for (var i = 0; i < data.length; i++) {
  369. data[i] = new Model(data[i]);
  370. }
  371. callback(data);
  372. };
  373. }
  374. orm._db.selectRecords(model, config);
  375. };
  376. Model.textsearch = function () {
  377. var args = arguments,
  378. callback = null,
  379. config = {},
  380. last_arg = arguments.length - 1;
  381. if (last_arg >= 0) {
  382. callback = arguments[last_arg];
  383. last_arg--;
  384. }
  385. if (!orm._db.searchRecords) {
  386. return callback(null);
  387. }
  388. //.textsearch(text, callback);
  389. //.textsearch(text, limit, callback);
  390. //.textsearch(limit, text, callback);
  391. for (var i = 0; i <= last_arg; i++) {
  392. switch (typeof arguments[i]) {
  393. case "number": // limit
  394. config.limit = arguments[i];
  395. break;
  396. case "string": // text
  397. config.text = arguments[i];
  398. break;
  399. }
  400. }
  401. if (!config.text) return callback(null);
  402. if (callback !== null) {
  403. config.callback = function (err, data) {
  404. if (err || data.length == 0) return callback(null);
  405. for (var i = 0; i < data.length; i++) {
  406. data[i] = new Model(data[i]);
  407. }
  408. callback(data);
  409. };
  410. }
  411. orm._db.searchRecords(model, config);
  412. };
  413. return Model;
  414. };
  415. exports.connect = function (/* uri_or_dbtype, [db_object], callback */) {
  416. var rawDb, callback, uri, dbType;
  417. if (arguments.length === 3) {
  418. callback = arguments[2];
  419. rawDb = arguments[1];
  420. dbType = arguments[0];
  421. } else {
  422. callback = arguments[1];
  423. var url = require("url");
  424. uri = url.parse(arguments[0]);
  425. if (!uri.protocol) {
  426. return callback(false, { "number": 1, "message": "Protocol not defined" });
  427. }
  428. dbType = uri.protocol.substr(0, uri.protocol.length - 1);
  429. }
  430. var path = require("path"), dbPath = __dirname + "/databases/" + dbType + ".js";
  431. path.exists(dbPath, function (exists) {
  432. if (!exists) {
  433. return callback(false, { "number": 2, "message": "Protocol not installed" });
  434. }
  435. var db = require(dbPath);
  436. var handleResult = function (success, info) {
  437. if (!success) return callback(false, info);
  438. return callback(true, new ORM(info));
  439. };
  440. if (rawDb) {
  441. db.use_db(rawDb, handleResult);
  442. } else {
  443. db.connect(uri, handleResult);
  444. }
  445. });
  446. };