Geddy uses the Model module for its model layer. Model is an abstract ORM that is compatible with many different types of databases, including Postgres, in-memory, MongoDB and Riak.
Model uses a pretty simple syntax for defining a model. (It should look familiar to anyone who has used an ORM like ActiveRecord, DataMapper, Django's models, or SQLAlchemy.)
defineProperties(properties)
defines the properties for your model.
properties [object]
: an object keyed by name of properties to define
var User = function () {
this.defineProperties({
login: {type: 'string', required: true}
, password: {type: 'string', required: true}
, lastName: {type: 'string'}
, firstName: {type: 'string'}
});
}
property(name, type, options)
defines a single property
name [string]
: the name of the property
type [string]
: the type of the property'string'
'text'
'number'
'int'
'boolean'
'object'
'array'
'datetime'
'date'
'time'
required [boolean]
: sets the property to be required
this.property('login', 'string', {required: true});
this.property('password', 'string', {required: true});
this.property('joined', 'datetime');
this.property('premium', 'boolean');
validatesPresent(property, options)
Sets up a validation to make sure that the property is present.
property [string]
: the name of the property to validate
message [string]
: a message to give the user if the validation fails
this.validatesPresent('login');
// makes sure that the login property is present
validatesFormat(property, regex, options)
Sets up a validation to make sure that the property is formatted correctly.
property [string]
: the name of the property to validate
regex [regex]
: a regular expression that the property value must pass
message [string]
: a message to give the user if the validation fails
this.validatesFormat('login', /[a-z]+/, {message: 'cannot contain numbers'});
// makes sure that the login property does not contain numbers
validatesLength(property, qualifier, options)
Sets up a validation to make sure that the property meets certain length requirements.
property [string]
: the name of the property to validate
min [number]
: the minimum length of the propertymax [number]
: the maximum length of the property- [number]: the exact length of the property
message [string]
: a message to give the user if the validation fails
this.validatesLength('login', {min: 3});
// makes sure that the login property is at least 3 characters long
this.validatesLength('login', {max: 20});
// makes sure that the login property is not longer than 20 characters
this.validatesLength('login', 3)
// makes sure that the login property is exactly 3 characters long
validatesConfirmed(property, param, options)
Sets up a validation to make sure that the property has been confirmed.
property [string]
: the name of the property to validate
param [string]
: the param required to match
message [string]
: a message to give the user if the validation fails
this.validatesConfirmed('password', 'confirmPassword');
// confirms that password and confirmPassword are equal
validatesWithFunction(property, fn, options)
Sets up a validation to make sure that the property has been confirmed.
property [string]
: the name of the property to validate
fn [function]
: a function which, when passed the value of the property, will return true or false
message [string]
: a message to give the user if the validation fails
this.validatesWithFunction('password', function (val) {
// Something that returns true or false
return val.length > 0;
});
// uses the function to see if th length of password is greater than 0
hasOne(model)
Sets up a has-one relationship between this model and another.
model [string]
: the name of the model that this model has one of.
this.hasOne('Profile');
// sets up a has one relationship
// (something) -> has one -> profile
hasMany(model)
Sets up a has-many relationship between this model and another.
model [string]
: the pluralized name of the model that this model has many of.
this.hasMany('Friends');
// sets up a has many relationship
// (something) -> has many -> friends
belongsTo(model)
Sets up a belongs-to relationship between this model and another. A belongs-to is often used as the inverse of a has-many or has-one. Note however that this is not required -- associations are unidirectional.
model [string]
: the singular name of the model that this model belongs to.
this.belongsTo('User');
// sets up a belongs-to relationship
// (something) -> belongs to -> a user
this.adapter
Defines the database adapter for this model
this.adapter = 'mongo';
// makes this model use mongo for it's database
this.adapter = 'riak'
this.adapter = 'postgres'
this.adapter = 'memory'
Instance methods can be defined in the model definition as well.
var User = function () {
...
this.someMethod = function () {
// Do some stuff
};
// sets up a someMethod method on each instance of this model
...
};
isValid()
Returns true if the model instance passes all validations, otherwise it returns false.
user.isValid()
save(fn)
Saves the instance to the database.
fn [function]
: the function to be called when saving is complete
user.save(function (err, data) {
// do things
});
// saves the user then calls the callback function
updateProperties(properties)
Updates the properties of a model and asserts that they are valid; This method will not call save on the instance.
properties [object]
: an object who's keys are property names and its values are the values to change the property to.
user.updateProperties({
login: 'alerxst'
});
// updates the login property and validates it
.add{target_model_name}( instance )
If a model has a hasMany relationship established with another model, you can use this method to add instaces of one model to it’s “parent” model.
- The name of the model you’d like to add
instace [modelInstance]
: The instance to add
var user = geddy.model.User.create(userParams);
var post = geddy.model.Post.create(postParams);
user.addPost(post);
.set{target_model_name}( instance )
If a model has a hasOne relationship established with another model, you can use this method to add an instace of one model to it’s “parent” model.
- The name of the model you’d like to set
instace [modelInstance]
: The instance to set
var user = geddy.model.User.create(userParams);
var account = geddy.model.Account.create(accountParams);
user.setAccount(account);
.get{target_model_name}( fn )
If a model has a hasOne relationship established with another model, you can use this method to add an instace of one model to it’s “parent” model.
hasMany
: the plural name of the model you’d like to get a collection ofhasOne
: the singular name of the model you like to get an instance of
fn [function]
: The function to call once the models are retrieved.
var user = geddy.model.User.create(params);
// hasOne
user.getAccount(function (err, account) {
// do stuff with the user’s account
});
// hasMany
user.getPosts(function (err, posts) {
// do stuff with the user’s posts
});
Static methods can be added by creating a method on the model definition object.
var User = function () {
this.property('login', 'string', {required: true});
this.property('password', 'string', {required: true});
};
User.findByLogin = function (login, callback) {
User.all({login: login}, callback);
}
create(params)
Creates a new model instance and returns it.
params [object]
: an object whos keys are model properties
var params = {
login: 'alex'
, password: 'lerxst'
, lastName: 'Lifeson'
, firstName: 'Alex'
};
var user = User.create(params);
first(query, options, fn)
Use the first
method to find a single item. You can pass it an id, or a set of query parameters in the form of an object-literal. In the case of a query, it will return the first item that matches, according to whatever sort you've specified.
query [string]
: if the query is a string, it will be assumed that it's an id
query [object]
: if the query is an object, it will be interpreted as a Query object
User.first('sdfs-asd-1', function (err, user) {
// do stuff with user
});
User.first({login: 'alerxst'}, function (err, user) {
// do stuff with user
});
all(query, options, fn)
Use the all
method to find lots of items. Pass it a set of query parameters in the form of an object-literal, where each key is a field to compare, and the value is either a simple value for comparison (equal to), or another object-literal where the key is the comparison-operator, and the value is the value to use for the comparison.
query [object]
: if the query is an object, it will be interpreted as a Query object
sort [object]
: each key is a property name, each value can either beasc
ordesc
User.all({location: 'san francisco'}, function (err, users) {
// do stuff with users
});
User.all({location: 'san francisco'}, {sort: {createdAt: 'desc'}}, function (err, users) {
// do stuff with users
});
remove(id, fn)
Remove an instance from the database by id.
id [string]
: the id of the instance to be removed
User.remove('abc-123', function (err, data) {
// do something now that it's removed.
});
Model uses a simple API for finding and sorting items. Again, it should look familiar to anyone who has used a similar ORM for looking up records. The only wrinkle with Model is that the API is (as you might expect for a NodeJS library) asynchronous.
eql
: equal tone
: not equal togt
: greater thanlt
: less thangte
: greater than or equallte
: less than or equallike
: like
A simple string-value for a query parameter is the same as 'eql'. {foo: 'bar'}
is the same as {foo: {eql: 'bar'}}
.
Model supports combining queries with OR and negating queries with NOT.
To perform an 'or' query, use an object-literal with a key of 'or', and an array of query-objects to represent each set of alternative conditions.
To negate a query with 'not', simply use a query-object where 'not' is the key, and the value is the set of conditions to negate.
{foo: 'BAR', bar: {ne: null}}
// Where "foo" is 'BAR' and "bar" is not null
{foo: {'like': 'B'}}
// Where "foo" begins with 'B'
{foo: {lt: 2112}, bar: 'BAZ'}
// Where foo is less than 2112, and bar is 'BAZ'
{or: [{foo: 'BAR'}, {bar: 'BAZ'}]}
// Where "foo" is 'BAR' OR "bar" is 'BAZ'
{or: [{foo {ne: 'BAR'}}, {bar: null}, {baz: {lt: 2112}}]}
// Where "foo" is not 'BAR' OR "bar" is null OR "baz" is less than 2112
{not: {foo: 'BAR', bar: 'BAZ'}}
// Where NOT ("foo" is 'BAR' and "bar" is 'BAZ')
{not: {foo: 'BAZ', bar: {lt: 1001}}}
// Where NOT ("foo" is 'BAZ' and "bar" is less than 1001)
{or: [{foo: {'like': 'b'}}, {foo: 'foo'}], not: {foo: 'baz'}}
// Where ("foo" is like 'b' OR "foo" is 'foo') and NOT "foo" is 'baz'
Both the base model 'constructors,' and model instances are EventEmitters. The emit events during the create/update/remove lifecycle of model instances. In all cases, the plain-named event is fired after the event in question, and the 'before'-prefixed event, of course happens before.
The 'constructor' for a model emits the following events:
- beforeCreate
- create
- beforeValidate
- validate
- beforeUpdateProperties
- updateProperties
- beforeSave (new instances, single and bulk)
- save (new instances, single and bulk)
- beforeUpdate (existing single instances, bulk updates)
- update (existing single instances, bulk updates)
- beforeRemove
- remove
Model-item instances emit these events:
- beforeUpdateProperties
- updateProperties
- beforeSave
- save
- beforeUpdate
- update