UNPKG

base-domain

Version:

simple module to help build Domain-Driven Design

268 lines (183 loc) 6.71 kB
'use strict' fs = require 'fs' EntityPool = require './entity-pool' DomainError = require './lib/domain-error' { normalize } = require('path') { isPromise } = require('./util') debug = require('debug')('base-domain:fixture-loader') ###* Load fixture data (only works in Node.js) @class FixtureLoader @module base-domain ### class FixtureLoader constructor: (@facade, @fixtureDirs = []) -> if not Array.isArray @fixtureDirs @fixtureDirs = [ @fixtureDirs ] @entityPool = new EntityPool @fixturesByModel = {} ###* @method load @public @param {Object} [options] @param {Boolean} [options.async] if true, returns Promise. @return {EntityPool|Promise(EntityPool)} ### load: (options = {}) -> try modelNames = [] for fixtureDir in @fixtureDirs for file in fs.readdirSync fixtureDir + '/data' [ modelName, ext ] = file.split('.') continue if ext not in ['coffee', 'js', 'json'] path = fixtureDir + '/data/' + file fx = require(path) fx.path = path fx.fixtureDir = fixtureDir @fixturesByModel[modelName] = fx modelNames.push modelName modelNames = @topoSort(modelNames) names = options.names ? modelNames modelNames = modelNames.filter (name) -> name in names if options.async return @saveAsync(modelNames).then => @entityPool else for modelName in modelNames @loadAndSaveModels(modelName) return @entityPool catch e if options.async then Promise.reject(e) else throw e ###* @private ### saveAsync: (modelNames) -> if not modelNames.length return Promise.resolve(true) modelName = modelNames.shift() Promise.resolve(@loadAndSaveModels(modelName)).then => @saveAsync(modelNames) ###* @private ### loadAndSaveModels: (modelName) -> fx = @fixturesByModel[modelName] data = switch typeof fx.data when 'string' @readTSV(fx.fixtureDir, fx.data) when 'function' fx.data.call(new Scope(@, fx), @entityPool) when 'object' fx.data try repo = @facade.createPreferredRepository(modelName) # TODO: enable to add 2nd argument (noParent: boolean) catch e console.error e.message return if not data? throw new Error("Invalid fixture in model '#{modelName}'. Check the fixture file: #{fx.path}") ids = Object.keys(data) debug('inserting %s models into %s', ids.length, modelName) # save models portion by portion, considering parallel connection size PORTION_SIZE = 5 do saveModelsByPortion = => return if ids.length is 0 idsPortion = ids.slice(0, PORTION_SIZE) ids = ids.slice(idsPortion.length) results = for id in idsPortion obj = data[id] obj.id = id @saveModel(repo, obj) if isPromise results[0] Promise.all(results).then => saveModelsByPortion() else saveModelsByPortion() saveModel: (repo, obj) -> result = repo.save obj, method : 'create' fixtureInsertion : true # save even if the repository is master include: entityPool: @entityPool if isPromise result result.then (entity) => @entityPool.set entity else @entityPool.set result ###* topological sort @method topoSort @private ### topoSort: (names) -> # adds dependent models namesWithDependencies = [] for el in names do add = (name = el) => return if name in namesWithDependencies namesWithDependencies.push name fx = @fixturesByModel[name] unless fx? throw new DomainError 'base-domain:modelNotFound', "model '#{name}' is not found. It might be written in some 'dependencies' property." add(depname) for depname in fx.dependencies ? [] # topological sort visited = {} sortedNames = [] for el in namesWithDependencies do visit = (name = el, ancestors = []) => fx = @fixturesByModel[name] return if visited[name]? ancestors.push(name) visited[name] = true for depname in fx.dependencies ? [] if depname in ancestors throw new DomainError 'base-domain:dependencyLoop', 'dependency chain is making loop' visit(depname, ancestors.slice()) sortedNames.push(name) return sortedNames ###* read TSV, returns model data @method readTSV @private ### readTSV: (fixtureDir, file) -> objs = {} lines = fs.readFileSync(fixtureDir + '/tsvs/' + file, 'utf8').split('\n') tsv = (line.split('\t') for line in lines) names = tsv.shift() # first line is title names.shift() # first column is id for data in tsv obj = {} id = data.shift() obj.id = id break if not id # omit reading all lines below the line whose id is empty for name, i in names break if not name # omit reading all columns at right side of the column whose title is empty value = data[i] value = Number(value) if value.match(/^[0-9]+$/) # regard number-like values as a number obj[name] = value objs[obj.id] = obj return objs ###* 'this' property in fixture's data function this.readTSV('xxx.tsv') is available module.exports = { data: function(entityPool) { this.readTSV('model-name.tsv'); } }; @class Scope @private ### class Scope constructor: (@loader, @fx) -> ###* @method readTSV @param {String} filename filename (directory is automatically set) @return {Object} tsv contents ### readTSV: (filename) -> @loader.readTSV(@fx.fixtureDir, filename) module.exports = FixtureLoader