Browse Source

savable into sliceSavable move

Ivan Asmer 1 year ago
parent
commit
1f3f9c150a
1 changed files with 361 additions and 360 deletions
  1. 361 360
      index.js

+ 361 - 360
index.js

@@ -2,412 +2,413 @@ const { MongoClient, ObjectID }  = require("mongodb");
 const {asynchronize, openPromise } = require('./asynchronize')
 
 const mm = db => {
-    class Savable {
-        constructor(obj, ref, empty=false){
-            this._id    = null
-            this._ref   = ref
-            this._class = this.__proto__.constructor.name
-            this._empty = true
-
-            Savable.addClass(this.__proto__.constructor)
-
-            if (obj){
-                this.populate(obj)
-                this._empty = empty
+    
+    function sliceSavable(userACL){
+        userACL = userACL.map(tag => tag.toString())
+        //console.log(userACL)
+        class Savable {
+            constructor(obj, ref, empty=false){
+                this._id    = null
+                this._ref   = ref
+                this._class = this.__proto__.constructor.name
+                this._empty = true
+    
+                Savable.addClass(this.__proto__.constructor)
+    
+                if (obj){
+                    this.populate(obj)
+                    this._empty = empty
+                }
             }
-        }
-
-        backupRelations(){
-            this._loadRelations = {};
-            for (const relation in this.__proto__.constructor.relations){
-                this._loadRelations[relation] = this[relation] instanceof Array ? [...this[relation]] : this[relation]
+    
+            backupRelations(){
+                this._loadRelations = {};
+                for (const relation in this.__proto__.constructor.relations){
+                    this._loadRelations[relation] = this[relation] instanceof Array ? [...this[relation]] : this[relation]
+                }
             }
-        }
-
-
-
-        populate(obj){
-            const convertSavables = (obj) => {
-                for (const key in obj){
-                    if (Savable.isSavable(obj[key])){
-                        obj[key] = (this._ref && 
-                                    Savable.equals(obj[key], this._ref)) ? 
-                                                       this._ref : 
-                                                       Savable.newSavable(obj[key], this)
-                    }
-                    else if (typeof obj[key] === 'object'){
-                        convertSavables(obj[key])
+    
+    
+    
+            populate(obj){
+                const convertSavables = (obj) => {
+                    for (const key in obj){
+                        if (Savable.isSavable(obj[key])){
+                            obj[key] = (this._ref && 
+                                        Savable.equals(obj[key], this._ref)) ? 
+                                                           this._ref : 
+                                                           Savable.newSavable(obj[key], this)
+                        }
+                        else if (typeof obj[key] === 'object'){
+                            convertSavables(obj[key])
+                        }
                     }
                 }
+    
+                Object.assign(this, obj)
+    
+    
+    
+                convertSavables(this)
+    
+                this.backupRelations()
+                //this._id = obj._id
             }
-
-            Object.assign(this, obj)
-
-
-
-            convertSavables(this)
-
-            this.backupRelations()
-            //this._id = obj._id
-        }
-
-        get _empty(){
-            return !!this.then
-        }
-
-        set _empty(value){
-            if (value){
-                //TODO: list of callbacks, because then can be called many times, and
-                //it's not reason to repeat query to db
-                this.then = (cb, err) => {
-
-                    if (!this._id)    err(new ReferenceError('Id is empty'))
-                    if (!this._class) err(new ReferenceError('Class is empty'))
-
-                    const promise = openPromise()
-
-                    this.collection.findOne(this._id).then( data => {
-                        if (!data){
-                            let error = new ReferenceError('Document Not Found')
-                            if (typeof err === 'function') err(error)
-                            else promise.reject(error)
-                        }
-                        else {
-                            delete this.then
-                            this.populate(data)
-                            if (typeof cb === 'function')
-                                promise.resolve(cb(this))
+    
+            get _empty(){
+                return !!this.then
+            }
+    
+            set _empty(value){
+                if (value){
+                    //TODO: list of callbacks, because then can be called many times, and
+                    //it's not reason to repeat query to db
+                    this.then = (cb, err) => {
+    
+                        if (!this._id)    err(new ReferenceError('Id is empty'))
+                        if (!this._class) err(new ReferenceError('Class is empty'))
+    
+                        const promise = openPromise()
+    
+                        this.collection.findOne(this._id).then( data => {
+                            if (!data){
+                                let error = new ReferenceError('Document Not Found')
+                                if (typeof err === 'function') err(error)
+                                else promise.reject(error)
+                            }
                             else {
-                                promise.resolve(this)
+                                delete this.then
+                                this.populate(data)
+                                if (typeof cb === 'function')
+                                    promise.resolve(cb(this))
+                                else {
+                                    promise.resolve(this)
+                                }
                             }
-                        }
-                    })
-                    return promise
+                        })
+                        return promise
+                    }
+                }
+                else {
+                    delete this.then
                 }
             }
-            else {
-                delete this.then
+    
+            get createdAt(){
+                return this._id ? new Date(this._id.getTimestamp()) : null
             }
-        }
-
-        get createdAt(){
-            return this._id ? new Date(this._id.getTimestamp()) : null
-        }
-
-        get collection(){
-            return db.collection(this._class)
-        }
-
-        async save(noSync=false){
-            if (this.validate && !(await this.validate())){
-                throw new SyntaxError(`validation error on entity ${this._id} of class ${this.constructor.name} (${this.name || this.key}) save`)
+    
+            get collection(){
+                return db.collection(this._class)
             }
-            if (this.empty) return this;
-
-            const syncRelations = async () => {
-                if (noSync) return
-                if (!(this && this.__proto__ && this.__proto__.constructor && this.__proto__.constructor.relations)) return 
-
-
-                async function getValueByField(field, savable) {
-                    let path = field.split('.');
-                    await savable//.catch(e => console.log('GET VALUE BY FIELD ERROR'));
-                    let result = savable;
-                    let prev;
-                    let lastKey = path.pop()
-                    while (prev = result, result = result[path.shift()] && path.length);
-                    return {value: prev[lastKey], obj: prev, lastKey};
+    
+            async save(noSync=false){
+                if (this.validate && !(await this.validate())){
+                    throw new SyntaxError(`validation error on entity ${this._id} of class ${this.constructor.name} (${this.name || this.key}) save`)
                 }
-
-                for (const relation in this.__proto__.constructor.relations){
-                    let backRef = this.__proto__.constructor.relations[relation]
-                    if (Array.isArray(backRef)) backRef = backRef[0]
-
-                    const loadRelation = this._loadRelations[relation]
-                    const loadRelationAsArray = loadRelation instanceof Savable ? [loadRelation] : loadRelation
-
-                    let {value, obj, lastKey: key} = await getValueByField(relation, this)
-                    const valueAsArray = value instanceof Savable ? [value] : value
-                    if (loadRelationAsArray){ //check for removed Refs
-                        const removedRefs = valueAsArray ? 
-                                loadRelationAsArray.filter(ref => !Savable.existsInArray(valueAsArray, ref)) : 
-                                loadRelationAsArray
-                        for (const ref of removedRefs){
-                            try { await ref } catch (e) {console.log('SYNC RELATIONS REMOVE ERROR') }
-
-                            if (ref instanceof Savable)
-                                await ref.removeRelation(this, relation)
-                        }
+                if (this.empty) return this;
+    
+                const syncRelations = async () => {
+                    if (noSync) return
+                    if (!(this && this.__proto__ && this.__proto__.constructor && this.__proto__.constructor.relations)) return 
+    
+    
+                    async function getValueByField(field, savable) {
+                        let path = field.split('.');
+                        await savable//.catch(e => console.log('GET VALUE BY FIELD ERROR'));
+                        let result = savable;
+                        let prev;
+                        let lastKey = path.pop()
+                        while (prev = result, result = result[path.shift()] && path.length);
+                        return {value: prev[lastKey], obj: prev, lastKey};
                     }
-                    if (valueAsArray){ //check for added refs
-                        for (const foreignSavable of valueAsArray) {
-                            try { await foreignSavable } catch (e) {console.log('SYNC RELATIONS ADD ERROR') }
-
-                            let foreignLoadRelationsAsArray = Savable.arrize(foreignSavable._loadRelations[backRef])
-                            if (foreignSavable && !Savable.existsInArray(foreignLoadRelationsAsArray, this)){
-                                await foreignSavable.setRelation(this, relation)
+    
+                    for (const relation in this.__proto__.constructor.relations){
+                        let backRef = this.__proto__.constructor.relations[relation]
+                        if (Array.isArray(backRef)) backRef = backRef[0]
+    
+                        const loadRelation = this._loadRelations[relation]
+                        const loadRelationAsArray = loadRelation instanceof Savable ? [loadRelation] : loadRelation
+    
+                        let {value, obj, lastKey: key} = await getValueByField(relation, this)
+                        const valueAsArray = value instanceof Savable ? [value] : value
+                        if (loadRelationAsArray){ //check for removed Refs
+                            const removedRefs = valueAsArray ? 
+                                    loadRelationAsArray.filter(ref => !Savable.existsInArray(valueAsArray, ref)) : 
+                                    loadRelationAsArray
+                            for (const ref of removedRefs){
+                                try { await ref } catch (e) {console.log('SYNC RELATIONS REMOVE ERROR') }
+    
+                                if (ref instanceof Savable)
+                                    await ref.removeRelation(this, relation)
+                            }
+                        }
+                        if (valueAsArray){ //check for added refs
+                            for (const foreignSavable of valueAsArray) {
+                                try { await foreignSavable } catch (e) {console.log('SYNC RELATIONS ADD ERROR') }
+    
+                                let foreignLoadRelationsAsArray = Savable.arrize(foreignSavable._loadRelations[backRef])
+                                if (foreignSavable && !Savable.existsInArray(foreignLoadRelationsAsArray, this)){
+                                    await foreignSavable.setRelation(this, relation)
+                                }
                             }
                         }
                     }
                 }
-            }
-
-            async function recursiveSlicer(obj){
-                let result = obj instanceof Array ? [] : {}
-                for (const key in obj){
-
-                    if (obj[key] && typeof obj[key] === 'object'){
-                        if (obj[key] instanceof Savable){
-                            if (!(obj[key]._id)){
-                                await obj[key].save().catch(err => console.log('ERR', err))
+    
+                async function recursiveSlicer(obj){
+                    let result = obj instanceof Array ? [] : {}
+                    for (const key in obj){
+    
+                        if (obj[key] && typeof obj[key] === 'object'){
+                            if (obj[key] instanceof Savable){
+                                if (!(obj[key]._id)){
+                                    await obj[key].save().catch(err => console.log('ERR', err))
+                                }
+                                result[key] = obj[key].shortData()
+                            }
+                            else {
+                                result[key] = await recursiveSlicer(obj[key])
                             }
-                            result[key] = obj[key].shortData()
                         }
                         else {
-                            result[key] = await recursiveSlicer(obj[key])
+                            result[key] = obj[key]
                         }
                     }
-                    else {
-                        result[key] = obj[key]
-                    }
+                    return result;
                 }
-                return result;
-            }
-
-            const {_id, _empty, _ref, _loadRelations, then, ...toSave} = await recursiveSlicer(this)
-
-            //TODO: UPSERT
-            if (!this._id){ //first time
-                const { insertedId } = await this.collection.insertOne(toSave)
-                this._id = insertedId
+    
+                const {_id, _empty, _ref, _loadRelations, then, ...toSave} = await recursiveSlicer(this)
+    
+                //TODO: UPSERT
+                if (!this._id){ //first time
+                    const { insertedId } = await this.collection.insertOne(toSave)
+                    this._id = insertedId
+                }
+                else { //update
+                    await this.collection.updateOne({_id: this._id},  {$set: toSave}).catch(err => console.log('UPDATE ERR', err))
+                }
+    
+                await syncRelations()
+                this.backupRelations()
+                return this
             }
-            else { //update
-                await this.collection.updateOne({_id: this._id},  {$set: toSave}).catch(err => console.log('UPDATE ERR', err))
+    
+            // method to get ref snapshot (empty object with some data). gives optimizations in related objects
+            // place for permission, owner, probably short relations 
+            shortData(){
+                const {_id, _class} = this
+                return { _id, _class }
             }
-
-            await syncRelations()
-            this.backupRelations()
-            return this
-        }
-
-        // method to get ref snapshot (empty object with some data). gives optimizations in related objects
-        // place for permission, owner, probably short relations 
-        shortData(){
-            const {_id, _class} = this
-            return { _id, _class }
-        }
-
-
-        async setRelation(ref, refRelationName){
-            await this
-            const ourRelation = ref.__proto__.constructor.relations[refRelationName]
-            const ourArray    = ourRelation instanceof Array 
-            const ourRelationName = ourArray ? ourRelation[0] : ourRelation
-
-            let shortQuery = {[ourRelationName]: ref.shortData()}
-            let query;
-
-            if (ourArray || this[ourRelationName] instanceof Array){
-                this[ourRelationName] = this[ourRelationName] || []
-                this[ourRelationName] = this[ourRelationName] instanceof Array ? this[ourRelationName] : [this[ourRelationName]]
-
-                if (!Savable.existsInArray(this[ourRelationName], ref)) {
-                    this[ourRelationName].push(ref)
-                    if (this._id && this._loadRelations[ourRelationName] instanceof Array){
-                        this._loadRelations[ourRelationName].push(ref)
+    
+    
+            async setRelation(ref, refRelationName){
+                await this
+                const ourRelation = ref.__proto__.constructor.relations[refRelationName]
+                const ourArray    = ourRelation instanceof Array 
+                const ourRelationName = ourArray ? ourRelation[0] : ourRelation
+    
+                let shortQuery = {[ourRelationName]: ref.shortData()}
+                let query;
+    
+                if (ourArray || this[ourRelationName] instanceof Array){
+                    this[ourRelationName] = this[ourRelationName] || []
+                    this[ourRelationName] = this[ourRelationName] instanceof Array ? this[ourRelationName] : [this[ourRelationName]]
+    
+                    if (!Savable.existsInArray(this[ourRelationName], ref)) {
+                        this[ourRelationName].push(ref)
+                        if (this._id && this._loadRelations[ourRelationName] instanceof Array){
+                            this._loadRelations[ourRelationName].push(ref)
+                        }
                     }
+    
+                    query = {$addToSet: shortQuery}
                 }
-
-                query = {$addToSet: shortQuery}
-            }
-            else {
-                this[ourRelationName] =  ref
-                this._id && (this._loadRelations[ourRelationName] = ref)
-                query = {$set: shortQuery}
-            }
-
-            console.log('SET RELATION:', query)
-
-            if (this._id){
+                else {
+                    this[ourRelationName] =  ref
+                    this._id && (this._loadRelations[ourRelationName] = ref)
+                    query = {$set: shortQuery}
+                }
+    
                 console.log('SET RELATION:', query)
-                await this.collection.updateOne({_id: this._id},  query).catch(err => console.log('UPDATE ERR', err))
+    
+                if (this._id){
+                    console.log('SET RELATION:', query)
+                    await this.collection.updateOne({_id: this._id},  query).catch(err => console.log('UPDATE ERR', err))
+                }
             }
-        }
-
-        async removeRelation(ref, refRelationName){ //i. e. this = child, ref = parent object, refRelationName = children in parent
-            await this
-            const ourRelation = ref.__proto__.constructor.relations[refRelationName]
-            const ourArray    = ourRelation instanceof Array 
-            const ourRelationName = ourArray ? ourRelation[0] : ourRelation
-
-
-            if (this._id){
-                const query = ourArray ? {$pull: {[ourRelationName]: ref.shortData()}}
-                                       : {$set:  {[ourRelationName]: null}}
-                console.log('REMOVE RELATION:', query)
-                await this.collection.updateOne({_id: this._id},  query).catch(err => console.log('UPDATE ERR', err))
+    
+            async removeRelation(ref, refRelationName){ //i. e. this = child, ref = parent object, refRelationName = children in parent
+                await this
+                const ourRelation = ref.__proto__.constructor.relations[refRelationName]
+                const ourArray    = ourRelation instanceof Array 
+                const ourRelationName = ourArray ? ourRelation[0] : ourRelation
+    
+    
+                if (this._id){
+                    const query = ourArray ? {$pull: {[ourRelationName]: ref.shortData()}}
+                                           : {$set:  {[ourRelationName]: null}}
+                    console.log('REMOVE RELATION:', query)
+                    await this.collection.updateOne({_id: this._id},  query).catch(err => console.log('UPDATE ERR', err))
+                }
+    
+                (this[ourRelationName] instanceof Array) ? this._loadRelations[ourRelationName] = this[ourRelationName] = this[ourRelationName].filter(ourRef => !ourRef.equals(ref))
+                                                         : this[ourRelationName] = null;
             }
-
-            (this[ourRelationName] instanceof Array) ? this._loadRelations[ourRelationName] = this[ourRelationName] = this[ourRelationName].filter(ourRef => !ourRef.equals(ref))
-                                                     : this[ourRelationName] = null;
-        }
-
-        async delete(noRefs=false){
-            if (!noRefs) for (const relation in this.__proto__.constructor.relations){
-                const backRef = this.__proto__.constructor.relations[relation]
-
-                const loadRelation = this._loadRelations && this._loadRelations[relation]
-                const loadRelationAsArray = loadRelation instanceof Savable ? [loadRelation] : loadRelation
-
-                if (loadRelationAsArray){
-                    for (const ref of loadRelationAsArray){
-                        try {
-                            await ref
+    
+            async delete(noRefs=false){
+                if (!noRefs) for (const relation in this.__proto__.constructor.relations){
+                    const backRef = this.__proto__.constructor.relations[relation]
+    
+                    const loadRelation = this._loadRelations && this._loadRelations[relation]
+                    const loadRelationAsArray = loadRelation instanceof Savable ? [loadRelation] : loadRelation
+    
+                    if (loadRelationAsArray){
+                        for (const ref of loadRelationAsArray){
+                            try {
+                                await ref
+                            }
+                            catch (e) {console.log('DELETE SYNC RELATIONS ERROR') }
+                            if (ref instanceof Savable)
+                                await ref.removeRelation(this, relation)
                         }
-                        catch (e) {console.log('DELETE SYNC RELATIONS ERROR') }
-                        if (ref instanceof Savable)
-                            await ref.removeRelation(this, relation)
                     }
                 }
+                const id  = this._id
+                const col = this._class && this.collection
+    
+                for (let key in this)
+                    delete this[key]
+    
+                delete this.__proto__
+    
+                if (col)
+                    return await col.deleteOne({_id: id})
             }
-            const id  = this._id
-            const col = this._class && this.collection
-
-            for (let key in this)
-                delete this[key]
-
-            delete this.__proto__
-
-            if (col)
-                return await col.deleteOne({_id: id})
-        }
-
-
-
-        static arrize(value){
-            if (Array.isArray(value)) return value
-            if (value) return [value]
-            return []
-        }
-
-        static equals(obj1, obj2){
-            if (!obj1 || !obj2) return false
-            if(!obj1._id) return obj1 === obj2
-            if(!obj2._id) return obj1 === obj2
-            return obj1._id.toString() === obj2._id.toString()
-        }
-
-        equals(obj){
-            return Savable.equals(this, obj)
-        }
-
-        static existsInArray(arr, obj){
-            if (!Array.isArray(arr)) return false
-
-            let filtered = arr.filter(item => Savable.equals(item, obj))
-            return filtered.length
-        }
-
-        static isSavable(obj){
-            return obj && obj._id && obj._class
-        }
-
-        static newSavable(obj, ref, empty=true){
-            let className = obj._class || "Savable"
-            className     = Savable.classes[className] ? className : "Savable"
-            if (obj.__proto__.constructor === Savable.classes[className]){
-                return obj
+    
+    
+    
+            static arrize(value){
+                if (Array.isArray(value)) return value
+                if (value) return [value]
+                return []
             }
-            
-            return new Savable.classes[className](obj, ref, empty)
-        }
-
-        static addClass(_class){ //explicit method to add class to Savable registry for instantiate right class later
-            (typeof _class == 'function') && (Savable.classes[_class.name] = _class)
-        }
-
-
-        static get m(){ 
-            return Savable._m = (Savable._m || (new Proxy({}, {
-                get(obj, _class){
-                    if (_class in obj){
-                        return obj[_class]
-                    }
-
-                    const applyCursorCalls = (cursor, calls) =>{
-                        if (!calls) return cursor;
-                        for (let [method, params] of Object.entries(calls)){
-                            if (typeof cursor[method] !== "function"){
-                                throw new SyntaxError(`Wrong cursor method ${method}`)
+    
+            static equals(obj1, obj2){
+                if (!obj1 || !obj2) return false
+                if(!obj1._id) return obj1 === obj2
+                if(!obj2._id) return obj1 === obj2
+                return obj1._id.toString() === obj2._id.toString()
+            }
+    
+            equals(obj){
+                return Savable.equals(this, obj)
+            }
+    
+            static existsInArray(arr, obj){
+                if (!Array.isArray(arr)) return false
+    
+                let filtered = arr.filter(item => Savable.equals(item, obj))
+                return filtered.length
+            }
+    
+            static isSavable(obj){
+                return obj && obj._id && obj._class
+            }
+    
+            static newSavable(obj, ref, empty=true){
+                let className = obj._class || "Savable"
+                className     = Savable.classes[className] ? className : "Savable"
+                if (obj.__proto__.constructor === Savable.classes[className]){
+                    return obj
+                }
+                
+                return new Savable.classes[className](obj, ref, empty)
+            }
+    
+            static addClass(_class){ //explicit method to add class to Savable registry for instantiate right class later
+                (typeof _class == 'function') && (Savable.classes[_class.name] = _class)
+            }
+    
+    
+            static get m(){ 
+                return Savable._m = (Savable._m || (new Proxy({}, {
+                    get(obj, _class){
+                        if (_class in obj){
+                            return obj[_class]
+                        }
+    
+                        const applyCursorCalls = (cursor, calls) =>{
+                            if (!calls) return cursor;
+                            for (let [method, params] of Object.entries(calls)){
+                                if (typeof cursor[method] !== "function"){
+                                    throw new SyntaxError(`Wrong cursor method ${method}`)
+                                }
+                                cursor = cursor[method](...params)
                             }
-                            cursor = cursor[method](...params)
+                            return cursor;
                         }
-                        return cursor;
-                    }
-
-                    return  obj[_class] = {
-                        * find(query, cursorCalls={}){
-                            //console.log(query)
-                            let cursor = applyCursorCalls(db.collection(_class).find(query), cursorCalls)
-                            let cursorGen = asynchronize({s: cursor.stream(), 
-                                                          chunkEventName: 'data', 
-                                                          endEventName: 'close',
-                                                          errEventName: 'error',
-                                                          countMethodName: 'count'})
-
-                            for (const pObj of cursorGen()){
-                                yield new Promise((ok, fail) => 
-                                    pObj.then(obj => (/*console.log(obj),*/ok(Savable.newSavable(obj, null, false))), 
-                                              err => fail(err)))
+    
+                        return  obj[_class] = {
+                            * find(query, cursorCalls={}){
+                                //console.log(query)
+                                let cursor = applyCursorCalls(db.collection(_class).find(query), cursorCalls)
+                                let cursorGen = asynchronize({s: cursor.stream(), 
+                                                              chunkEventName: 'data', 
+                                                              endEventName: 'close',
+                                                              errEventName: 'error',
+                                                              countMethodName: 'count'})
+    
+                                for (const pObj of cursorGen()){
+                                    yield new Promise((ok, fail) => 
+                                        pObj.then(obj => (/*console.log(obj),*/ok(Savable.newSavable(obj, null, false))), 
+                                                  err => fail(err)))
+                                }
+                            },
+                            async count(query, cursorCalls={}){
+                                let cursor = applyCursorCalls(db.collection(_class).find(query), cursorCalls)
+                                return await cursor.count(true)
+                            },
+                            async findOne(query){
+                                let result = await db.collection(_class).findOne(query)
+                                if (result)
+                                    return Savable.newSavable(result, null, false)
+                                return result
                             }
-                        },
-                        async count(query, cursorCalls={}){
-                            let cursor = applyCursorCalls(db.collection(_class).find(query), cursorCalls)
-                            return await cursor.count(true)
-                        },
-                        async findOne(query){
-                            let result = await db.collection(_class).findOne(query)
-                            if (result)
-                                return Savable.newSavable(result, null, false)
-                            return result
                         }
+                    },
+    
+                    set(obj, propName, value){
                     }
-                },
-
-                set(obj, propName, value){
-                }
-            })))
-        }
-
-        static get relations(){ 
-            //empty default relations, acceptable: {field: foreignField}, where:
-            //field and foreign field can be Savable, Array or Set
-            //both fields can be specified as "field", "field.subfield" 
-            //or field: {subfield: foreignField} //TODO later if needed
-            //TODO: move it into object instead of class to give more flexibility, for example
-            //if person has children, it can have backRef father or mother depending on sex:
-            //return {
-            //    children: this.sex === 'male' ? 'father': 'mother'
-            //}
-            //
-            //return {
-            //  parent: ["children"],
-            //  notebooks: "owner"
-            //}
-            return {}
+                })))
+            }
+    
+            static get relations(){ 
+                //empty default relations, acceptable: {field: foreignField}, where:
+                //field and foreign field can be Savable, Array or Set
+                //both fields can be specified as "field", "field.subfield" 
+                //or field: {subfield: foreignField} //TODO later if needed
+                //TODO: move it into object instead of class to give more flexibility, for example
+                //if person has children, it can have backRef father or mother depending on sex:
+                //return {
+                //    children: this.sex === 'male' ? 'father': 'mother'
+                //}
+                //
+                //return {
+                //  parent: ["children"],
+                //  notebooks: "owner"
+                //}
+                return {}
+            }
         }
-    }
-
-    Savable.classes                                  = {Savable}
-
+    
+        Savable.classes                                  = {Savable}
+    
     /**
      * sliceSavable - slice (limit) Savables for some permission
      * Array userACL - array of objectIDs, words or savable refs - current user, group objectid, or `tags` or `role` (ACL)
      */
 
-    function sliceSavable(userACL){
-        userACL = userACL.map(tag => tag.toString())
-        //console.log(userACL)
         class SlicedSavable extends Savable {
             constructor(...params){
                 super  (...params)