Skip to content

fix(model): tableName() with an argument fails loud in development instead of silently no-opping - #3134

Merged
bpamiri merged 1 commit into
developfrom
peter/issue-3079-tablename-guard
Jun 12, 2026
Merged

fix(model): tableName() with an argument fails loud in development instead of silently no-opping#3134
bpamiri merged 1 commit into
developfrom
peter/issue-3079-tablename-guard

Conversation

@bpamiri

@bpamiri bpamiri commented Jun 12, 2026

Copy link
Copy Markdown
Collaborator

Summary

tableName() on models is a zero-argument getter; the table setter is table(name) — unchanged since v2.5.1. CFML silently accepts extra positional arguments, so the 4.0-docs-era tableName("my_table") form in config() has ALWAYS been a silent no-op: the model keeps its convention table and the first finder throws Wheels.TableNotFound (or quietly hits the wrong table).

Design decision (maintainer): fail loud instead of adding a setter overload. PR #3104 made tableName(name) delegate to table(); this PR supersedes that approach because:

  • No code that ever worked breaks. Verified against 2.5-era source: tableName() has never accepted an argument, so the only call sites passing one are already-broken (silently no-opping) — surfacing them is strictly an improvement.
  • One canonical setter. table() stays the single way to map a model to a table; the docs have already been corrected back to it.
  • The trap should fail loud. A silent no-op that strands the model on its convention table is the worst failure mode — the new error names the fix explicitly.

Behavior

The gate matches the in-file prior art exactly: exists() in the same model/miscellaneous.cfc gates its Wheels.IncorrectArguments throw on $get("showErrorInformation"), as does paginationNav()'s Wheels.PaginationNav.InvalidArgument check. All internal callers grep-verified zero-arg (vendor/wheels, app, tests, cli/lucli).

Red/Green Evidence (Lucee 7 + SQLite, Docker harness)

The new throw-spec reuses PR #3104's verified non-tautological spec shape, inverted to expect the throw.

RED — specs run against unguarded develop (guard stashed):

HTTP 417
pass 17 fail 1 error 0
Failed: fails loud when passed a name and showErrorInformation is on, without changing the table
  - The incoming function did not throw an expected exception. Type=[Wheels.InvalidArgument]

GREEN — guard in place, same bundle:

HTTP 200
pass 18 fail 0 error 0

Full core suite:

pass 4416 fail 12 error 0 skipped 18

The 12 failures are all wheels.tests.specs.internal.testClientSpec — the known tolerated harness artifacts; matches the develop baseline exactly.

Changes

  • vendor/wheels/model/miscellaneous.cfc — argument-presence guard in tableName() (StructCount(arguments) && $get("showErrorInformation")Throw(type="Wheels.InvalidArgument", …)), plus doc comment explaining the getter/setter split and the prod no-op guarantee.
  • vendor/wheels/tests/specs/model/miscellaneousSpec.cfc — new describe "Tests that tableName rejects arguments - issue 3079": (a) dev throw + table untouched, (b) zero-arg getter unchanged, (c) production no-op (flips application.wheels.showErrorInformation with try/finally restore, the established pattern from paginationHelpersSpec).
  • CLAUDE.md — Model Quick Reference one-liner updated from "silent no-op" to the dev-throw behavior.
  • changelog.d/3079-tablename-getter-guard.fixed.mdfixed class: this fixes a silent failure mode (the no-op was the bug being reported), rather than adding new capability.

Related Issue

Fixes #3079

Type of Change

  • Bug fix

🤖 Generated with Claude Code

…stead of silently no-opping

