Summary
$registerModelSuperAliases() (the super<name> override convention from #3325) only runs from Model.cfc:init(). But the #3213 instance fast path builds model instances without calling init(), so instances returned by new(), create(), findByKey(), findAll(returnAs="objects"), etc. never get the super<name> aliases — only the model() class object does.
Repro
// app/models/SuperOverride.cfc (any model that overrides a mixin)
component extends="Model" {
public string function columnNames() {
return "wrapped:" & superColumnNames();
}
}
m = model("superOverride") // class object — HAS superColumnNames (goes through init())
StructKeyExists(m, "superColumnNames") // true
m2 = model("superOverride").new() // instance — MISSING superColumnNames
m2.columnNames() // throws Wheels.MethodNotFound: superColumnNames
An app model that overrides save() and delegates via this.supersave() (the documented "Overriding Core Methods" pattern) hits The method supersave was not found on every create()/save()/finder-row.
Root cause
The #3213 fast path in global/objects.cfm:$createObjectFromRoot() (the $initModelObject branch) constructs instances with:
local.instance = CreateObject("component", local.component);
local.rv = local.instance.$initModelObject(...);
if (StructKeyExists(local.instance, "onDIcomplete")) { local.instance.onDIcomplete(); }
CreateObject("component", ...) does not invoke init(), so $registerModelSuperAliases() never fires for these instances. The legacy DI path (getInstance()) and the model() class object both do call init(), which is why the class-object assertion in SuperOverrideSpec passes while instances break.
Why the existing test missed it
tests/specs/controller/SuperOverrideSpec.cfc ("registers super for a model override, unchanged") asserts StructKeyExists(m, "superColumnNames") on g.model("superOverride") — the class object — not on a .new()/.create() instance.
Suggested fix
Register the aliases on the one hook every instance path runs: call $registerModelSuperAliases() at the top of Model.cfc:$initModelObject() (in addition to init()). It is idempotent ($registerModelSuperAliases skips names that already resolve on this), so the legacy/DI path that calls both init() and $initModelObject() is unaffected.
Impact
Silent 500s for any app model that overrides a model mixin and delegates to the framework original via super<name>. Not caught by the current framework suite.
Summary
$registerModelSuperAliases()(thesuper<name>override convention from #3325) only runs fromModel.cfc:init(). But the #3213 instance fast path builds model instances without callinginit(), so instances returned bynew(),create(),findByKey(),findAll(returnAs="objects"), etc. never get thesuper<name>aliases — only themodel()class object does.Repro
// app/models/SuperOverride.cfc (any model that overrides a mixin) component extends="Model" { public string function columnNames() { return "wrapped:" & superColumnNames(); } }m = model("superOverride") // class object — HAS superColumnNames (goes through init()) StructKeyExists(m, "superColumnNames") // true m2 = model("superOverride").new() // instance — MISSING superColumnNames m2.columnNames() // throws Wheels.MethodNotFound: superColumnNamesAn app model that overrides
save()and delegates viathis.supersave()(the documented "Overriding Core Methods" pattern) hitsThe method supersave was not foundon everycreate()/save()/finder-row.Root cause
The #3213 fast path in
global/objects.cfm:$createObjectFromRoot()(the$initModelObjectbranch) constructs instances with:local.instance = CreateObject("component", local.component); local.rv = local.instance.$initModelObject(...); if (StructKeyExists(local.instance, "onDIcomplete")) { local.instance.onDIcomplete(); }CreateObject("component", ...)does not invokeinit(), so$registerModelSuperAliases()never fires for these instances. The legacy DI path (getInstance()) and themodel()class object both do callinit(), which is why the class-object assertion inSuperOverrideSpecpasses while instances break.Why the existing test missed it
tests/specs/controller/SuperOverrideSpec.cfc("registers super for a model override, unchanged") assertsStructKeyExists(m, "superColumnNames")ong.model("superOverride")— the class object — not on a.new()/.create()instance.Suggested fix
Register the aliases on the one hook every instance path runs: call
$registerModelSuperAliases()at the top ofModel.cfc:$initModelObject()(in addition toinit()). It is idempotent ($registerModelSuperAliasesskips names that already resolve onthis), so the legacy/DI path that calls bothinit()and$initModelObject()is unaffected.Impact
Silent 500s for any app model that overrides a model mixin and delegates to the framework original via
super<name>. Not caught by the current framework suite.