UNPKG

magic-models

Version:

A simple, free software magical ORM.

488 lines (420 loc) 11.9 kB
# Magic Models A simple, free software magical node.js ORM. For the moment, it only works with MariaDB. ## Installation ```bash npm install --python=python2 ``` ## Usage ### Connection ```javascript var db = require('magic-models')({ host: '127.0.0.1', user: 'root', password: 'toor', db: 'foo' }); db.on('loaded', function() { // event emitted when all the connexion to the database is etablished }).on('error', function(e) { // event emitted when an error occur while trying to connect the database }); ``` Once you are done, you can simply quit magic-models with the following: ```javascript db.exit(); // note that if you don't do it, the program may not exit ``` ### Executing raw queries You can make raw queries easily, with the following: ```javascript db.query(query, function(errors, rows, infos) { // errors is an array of strings, or null // rows is an array of rows, or undefined if an error occured // infos is an array containing the following informations: // insertId, affectedRows, numRows, query }); ``` ## Defining Models ```javascript db.define('User', { id: { type: 'int', key: 'primary' // the primary key field is saved in db.models.User.primaryKey ; if no primary key is specified, this value will be null. }, login: { type: 'varchar', length: 32, key: 'unique', validate: { isUnique: true }, required: 'Login is mandatory' }, mail: { type: 'varchar', length: 255, default: 'NULL' } }) ``` Note that the name of the model is singular and the ORM will look for a table with the plural name. To avoid this behaviour, you can specify a custom table name with the third argument of `db.define`: ```javascript db.define('User', fields, { tableName: 'Members' }); ``` It is also possible to specify the models directory with the following: ```javascript db.modelsDir(require('path').join(__dirname, './models')) ``` A list of directories is also possible: ```javascript db.modelsDir([require('path').join(__dirname, './models'), require('path').join(__dirname, './moreModels')]) ``` In both of this two cases, you need to define your models in this way: ```javascript module.exports = function(db) { db.define('User', { id: { type: 'int', key: 'primary' }, login: { type: 'varchar', length: 32, key: 'unique', validate: { isUnique: true }, required: 'Login is mandatory' }, mail: { type: 'varchar', length: 255, default: 'NULL' } }); } ``` You can define multiple times the same model, it will be updated : ```javascript db.define('User', { id: { type: 'int', key: 'primary' } }); db.define('User', { // The model 'User' will contain the two fields 'id' and 'login' login: { type: 'varchar', length: 32, key: 'unique', validate: { isUnique: true } } }); ``` You can avoid this behaviour by giving the `erase` option to the third argument of `db.define` : ```javascript db.define('User', { // The model 'User' will only contain the field 'login' login: { type: 'varchar', length: 32, key: 'unique', validate: { isUnique: true } }, { erase: true } }); ``` The models you define are in `db.models` ### Default values There is several ways to set default values: ```javascript default: "foo" // default value will be "foo" at the creation default: ["foo", "bar"] // default value will be "foo" at the creation and "bar" at the update default: { created: "foo", // default value will be "foo" at the creation and "bar" at the update modified: "bar" // you can specify only one of this two if you want } default: ["foo-bar"] // default value will be "foo-bar" at the creation and at the update defaultCreated: "foo" // default value will be "foo" at the creation defaultModified: "bar" // default value will be "bar" at the update defaultBoth: "foo-bar" // default value will be "foo-bar" at the creation and at the update ``` By default, the orm will look for a `createdAt` and a `modifiedAt` field. If they don't exist, the `.create()` and the `.update()` methods will not work. You can change the fields looking for or avoid this behaviour with the third argument of `db.define`: ```javascript db.define('User', fields, { createdAt: 'date' // the field containing the creation date will be 'date' modifiedAt: false // the orm will not try to update this field when doing an update }); ``` ### Models validation rules There is several ways to define validations rules: ```javascript // type 1: custom error message with builtin rule validate: { is: { val: regExp, msg: customMessage } } // type 2: default error message with builtin rule validate: { is: regExp } // type 3: default error message with custom rule validate: { custom: function(args, cb) { cb(true); // the validation fail if you call cb with false // never call the callback more than once, or there will be an undefined behaviour }, } // type 4: custom error message with several builtin rules validate: { custom: { is: regExp, not: regExp, msg: customMessage } } // type 5: custom error message with one or several custom rules validate: { custom: { first: function(args, cb) { ; }, second: function(args, cb) { ; }, msg: customMessage } } ``` Note that you can mix types 4 and 5 validations rules. For the moments, the following rules are builtin: ```javascript is: /^[a-z]*$/i // also accepts a string not: /^[0-9]*$/ // also accepts a string isIn: ["foo", "bar"] notIn: ["toto", "titi"] isUnique: true len: [4, 32] minLen: 4 maxLen: 32 between: [5, 10] min: 5 max: 10 isAfter: "1474-11-13" // also accepts a date object isBefore: "1605-11-05" // also accepts a date object isBetween: [new Date(1474, 10, 13), new Date(1605, 10, 5))] // also accepts strings isUrl: "https://npmjs.org" isIP: "127.0.0.1" // matchs IPv4 and IPv6 isIPv4: "127.0.0.1" isIPv6: "2001:db8:0:85a3::ac1f:8001" ``` In your custom validations rules, args will be this object: ```javascript { data: data, // an object containing all the values setted model: model, // the model checkField: { name: 'login', // the name of the field you have to check val: 'admin' // the value of the field you have to check }, rule: { name: 'custom', // the name of the rule val: [Function] // the value of the rule; for a custom rule, it is the validation function }, msg: message // the message that will be send if validation rule fail } ``` ## Models methods Once you have defined your model, the following methods will be available: Note that all of this methods are calling the db.query method. So, the callbacks of this methods are given to the db.query method and the arguments you will receive are the same. ```javascript db.define('User', fields); db.models.User.all({ fields: ['login', 'password'], where: { id: { 'gt': 5 } }, order: { id: 'DESC' }, group: 'login', // you can also give an array if you want to group several fields limit: 20, offset: 10, // it will work only if a limit is defined too count: true // will only return the number of rows }, function(errors, rows, infos) { // getting all the rows matching, with only the fields you specified // if you no specify fields, you will get all of the fields defined in the model // note that the order, group, limit, offset and count clauses only work for the method .find, .count and .all }); db.models.User.find(options, function(errors, rows, infos) { // this method is the same as .all, but you will get only one row. // rows will be an object containing the row // it you don't want to have this rows format, you can call .all with a limit: 1 option }); db.models.User.count(options, function(errors, rows, infos) { // this method is the same as .all, but rows will coutain the number of rows matching // rows will be an integer or an array of integers // it you don't want to have this rows format, you can call .all with a count: true option }); db.models.User.describe(function(errors, rows, infos) { // getting the description of the table in the database // rows will be an object with the field name as key }); db.models.User.create({ login: 'root', password: 'toor' }, function(errors, rows, infos) { // check for the validity of the values, but not the default values set in the model definition // create an user in the database with the login 'root' and the password 'toor' // rows will be an object containing all the inserted values }); db.models.User.update({ values: { login: 'admin' }, where: { id: 1 } }, function(errors, rows, infos) { // check for the validity of the values // change the login of the users who have the id '1' // rows will be an empty array }); db.models.User.delete({ id: 2 }, function(errors, rows, infos) { // remove the user who have the id '2' // rows will be an empty array } ``` If you give the callback as the first argument of this functions, it will works well, and the object usually used as first argument will be `{}`. ### Where You can combine a lot of options in the where clause: ```javascript where: { id: 5, // WHERE `id` = "5" id: [1, 5], // WHERE `id` IN("1", "5") id: { bewteen: [1, 5], // WHERE `id` BETWEEN "1" AND "5" gt: 5, // WHERE `id` > "5" gte: 5, // WHERE `id` >= "5" lt: 5, // WHERE `id` < "5" lte: 5, // WHERE `id` <= "5" ne: 5, // WHERE `id` != "5" eq: 5, // WHERE `id` = "5" not: 5 // WHERE `id` NOT "5" }, login: { like: "%admin%", // WHERE `login` LIKE "%admin%" match: /[a-z]*/i // WHERE `login` REGEXP "[a-z]*" } or: [ // WHERE ((`id` = "5") OR (`login` = "admin")) {id: 5}, {login: "admin"} ] } ``` You can combine all of this, including **AND** and **OR**, priorities will be respected. ```javascript where: { or: [ {type: 'dog', color: 'white', size: 'large'}, {type: 'cat', or: [ {size: 'small'}, {color: 'black'} ] } ] } ``` It'll generate the following request: ```sql WHERE ((`type` = "dog" AND `color` = "white" AND `size` = "large") OR (`type` = "cat" AND ((`size` = "small") OR (`color` = "black"))))' ``` ## Hooks You can add functions that will be called before and after each method. The following hooks are supported : ```javascript beforeFind(datas, callback); afterFind(errors, rows, infos, callback); beforeValidate(datas, callback); afterValidate(datas, callback); beforeCreate(datas, callback); afterCreate(errors, rows, infos, callback); beforeSave(datas, callback); afterSave(errors, rows, infos, callback); beforeUpdate(datas, callback); afterUpdate(errors, rows, infos, callback); beforeDelete(datas, callback); afterDelete(errors, rows, infos, callback); ``` ### Order of operations #### Create ```javascript // beforeValidate validate // afterValidate // beforeCreate // beforeSave create // afterSave // afterCreate ``` #### Update ```javascript // beforeValidate validate // afterValidate // beforeUpdate // beforeSave update // afterSave // afterUpdate ``` #### Delete ```javascript // beforeDelete delete // afterDelete ``` #### Find ```javascript // beforeFind find // afterFind ``` ### Declaring hooks Hooks have to be declared in the third parameter of `db.define`. ```javascript db.define('User', fields, { beforeValidate: function(datas, callback) { callback(datas); }, afterCreate: function(errors, rows, infos, callback) { callback(errors, rows, infos); } }); ``` Note that if you don't call the callback, it will results to an undefined behaviour. In the case of an **update**, the param `datas` will only contain the new values, not the where informations. ## Contributing If you think a feature is missing, you can open an issue, or try to make it and then do a pull request. If you find a bug, open an issue too. You can also fix it and do a pull request. If you think this doc is incomplete, you can improve it the same way. ### Author **Emeraude**