persistent-model-mixin.js

'use strict'

const _ = require('lodash')
const DatabaseError = require('../errors/server-error')

const defaultOptions = {
  underscored: true
}

const DATABASE_MODEL = Symbol('databaseModel')

module.exports = function PersistentModelMixin (Model, db, fields, userOptions) {
  const modelOptions = {
    instanceMethods: {
      toInternal: function () {
        return Model.fromDatabaseModel(this)
      }
    }
  }

  const options = _.merge({}, defaultOptions, modelOptions, userOptions)

  // Register model with Sequelize
  const DbModel = Model.DbModel = db.define(Model.name, fields, options)

  if (typeof Model.convertFromPersistent !== 'function') {
    Model.convertFromPersistent = function (data) {
      return data
    }
  }

  if (typeof Model.convertToPersistent !== 'function') {
    Model.convertToPersistent = function (data) {
      return data
    }
  }

  /**
   * Set data from database.
   *
   * Applies filters for conversion from database format to normal format.
   */
  Model.prototype.setDataPersistent = function (data) {
    this.setData(this._applyFilter(this.constructor.convertFromPersistent, data))
  }

  Model.prototype.getDataPersistent = function () {
    return this._applyFilter(this.constructor.convertToPersistent, this.getData())
  }

  Model.prototype.setDatabaseModel = function (dbModel) {
    this[DATABASE_MODEL] = dbModel
  }

  Model.prototype.getDatabaseModel = function () {
    if (!this[DATABASE_MODEL]) {
      this[DATABASE_MODEL] = DbModel.build(this.getDataPersistent())
    }
    return this[DATABASE_MODEL]
  }

  Model.prototype.create = function (options) {
    if (this[DATABASE_MODEL]) {
      throw new DatabaseError('Tried to create a model which already exists')
    }
    return this.getDatabaseModel().save(options)
  }

  Model.prototype.save = function (options) {
    return this.getDatabaseModel().update(this.getDataPersistent(), options)
  }

  Model.prototype.destroy = function (options) {
    return this.getDatabaseModel().destroy(options)
  }

  Model.fromDatabaseModel = function (dbModel) {
    const model = new Model()
    model.setDatabaseModel(dbModel)
    model.setDataPersistent(dbModel.toJSON())
    return model
  }

  Model.fromDataPersistent = function (data) {
    const model = new Model()
    model.setDataPersistent(data)
    return model
  }

  Model.findAll = function findAll (options) {
    return DbModel.findAll(options).then(function (results) {
      return results.map(Model.fromDatabaseModel.bind(Model))
    })
  }

  Model.findById = function findById (id, options) {
    return DbModel.findById(id, options).then(fromDatabaseModel)
  }

  Model.findOne = function findOne (options) {
    return DbModel.findOne(options).then(fromDatabaseModel)
  }

  for (let method of ['count', 'findAndCount', 'max', 'min', 'sum', 'truncate']) {
    Model[method] = DbModel[method].bind(DbModel)
  }

  Model.build = function build (values, options) {
    const model = Model.fromData(values)

    return DbModel.build(model.getDataPersistent(), options)
  }

  Model.create = function create (values, options) {
    const model = Model.fromData(values)

    return DbModel.create(model.getDataPersistent(), options).then(fromDatabaseModel)
  }

  Model.upsert = function upsert (values, options) {
    const model = Model.fromData(values)

    return DbModel.upsert(model.getDataPersistent(), options)
  }

  Model.findOrCreate = function findOrCreate (options) {
    return DbModel.findOrCreate(options).spread((instance, created) => {
      return [fromDatabaseModel(instance), created]
    })
  }

  Model.bulkCreate = function bulkCreate (records, options) {
    const models = records
      .map(Model.fromData.bind(Model))
      .map(_.method('getDataPersistent'))

    return DbModel.bulkCreate(models, options)
  }

  Model.buildExternal = function buildExternal (values, options) {
    const model = Model.fromDataExternal(values)

    return DbModel.build(model.getDataPersistent(), options)
  }

  Model.createExternal = function createExternal (values, options) {
    const model = Model.fromDataExternal(values)

    return DbModel.create(model.getDataPersistent(), options)
  }

  Model.upsertExternal = function upsertExternal (values, options) {
    const model = Model.fromDataExternal(values)

    return DbModel.upsert(model.getDataPersistent(), options)
  }

  Model.bulkCreateExternal = function bulkCreateExternal (records, options) {
    const models = records
      .map(Model.fromDataExternal.bind(Model))
      .map(_.method('getDataPersistent'))

    return DbModel.bulkCreate(models, options)
  }

  function fromDatabaseModel (result) {
    return result ? Model.fromDatabaseModel(result) : result
  }
}