Calling tableName("x") has always been a silent no-op — tableName() is a
zero-arg getter and table() is the canonical setter (unchanged since
v2.5.1) — but 4.0-era docs mistakenly taught it as a setter (issue #3079).
Rather than adding a setter overload (rejected: no code that ever worked
breaks, and the framework keeps one canonical setter), the getter now
throws Wheels.InvalidArgument pointing at table() when called with any
argument while showErrorInformation is on (development/testing — the same
gate exists() in the same file and paginationNav's InvalidArgument check
use). In production the call remains a no-op so upgrades never break a
running app.

Fixes #3079

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Peter Amiri <peter@alurium.com>

@wheels-bot wheels-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wheels Bot — Reviewer

TL;DR: This PR makes tableName() throw Wheels.InvalidArgument in development/testing when called with an argument (previously a silent no-op that stranded models on their convention table — issue #3079), while keeping production a no-op and the zero-arg getter byte-for-byte unchanged. The guard, exception type, gate, and test pattern all match established in-repo prior art exactly; the spec covers all three paths; docs and changelog are in order. Verdict: approve.

Verification notes (all claims in the PR body checked against the code)

Correctness

  • The guard at vendor/wheels/model/miscellaneous.cfc:186 short-circuits correctly: a zero-arg call has StructCount(arguments) == 0, so $get("showErrorInformation") is never even consulted on the getter path — the getter is unchanged (and trivially un-mutated, since the function never wrote state to begin with).
  • Production safety holds: with showErrorInformation off, the conjunction is false and the call falls through to the original return logic — same observable behavior as before the PR.
  • Internal-caller sweep independently re-verified: git grep finds no framework/app/CLI call site passing an argument to model tableName(); the argument-taking hits are the unrelated adapter method $tableName(list, action) (databaseAdapters/Base.cfc:304).

Conventions / prior art

  • Wheels.InvalidArgument is the established type for this class of error (JobWorker.cfc:387, model/associations.cfc:252, view/miscellaneous.cfc:564, view/pagination.cfc:603).
  • The showErrorInformation gate matches exists() in the same file (model/miscellaneous.cfc:100), as the doc comment claims.
  • StructCount(arguments) extra-argument detection is an existing framework idiom that already runs across the CI engine matrix (controller/redirection.cfc:75, controller/rendering.cfc:286).
  • String escaping in the throw is correct: doubled quotes for the embedded example (table(""my_table"")) and ##3079 to escape the hash in the interpolated detail string.

Cross-engine

  • No anti-pattern from the CLAUDE.md invariant list is touched (no closures-as-constructor-args, no attributeCollection = arguments, no bare tag statements, no Left(str, 0)). Worst conceivable engine divergence — an engine not populating arguments for an extra positional arg — would degrade to the pre-PR silent no-op, never a false-positive throw; and the new spec runs in the core suite on every engine × DB, so CI would surface exactly that divergence.

Tests

  • vendor/wheels/tests/specs/model/miscellaneousSpec.cfc:163-197 covers all three paths: dev throw + table untouched, zero-arg getter unchanged, production no-op. BDD wheels.WheelsTest syntax, and the _origShowErr try/finally restore matches the established pattern in paginationHelpersSpec.cfc:441-472 byte-for-byte. The c_o_r_e_authors expectation matches tests/_assets/models/Author.cfc:4. Red/green evidence in the PR body is plausible and the spec is non-tautological (RED run shows the expected single failure).

Docs

  • Changelog fragment changelog.d/3079-tablename-getter-guard.fixed.md uses a valid type (fixed) and a complete bullet line; no direct CHANGELOG.md edit.
  • CLAUDE.md Model Quick Reference one-liner accurately reflects the new dev-throw / prod-no-op split.

Commits

  • Single commit; header fix(model): tableName() with an argument fails loud in development instead of silently no-opping is 96 chars, valid type/scope, and the body explains the why (including the deliberate rejection of the #3104 setter-overload approach). DCO sign-off present and matches the author.

No blocking findings; nothing to request. A surgical fix with an honest paper trail.

@bpamiri
bpamiri merged commit 5e37877 into develop Jun 12, 2026
10 checks passed
@bpamiri
bpamiri deleted the peter/issue-3079-tablename-guard branch June 12, 2026 15:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

1 participant