UNPKG

modelling

Version:

Simple model part for custom mvc over express/connect routes.

380 lines (261 loc) 12.1 kB
# Modelling Simple model part for custom mvc over express/connect routes. The idea is quite simple. This is the model part, your express route functions are the controllers, and your html is the view part. It's just a wrapper over [Waterline](https://www.npmjs.org/package/waterline), to make it really easy to use with your express route middleware. Why do this when you have great mvc frameworks like [Sails](https://www.npmjs.org/package/sails)? The answer is simple, sometimes you don't need a framework. For example, if you are not creating an app, but a library, where you need to control part of the model, but you don't really know the whole model, a framework can be very annoying. ## Install Install via npm: npm install modelling To use it inside your project: ``` var orm = new modelling(options); ``` and then, for example, with express: ``` app.get('/route/:id', orm.use('mymodel'), function(req, res, next) { //all waterline collections are placed in req.model req.model.mymodel.findOne({id: req.params.id}).exec(...); }); ``` ## Options * __collections__ synonym for _"models"_. * __adapters__ Waterline adapters definition. For example: ``` var options = { adapters: { disk: require('sails-disk') } }; new modelling(options); ``` * __connections__ Waterline adapters definition. For example: ``` var options = { connections: { conn1: { adapter: 'disk' } } }; new modelling(options); ``` * __policies__ Policies to be applied to models. You write them in the form of express/connect middleware. They can be applied before or after retrieving the model. For example: ``` var options = { policies: { loggedIn: function(req, res, next) { if(req.session.userId) { next(); } else { res.redirect('/login'); } }, isAdmin: { fn: function(req, res, next) { (!req.model || !req.model.user) && return next('user model needed'); req.model.user.findOne({id: req.session.userId}, function(err, user) { if(!err && user && user.isAdmin) { next(); } else { next(err||'You are not Admin'); } }); }, after: true } } }; new modelling(options); ``` Notice in this example that we have 2 policies defined, _"loggedIn"_ to ensure user is logged in, and _"isAdmin"_ to check if user has admin rights. Because we need user data to check admin rights, we set _"after"_ property to true; * __models__ Waterline models definition, but with an __optional__ extra parameter. The extra parameter is _"policies"_, and is a string, or array of strings where you define the name of default policies to apply to model. Models __must__ comply with waterline model definition. For example: ``` var options = { models: { user: { identity: 'user', connection: 'conn1', schema: true, policies: 'loggedIn', attributes: { name: {type: 'string', required: true}, pass: {type: 'string', required: true}, isAdmin: 'boolean' } } } }; ``` ##API ###Adapters * **setAdapter([add,] [name,] adapter)** Returns modelling instance for chaining purposes. * __add__ Boolean. If _true_, the adapter definition will be added to the rest. Otherwise adapters definition will be replaced. Defaults _false_. * __name__ String. The name of the adapter definition. If ommited, _adapter_ definition must include the name as first property. * __adapter__ Object. The definition of the adapter. If _name_ parameter was ommited _adapter_ definition must include the name as first property. The following are equivalent: ``` instance.setAdapter('pg', require('sails-postgres')); ``` ``` instance.setAdapter({pg: require('sails-postgres'}); ``` If the adapter definition was already present, it will be overwritten. After all changes are made to adapters, _done_ function must be called to be applied in waterline. * **adapters([name])** Returns adapters definition. If _name_ is present, will return the definition of the specified adapter. * **remAdapter([name])** Returns modelling instance for chaining purposes. Deletes all adapters definition. If name is present, will only delete the definition for the specified adapter. After all changes are made to adapters, _done_ function must be called to be applied in waterline. ###Connections * **setConnection([add,] [name,] connection)** Returns modelling instance for chaining purposes. * __add__ Boolean. If _true_, the connection definition will be added to the rest. Otherwise connections definition will be replaced. Defaults _false_. * __name__ String. The name of the connection definition. If ommited, _connection_ definition must include the name as first property. * __connection__ Object. The definition of the connection. If _name_ parameter was ommited _connection_ definition must include the name as first property. The following are equivalent: ``` instance.setConnection('localhost', {adapter: 'disk'}); ``` ``` instance.setConnection({localhost: {adapter: 'disk'}}); ``` If the connection definition was already present, it will be overwritten. After all changes are made to connections, _done_ function must be called to be applied in waterline. * **connections([name])** Returns connections definition. If _name_ is present, will return the definition of the specified connection. * **remConnection([name])** Returns modelling instance for chaining purposes. Deletes all connections definition. If name is present, will only delete the definition for the specified connection. After all changes are made to connections, _done_ function must be called to be applied in waterline. ###Models * **setModel([add,] [name,] model)** Returns modelling instance for chaining purposes. * __add__ Boolean. If _true_, the model definition will be added to the rest. Otherwise models definition will be replaced. Defaults _false_. * __name__ String. The name of the model definition. If ommited, _model_ definition must include the name as first property. * __model__ Object. The definition of the model. If _name_ parameter was ommited _model_ definition must include the name as first property. The following are equivalent: ``` instance.setModel('localhost', {adapter: 'disk'}); ``` ``` instance.setModel({localhost: {adapter: 'disk'}}); ``` If the model definition was already present, it will be overwritten. After all changes are made to models, _done_ function must be called to be applied in waterline. * **models([name])** Returns models definition. If _name_ is present, will return the definition of the specified model. * **remModel([name])** Returns modelling instance for chaining purposes. Deletes all models definition. If name is present, will only delete the definition for the specified model. After all changes are made to models, _done_ function must be called to be applied in waterline. ###Policies * **setPolicy([add,] [name,] policy)** Returns modelling instance for chaining purposes. * __add__ Boolean. If _true_, the policy definition will be added to the rest. Otherwise policies definition will be replaced. Defaults _false_. * __name__ String. The name of the policy definition. If ommited, _policy_ definition must include the name as first property. * __policy__ Object. The definition of the policy. If _name_ parameter was ommited _policy_ definition must include the name as first property. The following are equivalent: ``` instance.setPolicy('pg', require('sails-postgres')); ``` ``` instance.setPolicy({pg: require('sails-postgres'}); ``` If the policy definition was already present, it will be overwritten. As policies are not a part of waterline, is not necesary to call _done_ function when you only change policies. * **policies([name])** Returns policies definition. If _name_ is present, will return the definition of the specified policy. * **remPolicy([name])** Returns modelling instance for chaining purposes. Deletes all policies definition. If name is present, will only delete the definition for the specified policy. As policies are not a part of waterline, is not necesary to call _done_ function when you only change policies. ###General * **done()** Returns void. This function is called when you want to apply changes in models, adapters or connections. * **use(options)** Returns a function that can be placed as express/connect middleware. If all policies comply, it places all models specified in `req.model`. __options__ can be a _string_, an _array_ of strings or an _object_. * If it's a __string__ it will be interpreted as the name of the model you want to access. Default policies of the model will be applied. * If it's an __array__ it will be interpreted as an array of names of the models you want to access. Default policies of all models will be applied. * If it's an __object__, it must contain a _models_ property, and optionally can contain a _policies_ property. * _models_: Has to be of type _string_ or _array_ of strings. * _policies_: Here you can define custom policies to implement in a particular route. You can also override default policies of models. If you redefine a policy to `false`, it will not be applied this time. For example: ``` app.get('/user/docs', orm.use('user'), function(req, res, next) { //"user" waterline's model definition is placed in req.model.user req.model.user.findOne({id: req.session.userId}).populate('docs').exec(...); }); ``` Get the _user_ model in this route. Notice you can _populate_ other models; ``` app.post('/user/docs', orm.use(['user', 'docs']), function(req, res, next) { req.model.user.findOne({id: req.session.userId}, function(err, user){ if(!err && user) { req.model.docs.find().exec(function(err, docs){ //do something }); } }); }); ``` This is an inefficient clone of the example above, but good for showing how to get both _user_ and _docs_ models in this route. Now, for the next example, we get a little more complicated. Suppose we are making _"The John's Club"_ application, and there is a form where users register themselves. Suppose there are two basic rules: * Your name must be 'John'. * There cannot be two Johns with the same lastname. Here is what we could do: ``` app.post('/johns', orm.use({ policies: { loggedIn: false, hasJohnName: function(req, res, next) { //ensure username is John if(req.body.name == 'John') { next(); } else { next("You'r not a John"); } }, uniqueLastname: { fn: function(req, res, next) { req.model.user.findOne({lastname: req.body.lastname}, function(err, user) { if(err) return next(err); if(user) return next('There is an other John with your lastname'); next(); } }, after: true } }, models: 'user' }), function(req, res, next) { //create john user }); ``` As you see, we've got 3 policies. * The first one actually overrides the loggedIn policy of user model. As this is the result of a post from an auto register form, we know the user won't be logged in. So we take this policy out. * The second policy applies before we retrieve the model. It ensures that the name of the user is actually 'John'. * The third rule applies after we retrieve the model. It ensures there is no other John with your lastname. <!-- ##What's next * Pub/Sub using socket.io for automatic data update in view (probably using Angular). * Automatic REST api for each model (still not sure about this). --> ## Help! Any suggestions, bug reports, bug fixes, Pull Requests, etc, are very wellcome ([here](https://github.com/agmoyano/modelling/issues)). Thanks for reading!.