Skip to content

Instantly share code, notes, and snippets.

@rsmoorthy
Created February 3, 2015 21:28
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save rsmoorthy/3f133a5f76744f3cf5bb to your computer and use it in GitHub Desktop.
Save rsmoorthy/3f133a5f76744f3cf5bb to your computer and use it in GitHub Desktop.
sails-hook-extmodel: Sails.js hook for extending Models

fw Extended Models

This hook extends the models, its definition and its behaviour.

For now, various features will get added in this hook. And we will migrate them to proper ones, as we go by.

Extended enum support

This feature extends the values of enum from other sources. Other sources could be:

Usage

{
	attributes: {
		name: 'string',
		country: {
			type: 'string',
			enum: []
		},
		fees: {
			type: 'integer',
			enum: [],
			enum_options: { model: 'regmaster', fields: ['amount'] }
		}
	},

  ext_attributes: {
      country: { enum_options: { identity: 'country' } }
  }
}

All options are specified using enum_options. The property enum_options can be specified under attributes or under ext_attributes

  1. JSON file (identity)

    • All files with *.enum.json are loaded automatically
    • Identity name (identify.enum.json) is specified using property identity (specified within enum_options)
    • You typically do this, if you want to share the enum options among diff models or if the number of enum options are large and hence needs to be kept in a separate file
    • The property enum can be kept empty, and is merged from the JSON file
  2. From another Model (model, fields)

    • The enum options are the name of the model and the fields from where they need to be fetched
    • These values are obtained dynamically, every time when the validation is requested
    • This function will update the enum property with the latest values, so the validations can happen correctly
/**
* fw extended models hook
*/
module.exports = function (sails) {
var includeAll = require('include-all');
var _ = require('lodash');
var hook = {
enumOptions: {},
// These are the extended properties that needs to be moved from
// _attributes to ext_attributes
extProperties: ['enum_options'],
defaults: {
// defaults
opts: {}
},
configure: function() {
},
// Load all files of *.enum.json
loadEnumerations: function() {
var options = includeAll({
dirname : sails.config.paths.models,
filter : /(.+)\.enum\.json$/,
replaceExpr : /^.*\//,
flattenDirectories: true,
});
_.extend(this.enumOptions, options);
sails.emit('hook:extmodel:loaded');
},
// Update models with enum options
enumUpdateModels: function(identity, key, modelId) {
var prop = sails.models[modelId]._attributes[key].enum;
prop = prop || [];
// Since ext_attributes is an object (map), get the keys (left hand side)
// Append to original enum using "union", so only uniq values are placed
prop = _.union(prop, _.keys(this.enumOptions[identity]));
sails.models[modelId]._attributes[key].enum = prop;
sails.models[modelId]._validator.validations[key].in = prop;
sails.models[modelId].definition[key].enum = prop;
},
// Move extended properties to their right place
moveExtProperties: function() {
var self = this;
// Move ext properties from attributes to ext_attributes
_.each(sails.models, function(modelConfig, modelId) {
sails.models[modelId].ext_attributes = modelConfig.ext_attributes || {};
_.each(modelConfig._attributes, function(opts, key) {
if(!(_.isObject(opts))) return;
_.each(opts, function(val, prop) {
// If Property in extProperties
if(_.indexOf(self.extProperties, prop) > -1) {
sails.models[modelId].ext_attributes[key] = sails.models[modelId].ext_attributes[key] || {};
sails.models[modelId].ext_attributes[key][prop] = val;
delete(sails.models[modelId]._attributes[key][prop]);
delete(sails.models[modelId]._validator.validations[key][prop]);
delete(sails.models[modelId].definition[key][prop]);
}
});
});
});
},
// All those properties (for all models) that have enum_options - identity
// (reading from a file)
updateEnumForIdentity: function() {
var self = this;
// Get each model and find params
_.each(sails.models, function(modelConfig, modelId) {
var extAttr = modelConfig.ext_attributes;
if(!extAttr) return;
_.each(extAttr, function(opts, key) {
// If the extended attribute is not in the original model
if(!modelConfig._attributes[key]) return;
if(opts.enum_options && opts.enum_options.identity && self.enumOptions[opts.enum_options.identity])
self.enumUpdateModels(opts.enum_options.identity, key, modelId);
});
});
},
// Set auto beforeValidate callback for each model
// Call this function once only
setAutoValidateCallback: function() {
var self = this;
// Get each model and find params
_.each(sails.models, function(modelConfig, modelId) {
var extAttr = modelConfig.ext_attributes;
if(!extAttr) return;
_.each(extAttr, function(opts, key) {
if(opts.enum_options && opts.enum_options.model && opts.enum_options.fields) {
sails.models[modelId]._callbacks.beforeValidate.push( function(values, cb) {
// Set our own function for beforeValidate
hook.updateEnumForModel(modelId, values, cb);
});
return false; // only once per model
}
});
});
},
// All those properties that have enum_options with `model` will be updated
// also check if such attributes are there in the values
updateEnumForModel: function(modelId, values, next) {
if(!sails.models[modelId].ext_attributes)
return next();
var myopts = {};
// Get all attributes that have enum_options as model
_.each(sails.models[modelId].ext_attributes, function(opts, key) {
if(! key in values) return;
if(!(_.isObject(opts))) return;
if(!opts.enum_options) return;
if(!opts.enum_options.model) return;
myopts.model = opts.enum_options.model;
myopts.fields = opts.enum_options.fields;
myopts.key = key;
return false;
});
if(!(myopts.model && myopts.fields))
return next();
// Now enum_options: { model: xx, fields: xx } is found
// Fetch from the model
if(!sails.models[myopts.model])
return next(new Error("Model: " + modelId + "key:" + key + " references enum_options for model:" + myopts.model + " which does not exist"));
async.series([
function(cb) {
sails.models[myopts.model].find().exec(function(err, rows) {
if(err) cb(err);
var prop = _.uniq(_.pluck(rows, myopts.fields));
sails.models[modelId]._attributes[myopts.key].enum = prop;
sails.models[modelId]._validator.validations[myopts.key].in = prop;
sails.models[modelId].definition[myopts.key].enum = prop;
return cb();
});
},
], function(err) {
if(err) return next(err);
return next();
});
},
updateModels: function() {
var self = this;
this.moveExtProperties();
this.updateEnumForIdentity();
},
// Run when sails loads-- be sure and call `next()`.
initialize: function (next) {
var self = this;
this.loadEnumerations();
// Check the models after the ORM gets loaded
sails.after(['hook:orm:loaded', 'hook:extmodel:loaded'], function() {
self.updateModels();
self.setAutoValidateCallback();
});
// If the models get reloaded
sails.on('hook:orm:reloaded', function() {
self.updateModels();
});
return next();
},
routes: {
before: {
},
after: {
}
}
};
return hook;
};
@leigh-johnson
Copy link

I'm fairly new to Sails.Js - is this code intended to extend sails/lib/hooks/index.js? I know it's not best practice to edit node modules directly, so where would this go if that's the case?

Edit: found http://sailsjs.org/#!/documentation/concepts/extending-sails/Hooks & reading now, sorry for the silly comment!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment