UNPKG

loopback-workspace

Version:
242 lines (205 loc) 7.22 kB
// Copyright IBM Corp. 2015,2016. All Rights Reserved. // Node module: loopback-workspace // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT var app = require('../../server/server'); var debug = require('debug')('workspace:model-definition'); module.exports = function(ModelDefinition) { app.once('ready', function() { ready(ModelDefinition); }); }; function ready(ModelDefinition) { var path = require('path'); var fs = require('fs'); var assert = require('assert'); var extend = require('util')._extend; var async = require('async'); var ConfigFile = app.models.ConfigFile; var _ = require('lodash'); /** * Defines a LoopBack `Model`. * * @class ModelDefinition * @inherits Definition */ /** * - `name` is required and must be unique per `Facet` * * @header Property Validation */ ModelDefinition.validatesUniquenessOf('name', { scopedTo: ['app'] }); ModelDefinition.validatesPresenceOf('name'); ModelDefinition.validatesFormatOf('name', { with: /^[\-_a-zA-Z0-9]+$/ }); ModelDefinition.getConfigFromCache = function(cache, modelDef) { var configData = this.getConfigFromData(modelDef); var relations = this.getEmbededRelations(); relations.forEach(function(relation) { var relatedData = getRelated(cache, modelDef.id, relation); if (relation.model === 'ModelAccessControl') { relatedData = relatedData.sort(function(a, b) { if (a.index < b.index) { return -1; } if (a.index > b.index) { return 1; } return 0; }); } configData[relation.as] = formatRelatedData(relation, relatedData); }); return configData; }; function getRelated(cache, id, relation) { var Definition = app.models[relation.model]; var cachedData = Definition.allFromCache(cache); assert(relation.type === 'hasMany', 'embed only supports hasMany'); assert(relation.foreignKey, 'embed requires foreignKey'); return cachedData.filter(function(cached) { return cached[relation.foreignKey] === id; }); } function formatRelatedData(relation, relatedData) { var result; assert(relation.embed && relation.embed.as, 'embed requires "as"'); switch (relation.embed.as) { case 'object': assert(relation.embed.key || relation.embed.keyGetter, 'embed as object requires "key" or "keyGetter"'); result = {}; relatedData.forEach(function(related) { var Definition = app.models[relation.model]; var key; if (relation.embed.key) { key = related[relation.embed.key]; } var keyGetter = relation.embed.keyGetter; if (keyGetter && typeof Definition[keyGetter] === 'function') { key = Definition[keyGetter](related.name, related); } result[key] = related; }); cleanRelatedData(result, relation); return result; break; case 'array': cleanRelatedData(relatedData, relation); return relatedData; break; } assert(false, relation.embed.as + ' is not supported by embed'); } ModelDefinition.getPath = function(facetName, obj) { if (obj.configFile) return obj.configFile; // TODO(ritch) the path should be customizable return path.join(facetName, ModelDefinition.settings.defaultDir, ModelDefinition.toFilename(obj.name) + '.json'); }; ModelDefinition.toFilename = function(name) { if (name === name.toUpperCase()) return name.toLowerCase(); if (~name.indexOf('-')) return name.toLowerCase(); var dashed = _.kebabCase(name); var split = dashed.split(''); if (split[0] === '-') split.shift(); return split.join(''); }; var removeById = ModelDefinition.removeById.bind(ModelDefinition); ModelDefinition.removeById = function(id, cb) { debug('removing model: %s', id); this.findById(id, function(err, modelDef) { if (err) { return cb(err); } if (!modelDef) { return cb(new Error('ModelDefinition ' + id + ' does not exist')); } if (modelDef.readonly) { return cb(new Error('Cannot remove readonly model ' + id)); } removeById(id, function(err) { if (err) return cb(err); function removeModelDef(cb) { var p = ModelDefinition.getPath(modelDef.facetName, modelDef); var file = new ConfigFile({ path: p }); file.remove(cb); } function removeModelDefJs(cb) { fs.unlink(modelDef.getScriptPath(), cb); } async.parallel([ removeModelDef, removeModelDefJs, ], function(err, results) { if (err) return cb(err); cb(null, { result: results }); }); }); }); }; ModelDefinition.destroyById = ModelDefinition.removeById; ModelDefinition.deleteById = ModelDefinition.removeById; ModelDefinition.prototype.remove = function(cb) { this.constructor.removeById(this.id, cb); }; ModelDefinition.prototype.destroy = ModelDefinition.prototype.remove; ModelDefinition.prototype.delete = ModelDefinition.prototype.remove; /** * Remove the foreign key from embeded data and sort the properties in * a well-defined order. * @private */ function cleanRelatedData(relatedData, relation) { assert(relation.foreignKey, 'embeded relation must have foreignKey'); var Entity = require('loopback').getModel(relation.model); for (var ix in relatedData) { var data = Entity.getConfigFromData(relatedData[ix]); delete data[relation.foreignKey]; delete data[relation.embed.key]; // Convert the disableInherit placeholder (myBaseProp: false) back to false if (relation.model === 'ModelProperty' && data.disableInherit) { data = false; } relatedData[ix] = data; } } ModelDefinition.observe('after save', function(ctx, next) { if (!ctx.isNewInstance) return next(); var def = ctx.instance; var scriptPath = def.getScriptPath(); fs.exists(scriptPath, function(exists) { if (exists) { next(); } else { createScript(def, scriptPath, next); } }); }); ModelDefinition.prototype.getClassName = function() { if (!this.name) return null; return _.capitalize(_.camelCase(this.name)); }; ModelDefinition.prototype.getScriptPath = function() { var configFilePath = ModelDefinition.getPath(this.facetName, this); var scriptFilePath = configFilePath.replace(/\.json$/, '.js'); return path.join( ConfigFile.getWorkspaceDir(), scriptFilePath ); }; var templatePath = path.join(__dirname, '..', '..', 'templates', 'scripts', 'model.js.tmpl'); var MODEL_SCRIPT_TEMPLATE = fs.readFileSync(templatePath, 'utf8'); function createScript(def, out, cb) { var script; try { script = _.template(MODEL_SCRIPT_TEMPLATE)({ modelDef: def, modelClassName: def.getClassName(), }); } catch (e) { return cb(e); } fs.writeFile(out, script, cb); } };