1 | 'use strict'
|
2 |
|
3 | EntityPool = require './entity-pool'
|
4 | DomainError = require './lib/domain-error'
|
5 | Util = require './util'
|
6 |
|
7 | debug = require('debug')('base-domain:fixture-loader')
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 | class FixtureLoader
|
16 |
|
17 | constructor: (@facade, @fixtureDirs = []) ->
|
18 | if not Array.isArray @fixtureDirs
|
19 | @fixtureDirs = [ @fixtureDirs ]
|
20 |
|
21 | @entityPool = new EntityPool
|
22 | @fixturesByModel = {}
|
23 |
|
24 |
|
25 | |
26 |
|
27 |
|
28 |
|
29 |
|
30 |
|
31 |
|
32 | load: (options = {}) ->
|
33 | { fs, requireFile } = @facade.constructor
|
34 |
|
35 | try
|
36 |
|
37 | modelNames = []
|
38 | for fixtureDir in @fixtureDirs
|
39 |
|
40 | for file in fs.readdirSync fixtureDir + '/data'
|
41 | [ modelName, ext ] = file.split('.')
|
42 | continue if ext not in ['coffee', 'js', 'json']
|
43 | path = fixtureDir + '/data/' + file
|
44 | fx = requireFile(path)
|
45 | fx.path = path
|
46 | fx.fixtureDir = fixtureDir
|
47 | @fixturesByModel[modelName] = fx
|
48 | modelNames.push modelName
|
49 |
|
50 | modelNames = @topoSort(modelNames)
|
51 |
|
52 | names = options.names ? modelNames
|
53 |
|
54 | modelNames = modelNames.filter (name) -> name in names
|
55 |
|
56 | if options.async
|
57 | return @saveAsync(modelNames).then => @entityPool
|
58 |
|
59 | else
|
60 | for modelName in modelNames
|
61 | @loadAndSaveModels(modelName)
|
62 |
|
63 | return @entityPool
|
64 |
|
65 | catch e
|
66 | if options.async then Promise.reject(e) else throw e
|
67 |
|
68 |
|
69 | |
70 |
|
71 |
|
72 | saveAsync: (modelNames) ->
|
73 |
|
74 | if not modelNames.length
|
75 | return Promise.resolve(true)
|
76 |
|
77 | modelName = modelNames.shift()
|
78 |
|
79 | Promise.resolve(@loadAndSaveModels(modelName)).then =>
|
80 | @saveAsync(modelNames)
|
81 |
|
82 |
|
83 | |
84 |
|
85 |
|
86 | loadAndSaveModels: (modelName) ->
|
87 |
|
88 | fx = @fixturesByModel[modelName]
|
89 |
|
90 | data =
|
91 | switch typeof fx.data
|
92 | when 'string'
|
93 | @readTSV(fx.fixtureDir, fx.data)
|
94 | when 'function'
|
95 | fx.data.call(new Scope(@, fx), @entityPool)
|
96 | when 'object'
|
97 | fx.data
|
98 |
|
99 | try
|
100 | repo = @facade.createPreferredRepository(modelName)
|
101 | catch e
|
102 | console.error e.message
|
103 | return
|
104 |
|
105 | if not data?
|
106 | throw new Error("Invalid fixture in model '#{modelName}'. Check the fixture file: #{fx.path}")
|
107 |
|
108 | ids = Object.keys(data)
|
109 | debug('inserting %s models into %s', ids.length, modelName)
|
110 |
|
111 |
|
112 | PORTION_SIZE = 5
|
113 | do saveModelsByPortion = =>
|
114 | return if ids.length is 0
|
115 |
|
116 | idsPortion = ids.slice(0, PORTION_SIZE)
|
117 | ids = ids.slice(idsPortion.length)
|
118 |
|
119 | results = for id in idsPortion
|
120 | obj = data[id]
|
121 | obj.id = id
|
122 | @saveModel(repo, obj)
|
123 |
|
124 | if Util.isPromise results[0]
|
125 | Promise.all(results).then =>
|
126 | saveModelsByPortion()
|
127 | else
|
128 | saveModelsByPortion()
|
129 |
|
130 |
|
131 |
|
132 | saveModel: (repo, obj) ->
|
133 |
|
134 | result = repo.save obj,
|
135 | method : 'create'
|
136 | fixtureInsertion : true
|
137 | include:
|
138 | entityPool: @entityPool
|
139 |
|
140 | if Util.isPromise result
|
141 | result.then (entity) =>
|
142 | @entityPool.set entity
|
143 |
|
144 | else
|
145 | @entityPool.set result
|
146 |
|
147 |
|
148 |
|
149 | |
150 |
|
151 |
|
152 |
|
153 |
|
154 |
|
155 | topoSort: (names) ->
|
156 |
|
157 |
|
158 | namesWithDependencies = []
|
159 |
|
160 | for el in names
|
161 | do add = (name = el) =>
|
162 |
|
163 | return if name in namesWithDependencies
|
164 |
|
165 | namesWithDependencies.push name
|
166 |
|
167 | fx = @fixturesByModel[name]
|
168 |
|
169 | unless fx?
|
170 | throw new DomainError 'base-domain:modelNotFound',
|
171 | "model '#{name}' is not found. It might be written in some 'dependencies' property."
|
172 |
|
173 | add(depname) for depname in fx.dependencies ? []
|
174 |
|
175 |
|
176 |
|
177 | visited = {}
|
178 | sortedNames = []
|
179 |
|
180 | for el in namesWithDependencies
|
181 |
|
182 | do visit = (name = el, ancestors = []) =>
|
183 |
|
184 | fx = @fixturesByModel[name]
|
185 |
|
186 | return if visited[name]?
|
187 |
|
188 | ancestors.push(name)
|
189 |
|
190 | visited[name] = true
|
191 |
|
192 | for depname in fx.dependencies ? []
|
193 |
|
194 | if depname in ancestors
|
195 | throw new DomainError 'base-domain:dependencyLoop',
|
196 | 'dependency chain is making loop'
|
197 |
|
198 | visit(depname, ancestors.slice())
|
199 |
|
200 | sortedNames.push(name)
|
201 |
|
202 | return sortedNames
|
203 |
|
204 |
|
205 | |
206 |
|
207 |
|
208 |
|
209 |
|
210 |
|
211 | readTSV: (fixtureDir, file) ->
|
212 | { fs } = @facade.constructor
|
213 |
|
214 | objs = {}
|
215 |
|
216 | lines = fs.readFileSync(fixtureDir + '/tsvs/' + file, 'utf8').split('\n')
|
217 |
|
218 | tsv = (line.split('\t') for line in lines)
|
219 |
|
220 | names = tsv.shift()
|
221 | names.shift()
|
222 |
|
223 | for data in tsv
|
224 | obj = {}
|
225 | id = data.shift()
|
226 | obj.id = id
|
227 |
|
228 | break if not id
|
229 |
|
230 | for name, i in names
|
231 | break if not name
|
232 | value = data[i]
|
233 | value = Number(value) if value.match(/^[0-9]+$/)
|
234 | obj[name] = value
|
235 |
|
236 | objs[obj.id] = obj
|
237 |
|
238 | return objs
|
239 |
|
240 |
|
241 |
|
242 |
|
243 |
|
244 |
|
245 |
|
246 |
|
247 |
|
248 |
|
249 |
|
250 |
|
251 |
|
252 |
|
253 |
|
254 |
|
255 | class Scope
|
256 |
|
257 | constructor: (@loader, @fx) ->
|
258 |
|
259 | |
260 |
|
261 |
|
262 |
|
263 |
|
264 | readTSV: (filename) ->
|
265 | @loader.readTSV(@fx.fixtureDir, filename)
|
266 |
|
267 |
|
268 | module.exports = FixtureLoader
|