1 |
|
2 |
|
3 | 'use strict';
|
4 |
|
5 | const _ = require('lodash');
|
6 | const Helpers = require('./helpers');
|
7 | const Promise = require('bluebird');
|
8 | const EagerBase = require('./base/eager');
|
9 | const getAttributeUnique = (models, attribute) => _.uniq(_.map(models, (m) => m.get(attribute)));
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 | class EagerRelation extends EagerBase {
|
16 |
|
17 |
|
18 | eagerFetch(relationName, handled, options) {
|
19 | const relatedData = handled.relatedData;
|
20 |
|
21 |
|
22 | if (relatedData.parentFk === null) return;
|
23 |
|
24 | if (relatedData.type === 'morphTo') {
|
25 | return this.morphToFetch(relationName, relatedData, options);
|
26 | }
|
27 |
|
28 | return handled
|
29 | .sync(Object.assign({}, options, {parentResponse: this.parentResponse}))
|
30 | .select()
|
31 | .tap((response) => this._eagerLoadHelper(response, relationName, handled, _.omit(options, 'parentResponse')));
|
32 | }
|
33 |
|
34 |
|
35 |
|
36 |
|
37 | morphToFetch(relationName, relatedData, options) {
|
38 | const columnNames = relatedData.columnNames || [];
|
39 | const morphName = relatedData.morphName;
|
40 | const typeColumn = columnNames[0] === undefined ? `${morphName}_type` : columnNames[0];
|
41 | const idColumn = columnNames[1] === undefined ? `${morphName}_id` : columnNames[1];
|
42 |
|
43 | const parentsByType = _.groupBy(this.parent, (model) => {
|
44 | const type = model.get(typeColumn);
|
45 |
|
46 | if (!type)
|
47 | throw new Error("The target polymorphic model could not be determined because it's missing the type attribute");
|
48 |
|
49 | return type;
|
50 | });
|
51 | const TargetByType = _.mapValues(parentsByType, (parents, type) =>
|
52 | Helpers.morphCandidate(relatedData.candidates, type)
|
53 | );
|
54 |
|
55 | return Promise.all(
|
56 | _.map(parentsByType, (parents, type) => {
|
57 | const Target = TargetByType[type];
|
58 | const idAttribute = _.result(Target.prototype, 'idAttribute');
|
59 | const ids = getAttributeUnique(parents, idColumn);
|
60 |
|
61 | return Target.query('whereIn', idAttribute, ids)
|
62 | .sync(options)
|
63 | .select()
|
64 | .tap((response) => {
|
65 | const clone = relatedData.instance('morphTo', Target, {
|
66 | morphName,
|
67 | columnNames,
|
68 | morphValue: type
|
69 | });
|
70 | return this._eagerLoadHelper(response, relationName, {relatedData: clone}, options);
|
71 | });
|
72 | })
|
73 | ).then(_.flatten);
|
74 | }
|
75 |
|
76 |
|
77 | _eagerLoadHelper(response, relationName, handled, options) {
|
78 | const relatedData = handled.relatedData;
|
79 | const isEmptyHasOne = response.length === 0 && relatedData.type === 'hasOne';
|
80 | const relatedModels = isEmptyHasOne ? [] : this.pushModels(relationName, handled, response, options);
|
81 |
|
82 | return Promise.try(() => {
|
83 |
|
84 | if (response.length > 0 && options.withRelated) {
|
85 | const relatedModel = relatedData.createModel();
|
86 |
|
87 |
|
88 |
|
89 | if (relatedData.type === 'morphTo') {
|
90 | const withRelated = this._filterRelated(relatedModel, options);
|
91 | if (withRelated.length === 0) return;
|
92 | options = _.extend({}, options, {withRelated: withRelated});
|
93 | }
|
94 |
|
95 | return new EagerRelation(relatedModels, response, relatedModel).fetch(options).return(response);
|
96 | }
|
97 | }).tap(() => {
|
98 | return Promise.map(relatedModels, (model) => model.triggerThen('fetched', model, model.attributes, options));
|
99 | });
|
100 | }
|
101 |
|
102 |
|
103 |
|
104 | _filterRelated(relatedModel, options) {
|
105 |
|
106 |
|
107 | return _.reduce(
|
108 | options.withRelated,
|
109 | function(memo, val) {
|
110 | for (const key in val) {
|
111 | const seg = key.split('.')[0];
|
112 | if (_.isFunction(relatedModel[seg])) memo.push(val);
|
113 | }
|
114 | return memo;
|
115 | },
|
116 | []
|
117 | );
|
118 | }
|
119 | }
|
120 |
|
121 | module.exports = EagerRelation;
|