UNPKG

tomjs

Version:
648 lines (603 loc) 28.8 kB
const require2 = require('tomjs/handlers/require2'); const Observable = require2('tomjs/handlers/object-observer'); const models_cfg = require2('tomjs/configs')().models; const { isObject, camelize, decamelize, isArray, select_fields, toBool, } = require2('tomjs/handlers/tools'); const mongoose = require2('mongoose'); if (isObject(models_cfg.mongoose_set)) { for (const key in models_cfg.mongoose_set) { mongoose.set(key, models_cfg.mongoose_set[key]); } } const mongoose_delete = require2('tomjs/models/mongoose_middleware/mongoose-delete'); const mongooseHidden = require2('mongoose-hidden')() const pluralize = require2('pluralize'); const humps = require2('humps'); const _ = require2('lodash'); const MongooseError = require2('tomjs/error/mongoose_error'); const update_at = require2('tomjs/models/mongoose_middleware/update_at'); const events = require2('tomjs/models/mongoose_middleware/events'); const update_assign = require2('tomjs/models/mongoose_middleware/update_assign'); const support_sequelize = require2('tomjs/models/mongoose_middleware/support_sequelize'); const deepPopulate = require2('tomjs/models/mongoose_middleware/deep_populate')(mongoose); const pagination = require2('tomjs/models/mongoose_middleware/pagination')(mongoose); const auth_cfg = require2('tomjs/configs')().auth; const Op = require2('tomjs/models/op')('mongodb'); const LoadClassSync = require2('tomjs/handlers/load_class_sync'); const system_id = "_id"; const _belongs_to_many_head = models_cfg.belongs_to_many._belongs_to_many_head; const _all_belongs_to_many_head = models_cfg.belongs_to_many._all_belongs_to_many_head; const type_name = 'mongodb'; class MongooseModel { constructor() { this.Op = Op; this.fillable = []; this.guarded = undefined; this.is_guard = true; //默认开启守护 支持 fillable 和 guarded 两个数组属性 this.isSoftDeletes = false; //默认不使用软删除 this.isDeepPopulate = true; this.isTimestamps = false; this.isUpdateAssign = false; this.isSupportSequelize = true; this.find_return_one = false; //为了兼容 sequelize find函数直接返回单个对象而不是数组 this.eventFunctions = undefined;//可以将自定义事件函数对象 this.relationshipsVirtuals = {}; //需要建立的关联虚拟字段 this._all_belongsToMany = {}; this._all_belongsToMany2 = {}; //this.populateShowFields = []; //this.populateHideFields = []; //获取Schema定义 if (this.Schema === undefined) { //发现没有this.Schema属性就尝试对其进行初始化操作 if (typeof (this.Init) == 'function') { this.Init(); } else { throw new Error(this.constructor.name + ' Not find function "Init"!'); } } if (this.Schema !== undefined) { //确定collection的名字 if (this.collectionModelName === undefined) { this.collectionModelName = this.constructor.name; //decamelize(this.constructor.name, ["model"]); } if (this.collection === undefined) { this.collection = decamelize(this.constructor.name, ["model"]); } if (this.SchemaOption === undefined) { this.SchemaOption = { collection: this.collection }; } else if (isObject(this.SchemaOption)) { if (this.SchemaOption.collection === undefined) { this.SchemaOption.collection = this.collection; } } if (this.SchemaOption !== undefined) { this.BuildSchema = new mongoose.Schema(this.Schema, this.SchemaOption); } else { this.BuildSchema = new mongoose.Schema(this.Schema); } this.BuildSchema.set('toJSON', { getters: true, virtuals: true }); this.BuildSchema.methods.getModelClassName = () => { return this.getModelClassName(); }; this.BuildSchema.methods.getRelationshipsVirtuals = () => { return this.relationshipsVirtuals; }; this.BuildSchema.methods.PopulateFields = () => { let populateShowFields = undefined; if (isArray(this.populateShowFields)) { if (this.populateShowFields.length > 0) { populateShowFields = this.populateShowFields; } } let populateHideFields = undefined; if (isArray(this.populateHideFields)) { populateHideFields = this.populateHideFields; } return select_fields(populateShowFields, populateHideFields); }; this.BuildSchema.methods.getAllBelongsToMany = () => { return this._all_belongsToMany2; }; let model_this = this; this.BuildSchema.statics.assign = function (doc) { let data = {}; if (model_this.is_guard) { if (isArray(model_this.fillable) && model_this.fillable.length > 0) { let len = model_this.fillable.length; let fieldname = undefined; for (let i = 0; i < len; i++) { fieldname = model_this.fillable[i]; if (doc[fieldname]) { data[fieldname] = doc[fieldname]; } } } else { data = doc; } if (isArray(model_this.guarded)) { let len = model_this.guarded.length; let fieldname = undefined; for (let i = 0; i < len; i++) { fieldname = model_this.guarded[i]; if (data[fieldname]) { delete data[fieldname]; } } } } else { data = doc; } for (let key in data) { if (!model_this.Schema[key]) { delete data[key]; } } return data; }; this.BuildSchema.methods.assign = function (doc, is_guard = true) { let data = {}; if (is_guard) { if (isArray(model_this.fillable) && model_this.fillable.length > 0) { let len = model_this.fillable.length; let fieldname = undefined; for (let i = 0; i < len; i++) { fieldname = model_this.fillable[i]; if (doc[fieldname] !== undefined) { data[fieldname] = doc[fieldname]; } } } else { data = doc; } if (isArray(model_this.guarded)) { let len = model_this.guarded.length; let fieldname = undefined; for (let i = 0; i < len; i++) { fieldname = model_this.guarded[i]; if (data[fieldname] !== undefined) { delete data[fieldname]; } } } } else { data = doc; } for (let key in data) { try { this[key] = data[key]; } catch (e) { delete data[key]; } } return data; }; this.buildRelationshipsVirtuals(); //构建虚拟字段 if (this.isSoftDeletes) { this.BuildSchema.plugin(mongoose_delete, { deleted_at: true, overrideMethods: 'all', indexFields: ['deleted'] }); } pagination(this.BuildSchema); if (this.isDeepPopulate) { deepPopulate(this.BuildSchema); } if (this.isTimestamps) { new update_at(this.BuildSchema); } if (this.isUpdateAssign) { new update_assign(this.BuildSchema); } if (this.isSupportSequelize) { new support_sequelize(this.BuildSchema); } new events(this.BuildSchema, this.eventFunctions || this); } else { throw new Error(this.constructor.name + ' Not find "Schema"!'); }; } getTypeName() { return type_name; } virtual_observe(current, belongsToMany, target) { let source = current[belongsToMany.belongs_to_many]; let observableArr = Observable.from(target); observableArr.observe(async (changes) => { if (changes.length > 0 && changes[0].type == "delete") { changes.sort(function (x, y) { if (x.path[0] < y.path[0]) { return 1; } else if (x.path[0] > y.path[0]) { return -1; } else { return 0; } }); } for (let idx in changes) { let change = changes[idx]; try { if (change.path.length <= 1) { let index = change.path[0]; switch (change.type) { case "update": { let sourceRecord = source[index]; let { localField, foreignField } = sourceRecord.schema.virtuals[belongsToMany.populateField].options; let isChange = false; if (sourceRecord[localField] instanceof mongoose.Types.ObjectId) { isChange = mongoose.Types.ObjectId(sourceRecord[localField]).toString() != mongoose.Types.ObjectId(change.value[foreignField]).toString(); } else { isChange = sourceRecord[localField] === change.value[foreignField]; } if (isChange) { sourceRecord[localField] = change.value[foreignField]; await sourceRecord.save(); } break; } case "insert": { let sourceRecord = undefined; let db = undefined; if (isArray(source) && source.length > 0) { db = source[0].db; sourceRecord = source[0]; } else { db = source.db; sourceRecord = source; } let { localField, foreignField } = sourceRecord.schema.virtuals[belongsToMany.populateField].options; let new_rec = {}; new_rec[belongsToMany.foreignField] = current[belongsToMany.localField]; new_rec[localField] = change.value[foreignField]; let value = await db.models[belongsToMany.ref].create(new_rec); source.splice(index, 0, value); break; } case "delete": { let sourceRecord = source[index]; await sourceRecord.delete(); break; } default: { break; } } } } catch (e) { if (e.name != 'TypeError') { throw e; } } } }); return observableArr; } buildRelationshipsVirtuals() { let _this = this; let virtual_field; let hidden_info = {}; for (let idx in this.relationshipsVirtuals) { this.BuildSchema.virtual(idx, this.relationshipsVirtuals[idx]); if (this._all_belongsToMany[idx]) { hidden_info[idx] = 'hideJSON'; let belongsToMany = this._all_belongsToMany[idx]; virtual_field = this.BuildSchema.virtual(belongsToMany.fieldName); virtual_field.get(function () { if (this[_all_belongs_to_many_head + belongsToMany.fieldName] === undefined) { if (isArray(this[idx])) { let new_arr = []; let len = this[idx].length; for (let i = 0; i < len; i++) { if (isObject(this[idx][i][belongsToMany.populateField])) { this[idx][i][belongsToMany.populateField][models_cfg.belongs_to_many.pivot] = this[idx][i]; } new_arr.push(this[idx][i][belongsToMany.populateField]); } this[_all_belongs_to_many_head + belongsToMany.fieldName] = _this.virtual_observe(this, belongsToMany, new_arr); } else if (isObject(this[idx])) { this[_all_belongs_to_many_head + belongsToMany.fieldName] = this[idx][belongsToMany.populateField]; } } return this[_all_belongs_to_many_head + belongsToMany.fieldName]; }); //virtual_field.set(function(val) {});//应该用不到 设置虚拟属性的值,可以直接对象其提供对象即可。 } } this.BuildSchema.plugin(mongooseHidden, { virtuals: hidden_info, defaultHidden: models_cfg.defaultHidden }); } baseHas(field_name = "", { ref, localField, foreignField, justOne, match, select, options, count } = {}) { if (field_name.length <= 0) { throw new MongooseError("field_name is empty"); } if (!ref) { ref = camelize(field_name, null, true, true, 'Model') //如果未有效定义ref 就采用field_name的复数形式 } if (!localField) { localField = system_id; } if (!foreignField) { foreignField = pluralize.singular(field_name) + system_id; } justOne = justOne ? true : false; this.relationshipsVirtuals[field_name] = { ref, localField, foreignField, justOne, match, select, options, count }; } hasOne(field_name = "", options = {}) { if (field_name.length <= 0) { throw new MongooseError("hasOne field_name is empty"); } options.justOne = true; this.baseHas(field_name, options); } hasMany(field_name = "", options = {}) { if (field_name.length <= 0) { throw new MongooseError("hasMany field_name is empty"); } options.justOne = false; if (!options['foreignField']) { let field = this.collection; if (field === undefined) { field = decamelize(this.constructor.name, ["model"], false); } options.foreignField = field + system_id; } this.baseHas(field_name, options); } baseBelongs(field_name = "", { ref, localField, foreignField, justOne, match, select, options, count } = {}) { if (field_name.length <= 0) { throw new MongooseError("field_name is empty"); } if (!ref) { ref = camelize(field_name, null, true, true, 'Model'); //如果未有效定义ref 就采用field_name的复数形式 } if (!localField) { localField = pluralize.singular(field_name) + system_id; } if (!foreignField) { foreignField = system_id; } justOne = justOne ? true : false; this.relationshipsVirtuals[field_name] = { ref, localField, foreignField, justOne, match, select, options, count }; } belongsTo(field_name = "", options = {}) { if (field_name.length <= 0) { throw new MongooseError("hasOne field_name is empty"); } options.justOne = true; this.baseBelongs(field_name, options); } //fieldName: groups //ref: EmpGroupModel //localField: _id //foreignField: emp_id//EmpGroupModel. //populateField: group//EmpGroupModel belongsToMany(fieldName = "", { ref, localField, foreignField, populateField, match, select, options, count } = {}) { if (fieldName.length <= 0) { throw new MongooseError("belongsToMany fieldName is empty"); } let fromNameMe = decamelize(this.constructor.name, ["model"], false); let fromNameTo = pluralize.singular(fieldName); if (!ref) { ref = _.join([humps.pascalize(fromNameMe), humps.pascalize(fromNameTo)].sort(), '') + 'Model'; //如果未有效定义ref 就采用field_name的复数形式 } if (!localField) { localField = system_id; } if (!foreignField) { foreignField = fromNameMe + system_id; } if (!populateField) { populateField = fromNameTo; } let belongs_to_many = _belongs_to_many_head + fieldName; this.hasMany(belongs_to_many, { ref, localField, foreignField, match, select, options, count }); this._all_belongsToMany[belongs_to_many] = { belongs_to_many, fieldName, ref, localField, foreignField, populateField, match, select, options, count }; this._all_belongsToMany2[fieldName] = this._all_belongsToMany[belongs_to_many]; } hasManyThrough(fieldName = "", through = "", { localField, foreignField, populateField, match, select, options, count } = {}) { this.belongsToMany(fieldName, { ref: through, localField, foreignField, populateField, match, select, options, count }); } morphTo(fieldName = "", { localField = "", modelField = "", foreignField, justOne, match, select, options, count } = {}) { let ref = undefined; if (fieldName.length <= 0) { throw new MongooseError("fieldName is empty"); } if (localField.length <= 0) { localField = decamelize(fieldName, '', false) + system_id; } if (typeof (modelField) == 'string') { if (modelField.length <= 0) { modelField = decamelize(fieldName, '', false) + '_type'; } let idx = modelField; ref = function () { let model_name = this[idx]; if (!mongoose.models[model_name]) { let model = LoadClassSync('model', model_name); let model_object = model.Model(); } return model_name; }; } else if (typeof (modelField) == 'function') { ref = modelField; } if (ref === undefined) { throw new MongooseError("modelField type error"); } if (!foreignField) { foreignField = system_id; } justOne = toBool(justOne || true); this.relationshipsVirtuals[fieldName] = { ref, localField, foreignField, justOne, match, select, options, count }; } morphMany(fieldName = "", { ref = "", model = "", modelField = "", localField, foreignField, match, select, options, count } = {}) { if (fieldName.length <= 0) { throw new MongooseError("fieldName is empty"); } if (ref.length <= 0) { ref = camelize(fieldName, null, true, true, 'Model'); } if (model.length <= 0) { model = this.getModelClassName(); } if (model.length > 0) { let object = {}; if (!modelField) { modelField = decamelize(ref, 'model', false) + '_type'; } object[modelField] = model; if (isObject(match)) { match = Object.assign(match, object); } else { match = object; } } if (!foreignField) { foreignField = decamelize(ref, 'model', false) + '_id'; } let justOne = false; return this.baseHas(fieldName, { ref, localField, foreignField, justOne, match, select, options, count }); } morphedByMany(fieldName = "", through = "", model = "", { modelField = "", localField, foreignField, populateField, match, select, options, count } = {}) { if (model.length > 0) { let object = {}; if (modelField.length <= 0) { modelField = decamelize(through, 'model', false) + '_type'; } object[modelField] = model; if (isObject(match)) { match = Object.assign(match, object); } else { match = object; } } if (!populateField && (typeof (through) == "string")) { populateField = decamelize(through, 'Model', false); } return this.hasManyThrough(fieldName, through, { localField, foreignField, populateField, match, select, options, count }); } morphToMany(fieldName = "", through = "", { model = "", modelField, localField, foreignField, populateField, match, select, options, count } = {}) { if (fieldName.length <= 0) { throw new MongooseError("fieldName is empty"); } if (through.length <= 0) { throw new MongooseError("through is empty"); } if (model.length <= 0) { model = this.getModelClassName(); } if (model.length > 0) { let object = {}; if (!modelField) { modelField = decamelize(through, 'model', false) + '_type'; } object[modelField] = model; if (isObject(match)) { match = Object.assign(match, object); } else { match = object; } } if (!populateField && (typeof (fieldName) == "string")) { populateField = pluralize.singular(fieldName); } if (!foreignField) { foreignField = decamelize(through, 'model', false) + '_id'; } return this.hasManyThrough(fieldName, through, { localField, foreignField, populateField, match, select, options, count }); } timestamps(index = true) { this.isTimestamps = true; this.Schema['created_at'] = { type: Date, index, default: Date.now, }; this.Schema['updated_at'] = { type: Date, index, default: null, }; } updateAssign() { this.isUpdateAssign = true; } softDeletes() { this.isSoftDeletes = true; //标记启用软删除 } tokenVersion() { this.Schema[auth_cfg.jwt_key_token_version] = { type: Number, default: 0, min: 0, }; } language() { this.fillable.push('language'); this.Schema['language'] = { type: String, default: '', }; } getModelClassName() { return this.constructor.name; } Model(conn, collectionModelName) { if (conn === undefined) { conn = mongoose; } if (collectionModelName === undefined) { collectionModelName = this.collectionModelName; } let model_obj = conn.model(collectionModelName, this.BuildSchema); model_obj.Op = Op; let model_this = this; model_obj.getModelClassName = () => { return this.getModelClassName(); }; model_obj.CreateNotGuard = function (doc, options, callback) { return model_obj.__proto__.create.apply(this, arguments); } // model_obj.find = function () { // return model_obj.__proto__.find.apply(this, arguments).where('undefined').ne(true); // } model_obj.findAll = function () { return model_obj.find.apply(this, arguments); } if (this.find_return_one) { model_obj.find = function () { return model_obj.findOne.apply(this, arguments); } } if (model_this.isSoftDeletes) { model_obj.deleteOne = function () { return model_obj.delete.apply(this, arguments); } model_obj.deleteMany = function () { return model_obj.delete.apply(this, arguments); } } else { model_obj.deleteById = function (id) { return model_obj.deleteOne.call(this, { _id: id }); } } model_obj.create = function (doc, options, callback) { // if (arguments.length === 2 && // options && // options.session && // options.session.constructor.name === 'ClientSession') { // if (!Array.isArray(doc)) { // arguments[0] = [doc]; // doc = [doc]; // } // } const isGuard = this.is_guard === undefined ? model_this.is_guard : this.is_guard; if (isGuard == true) { let data = Array.isArray(doc) ? [] : {}; if (isArray(model_this.fillable) && model_this.fillable.length > 0) { let len = model_this.fillable.length; let fieldname = undefined; for (let i = 0; i < len; i++) { fieldname = model_this.fillable[i]; if (Array.isArray(doc)) { for (const key in doc) { if (doc[key][fieldname]) { if (data[key] === undefined) { data[key] = {}; } data[key][fieldname] = doc[key][fieldname]; } } } else { if (doc[fieldname]) { data[fieldname] = doc[fieldname]; } } } } else { data = doc; } if (isArray(model_this.guarded)) { let len = model_this.guarded.length; let fieldname = undefined; for (let i = 0; i < len; i++) { fieldname = model_this.guarded[i]; if (Array.isArray(data)) { for (const key in data) { if (data[key][fieldname]) { delete data[key][fieldname]; } } } else { if (data[fieldname]) { delete data[fieldname]; } } } } arguments[0] = data; } return model_obj.__proto__.create.apply(this, arguments); }; if (typeof (this.modelAfter) == 'function') { let obj = this.modelAfter(model_obj); if (isObject(obj)) { model_obj = obj; } } return model_obj; } } module.exports = MongooseModel;