Skip to content

Model super<name> override aliases are missing on instances created via new()/create()/finders #3515

Description

@bpamiri

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions