import CableConnectionPool from “./cable-connection-pool” import Collection from “./collection” import CommandsPool from “./commands-pool” import CustomError from “./custom-error” import FormDataToObject from “./form-data-to-object” import ModelName from “./model-name” import Money from “js-money” import objectToFormData from “object-to-formdata”
const inflection = require(“inflection”)
export default class BaseModel {
static modelClassData() { throw new Error("modelClassData should be overriden by child") } static async find(id) { var primaryKeyName = this.modelClassData().primaryKey var query = {} query[`${primaryKeyName}_eq`] = id var model = await this.ransack(query).first() if (model) { return model } else { throw new CustomError("Record not found") } } static modelName() { return new ModelName({modelClassData: this.modelClassData()}) } static ransack(query = {}) { return new Collection({modelClass: this}, {ransack: query}) } constructor(args = {}) { this.changes = {} this.newRecord = args.isNewRecord this.relationshipsCache = {} if (args && args.data && args.data.a) { this._readModelDataFromArgs(args) } else if (args.a) { this.modelData = args.a } else if (args) { this.modelData = args } else { this.modelData = {} } } isAssociationLoaded(associationName) { if (associationName in this.relationshipsCache) return true return false } connect(eventName, callback) { var cableSubscription = CableConnectionPool.current().connectEvent(this.modelClassData().name, this._primaryKey(), eventName, callback) return cableSubscription } static connectCreated(callback) { var cableSubscription = CableConnectionPool.current().connectCreated(this.modelClassData().name, callback) return cableSubscription } connectDestroyed(callback) { var cableSubscription = CableConnectionPool.current().connectDestroyed(this.modelClassData().name, this._primaryKey(), callback) return cableSubscription } connectUpdated(callback) { var cableSubscription = CableConnectionPool.current().connectUpdate(this.modelClassData().name, this._primaryKey(), callback) return cableSubscription } assignAttributes(newAttributes) { for(var key in newAttributes) { var oldValue = this._getAttribute(key) var originalValue = this.modelData[key] var newValue = newAttributes[key] if (newValue != oldValue) { if (newValue == originalValue) { delete this.changes[key] } else { this.changes[key] = newValue } } } } cacheKey() { if (this.isPersisted()) { var keyParts = [ this.modelClassData().paramKey, this._primaryKey() ] if ("updated_at" in this.modelData) { keyParts.push(`updatedAt-${this.updatedAt().getTime()}`) } return keyParts.join("-") } else { return this.uniqueKey() } } async create() { var paramKey = this.modelClassData().paramKey var modelData = this.getAttributes() var dataToUse = {} dataToUse[paramKey] = modelData var response = await CommandsPool.addCommand({args: dataToUse, command: `${this.modelClassData().collectionName}-create`, collectionName: this.modelClassData().collectionName, primaryKey: this._primaryKey(), type: "create"}, {}) if (response.success) { if (response.model) { this._setNewModelData(response.model.a) this.changes = {} } return {model: this, response: response} } else { throw new new CustomError("Response wasn't successful", {model: this, response: response}) } } async createRaw(data) { var formData = FormDataToObject.toObject(data) var response = await CommandsPool.addCommand({args: formData, command: `${this.modelClassData().collectionName}-create`, collectionName: this.modelClassData().collectionName, primaryKey: this._primaryKey(), type: "create"}, {}) if (response.success) { if (response.model) { this._setNewModelData(response.model.a) this.changes = {} } return {model: this, response: response} } else { throw new CustomError("Response wasn't successful", {model: this, response: response}) } } async destroy() { var response = await CommandsPool.addCommand({command: `${this.modelClassData().collectionName}-destroy`, collectionName: this.modelClassData().collectionName, primaryKey: this._primaryKey(), type: "destroy"}, {}) if (response.success) { if (response.model) { this._setNewModelData(response.model.a) this.changes = {} } return {model: this, response: response} } else { throw new CustomError("Response wasn't successful", {model: this, response: response}) } } getAttributes() { return Object.assign(this.modelData, this.changes) } static humanAttributeName(attributeName) { var keyName = this.modelClassData().i18nKey return I18n.t(`activerecord.attributes.${keyName}.${BaseModel.snakeCase(attributeName)}`) } static snakeCase(string) { return inflection.underscore(string) } isAttributeChanged(attributeName) { var attributeNameUnderscore = inflection.underscore(attributeName) var attributeData = this.modelClassData().attributes.find(attribute => attribute.name == attributeNameUnderscore) if (!attributeData) { var attributeNames = this.modelClassData().attributes.map(attribute => attribute.name) throw new Error(`Couldn't find an attribute by that name: "${attributeName}" in: ${attributeNames.join(", ")}`) } if (!(attributeNameUnderscore in this.changes)) return false var oldValue = this.modelData[attributeNameUnderscore] var newValue = this.changes[attributeNameUnderscore] var changedMethod = this[`_is${inflection.camelize(attributeData.type, true)}Changed`] if (!changedMethod) throw new Error(`Don't know how to handle type: ${attributeData.type}`) return changedMethod(oldValue, newValue) } savedChangeToAttribute(attributeName) { if (!this.previousModelData) return false var attributeNameUnderscore = inflection.underscore(attributeName) var attributeData = this.modelClassData().attributes.find(attribute => attribute.name == attributeNameUnderscore) if (!attributeData) { var attributeNames = this.modelClassData().attributes.map(attribute => attribute.name) throw new Error(`Couldn't find an attribute by that name: "${attributeName}" in: ${attributeNames.join(", ")}`) } if (!(attributeNameUnderscore in this.previousModelData)) return true var oldValue = this.previousModelData[attributeNameUnderscore] var newValue = this.modelData[attributeNameUnderscore] var changedMethodName = `_is${inflection.camelize(attributeData.type)}Changed` var changedMethod = this[changedMethodName] if (!changedMethod) throw new Error(`Don't know how to handle type: ${attributeData.type}`) return changedMethod(oldValue, newValue) } _setNewModelData(modelData) { this.previousModelData = this.modelData this.modelData = modelData } _isDateChanged(oldValue, newValue) { if (Date.parse(oldValue) != Date.parse(newValue)) return true } _isIntegerChanged(oldValue, newValue) { if (parseInt(oldValue) != parseInt(newValue)) return true } _isStringChanged(oldValue, newValue) { var oldConvertedValue = `${oldValue}` var newConvertedValue = `${newValue}` if (oldConvertedValue != newConvertedValue) return true } isChanged() { var keys = Object.keys(this.changes) if (keys.length > 0) { return true } else { return false } } isNewRecord() { if (this.newRecord === false) { return false } else if ("id" in this.modelData && this.modelData.id) { return false } else { return true } } isPersisted() { return !this.isNewRecord() } modelClassData() { return this.constructor.modelClassData() } async reload() { var primaryKeyName = this.modelClassData().primaryKey var query = {} query[`${primaryKeyName}_eq`] = this._primaryKey() var model = await this.constructor.ransack(query).first() this._setNewModelData(model.modelData) this.changes = {} } save() { if (this.isNewRecord()) { return this.create() } else { return this.update() } } saveRaw(rawData) { if (this.isNewRecord()) { return this.createRaw(rawData) } else { return this.updateRaw(rawData) } } async update(newAttributes = null) { if (newAttributes) this.assignAttributes(newAttributes) if (this.changes.length == 0) return resolve({model: this}) var paramKey = this.modelClassData().paramKey var modelData = this.changes var dataToUse = {} dataToUse[paramKey] = modelData var response = await CommandsPool.addCommand({args: dataToUse, command: `${this.modelClassData().collectionName}-update`, collectionName: this.modelClassData().collectionName, primaryKey: this._primaryKey(), type: "update"}, {}) if (response.success) { if (response.model) { this._setNewModelData(response.model.a) this.changes = {} } return {"model": this, "response": response} } else { throw new CustomError("Response wasn't successful", {"model": this, "response": response}) } } async updateRaw(data) { var formData = FormDataToObject.toObject(data) var response = await CommandsPool.addCommand({args: formData, command: `${this.modelClassData().collectionName}-update`, collectionName: this.modelClassData().collectionName, primaryKey: this._primaryKey(), type: "update"}, {}) if (response.success) { if (response.model) { this._setNewModelData(response.model.a) this.changes = {} } return {model: this, response: response} } else { throw new CustomError("Response wasn't successful", {"model": this, "response": response}) } } isValid() { throw new Error("Not implemented yet") } async isValidOnServer() { var modelData = this.getAttributes() var paramKey = this.modelClassData().paramKey var dataToUse = {} dataToUse[paramKey] = modelData var response = await CommandsPool.addCommand({args: dataToUse, command: `${this.modelClassData().collectionName}-valid`, collectionName: this.modelClassData().collectionName, primaryKey: this._primaryKey(), type: "valid"}, {}) return {valid: response.valid, errors: response.errors} } modelClass() { return this.constructor } preloadRelationship(relationshipName, model) { this.relationshipsCache[BaseModel.snakeCase(relationshipName)] = model } uniqueKey() { if (!this.uniqueKeyValue) { var min = 500000000000000000 var max = 999999999999999999 var randomBetween = Math.floor(Math.random() * (max - min + 1) + min) this.uniqueKeyValue = randomBetween } return this.uniqueKeyValue } static _callCollectionCommand(args, commandArgs) { return CommandsPool.addCommand(args, commandArgs) } _callMemberCommand(args, commandArgs) { return CommandsPool.addCommand(args, commandArgs) } static _postDataFromArgs(args) { var postData if (args) { if (args instanceof FormData) { postData = args } else { postData = objectToFormData(args, {}, null, "args") } } else { postData = new FormData() } return postData } _getAttribute(attributeName) { if (attributeName in this.changes) { return this.changes[attributeName] } else if (attributeName in this.modelData) { return this.modelData[attributeName] } else if (this.isNewRecord()) { // Return null if this is a new record and the attribute name is a recognized attribute var attributes = this.modelClassData().attributes for(var attribute of attributes) { if (attribute.name == attributeName) return null } } throw new Error(`No such attribute: ${this.modelClassData().name}#${attributeName}`) } _getAttributeDateTime(attributeName) { var value = this._getAttribute(attributeName) if (!value) { return value } else if (value instanceof Date) { return value } else { return new Date(value) } } _isPresent(value) { if (!value) { return false } else if (typeof value == "string" && value.match(/^\s*$/)) { return false } return true } _getAttributeMoney(attributeName) { var value = this._getAttribute(attributeName) if (!value) return null var cents = value.amount var currency = value.currency return Money.fromInteger(cents, currency) } async _loadBelongsToReflection(args, queryArgs = {}) { if (args.reflectionName in this.relationshipsCache) { return this.relationshipsCache[args.reflectionName] } else { var collection = new Collection(args, queryArgs) var model = await collection.first() this.relationshipsCache[args.reflectionName] = model return model } } _readBelongsToReflection(args) { if (!(args.reflectionName in this.relationshipsCache)) { if (this.isNewRecord()) return null throw new Error(`${this.modelClassData().name}#${args.reflectionName} hasn't been loaded yet`) } return this.relationshipsCache[args.reflectionName] } async _loadHasOneReflection(args, queryArgs = {}) { if (args.reflectionName in this.relationshipsCache) { return this.relationshipsCache[args.reflectionName] } else { var collection = new Collection(args, queryArgs) var model = await collection.first() this.relationshipsCache[args.reflectionName] = model return model } } _readHasOneReflection(args) { if (!(args.reflectionName in this.relationshipsCache)) { if (this.isNewRecord()) return null throw new Error(`${this.modelClassData().name}#${args.reflectionName} hasn't been loaded yet`) } return this.relationshipsCache[args.reflectionName] } _readModelDataFromArgs(args) { this.modelData = args.data.a this.includedRelationships = args.data.r } _readIncludedRelationships(included) { if (!this.includedRelationships) return for(var relationshipName in this.includedRelationships) { var relationshipData = this.includedRelationships[relationshipName] var relationshipClassData = this.modelClassData().relationships.find(relationship => relationship.name == relationshipName) if (!relationshipClassData) throw new Error(`No relationship on ${this.modelClassData().name} by that name: ${relationshipName}`) var relationshipType = relationshipClassData.collectionName if (!relationshipData) { this.relationshipsCache[relationshipName] = null } else if (Array.isArray(relationshipData)) { var result = [] for(var relationshipId of relationshipData) { var model = included.getModel(relationshipType, relationshipId) result.push(model) } this.relationshipsCache[relationshipName] = result } else { var model = included.getModel(relationshipType, relationshipData) this.relationshipsCache[relationshipName] = model } } } _primaryKey() { return this._getAttribute(this.modelClassData().primaryKey) } static _token() { var csrfTokenElement = document.querySelector("meta[name='csrf-token']") if (csrfTokenElement) return csrfTokenElement.getAttribute("content") }
}