From 54f4b99a1edf4680ea98fc41452e613c176770a9 Mon Sep 17 00:00:00 2001 From: Jelle De Loecker Date: Sun, 10 Mar 2024 13:54:54 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20Fix=20unnamed=20sub-schemas=20no?= =?UTF-8?q?t=20being=20able=20to=20be=20normalized=20properly?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 1 + lib/app/helper/enum_values.js | 15 ++-- lib/app/helper_field/schema_field.js | 35 ++++---- test/04-field.js | 115 +++++++++++++++++++++++++-- 4 files changed, 138 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ad7cc59..89d6061b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ * Add `should_add_exports()` SCSS function * Add `postcss-prune-var` dependency to remove unused variables from CSS files * Don't make `Alchemy#getResource()` helper method overwrite params data +* Fix unnamed sub-schemas not being able to be normalized properly ## 1.4.0-alpha.3 (2024-02-25) diff --git a/lib/app/helper/enum_values.js b/lib/app/helper/enum_values.js index b84c3399..eead455b 100644 --- a/lib/app/helper/enum_values.js +++ b/lib/app/helper/enum_values.js @@ -110,7 +110,7 @@ EnumMap.setMethod(function set(name, value) { * * @author Jelle De Loecker * @since 1.2.1 - * @version 1.3.6 + * @version 1.4.0 * * @param {WeakMap} wm * @@ -136,26 +136,29 @@ EnumMap.setMethod(function toHawkejs(wm) { cloned_entry.type = 'function'; } + // Always add the `schema` property if it's there if (val.schema) { cloned_entry.schema = JSON.clone(val.schema, 'toHawkejs', wm); } - let key, + let field_key, field_val; - for (key in val) { + // Now we need to look for other `Schema`-like instances + // and add those too + for (field_key in val) { - if (key == 'schema') { + if (field_key == 'schema') { continue; } - field_val = val[key]; + field_val = val[field_key]; if (field_val) { const SchemaClass = Blast.isBrowser ? Classes.Alchemy.Client.Schema : Classes.Alchemy.Schema; if (field_val instanceof SchemaClass) { - cloned_entry[key] = JSON.clone(field_val, 'toHawkejs', wm); + cloned_entry[field_key] = JSON.clone(field_val, 'toHawkejs', wm); } } } diff --git a/lib/app/helper_field/schema_field.js b/lib/app/helper_field/schema_field.js index 3f1dc9c9..3ad9adc0 100644 --- a/lib/app/helper_field/schema_field.js +++ b/lib/app/helper_field/schema_field.js @@ -418,34 +418,31 @@ SchemaField.setMethod(function _toApp(query, options, value, callback) { SchemaField.setMethod(async function _toAppFromValue(query, options, value, callback) { var that = this, - recursive, Dummy, item, name; // Don't get schema associated records if recursive is disabled - recursive = options.recursive; + let get_associations_recursive_level = options.recursive; - if (recursive == null) { + if (get_associations_recursive_level == null) { if (this.options?.recursive != null) { - recursive = this.options.recursive; + get_associations_recursive_level = this.options.recursive; } else { - recursive = 1; + get_associations_recursive_level = 1; } } - if (recursive && Blast.isBrowser) { + if (get_associations_recursive_level && Blast.isBrowser) { // @TODO: this will mostly fail on the browser, so disable it for now. // Maybe make it configurable later - recursive = 0; + get_associations_recursive_level = 0; } let record; if (options.parent_value) { - record = { - [this.schema.name] : options.parent_value - }; + record = options.parent_value; } else if (options._root_data) { let root_data = options._root_data; @@ -454,14 +451,16 @@ SchemaField.setMethod(async function _toAppFromValue(query, options, value, call root_data = root_data[this.schema.name]; } + record = root_data; + } else { record = { - [this.schema.name] : root_data + [this.name] : value, }; - } else { + } + + if (this.schema.name) { record = { - [this.schema.name] : { - [this.name] : value, - } + [this.schema.name] : record, }; } @@ -490,10 +489,12 @@ SchemaField.setMethod(async function _toAppFromValue(query, options, value, call }); value = await Function.parallel(4, tasks); + } else { + console.warn('Failed to find sub schema for', this.name, 'in', record); } // Get associated records if the subschema has associations defined - if (recursive && this.field_schema && !Object.isEmpty(this.field_schema.associations)) { + if (get_associations_recursive_level && this.field_schema && !Object.isEmpty(this.field_schema.associations)) { name = this.name + 'FieldModel'; Dummy = alchemy.getModel('Model', false); @@ -516,7 +517,7 @@ SchemaField.setMethod(async function _toAppFromValue(query, options, value, call sub_criteria.setOption('_root_data', options._root_data); sub_criteria.setOption('_parent_field', that); sub_criteria.setOption('_parent_model', that.schema.model_name); - sub_criteria.setOption('recursive', recursive); + sub_criteria.setOption('recursive', get_associations_recursive_level); sub_criteria.setOption('associations', this.field_schema.associations); diff --git a/test/04-field.js b/test/04-field.js index 9dff00b5..6e4cb7d8 100644 --- a/test/04-field.js +++ b/test/04-field.js @@ -861,13 +861,14 @@ describe('Field.FixedDecimal', function() { }); }); +let flobotonum_to_app_counter = 0; +let flobotonum_to_ds_counter = 0; + describe('Field.Schema', function() { before(function(next) { next = Function.regulate(next); - let to_app_counter = 0; - const FlobotonumField = Function.inherits('Alchemy.Field', 'Flobotonum'); FlobotonumField.setDatatype('object'); FlobotonumField.setSelfContained(true); @@ -875,16 +876,24 @@ describe('Field.Schema', function() { let result = { type : 'flobotonum', - value : value, + value : value?.main_value, to_apped : true, - to_app_counter : ++to_app_counter, + to_app_counter : ++flobotonum_to_app_counter, }; callback(null, result); }); FlobotonumField.setMethod(function _toDatasource(value, data, datasource, callback) { - callback(null, value.value); + + let main_value = value.value; + + let value_wrapper = { + main_value, + to_ds_counter: ++flobotonum_to_ds_counter, + }; + + callback(null, value_wrapper); }); let QuestComponents = alchemy.getClassGroup('all_quest_component'); @@ -1036,6 +1045,102 @@ describe('Field.Schema', function() { assert.strictEqual(second.settings?.flobotonum?.type, 'flobotonum', 'The value should have been passed through `toApp`'); assert.strictEqual(second.settings?.flobotonum?.value?.morestuff, true, 'The inner flobotonum value should have been saved'); assert.strictEqual(second.settings?.flobotonum?.to_app_counter, 6, 'This should have been the sixth `toApp` call for this field'); + }); + + it('should handle nested schemas with custom property names', async () => { + + flobotonum_to_app_counter = 0; + + const PropertyType = Function.inherits('Alchemy.Base', 'PropertyType', 'PropertyType'); + + PropertyType.constitute(function setConfigSchema() { + // Create a new schema + let configuration_schema = alchemy.createSchema(); + this.configuration_schema = configuration_schema; + }); + + PropertyType.constitute(function setValueSchema() { + // Create a new schema + let value_schema = alchemy.createSchema(); + this.value_schema = value_schema; + }); + + const StringType = Function.inherits('PropertyType', 'String'); + + StringType.constitute(function setSchema() { + + this.value_schema.addField('value', 'String', { + description : 'The actual value of this string property', + }); + + this.configuration_schema.addField('max_length', 'Number', { + description : 'The maximum length of the string', + }); + + this.configuration_schema.addField('flobotonum', 'Flobotonum'); + }); + + const UnitType = Function.inherits('Alchemy.Base', 'UnitType', 'UnitType'); + const TypeDefinitionType = Function.inherits('UnitType', 'TypeDefinition'); + + TypeDefinitionType.constitute(function setSchema() { + + this.schema = alchemy.createSchema(); + + this.schema.addField('defined_type', 'Enum', { + description : 'The defined type of this property', + values : PropertyType.getDescendantsDict(), + }); + + this.schema.addField('defined_type_configuration', 'Schema', { + description : 'The configuration of the defined type', + schema : 'defined_type.configuration_schema', + }); + }); + + let pledge = new Classes.Pledge.Swift(); + + const Unit = Function.inherits('Alchemy.Model', 'SwUnit'); + Unit.constitute(function addFields() { + + this.addField('unit_type', 'Enum', { + description : 'The type of this unit', + values : UnitType.getDescendantsDict(), + }); + + this.addField('unit_type_settings', 'Schema', { + schema : 'unit_type', + }); + + pledge.resolve(); + }); + + await pledge; + + let UnitModel = Model.get('SwUnit'); + let unit = UnitModel.createDocument(); + unit.unit_type = 'type_definition'; + unit.unit_type_settings = { + defined_type : 'string', + defined_type_configuration : { + max_length : '10', + flobotonum: { + value : { + xstuff : true + } + } + }, + }; + + await unit.save(); + + unit = await UnitModel.findByPk(unit._id); + const type_settings = unit.unit_type_settings || {}; + assert.strictEqual(unit.unit_type, 'type_definition'); + assert.strictEqual(type_settings.defined_type, 'string'); + assert.strictEqual(type_settings.defined_type_configuration?.max_length, 10); + assert.strictEqual(type_settings.defined_type_configuration?.flobotonum?.value?.xstuff, true); + assert.strictEqual(type_settings.defined_type_configuration?.flobotonum?.to_app_counter, 2); }); }); \ No newline at end of file