[IMP] mail: split related paths only once

closes odoo/odoo#102501

X-original-commit: ab802a367c1e115e6f02a3657f42fae4e691ad28
Signed-off-by: Sébastien Theys (seb) <seb@odoo.com>
Signed-off-by: Alexandre Kühn (aku) <aku@odoo.com>
This commit is contained in:
Louis Wicket (wil)
2022-10-06 23:14:13 +02:00
parent 79f1da291b
commit 30a9ecc14c
5 changed files with 79 additions and 12 deletions
+11 -1
View File
@@ -136,7 +136,7 @@ function addOnChanges(modelName, onChanges) {
if (!Object.prototype.hasOwnProperty.call(onChange, 'methodName')) {
throw new Error("at least one onChange definition lacks a methodName (the name of the method to be called on change).");
}
if (!Array.isArray(onChange.dependencies) || onChange.dependencies.some(d => typeof d !== 'string')) {
if (!Array.isArray(onChange.dependencies)) {
throw new Error("onChange dependencies must be an array of strings.");
}
if (typeof onChange.methodName !== 'string') {
@@ -148,6 +148,16 @@ function addOnChanges(modelName, onChanges) {
throw new Error(`unknown key "${key}" in onChange definition. Allowed keys: ${allowedKeys.join(", ")}.`);
}
}
// paths of dependencies are splitted now to avoid having to do it
// each time the path is followed.
const splittedDependencies = [];
for (const dependency of onChange.dependencies) {
if (typeof dependency !== 'string') {
throw new Error("onChange dependencies must be an array of strings.");
}
splittedDependencies.push(dependency.split('.'));
}
onChange.dependencies = splittedDependencies;
}
}, `Cannot add onChanges to model "${modelName}": `);
registry.get(modelName).get('onChanges').push(...onChanges);
+21 -4
View File
@@ -124,7 +124,15 @@ export class ModelField {
* the first operation that is able to determine an order for the two
* records.
*/
this.sort = sort;
this.sort;
/**
* Sorted fields define sorting rules that can depend on related fields.
* The path to a related field is defined by a period-separated sequence
* of the relations to follow. To avoid wasting performances splitting
* the path each time a relation must be followed, it is spllited at
* field instantiation and stored here.
*/
this.sortedFieldSplittedPaths;
/**
* Determines whether and how elements of this field should be summed
* into a counter field (only makes sense for relational x2many).
@@ -150,16 +158,25 @@ export class ModelField {
this.required = true;
}
}
/**
* Automatically make relateds readonly.
*/
if (this.related) {
// Automatically make relateds readonly.
this.readonly = true;
}
if (this.compute) {
// Automatically make computes readonly.
this.readonly = true;
}
if (sort) {
this.sort = [];
// Only keep unique paths, as they will be used to create listeners,
// and we do not want to create useless duplicate listeners.
const relatedPathSet = new Set();
for (const [compareRule, pathAsString] of sort) {
relatedPathSet.add(pathAsString);
this.sort.push([compareRule, pathAsString.split('.')]);
}
this.sortedFieldSplittedPaths = [...relatedPathSet].map(pathAsString => pathAsString.split('.'));
}
}
/**
@@ -57,8 +57,7 @@ export class RelationSet {
// access all useful values of current record (and relations) to mark them as dependencies
this.record.modelManager.startListening(listener);
const compareDefinition = this.field.sort;
const relatedPathSet = new Set(compareDefinition.map(operation => operation[1])); // only keep unique paths to avoid unnecessary listeners
for (const relatedPath of relatedPathSet) {
for (const relatedPath of this.field.sortedFieldSplittedPaths) {
followRelations(value, relatedPath);
}
this.record.modelManager.stopListening(listener);
@@ -526,6 +526,35 @@ export class ModelManager {
}
}
/**
* @private
* @throws {Error}
*/
_checkOnChangesOnModels() {
for (const model of Object.values(this.models)) {
for (const { dependencies, methodName } of registry.get(model.name).get('onChanges')) {
for (const dependency of dependencies) {
let currentModel = model;
let currentField;
for (const fieldName of dependency) {
if (!currentModel) {
throw new Error(`OnChange '${methodName}' defines a dependency with path '${dependency.join('.')}', but this dependency does not resolve: ${currentField} is not a relational field, therefore there is no relation to follow.`);
}
currentField = currentModel.__fieldMap.get(fieldName);
if (!currentField) {
throw new Error(`OnChange '${methodName}' defines a dependency with path '${dependency.join('.')}', but this path does not resolve: ${currentModel}/${fieldName} does not exist.`);
}
if (currentField.to) {
currentModel = this.models[currentField.to];
} else {
currentModel = undefined;
}
}
}
}
}
}
/**
* @private
* @throws {Error} in case some fields are not correct.
@@ -580,6 +609,20 @@ export class ModelManager {
if (![model.name, 'Record'].includes(inverseField.to)) {
throw new Error(`${field} has its inverse ${inverseField} referring to an invalid model (${inverseField.to}).`);
}
if (field.sort) {
for (const path of field.sortedFieldSplittedPaths) {
let currentField = field;
for (const fieldName of path) {
if (!currentField.to) {
throw new Error(`Field ${field} defines a sort with path '${path.join('.')}', but this path does not resolve: ${currentField} is not a relational field, therefore there is no relation to follow.`);
}
if (!this.models[currentField.to].__fieldMap.has(fieldName)) {
throw new Error(`Field ${field} defines a sort with path '${path.join('.')}', but this path does not resolve: ${this.models[currentField.to]}/${fieldName} does not exist.`);
}
currentField = this.models[currentField.to].__fieldMap.get(fieldName);
}
}
}
}
for (const identifyingField of model.__identifyingFieldNames) {
const field = model.__fieldMap.get(identifyingField);
@@ -921,6 +964,7 @@ export class ModelManager {
* should have matching reversed relation.
*/
this._checkProcessedFieldsOnModels();
this._checkOnChangesOnModels();
}
/**
+2 -5
View File
@@ -6,15 +6,12 @@
* is undefined.
*
* @param {Record} record
* @param {string} relatedPath field names, dot separated to follow relations
* @param {string[]} relatedPath Array of field names.
* @returns {any}
*/
export function followRelations(record, relatedPath) {
let target = record;
for (const field of relatedPath.split('.')) {
if (!target.constructor.__fieldMap.has(field)) {
throw Error(`field(${field}) does not exist on ${target}`);
}
for (const field of relatedPath) {
target = target[field];
if (!target) {
break;