Scopes
This commit is contained in:
parent
14398c24d7
commit
8e05e59933
12
README.md
12
README.md
|
@ -34,8 +34,8 @@ var User = schema.define('User', {
|
|||
User.hasMany(Post, {as: 'posts', foreignKey: 'userId'});
|
||||
// creates instance methods:
|
||||
// user.posts(conds)
|
||||
// user.buildPost(data) // like new Post({userId: user.id});
|
||||
// user.createPost(data) // build and save
|
||||
// user.posts.build(data) // like new Post({userId: user.id});
|
||||
// user.posts.create(data) // build and save
|
||||
|
||||
Post.belongsTo(User, {as: 'author', foreignKey: 'userId'});
|
||||
// creates instance methods:
|
||||
|
@ -48,7 +48,7 @@ s.automigrate(); // required only for mysql NOTE: it will drop User and Post tab
|
|||
// work with models:
|
||||
var user = new User;
|
||||
user.save(function (err) {
|
||||
var post = user.buildPost({title: 'Hello world'});
|
||||
var post = user.posts.build({title: 'Hello world'});
|
||||
post.save(console.log);
|
||||
});
|
||||
|
||||
|
@ -65,9 +65,9 @@ Post.all({userId: user.id});
|
|||
// the same as prev
|
||||
user.posts(cb)
|
||||
// same as new Post({userId: user.id});
|
||||
user.buildPost
|
||||
user.posts.build
|
||||
// save as Post.create({userId: user.id}, cb);
|
||||
user.createPost(cb)
|
||||
user.posts.create(cb)
|
||||
// find instance by id
|
||||
User.find(1, cb)
|
||||
// count instances
|
||||
|
@ -139,8 +139,8 @@ If you have found a bug please write unit test, and make sure all other tests st
|
|||
### Common:
|
||||
|
||||
+ transparent interface to APIs
|
||||
+ validations
|
||||
+ -before and -after hooks on save, update, destroy
|
||||
+ scopes
|
||||
+ default values
|
||||
+ more relationships stuff
|
||||
+ docs
|
||||
|
|
|
@ -309,45 +309,46 @@ AbstractClass.hasMany = function (anotherClass, params) {
|
|||
// each instance of this class should have method named
|
||||
// pluralize(anotherClass.modelName)
|
||||
// which is actually just anotherClass.all({thisModelNameId: this.id}, cb);
|
||||
this.prototype[methodName] = function (cond, cb) {
|
||||
var actualCond;
|
||||
if (arguments.length === 1) {
|
||||
actualCond = {};
|
||||
cb = cond;
|
||||
} else if (arguments.length === 2) {
|
||||
actualCond = cond;
|
||||
} else {
|
||||
throw new Error(anotherClass.modelName + ' only can be called with one or two arguments');
|
||||
}
|
||||
actualCond[fk] = this.id;
|
||||
return anotherClass.all(actualCond, cb);
|
||||
};
|
||||
defineScope(this.prototype, anotherClass, methodName, function () {
|
||||
var x = {};
|
||||
x[fk] = this.id;
|
||||
return x;
|
||||
}, {
|
||||
find: find,
|
||||
destroy: destroy
|
||||
});
|
||||
|
||||
// obviously, anotherClass should have attribute called `fk`
|
||||
anotherClass.schema.defineForeignKey(anotherClass.modelName, fk);
|
||||
|
||||
// and it should have create/build methods with binded thisModelNameId param
|
||||
this.prototype['build' + anotherClass.modelName] = function (data) {
|
||||
data = data || {};
|
||||
data[fk] = this.id; // trick! this.fk defined at runtime (when got it)
|
||||
// but we haven't instance here to schedule this action
|
||||
return new anotherClass(data);
|
||||
};
|
||||
function find(id, cb) {
|
||||
anotherClass.find(id, function (err, inst) {
|
||||
if (err) return cb(err);
|
||||
if (inst[fk] === this.id) {
|
||||
cb(null, inst);
|
||||
} else {
|
||||
cb(new Error('Permission denied'));
|
||||
}
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
this.prototype['create' + anotherClass.modelName] = function (data, cb) {
|
||||
if (typeof data === 'function') {
|
||||
cb = data;
|
||||
data = {};
|
||||
}
|
||||
this['build' + anotherClass.modelName](data).save(cb);
|
||||
};
|
||||
function destroy(id, cb) {
|
||||
this.find(id, function (err, inst) {
|
||||
if (err) return cb(err);
|
||||
if (inst) {
|
||||
inst.destroy(cb);
|
||||
} else {
|
||||
cb(new Error('Not found'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
AbstractClass.belongsTo = function (anotherClass, params) {
|
||||
var methodName = params.as;
|
||||
var fk = params.foreignKey;
|
||||
// anotherClass.schema.defineForeignKey(anotherClass.modelName, fk);
|
||||
this.schema.defineForeignKey(anotherClass.modelName, fk);
|
||||
this.prototype[methodName] = function (p, cb) {
|
||||
if (p instanceof AbstractClass) { // acts as setter
|
||||
this[fk] = p.id;
|
||||
|
@ -360,9 +361,88 @@ AbstractClass.belongsTo = function (anotherClass, params) {
|
|||
} else if (!p) { // acts as sync getter
|
||||
return this.cachedRelations[methodName] || this[fk];
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
AbstractClass.scope = function (name, params) {
|
||||
defineScope(this, this, name, params);
|
||||
};
|
||||
|
||||
function defineScope(class, targetClass, name, params, methods) {
|
||||
|
||||
// collect meta info about scope
|
||||
if (!class._scopeMeta) {
|
||||
class._scopeMeta = {};
|
||||
}
|
||||
|
||||
// anly make sence to add scope in meta if base and target classes
|
||||
// are same
|
||||
if (class === targetClass) {
|
||||
class._scopeMeta[name] = params;
|
||||
} else {
|
||||
if (!targetClass._scopeMeta) {
|
||||
targetClass._scopeMeta = {};
|
||||
}
|
||||
}
|
||||
|
||||
Object.defineProperty(class, name, {
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
get: function () {
|
||||
var f = function caller(cond, cb) {
|
||||
var actualCond;
|
||||
if (arguments.length === 1) {
|
||||
actualCond = {};
|
||||
cb = cond;
|
||||
} else if (arguments.length === 2) {
|
||||
actualCond = cond;
|
||||
} else {
|
||||
throw new Error('Method only can be called with one or two arguments');
|
||||
}
|
||||
|
||||
return targetClass.all(merge(actualCond, caller._scope), cb);
|
||||
};
|
||||
f._scope = typeof params === 'function' ? params.call(this) : params;
|
||||
f.build = build;
|
||||
f.create = create;
|
||||
f.destroyAll = destroyAll;
|
||||
for (var i in methods) {
|
||||
f[i] = methods;
|
||||
}
|
||||
|
||||
// define sub-scopes
|
||||
Object.keys(targetClass._scopeMeta).forEach(function (name) {
|
||||
Object.defineProperty(f, name, {
|
||||
enumerable: false,
|
||||
get: function () {
|
||||
merge(f._scope, targetClass._scopeMeta[name]);
|
||||
return f;
|
||||
}
|
||||
});
|
||||
}.bind(this));
|
||||
return f;
|
||||
}
|
||||
});
|
||||
|
||||
// and it should have create/build methods with binded thisModelNameId param
|
||||
function build(data) {
|
||||
data = data || {};
|
||||
return new targetClass(merge(this._scope, data));
|
||||
}
|
||||
|
||||
function create(data, cb) {
|
||||
if (typeof data === 'function') {
|
||||
cb = data;
|
||||
data = {};
|
||||
}
|
||||
this.build(data).save(cb);
|
||||
}
|
||||
|
||||
function destroyAll(id, cb) {
|
||||
// implement me
|
||||
}
|
||||
}
|
||||
|
||||
// helper methods
|
||||
//
|
||||
function isdef(s) {
|
||||
|
@ -371,9 +451,12 @@ function isdef(s) {
|
|||
}
|
||||
|
||||
function merge(base, update) {
|
||||
Object.keys(update).forEach(function (key) {
|
||||
base[key] = update[key];
|
||||
});
|
||||
base = base || {};
|
||||
if (update) {
|
||||
Object.keys(update).forEach(function (key) {
|
||||
base[key] = update[key];
|
||||
});
|
||||
}
|
||||
return base;
|
||||
}
|
||||
|
||||
|
|
|
@ -62,9 +62,7 @@ function Text() {
|
|||
Schema.Text = Text;
|
||||
|
||||
Schema.prototype.automigrate = function (cb) {
|
||||
if (this.adapter.freezeSchema) {
|
||||
this.adapter.freezeSchema();
|
||||
}
|
||||
this.freeze();
|
||||
if (this.adapter.automigrate) {
|
||||
this.adapter.automigrate(cb);
|
||||
} else {
|
||||
|
@ -72,6 +70,12 @@ Schema.prototype.automigrate = function (cb) {
|
|||
}
|
||||
};
|
||||
|
||||
Schema.prototype.freeze = function freeze() {
|
||||
if (this.adapter.freezeSchema) {
|
||||
this.adapter.freezeSchema();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Define class
|
||||
* @param className
|
||||
|
|
|
@ -50,8 +50,9 @@ function testOrm(schema) {
|
|||
User.hasMany(Post, {as: 'posts', foreignKey: 'userId'});
|
||||
// creates instance methods:
|
||||
// user.posts(conds)
|
||||
// user.buildPost(data) // like new Post({userId: user.id});
|
||||
// user.createPost(data) // build and save
|
||||
// user.posts.build(data) // like new Post({userId: user.id});
|
||||
// user.posts.create(data) // build and save
|
||||
// user.posts.find
|
||||
|
||||
Post.belongsTo(User, {as: 'author', foreignKey: 'userId'});
|
||||
// creates instance methods:
|
||||
|
@ -280,9 +281,9 @@ function testOrm(schema) {
|
|||
User.create(function (err, u) {
|
||||
if (err) return console.log(err);
|
||||
test.ok(u.posts, 'Method defined: posts');
|
||||
test.ok(u.buildPost, 'Method defined: buildPost');
|
||||
test.ok(u.createPost, 'Method defined: createPost');
|
||||
u.createPost(function (err, post) {
|
||||
test.ok(u.posts.build, 'Method defined: posts.build');
|
||||
test.ok(u.posts.create, 'Method defined: posts.create');
|
||||
u.posts.create(function (err, post) {
|
||||
if (err) return console.log(err);
|
||||
test.ok(post.author(), u.id);
|
||||
u.posts(function (err, posts) {
|
||||
|
@ -293,6 +294,36 @@ function testOrm(schema) {
|
|||
});
|
||||
});
|
||||
|
||||
it('should support scopes', function (test) {
|
||||
var wait = 2;
|
||||
|
||||
test.ok(Post.scope, 'Scope supported');
|
||||
Post.scope('published', {published: true});
|
||||
test.ok(typeof Post.published === 'function');
|
||||
test.ok(Post.published._scope.published = true);
|
||||
var post = Post.published.build();
|
||||
test.ok(post.published, 'Can build');
|
||||
test.ok(post.isNewRecord());
|
||||
Post.published.create(function (err, psto) {
|
||||
if (err) return console.log(err);
|
||||
test.ok(psto.published);
|
||||
test.ok(!psto.isNewRecord());
|
||||
done();
|
||||
});
|
||||
|
||||
User.create(function (err, u) {
|
||||
if (err) return console.log(err);
|
||||
test.ok(typeof u.posts.published == 'function');
|
||||
test.ok(u.posts.published._scope.published);
|
||||
test.equal(u.posts.published._scope.userId, u.id);
|
||||
done();
|
||||
});
|
||||
|
||||
function done() {
|
||||
if (--wait === 0) test.done();
|
||||
};
|
||||
});
|
||||
|
||||
it('should destroy all records', function (test) {
|
||||
Post.destroyAll(function (err) {
|
||||
Post.all(function (err, posts) {
|
||||
|
|
Loading…
Reference in New Issue