lib/record.js
/**
* Copyright 2015 Oursky Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import uuid from 'uuid';
import _ from 'lodash';
import {toJSON, fromJSON} from './util';
import ACL from './acl';
const defaultAttrs = {
_id: null,
_type: null
};
const _metaAttrs = {
_created_at: { //eslint-disable-line
parser: (v) => {
return new Date(v);
},
newKey: 'createdAt'
},
_updated_at: { //eslint-disable-line
parser: (v) => {
return new Date(v);
},
newKey: 'updatedAt'
},
_ownerID: {
parser: (v) => {
return v;
},
newKey: 'ownerID'
},
_created_by: { //eslint-disable-line
parser: (v) => {
return v;
},
newKey: 'createdBy'
},
_updated_by: { //eslint-disable-line
parser: (v) => {
return v;
},
newKey: 'updatedBy'
},
_access: {
parser: (v) => {
let acl = v;
if (v && v.toJSON) {
acl = v.toJSON();
}
return ACL.fromJSON(acl);
},
newKey: '_access'
}
};
const _metaKey = _.map(_metaAttrs, function (obj) {
return obj.newKey;
});
let _defaultACL = (new ACL()).toJSON();
export default class Record {
constructor(recordType, attrs = defaultAttrs) {
if (!Record.validType(recordType)) {
throw new Error(
'RecordType is not valid. Please start with alphanumeric string.');
}
this._recordType = recordType;
// Favouring `id`, since `id` will always contains type information if
// exist.
let id = attrs.id || attrs._id;
if (id === null || id === undefined) {
id = uuid.v4();
} else {
let [type, name] = Record.parseID(id);
if (type !== this._recordType) {
throw new Error('_id is not valid. RecordType mismatch.');
}
id = name;
}
this._id = id;
this._access = Record.defaultACL;
this.update(attrs);
this.updateTransient(attrs._transient);
}
get recordType() {
return this._recordType;
}
get id() {
return this._recordType + '/' + this._id;
}
get access() {
return this._access;
}
setAccess(acl) {
this._access = acl || Record.defaultACL;
}
get attributeKeys() {
let keys = Object.keys(this);
return _.filter(keys, function (value) {
return value.indexOf('_') !== 0 && !_.includes(_metaKey, value);
});
}
get $transient() {
return this._transient;
}
update(attrs) {
_.each(this.attributeKeys, function (key) {
delete this[key];
}, this);
_.each(attrs, function (value, key) {
if (key.indexOf('_') !== 0) {
if (_.isObject(value)) {
this[key] = fromJSON(value);
} else {
this[key] = value;
}
} else if (key in _metaAttrs) {
let meta = _metaAttrs[key];
this[meta.newKey] = meta.parser(value);
}
}, this);
}
setPublicNoAccess() {
this.access.setPublicNoAccess();
}
setPublicReadOnly() {
this.access.setPublicReadOnly();
}
setPublicReadWriteAccess() {
this.access.setPublicReadWriteAccess();
}
setNoAccessForRole(role) {
this.access.setNoAccessForRole(role);
}
setReadOnlyForRole(role) {
this.access.setReadOnlyForRole(role);
}
setReadWriteAccessForRole(role) {
this.access.setReadWriteAccessForRole(role);
}
setNoAccessForUser(user) {
this.access.setNoAccessForUser(user);
}
setReadOnlyForUser(user) {
this.access.setReadOnlyForUser(user);
}
setReadWriteAccessForUser(User) {
this.access.setReadWriteAccessForUser(User);
}
hasPublicReadAccess() {
this.access.hasPublicReadAccess();
}
hasPublicWriteAccess() {
this.access.hasPublicWriteAccess();
}
hasReadAccess(role) {
this.access.hasReadAccess(role);
}
hasWriteAccess(role) {
this.access.hasWriteAccess(role);
}
hasReadAccessForRole(role) {
this.access.hasReadAccessForRole(role);
}
hasWriteAccessForRole(role) {
this.access.hasWriteAccessForRole(role);
}
hasReadAccessForUser(user) {
this.access.hasReadAccessForUser(user);
}
hasWriteAccessForUser(user) {
this.access.hasWriteAccessForUser(user);
}
updateTransient(transient_, merge = false) {
var newTransient = merge ? _.clone(this._transient) : {};
_.each(transient_, function (value, key) {
// If value is an object and `_id` field exists, assume
// that it is a record.
if (_.isObject(value) && '_id' in value) {
newTransient[key] = recordDictToObj(value);
} else if (_.isObject(value)) {
newTransient[key] = fromJSON(value);
} else {
newTransient[key] = value;
}
});
this._transient = newTransient;
}
toJSON() {
let payload = {
_id: this.id,
_access: this.access.toJSON()
};
_.each(this.attributeKeys, function (key) {
payload[key] = toJSON(this[key]);
}, this);
return payload;
}
static get defaultACL() {
return ACL.fromJSON(_defaultACL);
}
static set defaultACL(acl) {
// saving serialized data in order to get copy of
// the ACL object on `get defaultACL()`.
_defaultACL = (acl || new ACL()).toJSON();
}
static validType(recordType) {
return recordType && recordType.indexOf('_') !== 0;
}
static parseID(id) {
let tuple = id.split('/');
if (tuple.length < 2) {
throw new Error(
'_id is not valid. _id has to be in the format `type/id`');
}
return [tuple[0], tuple.slice(1).join('/')];
}
static extend(recordType, instFunc) {
if (!Record.validType(recordType)) {
throw new Error(
'RecordType is not valid. Please start with alphanumeric string.');
}
let RecordProto = {};
function RecordCls(attrs = defaultAttrs) {
Record.call(this, recordType, attrs);
}
_.assign(RecordProto, instFunc, {
constructor: RecordCls
});
RecordCls.prototype = _.create(Record.prototype, RecordProto);
RecordCls.recordType = recordType;
return RecordCls;
}
}
function recordDictToObj(dict) {
const Cls = Record.extend(dict._id.split('/')[0]);
return new Cls(dict);
}