var fs = require('fs'); var path = require('path'); var Schema = require('./schema').Schema; var existsSync = fs.existsSync || path.existsSync; if (global.railway) { railway.orm._schemas = []; } module.exports = function init(root) { var railway, app, models; if (typeof root !== 'object' || (root.constructor.name !== 'Compound' && root.constructor.name !== 'CompoundServer')) { railway = global.railway; app = global.app; models = app.models; } else { railway = root; app = railway.app; root = railway.root; models = railway.models; } railway.orm._schemas = []; var confFile = (root || app.root) + '/config/database'; var config = {}; try { var cf = require(confFile); if (cf instanceof Array) cf = cf[0]; if (typeof cs === 'function') { config = cs(railway); } else { config = cf[app.set('env')]; } } catch (e) { console.log('Could not load config/database.{js|json|yml}'); throw e; } // when driver name started with point - look for driver in app root (relative path) if (config.driver && config.driver.match(/^\./)) { config.driver = path.join(app.root, config.driver); } var schema = new Schema(config && config.driver || 'memory', config); schema.log = log; if (!schema.adapter) throw new Error('Adapter is not defined'); if (schema.waitForConnect) { schema.on('connected', function() { loadSchema(schema, railway, app, models); }); } else { loadSchema(schema, railway, app, models); } // check validations and display warning var displayWarning = false; Object.keys(models).forEach(function (model) { var Model = models[model]; if (Model._validations) { displayWarning = true; } }); if (displayWarning) { var $ = railway.utils.stylize.$; // require('util').puts($('WARNING:').bold.red + ' ' + $('I can see that you\'ve added validation to db/schema.js. However schema.js file is only used to describe database schema. Therefore validations configured in db/schema.js will be ignored.\nFor business logic (incl. validations) please create models as separate .js files here: app/models/*.js').yellow); } function loadSchema(schema, railway, app, models) { railway.orm._schemas.push(schema); var context = prepareContext(models, railway, app, schema); // run schema first var schemaFile = (root || app.root) + '/db/schema.'; if (existsSync(schemaFile + 'js')) { schemaFile += 'js'; } else if (existsSync(schemaFile + 'coffee')) { schemaFile += 'coffee'; } else { schemaFile = false; } if (schemaFile) { var code = fs.readFileSync(schemaFile).toString(); if (schemaFile.match(/\.coffee$/)) { code = require('coffee-script').compile(code); } var fn = new Function('context', 'require', 'with(context){(function(){' + code + '})()}'); fn(context, require); } // autoupdate if set app.enable('autoupdate') or freeze schemas by default railway.orm._schemas.forEach(function (schema) { if(app.enabled('autoupdate')){ schema.autoupdate(); } else { schema.freeze(); } }); } function log(str, startTime) { var $ = railway.utils.stylize.$; var m = Date.now() - startTime; railway.utils.debug(str + $(' [' + (m < 10 ? m : $(m).red) + ' ms]').bold); app.emit('app-event', { type: 'query', param: str, time: m }); } function prepareContext(models, railway, app, defSchema, done) { var ctx = {app: app}, _models = {}, settings = {}, cname, schema, connected = 0, wait = 0, nonJugglingSchema = false; done = done || function () {}; /** * Multiple schemas support * example: * schema('redis', {url:'...'}, function () { * describe models using redis connection * ... * }); * schema(function () { * describe models stored in memory * ... * }); */ ctx.schema = function () { var name = argument('string'); var opts = argument('object') || {}; var def = argument('function') || function () {}; schema = new Schema(name || opts.driver || 'memory', opts); railway.orm._schemas.push(schema); wait += 1; ctx.gotSchema = true; schema.on('log', log); schema.on('connected', function () { if (wait === ++connected) done(); }); def(); schema = false; }; /** * Use custom schema driver */ ctx.customSchema = function () { var def = argument('function') || function () {}; nonJugglingSchema = true; def(); Object.keys(ctx.exports).forEach(function (m) { ctx.define(m, ctx.exports[m]); }); nonJugglingSchema = false; }; ctx.exports = {}; ctx.module = { exports: ctx.exports }; /** * Define a class in current schema */ ctx.describe = ctx.define = function (className, callback) { var m; cname = className; _models[cname] = {}; settings[cname] = {}; if (nonJugglingSchema) { m = callback; } else { callback && callback(); m = (schema || defSchema).define(className, _models[cname], settings[cname]); } if (global.railway) { global[cname] = m; } return models[cname] = ctx[cname] = m; }; /** * Define a property in current class */ ctx.property = function (name, type, params) { if (!params) params = {}; if (typeof type !== 'function' && typeof type === 'object' && !(type instanceof Array)) { params = type; type = String; } params.type = type || String; _models[cname][name] = params; }; /** * Set custom table name for current class * @param name - name of table */ ctx.setTableName = function (name) { if (cname) settings[cname].table = name; }; /** * Set configuration param * * @param name - name of param. * @param value - value. */ ctx.set = function (name, value) { if (cname) settings[cname][name] = value; }; ctx.pathTo = railway.map && railway.map.pathTo || {}; /** * If the Schema has additional types, add them to the context * e.g. MySQL has an additional Point type */ if (Schema.types && Object.keys(Schema.types).length) { for (var typeName in Schema.types) { ctx[typeName] = Schema.types[typeName]; } } return ctx; function argument(type) { var r; [].forEach.call(arguments.callee.caller.arguments, function (a) { if (!r && typeof a === type) r = a; }); return r; } } };