Skip to content

Commit 04da98d

Browse files
bpamiriclaude
andauthored
docs: correct five stale CLAUDE.md claims from the guide behavioral audit (batch 2) (#3091)
* docs: correct five stale CLAUDE.md claims found by the guide behavioral audit - Model Quick Reference: tableName() is a getter, the setter is table() (#3079) - RateLimiter keyFunction: middleware context has no cgi key; use the real cgi scope with a Len() guard (#3074) - Anti-Pattern 5: execute() has no parameters argument; NOW() is not portable (fails on SQLite and SQL Server) — use CURRENT_TIMESTAMP - Anti-Pattern 8: Wheels.ActionNotAllowed currently surfaces as HTTP 500, not the intended 404 (#3075) - Background Jobs: the advertised wheels jobs worker CLI does not exist; point at the programmatic queue API (#3090) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Peter Amiri <peter@alurium.com> * docs: hoist the keyFunction closure per Cross-Engine Invariant 5 The corrected RateLimiter snippet itself used an inline closure as a constructor named argument — the Adobe CF ArrayStoreException pattern this same file documents. Caught by the Reviewer; hoisted to match the RIGHT form in .ai/wheels/cross-engine-compatibility.md. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Peter Amiri <peter@alurium.com> --------- Signed-off-by: Peter Amiri <peter@alurium.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent 600548e commit 04da98d

1 file changed

Lines changed: 16 additions & 16 deletions

File tree

CLAUDE.md

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -121,13 +121,14 @@ Model finders return query objects, not arrays. Loop accordingly.
121121
```
122122

123123
### 5. Migration Seed Data — Direct SQL Only
124-
Parameter binding in `execute()` is unreliable. Use inline SQL.
124+
`execute()` accepts only a SQL string — there is no `parameters` argument (`Migration.cfc`: `execute(required string sql)`). Use inline SQL.
125125
```cfm
126126
// WRONG
127127
execute(sql="INSERT INTO roles (name) VALUES (?)", parameters=[{value="admin"}]);
128128
129-
// RIGHT — and use NOW() for database-agnostic dates (MySQL/PG/MSSQL/H2/SQLite)
130-
execute("INSERT INTO roles (name, createdAt, updatedAt) VALUES ('admin', NOW(), NOW())");
129+
// RIGHT — and use CURRENT_TIMESTAMP for database-agnostic dates (MySQL/PG/MSSQL/H2/SQLite).
130+
// NOW() fails on SQLite (the `wheels new` default DB) and SQL Server; no adapter rewrites it.
131+
execute("INSERT INTO roles (name, createdAt, updatedAt) VALUES ('admin', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)");
131132
```
132133

133134
### 6. Route Order Matters
@@ -149,7 +150,7 @@ function authenticate() { ... }
149150
private function authenticate() { ... }
150151
```
151152

152-
Conversely, public **framework helpers** mixed onto every controller (`env`, `model`, `redirectTo`, `linkTo`, the `is*` request predicates, the flash helpers, …) are auto-excluded from the routable surface. At app start `application.wheels.protectedControllerMethods` is built from the `wheels.Global` + `wheels.controller.*` + `wheels.view.*` mixin surface (the same `getMetaData().functions` set `$integrateComponents` mixes in), and `$callAction()` throws `Wheels.ActionNotAllowed` → 404 for any action whose name matches one. So a helper can't be invoked as an action — but you also **can't name a user action after a framework helper** (it 404s instead of dispatching). The standard REST action names (`index`, `show`, `new`, `edit`, `create`, `update`, `delete`) are not helpers, so they're unaffected ([#2845](https://github.com/wheels-dev/wheels/pull/2845)).
153+
Conversely, public **framework helpers** mixed onto every controller (`env`, `model`, `redirectTo`, `linkTo`, the `is*` request predicates, the flash helpers, …) are auto-excluded from the routable surface. At app start `application.wheels.protectedControllerMethods` is built from the `wheels.Global` + `wheels.controller.*` + `wheels.view.*` mixin surface (the same `getMetaData().functions` set `$integrateComponents` mixes in), and `$callAction()` throws `Wheels.ActionNotAllowed` for any action whose name matches one — intended to fall through to the 404 path, but it currently surfaces as HTTP 500 in every environment ([#3075](https://github.com/wheels-dev/wheels/issues/3075)). So a helper can't be invoked as an action — but you also **can't name a user action after a framework helper** (it errors instead of dispatching). The standard REST action names (`index`, `show`, `new`, `edit`, `create`, `update`, `delete`) are not helpers, so they're unaffected ([#2845](https://github.com/wheels-dev/wheels/pull/2845)).
153154

154155
### 9. Always cfparam View Variables
155156
Every variable passed from controller to view needs a cfparam at the top of the view file.
@@ -258,7 +259,7 @@ For new migrator helpers or anywhere you accept a column-name argument: declare
258259
component extends="Model" {
259260
function config() {
260261
// Table/key (only if non-conventional)
261-
tableName("tbl_users");
262+
table("tbl_users"); // setter is table(); tableName() is a getter — tableName("x") is a silent no-op (#3079)
262263
setPrimaryKey("userId");
263264
264265
// Associations — all named params when using options
@@ -422,11 +423,17 @@ new wheels.middleware.RateLimiter() /
422423
new wheels.middleware.RateLimiter(maxRequests=100, windowSeconds=120, strategy="slidingWindow")
423424
new wheels.middleware.RateLimiter(maxRequests=50, windowSeconds=60, strategy="tokenBucket")
424425
new wheels.middleware.RateLimiter(storage="database") // auto-creates wheels_rate_limits
425-
new wheels.middleware.RateLimiter(keyFunction=function(req) { // rate-limit per API key
426-
return req.cgi.http_x_api_key ?: "anonymous";
427-
})
426+
// rate-limit per API key — hoist the closure first: an inline function literal
427+
// as a constructor named arg crashes Adobe CF (Cross-Engine Invariant 5)
428+
var apiKeyFn = function(req) {
429+
var apiKey = cgi.http_x_api_key;
430+
return Len(apiKey) ? apiKey : "anonymous";
431+
};
432+
new wheels.middleware.RateLimiter(keyFunction=apiKeyFn)
428433
```
429434

435+
The `keyFunction` receives the dispatch middleware context `{params, route, pathInfo, method}` — it has **no `cgi` key** ([#3074](https://github.com/wheels-dev/wheels/issues/3074)), so `req.cgi.*` silently collapses every client into one bucket. Read the real `cgi` scope directly, and guard with `Len()` (a missing header reads as empty string, not undefined, so `?:` never fires).
436+
430437
Strategies: `fixedWindow` (default), `slidingWindow`, `tokenBucket`. Storage: `memory` or `database`. Emits `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`. Returns `429` with `Retry-After` when exceeded.
431438

432439
`windowSeconds` must be > 0; `maxRequests` must be >= 0. Invalid values throw `Wheels.RateLimiter.InvalidConfiguration` at construction. `maxRequests = 0` is a valid kill-switch.
@@ -649,14 +656,7 @@ result = (new wheels.Job()).processQueue(queue="mailers", limit=10);
649656
stats = (new wheels.Job()).queueStats();
650657
```
651658

652-
Worker CLI:
653-
```bash
654-
wheels jobs work --queue=mailers --interval=3
655-
wheels jobs status [--format=json]
656-
wheels jobs retry --queue=mailers
657-
wheels jobs purge --completed --failed --older-than=30
658-
wheels jobs monitor
659-
```
659+
Worker CLI: none yet — `wheels jobs work|status|retry|purge|monitor` do not exist (`cli/lucli/Module.cfc` has no `jobs` command; invoking one errors — [#3090](https://github.com/wheels-dev/wheels/issues/3090)). Drive queues programmatically via `processQueue()` / `queueStats()` (above), e.g. from a scheduled task or cron-invoked script.
660660

661661
Backoff: `this.baseDelay = 2`, `this.maxDelay = 3600` in `config()`. Formula: `Min(baseDelay * 2^attempt, maxDelay)`. The `wheels_jobs` table is auto-created on first enqueue/processing — no migration needed.
662662

0 commit comments

Comments
 (0)