moko v0.1.5
Moko
Generator-powered, highly extendible models made for use with co.
Installation
npm install mokoExample Usage
var moko = require('moko'),
validators = require('moko-validators'),
mongo = require('moko-mongo');
var User = moko('User');
User
.attr('_id')
.attr('name', { required: true })
.attr('email', { format: 'email' })
User
.use(validators)
.use(mongo('localhost:27017/moko-test'));
co(function*() {
var user = yield new User();
user.name = 'Ryan';
user.email = 'ryan@slingingcode.com';
yield user.save();
console.log(user._id);
})();Table of Contents
Philosophy
moko is the spiritual successor to
modella, updated for use with ECMA
6 generators.
moko provides bare-bones models and an API to extend them. Plugins are mixed
into models adding functionality as needed. Plugins are easy to write (see the Moko Plugin Creation
Guide) and
readily shared within the moko community (see the Moko Plugin List)
The organization is open for those who would like to join. Simply reach out to Ryan and say hi!
Events
Moko provides two types of events, async (powered by generators) and sync
(powered by functions). async events happen before an operation and allow you to mutate the data,
while sync events allow you to react to changes. In general, async events
end with ing (eg. saving, creating, initializing).
Plugin authors are also encouraged to emit their own events to make it easy for users to hook into the plugins.
Built in async events:
Moko will emit the following async events. Notice that you must use generators for async events, although your generators do not necessarily need to yield themselves.
initializing(attrs)- called when a model is first initialzedsaving(dirty)- called before savecreating(dirty)- called before save when the model did not exist priorupdating(dirty)- called before save when the model did exist prior
Examples:
User.on('initializing', function*(user, attrs) {
attrs.name = 'Bob';
});
var user = yield new User({name: 'Stephen'});
console.log(user.name) // Logs "Bob";User.on('creating', function*(user, attrs) {
attrs.createdAt = new Date();
});
var user = yield new User({name: 'Stephen'});
yield user.save();User.on('saving', function*(u, dirty) {
var others = yield User.find({name: u.name}).count();
if(others) throw new Error('Will not save with non-unique name');
});Built in sync events:
Function (not generator) events are emitted after something happens on the model.
Built in events include:
initialize(instance)- called after an instance is done initializingchange(attr, newVal, oldVal)- called when an attr changeschange attr(newVal, oldVal)- called whenattrchangessave- called after savecreate- called after save when model did not exist priorupdate- called after save when model did exist prior
User
.attr('name')
.attr('email');
User.on('change name', function(user, name, old) {
console.log("User changed name from %s to %s", old, name);
});
co(function*() {
var user = yield new User({name: 'Bob'});
user.name = 'Steve';
})();Fire and forget email sending on user-creation.
User.on('create', function(user) {
emails.sendWelcomeEmail(u.email, function() { }) // anonymous callback fn
});API Documentation
Creating and Configuring Models
Model Creation
To create a Model, simply call moko with the name of the model. If preferred
you can also call new Moko(name).
var moko = require('moko');
var User = moko('User');
User instanceof moko // true
console.log(User.modelName) // => 'User'
// or
var Person = new moko('Person');Model Configuration
All model configuration methods are chainable.
Model.attr(name, opts)
Defines attribute name on instances, adding change events. (see
events below)
opts is an object that can be used by plugins.
var User = moko('User');
User
.attr('name', { required: true })
.attr('age', { type: Number });
User.on('change name', function(u, name, old) {
console.log(old + ' changed name to ' + name);
});
var user = yield new User({name: 'Steve'});
user.name = 'Bob';Model.validate(fn*)
Adds a validator fn*(instance) which can add errors to an instance.
var User = moko('User');.attr('name');
var requireNameSteve = function*(user) {
if(user.name != 'Steve') user.error('name', 'must be steve');
};
User.validate(requireNameSteve);Model.use(plugin)
Configures a model to use a plugin. See the list of plugins or the plugin creation guide to get started writing your own.
var mongo = require('moko-mongo'),
validators = require('moko-validators'),
timestamps = require('moko-timestamps');
var db = yield mongo('mongodb://localhost:27017/moko-test');
User
.use(db)
.use(validators)
.use(timestamps);Event Methods
A moko Model mixes in co-emitter, see the full documentation for details. It exposes the following methods:
on(event, fn*)- add a listener for an eventonce(event, fn*)- add a one-time listener for an eventemit(event, ...args)- emit an event with...argsoff(event, listener)- remove a listener for an eventremoveAllListeners()- removes all listenershasListeners(event)- check whether an event has listenerslisteners(event)- get an array of listeners for an event
Working with Instances
Instances are created by yielding to a new Model. This allows async events
to happen on initializing (such as pre-populating relations from the
database).
var user = yield new User({name: 'Bob'});instance.set(attrs)
Takes an object of attrs and sets the models properties accordingly. If an
attribute is passed in that isn't defined on the model, it will be skipped.
var User = moko('User');
User.attr('name');
var bob = yield new User();
bob.set({name: 'Bob', age: 24});
bob.name == 'Bob' // true
bob.age === undefined // true, age wasn't a defined attrinstance.toJSON()
Returns a cloned object of the instances attrs.
this.body = user.toJSON(); // inside koaPersistence / Sync Layer
moko provides a variety of methods to persist models to a sync layer. Out of
the box it does not use have any sync layer baked in, so without using one (as a
plugin) these methods can throw errors.
instance.primary(val)
If val is undefined, returns the primary key of the model (by default
instance._id or instance.id, whichever exists.
If val is specified, sets the primary key attribute (instance._id or
instance.id).
You can also specify a primary key manually at time of attribute definition:
var User = moko('User').attr('username', { primary: true });
var user = yield new User();
user.primary('bob');
console.log(user.username) // 'Bob'
user.primary() // 'Bob'instance.isNew()
Returns whether instance.primary() is defined.
var userA = yield new User({_id: 123 });
userA.isNew(); // false
var userB = yield new User();
userB.isNew(); // trueinstance.save(skipValidations)
Will save if a sync-layer plugin has been registered. Will only save if the model is valid, otherwise will throw an error.
To save regardless of being valid or not, pass in skipValidations as true.
try {
yield user.save();
} catch(e) {
// deal with error;
}instance.remove()
Will remove the model if the sync layer provides it.
Will set instance.removed to true.
yield user.remove();Errors and Validation
instance.error(attr, reason)
Registers an error (reason) on attr.
if(!user.name) user.error('name', 'is required');instance.errors(attr)
Returns an error object, in the format of {attr: [errors]}.
If attr is specified, returns an array of errors for that attr.
If no errors are registered for attr it returns an empty array.
user.error('name', 'is stupid');
user.error('name', 'is short');
user.error('age', 'is too young');
user.errors() // { name: ['is stupid', 'is short'], age: ['is too young'] }
user.errors('name') // ['is stupid', 'is short']
user.errors('favoriteColor') // []instance.isValid(attr)
Runs all validators, and checks whether the instance has any errors or not.
If attr is provided, reports whether that specific attr is valid.
To support async validations, you must yield to isValid.
var valid = yield user.isValid();Utilities
Moko exports common utilities to make it so that plugins don't need to end up requiring the same modules.
Built in utilities include:
moko.utils.clone- does a deep clone of an objectmoko.utils.isGenerator- returns true if a function is a generatormoko.utils.type- returns a string representation of type (eg.array)
