1 |
|
2 |
|
3 |
|
4 | 'use strict';
|
5 |
|
6 | const Base = require('../base/Behavior');
|
7 |
|
8 | module.exports = class RelationChangeBehavior extends Base {
|
9 |
|
10 | _changes = {};
|
11 | _relations = {};
|
12 |
|
13 | constructor (config) {
|
14 | super(config);
|
15 | this.setHandler(ActiveRecord.EVENT_BEFORE_VALIDATE, this.beforeValidate);
|
16 | this.setHandler(ActiveRecord.EVENT_BEFORE_INSERT, this.beforeSave);
|
17 | this.setHandler(ActiveRecord.EVENT_BEFORE_UPDATE, this.beforeSave);
|
18 | this.setHandler(ActiveRecord.EVENT_AFTER_INSERT, this.afterSave);
|
19 | this.setHandler(ActiveRecord.EVENT_AFTER_UPDATE, this.afterSave);
|
20 | }
|
21 |
|
22 | beforeValidate () {
|
23 | return this.resolveChanges();
|
24 | }
|
25 |
|
26 | beforeSave () {
|
27 | return this.resolveChanges();
|
28 | }
|
29 |
|
30 | async afterSave () {
|
31 | for (const key of Object.keys(this._changes)) {
|
32 | await this.changeRelations(this._changes[key], key);
|
33 | }
|
34 | }
|
35 |
|
36 | getChanges (name) {
|
37 | return Object.prototype.hasOwnProperty.call(this._changes, name) ? this._changes[name] : null;
|
38 | }
|
39 |
|
40 | getRelation (name) {
|
41 | if (!Object.prototype.hasOwnProperty.call(this._relations, name)) {
|
42 | this._relations[name] = this.owner.getRelation(name);
|
43 | }
|
44 | return this._relations[name];
|
45 | }
|
46 |
|
47 | async resolveChanges () {
|
48 | if (this._resolved) {
|
49 | return;
|
50 | }
|
51 | this._resolved = true;
|
52 | for (const name of this.getActiveRelationNames()) {
|
53 | const value = this.owner.get(name);
|
54 | const oldValue = this.owner.getOldAttr(name);
|
55 | if (value && value !== oldValue) {
|
56 | this._changes[name] = CommonHelper.parseRelationChanges(value);
|
57 | }
|
58 | this.restoreValue(name, oldValue);
|
59 | if (this._changes[name]) {
|
60 | await this.resolveLinks(name);
|
61 | await this.resolveByRelated('unlinks', name);
|
62 | await this.resolveByRelated('deletes', name);
|
63 | }
|
64 | }
|
65 | }
|
66 |
|
67 | getActiveRelationNames () {
|
68 | let names = [];
|
69 | for (const item of this.owner.getValidators()) {
|
70 | if (item instanceof RelationValidator && item.isActive(this.owner.scenario)) {
|
71 | names = names.concat(item.attrs);
|
72 | }
|
73 | }
|
74 | return ArrayHelper.unique(names);
|
75 | }
|
76 |
|
77 | restoreValue (name, value) {
|
78 | if (value !== undefined) {
|
79 | return this.owner.set(name, value);
|
80 | }
|
81 | const relation = this.getRelation(name);
|
82 | this.owner.set(name, relation.isInternalArray() ? [] : null);
|
83 | }
|
84 |
|
85 | async resolveLinks (name) {
|
86 | const changes = this._changes[name];
|
87 | if (changes.links.length) {
|
88 | changes.links = await this.getRelation(name).model.findById(changes.links).all();
|
89 | }
|
90 | }
|
91 |
|
92 | async resolveByRelated (key, name) {
|
93 | const changes = this._changes[name];
|
94 | if (changes[key].length) {
|
95 | const query = this.getRelation(name);
|
96 | changes[key] = await query.and(['ID', query.model.PK, changes[key]]).all();
|
97 | }
|
98 | }
|
99 |
|
100 | async changeRelations (data, name) {
|
101 | if (!data) {
|
102 | return;
|
103 | }
|
104 | delete this._changes[name];
|
105 | if (Array.isArray(data.deletes)) {
|
106 | this.owner.constructor.delete(data.deletes);
|
107 | }
|
108 | if (Array.isArray(data.unlinks)) {
|
109 | if (this.owner.getDeleteOnUnlink().includes(name)) {
|
110 | this.owner.constructor.delete(data.unlinks);
|
111 | } else {
|
112 | for (const model of data.unlinks) {
|
113 | await this.owner.getLinker().unlink(name, model);
|
114 | }
|
115 | }
|
116 | }
|
117 | if (Array.isArray(data.links)) {
|
118 | for (const model of data.links) {
|
119 | await this.owner.getLinker().link(name, model);
|
120 | }
|
121 | }
|
122 | return PromiseHelper.setImmediate();
|
123 | }
|
124 |
|
125 | async getLinkedDocs (name) {
|
126 | if (this._linkedDocs !== undefined) {
|
127 | return this._linkedDocs;
|
128 | }
|
129 | const relation = this.getRelation(name);
|
130 | const docs = await relation.raw().all();
|
131 | const result = {};
|
132 | for (const doc of docs) {
|
133 | result[doc[relation.model.PK]] = doc;
|
134 | }
|
135 | const data = this._changes[name];
|
136 | if (data) {
|
137 | for (const model of data.links) {
|
138 | result[model.getId()] = model.getAttrMap();
|
139 | }
|
140 | for (const model of data.unlinks.concat(data.deletes)) {
|
141 | delete result[model.getId()];
|
142 | }
|
143 | }
|
144 | this._linkedDocs = Object.values(result);
|
145 | return this._linkedDocs;
|
146 | }
|
147 |
|
148 |
|
149 |
|
150 | async checkExists (name) {
|
151 | const relation = this.getRelation(name);
|
152 | if (relation.isMultiple()) {
|
153 | throw new Error(`Multiple relation cannot be checked for exist: ${name}`);
|
154 | }
|
155 | const docs = await this.getLinkedDocs(name);
|
156 | if (docs.length === 0) {
|
157 | return null;
|
158 | }
|
159 | if (docs.length !== 1) {
|
160 | throw new Error('Invalid relation changes');
|
161 | }
|
162 | return relation.isBackRef()
|
163 | ? await this.checkBackRefExist(relation, docs[0])
|
164 | : await this.checkRefExist(relation, docs[0]);
|
165 | }
|
166 |
|
167 | async checkRefExist ({refKey, linkKey}, doc) {
|
168 | const ids = await this.owner.find({[linkKey]: doc[refKey]}).limit(2).ids();
|
169 | return this.isExistingId(this.owner.getId(), ids);
|
170 | }
|
171 |
|
172 | async checkBackRefExist ({model, refKey, linkAttr}, doc) {
|
173 | const ids = await model.find({[refKey]: this.owner.get(linkAttr)}).limit(2).ids();
|
174 | return this.isExistingId(doc[model.PK], ids);
|
175 | }
|
176 |
|
177 | isExistingId (id, ids) {
|
178 | return ids.length === 1 ? !CommonHelper.isEqual(id, ids[0]) : ids.length > 1;
|
179 | }
|
180 | };
|
181 |
|
182 | const ArrayHelper = require('../helper/ArrayHelper');
|
183 | const CommonHelper = require('../helper/CommonHelper');
|
184 | const PromiseHelper = require('../helper/PromiseHelper');
|
185 | const ActiveRecord = require('../db/ActiveRecord');
|
186 | const RelationValidator = require('../validator/RelationValidator'); |
\ | No newline at end of file |