From 616063a4dc76a022b06a0a7030c07015a6f64419 Mon Sep 17 00:00:00 2001 From: ehennestad Date: Thu, 23 Oct 2025 14:45:52 +0200 Subject: [PATCH 1/7] Add autoexpansion of rows when adding new entries to MetaTable Added autoexpansion of dynamic table variables when adding rows to table Introduced an AutoUpdateValues option to addMissingVarsToMetaTable, addTable, addEntries, and appendTableRows methods, allowing control over whether update functions are automatically called when adding new variables or rows. Refactored method signatures to use MATLAB's arguments block for improved input validation. Added a newLike static method to create MetaTable instances with properties copied from an existing MetaTable. --- code/+nansen/+metadata/@MetaTable/MetaTable.m | 93 +++++++++++++------ 1 file changed, 67 insertions(+), 26 deletions(-) diff --git a/code/+nansen/+metadata/@MetaTable/MetaTable.m b/code/+nansen/+metadata/@MetaTable/MetaTable.m index 77cb7ca2..c61d46ec 100644 --- a/code/+nansen/+metadata/@MetaTable/MetaTable.m +++ b/code/+nansen/+metadata/@MetaTable/MetaTable.m @@ -45,8 +45,6 @@ MetaTableKey = ''; MetaTableName = ''; - MetaTableClass = ''; - MetaTableIdVarname = ''; % MetaTableMembers - cell array of character vectors representing % unique identifiers for all entries of the table @@ -66,6 +64,8 @@ properties (SetAccess = private) ItemClassName = ''; + MetaTableClass = ''; + MetaTableIdVarname = ''; end properties (Dependent) @@ -765,18 +765,20 @@ function checkIfMetaTableComplete(obj, options) obj.MetaTableVariables = obj.entries.Properties.VariableNames; end - function addMissingVarsToMetaTable(obj, metaTableType) + function addMissingVarsToMetaTable(obj, metaTableType, options) %addMissingVarsToMetaTable Add variable to table if it is missing. % - % If a table is present in the table variable definitions, but - % missing from the table, this functions adds a new variable to + % If a table variable is present in the table variable definitions, + % but missing from the table, this functions adds a new variable to % the table and initializes with the default value based on the % table variable definition. - - if nargin < 2 - metaTableType = 'session'; - end - + + arguments + obj (1,1) nansen.metadata.MetaTable + metaTableType = 'session' + options.AutoUpdateValues (1,1) logical = true + end + tableVarNames = obj.entries.Properties.VariableNames; currentProject = nansen.getCurrentProject(); @@ -811,13 +813,15 @@ function addMissingVarsToMetaTable(obj, metaTableType) defaultValue = fcnResult; end obj.addTableVariable(thisName, defaultValue) - - if refVariableAttributes{thisRowIndex, 'HasUpdateFunction'} - % Update for all items of the metatable - tableRowInd = 1:height(obj.entries); - updateFcnName = refVariableAttributes{thisRowIndex, 'UpdateFunctionName'}{1}; - wasUpdated = obj.updateTableVariable(thisName, tableRowInd, str2func(updateFcnName)); %#ok - % Todo: Show warning if any fails to update? + + if options.AutoUpdateValues + if refVariableAttributes{thisRowIndex, 'HasUpdateFunction'} + % Update for all items of the metatable + tableRowInd = 1:height(obj.entries); + updateFcnName = refVariableAttributes{thisRowIndex, 'UpdateFunctionName'}{1}; + wasUpdated = obj.updateTableVariable(thisName, tableRowInd, str2func(updateFcnName)); %#ok + % Todo: Show warning if any fails to update? + end end end if not( isempty(obj.filepath) ) @@ -966,7 +970,7 @@ function appendTable(obj, T) obj.addTable(T) end - function addTable(obj, T) + function addTable(obj, T, options) %addTable Add table rows to the MetaTable % % addTable(obj, T) adds rows from a table directly to the @@ -975,6 +979,13 @@ function addTable(obj, T) % from external sources or merging MetaTables. % Set MetaTable class if this is the first time entries are added + + arguments + obj (1,1) nansen.metadata.MetaTable + T (:,:) table + options.AutoUpdateValues (1,1) logical = true + end + if isempty(obj.MetaTableMembers) if isempty(obj.MetaTableClass) % Don't override if already set obj.MetaTableClass = 'table'; @@ -993,11 +1004,11 @@ function addTable(obj, T) end % Use common append logic - obj.appendTableRows(T); + obj.appendTableRows(T, "AutoUpdateValues", options.AutoUpdateValues); end % Add entry/entries to MetaTable table - function addEntries(obj, newEntries) + function addEntries(obj, newEntries, options) %addEntries Add schema object entries to the MetaTable % % addEntries(obj, newEntries) adds one or more schema objects @@ -1006,6 +1017,13 @@ function addEntries(obj, newEntries) % then converted to a table and appended. % Make sure entries are based on the MetadataEntity class. + + arguments + obj (1,1) nansen.metadata.MetaTable + newEntries + options.AutoUpdateValues (1,1) logical = true + end + isValid = isa(newEntries, 'nansen.metadata.abstract.MetadataEntity'); message = 'MetaTable entries must inherit from the MetadataEntity class'; @@ -1028,7 +1046,7 @@ function addEntries(obj, newEntries) newTableRows = newEntries.makeTable(); % Use common append logic - obj.appendTableRows(newTableRows); + obj.appendTableRows(newTableRows, "AutoUpdateValues", options.AutoUpdateValues); end function entries = getEntry(obj, listOfEntryIds) @@ -1516,7 +1534,7 @@ function updateEntries(obj, listOfEntryIds) end end - function appendTableRows(obj, newTableRows) + function appendTableRows(obj, newTableRows, options) %appendTableRows Append table rows to MetaTable with duplicate checking % % This is a private helper method that consolidates the common @@ -1528,7 +1546,13 @@ function appendTableRows(obj, newTableRows) % - Sorting by ID % % This method is called by both addEntries and addTable. - + + arguments + obj (1,1) nansen.metadata.MetaTable + newTableRows + options.AutoUpdateValues (1,1) logical = true + end + if isempty(newTableRows) return end @@ -1554,19 +1578,25 @@ function appendTableRows(obj, newTableRows) if isempty(newEntryIds) return; % Nothing to add end + + % Temporarily create a new MetaTable and add missing table + % variables + tempTable = nansen.metadata.MetaTable.newLike(newTableRows, obj); + tempTable.addMissingVarsToMetaTable(tmpMetaTable.MetaTableClass, ... + "AutoUpdateValues", options.AutoUpdateValues); - % Todo: + % Todo (alternative to creating a temp table): % - expand entries if table has dynamic table variables % % updateEntries(obj, listOfEntryIds) [Not implemented] % Concatenate tables try % Try direct concatenation - obj.entries = [obj.entries; newTableRows]; + obj.entries = [obj.entries; tmpMetaTable.entries]; catch % Fallback: convert to struct, concatenate, then back to table obj.entries = struct2table([table2struct(obj.entries); ... - table2struct(newTableRows)]); + table2struct(tmpMetaTable.entries)]); end % Update member list @@ -1822,6 +1852,17 @@ function openMetaTableFromName(obj, inputName) end methods (Static) + function metaTable = newLike(entries, metaTable) + arguments + entries table + metaTable (1,1) nansen.metadata.MetaTable + end + + metaTable = nansen.metadata.MetaTable(entries, ... + "ItemClassName", metaTable.ItemClassName, ... + "MetaTableClass", metaTable.MetaTableClass, ... + "MetaTableIdVarname", metaTable.MetaTableIdVarname); + end function metaTable = new(varargin) %NEW Create a new MetaTable From f2f68c13ba5acc7373b619812ad70a130ff2cc45 Mon Sep 17 00:00:00 2001 From: ehennestad Date: Thu, 23 Oct 2025 15:03:30 +0200 Subject: [PATCH 2/7] Update MetaTable.m Fix inconsistent variable names --- code/+nansen/+metadata/@MetaTable/MetaTable.m | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/code/+nansen/+metadata/@MetaTable/MetaTable.m b/code/+nansen/+metadata/@MetaTable/MetaTable.m index c61d46ec..ec0307e9 100644 --- a/code/+nansen/+metadata/@MetaTable/MetaTable.m +++ b/code/+nansen/+metadata/@MetaTable/MetaTable.m @@ -1581,8 +1581,8 @@ function appendTableRows(obj, newTableRows, options) % Temporarily create a new MetaTable and add missing table % variables - tempTable = nansen.metadata.MetaTable.newLike(newTableRows, obj); - tempTable.addMissingVarsToMetaTable(tmpMetaTable.MetaTableClass, ... + tempMetaTable = nansen.metadata.MetaTable.newLike(newTableRows, obj); + tempMetaTable.addMissingVarsToMetaTable(tmpMetaTable.MetaTableClass, ... "AutoUpdateValues", options.AutoUpdateValues); % Todo (alternative to creating a temp table): @@ -1592,11 +1592,11 @@ function appendTableRows(obj, newTableRows, options) % Concatenate tables try % Try direct concatenation - obj.entries = [obj.entries; tmpMetaTable.entries]; + obj.entries = [obj.entries; tempMetaTable.entries]; catch % Fallback: convert to struct, concatenate, then back to table obj.entries = struct2table([table2struct(obj.entries); ... - table2struct(tmpMetaTable.entries)]); + table2struct(tempMetaTable.entries)]); end % Update member list From 3e52ea21b06d1c8ac7df93ce056224df0b8a5c14 Mon Sep 17 00:00:00 2001 From: ehennestad Date: Thu, 23 Oct 2025 15:15:47 +0200 Subject: [PATCH 3/7] Update MetaTable.m --- code/+nansen/+metadata/@MetaTable/MetaTable.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/+nansen/+metadata/@MetaTable/MetaTable.m b/code/+nansen/+metadata/@MetaTable/MetaTable.m index ec0307e9..d7ff5fdd 100644 --- a/code/+nansen/+metadata/@MetaTable/MetaTable.m +++ b/code/+nansen/+metadata/@MetaTable/MetaTable.m @@ -1582,7 +1582,7 @@ function appendTableRows(obj, newTableRows, options) % Temporarily create a new MetaTable and add missing table % variables tempMetaTable = nansen.metadata.MetaTable.newLike(newTableRows, obj); - tempMetaTable.addMissingVarsToMetaTable(tmpMetaTable.MetaTableClass, ... + tempMetaTable.addMissingVarsToMetaTable(tempMetaTable.MetaTableClass, ... "AutoUpdateValues", options.AutoUpdateValues); % Todo (alternative to creating a temp table): From ea1c739e6bda0178b8e24f69e1ea0fa42a7b97f2 Mon Sep 17 00:00:00 2001 From: ehennestad Date: Mon, 27 Oct 2025 10:38:23 +0100 Subject: [PATCH 4/7] Add project fixture to tests that need a project --- tests/+nansen/+unittest/+metadata/MetaTableAdvancedTest.m | 3 +++ tests/+nansen/+unittest/+metadata/MetaTableTest.m | 3 +++ 2 files changed, 6 insertions(+) diff --git a/tests/+nansen/+unittest/+metadata/MetaTableAdvancedTest.m b/tests/+nansen/+unittest/+metadata/MetaTableAdvancedTest.m index ece71a6c..6a56cbcd 100644 --- a/tests/+nansen/+unittest/+metadata/MetaTableAdvancedTest.m +++ b/tests/+nansen/+unittest/+metadata/MetaTableAdvancedTest.m @@ -30,6 +30,9 @@ function setupTestEnvironment(~) error('MetaTable class not found on path'); end end + function setupProject(testCase) + testCase.applyFixture(nansen.fixture.ProjectFixture) + end end methods (TestMethodSetup) diff --git a/tests/+nansen/+unittest/+metadata/MetaTableTest.m b/tests/+nansen/+unittest/+metadata/MetaTableTest.m index 82c5a645..8ff8cbb3 100644 --- a/tests/+nansen/+unittest/+metadata/MetaTableTest.m +++ b/tests/+nansen/+unittest/+metadata/MetaTableTest.m @@ -35,6 +35,9 @@ function setupTestEnvironment(~) error('MetaTable class not found on path'); end end + function setupProject(testCase) + testCase.applyFixture(nansen.fixture.ProjectFixture) + end end methods (TestMethodSetup) From 350fc9633966a2ab6279538ba36ebb8d0609ea4d Mon Sep 17 00:00:00 2001 From: ehennestad Date: Wed, 29 Oct 2025 07:45:40 +0100 Subject: [PATCH 5/7] debug failing test --- .../+metadata/+abstract/@MetadataEntity/MetadataEntity.m | 1 + tests/+nansen/+unittest/+metadata/MetadataEntityTest.m | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/code/+nansen/+metadata/+abstract/@MetadataEntity/MetadataEntity.m b/code/+nansen/+metadata/+abstract/@MetadataEntity/MetadataEntity.m index 28f3451c..cedf0703 100644 --- a/code/+nansen/+metadata/+abstract/@MetadataEntity/MetadataEntity.m +++ b/code/+nansen/+metadata/+abstract/@MetadataEntity/MetadataEntity.m @@ -122,6 +122,7 @@ function updateDynamicVariable(obj, variableName) currentProject = nansen.getCurrentProject(); variableAttributes = currentProject.getTable('TableVariable'); + disp(variableAttributes) keep = variableAttributes.TableType == lower(entityType) ... & variableAttributes.IsCustom; diff --git a/tests/+nansen/+unittest/+metadata/MetadataEntityTest.m b/tests/+nansen/+unittest/+metadata/MetadataEntityTest.m index b23a4f3a..cfb698b6 100644 --- a/tests/+nansen/+unittest/+metadata/MetadataEntityTest.m +++ b/tests/+nansen/+unittest/+metadata/MetadataEntityTest.m @@ -31,7 +31,13 @@ function testGetSessionObject(testCase) sessionObject.addDynamicTableVariables() - sessionObject.updateDynamicVariable('BrainRegion') + try + sessionObject.updateDynamicVariable('BrainRegion') + catch + pause(1) + rehash + sessionObject.updateDynamicVariable('BrainRegion') + end %keyboard end From c53eea60fb089096a965c9793788abdaa5e153cb Mon Sep 17 00:00:00 2001 From: ehennestad Date: Wed, 29 Oct 2025 12:02:10 +0100 Subject: [PATCH 6/7] Attempt fix failing test --- .../+abstract/@MetadataEntity/MetadataEntity.m | 1 - .../+unittest/+metadata/MetadataEntityTest.m | 13 +++++-------- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/code/+nansen/+metadata/+abstract/@MetadataEntity/MetadataEntity.m b/code/+nansen/+metadata/+abstract/@MetadataEntity/MetadataEntity.m index cedf0703..28f3451c 100644 --- a/code/+nansen/+metadata/+abstract/@MetadataEntity/MetadataEntity.m +++ b/code/+nansen/+metadata/+abstract/@MetadataEntity/MetadataEntity.m @@ -122,7 +122,6 @@ function updateDynamicVariable(obj, variableName) currentProject = nansen.getCurrentProject(); variableAttributes = currentProject.getTable('TableVariable'); - disp(variableAttributes) keep = variableAttributes.TableType == lower(entityType) ... & variableAttributes.IsCustom; diff --git a/tests/+nansen/+unittest/+metadata/MetadataEntityTest.m b/tests/+nansen/+unittest/+metadata/MetadataEntityTest.m index cfb698b6..8bdf21f6 100644 --- a/tests/+nansen/+unittest/+metadata/MetadataEntityTest.m +++ b/tests/+nansen/+unittest/+metadata/MetadataEntityTest.m @@ -29,16 +29,13 @@ function testGetSessionObject(testCase) sourceFile = fullfile(sourceFile.folder, sourceFile.name); copyfile(sourceFile, targetFolder) + % Pause for a second to to give some time for internal + % registration of the new table variable. + % Todo: this shoud be immediatelty updated + pause(1) sessionObject.addDynamicTableVariables() - try - sessionObject.updateDynamicVariable('BrainRegion') - catch - pause(1) - rehash - sessionObject.updateDynamicVariable('BrainRegion') - end - %keyboard + sessionObject.updateDynamicVariable('BrainRegion') end function testAddTableVariable(testCase) From e5eb4aed0f37bd62dbe611e471723f0308308b8d Mon Sep 17 00:00:00 2001 From: ehennestad Date: Wed, 29 Oct 2025 12:58:41 +0100 Subject: [PATCH 7/7] Update MetadataEntityTest.m Fixed typos --- tests/+nansen/+unittest/+metadata/MetadataEntityTest.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/+nansen/+unittest/+metadata/MetadataEntityTest.m b/tests/+nansen/+unittest/+metadata/MetadataEntityTest.m index 8bdf21f6..245d0560 100644 --- a/tests/+nansen/+unittest/+metadata/MetadataEntityTest.m +++ b/tests/+nansen/+unittest/+metadata/MetadataEntityTest.m @@ -31,7 +31,7 @@ function testGetSessionObject(testCase) % Pause for a second to to give some time for internal % registration of the new table variable. - % Todo: this shoud be immediatelty updated + % Todo: this should be immediately updated pause(1) sessionObject.addDynamicTableVariables()