Model and Constraint

Overview

Models represent the domain-specific knowledge and data in an application, which contain the essential fields and behaviors of the data you’re storing. Models can be used in communication, view rendering, database object/relation mapping and so on. Models in bearcat are also simple javaScript objects but with some magic attributes. The behaviors in models can be conversions, validations, computed properties, access controll and so on, in bearcat, all these behaviors are abstrated with constaint and filter.

Model

A simple model in bearcat can be defined as follows:

1
2
3
4
5
6
var SimpleModel = function() {
this.$mid = "simpleModel";
this.num1 = 0;
}
module.exports = SimpleModel;

this model has a simple attribute num1,

with $mid attribute, it tells bearcat the unique id of this model, so that we can get the model by call bearcat.getModel

1
var simpleModel = bearcat.getModel('simpleModel');

then we can set/get value for this model

1
2
simpleModel.$set('num1', 10);
var num1 = simpleModel.$get('num1'); // num1 === 10

you can alao pack data to this model

1
2
3
4
simpleModel.$pack({
'num1': 5
});
num1 = simpleModel.$get('num1'); // num1 === 5

in model attribute, you can add some magic value string which is prefixed with $
for example, you can setup the type of the attribute

1
this.num2 = "$type:Number";

then when you set with a String value to this attribute, it will return an Error object

1
2
3
4
var r = simpleModel.$set('num2', 'aaa');
if (r) {
console.log(r.stack);
}

you can also setup the default value of the attribute

1
this.num3 = "$type:Number;default:20";
1
var num3 = simpleModel.$get('num3'); // num3 === 20

more details about model api, you can visit model api
more details about model magic attribute value, you can visit model magic attribute value

Model filter

Filters are abstractions of operations towards models, validations can be before or after filters, data conversions can be after filters.

for before filter example, you can define a model with a validation function checkNum

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var FilterModel = function() {
this.$mid = "filterModel";
this.num = 0;
}
FilterModel.prototype.checkNum = function(key, value) {
if (typeof value !== 'number') {
return new Error('num must be number');
}
if (value > 10) {
return new Error('num must be small than 10');
}
}
module.exports = FilterModel;
1
2
3
4
5
6
7
8
9
10
11
12
var filterModel = bearcat.getModel('filterModel');
var r = filterModel.$before('checkNum').$set('num', 5); // ok
r = filterModel.$before('checkNum').$set('num', 'aaa'); // error with the checkNum validation, r is the Error object
if(r) {
console.log(r.stack);
}
r = filterModel.$before('checkNum').$set('num', 20);
if (r) {
console.log(r.stack);
}

for after filter example, you can define a model with a transform function transformNum

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var FilterModel = function() {
this.$mid = "filterModel";
this.num = 0;
}
FilterModel.prototype.checkNum = function(key, value) {
if (typeof value !== 'number') {
return new Error('num must be number');
}
if (value > 10) {
return new Error('num must be small than 10');
}
}
FilterModel.prototype.transformNum = function() {
this.num = 12345;
}
module.exports = FilterModel;
1
2
3
var filterModel = bearcat.getModel('modelId');
filterModel.$after('transformNum').$set('num', 3); // set num to 10, with the after filter transformNum
var num = filterModel.$get('num'); // the num is now 12345

notes:

model magic attribute value

in model attribute, you can define the following attribute values

Model constaint

Constraints define what models should be and should not be. However, as javaScript is a dynamic script language, it does not define types, not to mention constaints. Developers have to write validation functions and invoke these functions when validation needed. In this way, constaint is sperated from model, you can not tell what the model really should be when days later, and the invocation codes can not be shared quite well.

In bearcat, constaints are part of models. Moreover simple constaints can build up complex constaints which as constaints combinations

define a constaint just like defining an object

notNullConstraint.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var Util = require('util');
var NotNullConstraint = function() {
this.$cid = "myNotNull";
this.message = "%s must be not null for value %s";
}
NotNullConstraint.prototype.validate = function(key, value) {
var message = this.message;
if (value === null || typeof value === 'undefined') {
return new Error(Util.format(message, key, value));
}
}
module.exports = NotNullConstraint;

with $cid attribute, we define a constraint with an id of myNotNull, a constraint must implement the validate method, which takes key and value as the arguments
when the model attribute value does not match with the constraint, just return the Error object.

To use this constraint in model, just add the constraint id to the specific attribute

1
2
3
4
5
6
var ConstaintModel = function() {
this.$mid = "constaintModel";
this.num1 = "$myNotNull";
}
module.exports = ConstaintModel;

therefore, when you set the num with null, it will return Error object

1
2
3
4
5
var constaintModel = bearcat.getModel('constaintModel');
var r = constaintModel.$set("num1"); // the Error object
if(r) {
console.log(r.stack);
}

Model constraint composition

constraints can be composed into higher level constraints

Constraint composition is userful in several ways:

sizeConstraint.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var SizeConstraint = function() {
this.$cid = "mySize";
this.$constraint = "$myNotNull";
this.message = "key %s value %s length over max %d";
this.max = null;
}
SizeConstraint.prototype.validate = function(key, value) {
var message = this.message;
var maxLen = this.max;
if (maxLen === null) {
return;
}
if (value && value.length > maxLen) {
return new Error(Util.format(message, key, value, maxLen));
}
}
module.exports = SizeConstraint;

with this.$constraint = “$myNotNull”;, we add myNotNull constraint to this constraint, we can add more constraints as a composition, all these constraint will be checked in order when needed

1
this.$constraint = "$myNotNull;myType"

when using this constraint, just add mySize to the specific attribute

constraintModel.js

1
2
3
4
5
6
var ConstaintModel = function() {
this.$mid = "constaintModel";
this.num = "$mySize";
}
module.exports = ConstaintModel;
1
2
3
4
r = constaintModel.$set("num2"); // the Error object
if (r) {
console.log(r.stack);
}

model constraint arguments

constraint can have arguments, which makes it more reuseable as a higher level constraint

in the above example, sizeConstraint has the argument of max

1
2
3
4
5
6
7
8
var ConstaintModel = function() {
this.$mid = "constaintModel";
this.num1 = "$myNotNull";
this.num2 = "$mySize";
this.value = "$mySize(max=5)";
}
module.exports = ConstaintModel;

add constraintModel with this.value = “$mySize(max=5)”

1
2
3
4
5
6
7
8
9
10
constaintModel.$set("value", "aaa"); // ok
var value = constaintModel.$get("value");
console.log(value);
r = constaintModel.$set("value", "aaaaaa"); // the Error object
if (r) {
console.log(r.stack);
}

Builtin constraints

bearcat provides some builtin constraints as primitive constraints

you can use these constraints by default

Model examples

for model examples, you can visit model examples