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 | for (const model of data.deletes) {
|
107 | await model.delete();
|
108 | }
|
109 | }
|
110 | if (Array.isArray(data.unlinks)) {
|
111 | for (const model of data.unlinks) {
|
112 | await this.owner.getLinker().unlink(name, model);
|
113 | }
|
114 | }
|
115 | if (Array.isArray(data.links)) {
|
116 | for (const model of data.links) {
|
117 | await this.owner.getLinker().link(name, model);
|
118 | }
|
119 | }
|
120 | return PromiseHelper.setImmediate();
|
121 | }
|
122 |
|
123 | async getLinkedDocs (name) {
|
124 | if (this._linkedDocs !== undefined) {
|
125 | return this._linkedDocs;
|
126 | }
|
127 | const relation = this.getRelation(name);
|
128 | const docs = await relation.raw().all();
|
129 | const result = {};
|
130 | for (const doc of docs) {
|
131 | result[doc[relation.model.PK]] = doc;
|
132 | }
|
133 | const data = this._changes[name];
|
134 | if (data) {
|
135 | for (const model of data.links) {
|
136 | result[model.getId()] = model.getAttrMap();
|
137 | }
|
138 | for (const model of data.unlinks.concat(data.deletes)) {
|
139 | delete result[model.getId()];
|
140 | }
|
141 | }
|
142 | this._linkedDocs = Object.values(result);
|
143 | return this._linkedDocs;
|
144 | }
|
145 |
|
146 |
|
147 |
|
148 | async checkExists (name) {
|
149 | const relation = this.getRelation(name);
|
150 | if (relation.isMultiple()) {
|
151 | throw new Error(`Multiple relation cannot be checked for exist: ${name}`);
|
152 | }
|
153 | const docs = await this.getLinkedDocs(name);
|
154 | if (docs.length === 0) {
|
155 | return null;
|
156 | }
|
157 | if (docs.length !== 1) {
|
158 | throw new Error('Invalid relation changes');
|
159 | }
|
160 | return relation.isBackRef()
|
161 | ? await this.checkBackRefExist(relation, docs[0])
|
162 | : await this.checkRefExist(relation, docs[0]);
|
163 | }
|
164 |
|
165 | async checkRefExist ({refKey, linkKey}, doc) {
|
166 | const ids = await this.owner.find({[linkKey]: doc[refKey]}).limit(2).ids();
|
167 | return this.isExistingId(this.owner.getId(), ids);
|
168 | }
|
169 |
|
170 | async checkBackRefExist ({model, refKey, linkAttr}, doc) {
|
171 | const ids = await model.find({[refKey]: this.owner.get(linkAttr)}).limit(2).ids();
|
172 | return this.isExistingId(doc[model.PK], ids);
|
173 | }
|
174 |
|
175 | isExistingId (id, ids) {
|
176 | return ids.length === 1 ? !CommonHelper.isEqual(id, ids[0]) : ids.length > 1;
|
177 | }
|
178 | };
|
179 |
|
180 | const ArrayHelper = require('../helper/ArrayHelper');
|
181 | const CommonHelper = require('../helper/CommonHelper');
|
182 | const PromiseHelper = require('../helper/PromiseHelper');
|
183 | const ActiveRecord = require('../db/ActiveRecord');
|
184 | const RelationValidator = require('../validator/RelationValidator'); |
\ | No newline at end of file |