From ee24b38e53cb44892eacf7a268d7d81ac71c1a96 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Fri, 20 Mar 2026 10:55:27 -0600 Subject: [PATCH 1/2] Add BoxLang metadata regressions and compatibility fixes --- models/BaseEntity.cfc | 73 +++++++++++-------- .../integration/BaseEntity/MetadataSpec.cfc | 21 ++++++ 2 files changed, 65 insertions(+), 29 deletions(-) diff --git a/models/BaseEntity.cfc b/models/BaseEntity.cfc index 0b20eff..83157de 100644 --- a/models/BaseEntity.cfc +++ b/models/BaseEntity.cfc @@ -2729,10 +2729,20 @@ component accessors="true" { var meta = {}; meta[ "originalMetadata" ] = util.getInheritedMetadata( this ); meta[ "localMetadata" ] = getMetadata( this ); + var hasAccessorsMetadata = false; + if ( meta.localMetadata.keyExists( "accessors" ) ) { + hasAccessorsMetadata = lCase( trim( meta.localMetadata.accessors & "" ) ) == "true"; + } + // BoxLang 1.11 exposes component metadata attributes inside `annotations`. if ( - !meta[ "localMetadata" ].keyExists( "accessors" ) || - meta[ "localMetadata" ].accessors == false + !hasAccessorsMetadata && + meta.localMetadata.keyExists( "annotations" ) && + isStruct( meta.localMetadata.annotations ) && + meta.localMetadata.annotations.keyExists( "accessors" ) ) { + hasAccessorsMetadata = lCase( trim( meta.localMetadata.annotations.accessors & "" ) ) == "true"; + } + if ( !hasAccessorsMetadata ) { throw( type = "QuickAccessorsMissing", message = 'This instance is missing `accessors="true"` in the component metadata. This is required for Quick to work properly. Please add it to your component metadata and reinit your application.' @@ -2767,7 +2777,8 @@ component accessors="true" { if ( len( meta.originalMetadata.discriminatorValue ) ) { try { - var parentMeta = getComponentMetadata( meta.parentDefinition.meta.fullName ); + var parentMeta = reference.get_Meta().originalMetadata; + param parentMeta.discriminatorColumn = ""; meta.parentDefinition[ "discriminatorValue" ] = meta.originalMetadata.discriminatorValue; meta.parentDefinition[ "discriminatorColumn" ] = parentMeta.discriminatorColumn; } catch ( any e ) { @@ -2780,20 +2791,13 @@ component accessors="true" { } } - var baseEntityFunctionNames = variables._cache.getOrSet( "quick-metadata:BaseEntity", function() { - return arrayReduce( - getComponentMetadata( "quick.models.BaseEntity" ).functions, - function( acc, func ) { - arguments.acc[ arguments.func.name ] = ""; - return arguments.acc; - }, - {} - ); - } ); - meta[ "functionNames" ] = generateFunctionNameArray( - from = meta.originalMetadata.functions, - without = baseEntityFunctionNames - ); + var functionsForRelationshipDetection = []; + if ( meta.localMetadata.keyExists( "functions" ) && isArray( meta.localMetadata.functions ) ) { + functionsForRelationshipDetection = meta.localMetadata.functions; + } else if ( meta.originalMetadata.keyExists( "functions" ) && isArray( meta.originalMetadata.functions ) ) { + functionsForRelationshipDetection = meta.originalMetadata.functions; + } + meta[ "functionNames" ] = generateFunctionNameArray( from = functionsForRelationshipDetection ); param meta.originalMetadata.properties = []; @@ -2926,18 +2930,29 @@ component accessors="true" { * @return An attribute struct with all the keys needed. */ private struct function paramAttribute( required struct attr ) { - param attr.column = arguments.attr.name; - param attr.persistent = true; - param attr.nullValue = ""; - param attr.convertToNull = true; - param attr.casts = ""; - param attr.readOnly = false; - param attr.sqltype = ""; - param attr.insert = true; - param attr.update = true; - param attr.virtual = false; - param attr.exclude = false; - param attr.isParentColumn = false; + if ( + !arguments.attr.keyExists( "persistent" ) && + arguments.attr.keyExists( "annotations" ) && + isStruct( arguments.attr.annotations ) && + arguments.attr.annotations.keyExists( "persistent" ) + ) { + arguments.attr.persistent = arguments.attr.annotations.persistent; + } + param attr.column = arguments.attr.name; + param attr.persistent = true; + param attr.nullValue = ""; + param attr.convertToNull = true; + param attr.casts = ""; + param attr.readOnly = false; + param attr.sqltype = ""; + param attr.insert = true; + param attr.update = true; + param attr.virtual = false; + param attr.exclude = false; + param attr.isParentColumn = false; + if ( !isBoolean( attr.persistent ) ) { + attr.persistent = lCase( trim( attr.persistent & "" ) ) == "true"; + } variables._nullValues[ attr.name ] = attr.nullValue; return arguments.attr; } diff --git a/tests/specs/integration/BaseEntity/MetadataSpec.cfc b/tests/specs/integration/BaseEntity/MetadataSpec.cfc index 523eb11..d56882d 100644 --- a/tests/specs/integration/BaseEntity/MetadataSpec.cfc +++ b/tests/specs/integration/BaseEntity/MetadataSpec.cfc @@ -94,6 +94,27 @@ component extends="tests.resources.ModuleIntegrationSpec" { .notToHaveKey( "discriminatorColumn" ); } ); } ); + + describe( "boxlang metadata compatibility", function() { + it( + "can read accessors metadata from annotations", + function() { + expect( function() { + getInstance( "User" ); + } ).notToThrow(); + }, + skip = !server.keyExists( "boxlang" ) + ); + + it( + "can read property persistent metadata from annotations", + function() { + var link = getInstance( "Link" ); + expect( link.get_Attributes() ).notToHaveKey( "wirebox" ); + }, + skip = !server.keyExists( "boxlang" ) + ); + } ); } ); } From 8bcf976e28434953f25bba73f5056651a19229a5 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Fri, 20 Mar 2026 11:32:58 -0600 Subject: [PATCH 2/2] Fix cross-engine spec syntax and relationship metadata detection --- models/BaseEntity.cfc | 25 ++++++++++++++++--- .../integration/BaseEntity/MetadataSpec.cfc | 8 +++--- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/models/BaseEntity.cfc b/models/BaseEntity.cfc index 83157de..c5f8736 100644 --- a/models/BaseEntity.cfc +++ b/models/BaseEntity.cfc @@ -2791,13 +2791,30 @@ component accessors="true" { } } + var baseEntityFunctionNames = variables._cache.getOrSet( "quick-metadata:BaseEntity", function() { + return arrayReduce( + getComponentMetadata( "quick.models.BaseEntity" ).functions, + function( acc, func ) { + arguments.acc[ arguments.func.name ] = ""; + return arguments.acc; + }, + {} + ); + } ); var functionsForRelationshipDetection = []; - if ( meta.localMetadata.keyExists( "functions" ) && isArray( meta.localMetadata.functions ) ) { - functionsForRelationshipDetection = meta.localMetadata.functions; - } else if ( meta.originalMetadata.keyExists( "functions" ) && isArray( meta.originalMetadata.functions ) ) { + if ( + meta.originalMetadata.keyExists( "functions" ) && + isArray( meta.originalMetadata.functions ) && + !meta.originalMetadata.functions.isEmpty() + ) { functionsForRelationshipDetection = meta.originalMetadata.functions; + } else if ( meta.localMetadata.keyExists( "functions" ) && isArray( meta.localMetadata.functions ) ) { + functionsForRelationshipDetection = meta.localMetadata.functions; } - meta[ "functionNames" ] = generateFunctionNameArray( from = functionsForRelationshipDetection ); + meta[ "functionNames" ] = generateFunctionNameArray( + from = functionsForRelationshipDetection, + without = baseEntityFunctionNames + ); param meta.originalMetadata.properties = []; diff --git a/tests/specs/integration/BaseEntity/MetadataSpec.cfc b/tests/specs/integration/BaseEntity/MetadataSpec.cfc index d56882d..d5777e1 100644 --- a/tests/specs/integration/BaseEntity/MetadataSpec.cfc +++ b/tests/specs/integration/BaseEntity/MetadataSpec.cfc @@ -97,8 +97,8 @@ component extends="tests.resources.ModuleIntegrationSpec" { describe( "boxlang metadata compatibility", function() { it( - "can read accessors metadata from annotations", - function() { + title = "can read accessors metadata from annotations", + body = function() { expect( function() { getInstance( "User" ); } ).notToThrow(); @@ -107,8 +107,8 @@ component extends="tests.resources.ModuleIntegrationSpec" { ); it( - "can read property persistent metadata from annotations", - function() { + title = "can read property persistent metadata from annotations", + body = function() { var link = getInstance( "Link" ); expect( link.get_Attributes() ).notToHaveKey( "wirebox" ); },