diff --git a/lib/model-builder.js b/lib/model-builder.js index b152bbf6..89cd6793 100644 --- a/lib/model-builder.js +++ b/lib/model-builder.js @@ -9,6 +9,7 @@ var assert = require('assert'); var DefaultModelBaseClass = require('./model.js'); var List = require('./list.js'); var ModelDefinition = require('./model-definition.js'); +var mergeSettings = require('./utils').mergeSettings; // Set up types require('./types')(ModelBuilder); @@ -283,11 +284,15 @@ ModelBuilder.prototype.define = function defineClass(className, properties, sett }); // Merge the settings + subclassSettings = mergeSettings(settings, subclassSettings); + + /* Object.keys(settings).forEach(function (key) { if(subclassSettings[key] === undefined) { subclassSettings[key] = settings[key]; } }); + */ // Define the subclass var subClass = modelBuilder.define(className, subclassProperties, subclassSettings, ModelClass); diff --git a/lib/utils.js b/lib/utils.js index 56a24334..2dd6da47 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -3,6 +3,7 @@ exports.fieldsToArray = fieldsToArray; exports.selectFields = selectFields; exports.removeUndefined = removeUndefined; exports.parseSettings = parseSettings; +exports.mergeSettings = mergeSettings; var traverse = require('traverse'); @@ -127,3 +128,53 @@ function parseSettings(urlStr) { } return settings; } + +/** + * Merge model settings + * + * Folked from https://github.com/nrf110/deepmerge/blob/master/index.js + * + * The original function tries to merge array items if they are objects + * + * @param {Object} target The target settings object + * @param {Object} src The source settings object + * @returns {Object} The merged settings object + */ +function mergeSettings(target, src) { + var array = Array.isArray(src); + var dst = array && [] || {}; + + if (array) { + target = target || []; + dst = dst.concat(target); + src.forEach(function (e, i) { + if (typeof target[i] === 'undefined') { + dst[i] = e; + } else { + if (target.indexOf(e) === -1) { + dst.push(e); + } + } + }); + } else { + if (target && typeof target === 'object') { + Object.keys(target).forEach(function (key) { + dst[key] = target[key]; + }); + } + Object.keys(src).forEach(function (key) { + if (typeof src[key] !== 'object' || !src[key]) { + dst[key] = src[key]; + } + else { + if (!target[key]) { + dst[key] = src[key] + } else { + dst[key] = mergeSettings(target[key], src[key]) + } + } + }); + } + + return dst; +} \ No newline at end of file diff --git a/test/loopback-dl.test.js b/test/loopback-dl.test.js index 75c0115e..229d1526 100644 --- a/test/loopback-dl.test.js +++ b/test/loopback-dl.test.js @@ -649,6 +649,96 @@ describe('Load models from json', function () { done(null, customer); }); + + it('should be able to extend models with merged settings', function (done) { + var modelBuilder = new ModelBuilder(); + + var User = modelBuilder.define('User', { + name: String + }, { + defaultPermission: 'ALLOW', + acls: [ + { + principalType: 'ROLE', + principalId: '$everyone', + permission: 'ALLOW' + } + ], + relations: { + posts: { + type: 'hasMany', + model:'Post' + } + } + }); + + var Customer = User.extend('Customer', + {customerId: {type: String, id: true}}, + { + defaultPermission: 'DENY', + acls: [ + { + principalType: 'ROLE', + principalId: '$unauthenticated', + permission: 'DENY' + } + ], + relations: { + orders: { + type: 'hasMany', + model:'Order' + } + } + } + ); + + assert.deepEqual(User.settings, { + defaultPermission: 'ALLOW', + acls: [ + { + principalType: 'ROLE', + principalId: '$everyone', + permission: 'ALLOW' + } + ], + relations: { + posts: { + type: 'hasMany', + model:'Post' + } + }, + strict: false + }); + + assert.deepEqual(Customer.settings, { + defaultPermission: 'DENY', + acls: [ + { + principalType: 'ROLE', + principalId: '$everyone', + permission: 'ALLOW' + }, + { + principalType: 'ROLE', + principalId: '$unauthenticated', + permission: 'DENY' + } + ], + relations: { + posts: { + type: 'hasMany', + model:'Post' + }, + orders: { + type: 'hasMany', + model:'Order' + } + }, + strict: false + }); + + done(); + }); }); describe('DataSource constructor', function(){