// Automatically detect and prevent common Wheels framework errors before code is generated. This skill activates during ANY Wheels code generation (models, controllers, views, migrations) to validate patterns and prevent known issues. Scans for mixed arguments, query/array confusion, non-existent helpers, and database-specific SQL.
| name | Wheels Anti-Pattern Detector |
| description | Automatically detect and prevent common Wheels framework errors before code is generated. This skill activates during ANY Wheels code generation (models, controllers, views, migrations) to validate patterns and prevent known issues. Scans for mixed arguments, query/array confusion, non-existent helpers, and database-specific SQL. |
This skill runs AUTOMATICALLY during any Wheels code generation to catch common errors before they're written to files.
Activate automatically during:
๐ด CRITICAL: The CLI wheels g migration command generates migrations with string boolean values that silently fail.
Detection Pattern:
createTable\s*\([^)]*force\s*=\s*['"][^'"]*['"]
createTable\s*\([^)]*id\s*=\s*['"][^'"]*['"]
Examples to Detect:
โ t = createTable(name='users', force='false', id='true', primaryKey='id');
โ t = createTable(name='posts', force='false');
โ t = createTable(name='comments', id='true');
Auto-Fix:
โ
t = createTable(name='users');
โ
t = createTable(name='posts');
โ
t = createTable(name='comments');
Error Message:
โ ๏ธ CRITICAL: CLI-generated string boolean values detected
Line: 5
Found: createTable(name='users', force='false', id='true', primaryKey='id')
Fix: createTable(name='users')
CLI generators create STRING booleans ('false', 'true') that don't work.
Remove force/id/primaryKey parameters and use Wheels defaults instead.
This will cause "NoPrimaryKey" errors if not fixed!
๐ด CRITICAL: Models must explicitly call setPrimaryKey("id") in config(), even when migrations are correct.
Detection Pattern:
component\s+extends\s*=\s*["']Model["'][\s\S]*?function\s+config\s*\(\s*\)\s*\{(?![\s\S]*?setPrimaryKey)
Examples to Detect:
โ component extends="Model" {
function config() {
table("users");
hasMany(name="posts"); // Missing setPrimaryKey!
}
}
Auto-Fix:
โ
component extends="Model" {
function config() {
table("users");
setPrimaryKey("id"); // Added!
hasMany(name="posts");
}
}
Error Message:
โ ๏ธ CRITICAL: Missing setPrimaryKey() in model config()
Line: 3
Found: config() without setPrimaryKey() declaration
Fix: Add setPrimaryKey("id") as first line after table() declaration
Even with correct migrations, Wheels ORM requires explicit primary key declaration.
This will cause "Wheels.NoPrimaryKey" errors if not added!
๐ด CRITICAL: Accessing properties in beforeCreate/beforeValidation callbacks without existence check causes errors.
Detection Pattern:
function\s+(beforeCreate|beforeValidation|setDefaults)[\s\S]*?if\s*\(\s*!len\s*\(\s*this\.\w+\s*\)\s*\)(?![\s\S]*?structKeyExists)
Examples to Detect:
โ function setDefaults() {
if (!len(this.followersCount)) { // Error if property doesn't exist!
this.followersCount = 0;
}
}
Auto-Fix:
โ
function setDefaults() {
if (!structKeyExists(this, "followersCount") || !len(this.followersCount)) {
this.followersCount = 0;
}
}
Error Message:
โ ๏ธ CRITICAL: Property access without structKeyExists() check
Line: 15
Found: if (!len(this.followersCount)) in beforeCreate callback
Fix: if (!structKeyExists(this, "followersCount") || !len(this.followersCount))
In beforeCreate/beforeValidation callbacks, properties may not exist yet.
This will cause "no accessible Member" errors if not checked!
๐ด CRITICAL: Validation functions use "properties" (plural) not "property" (singular).
Detection Pattern:
validates\w+Of\s*\(\s*property\s*=
Examples to Detect:
โ validatesPresenceOf(property="username,email")
โ validatesUniquenessOf(property="email")
โ validatesFormatOf(property="email", regEx="...")
Auto-Fix:
โ
validatesPresenceOf(properties="username,email")
โ
validatesUniquenessOf(properties="email")
โ
validatesFormatOf(properties="email", regEx="...")
Error Message:
โ ๏ธ CRITICAL: Wrong validation parameter name
Line: 8
Found: validatesPresenceOf(property="username")
Fix: validatesPresenceOf(properties="username")
Wheels validation functions use "properties" (PLURAL), not "property".
This validation will be silently ignored if not fixed!
Detection Pattern:
(hasMany|belongsTo|hasManyThrough|validatesPresenceOf|validatesUniquenessOf|validatesFormatOf|validatesLengthOf|findByKey|findAll|findOne)\s*\(\s*"[^"]+"\s*,\s*\w+\s*=
Examples:
โ hasMany("comments", dependent="delete")
โ belongsTo("user", foreignKey="userId")
โ validatesPresenceOf("title", message="Required")
โ findByKey(params.key, include="comments")
โ findAll(order="id DESC", where="active = 1")
Auto-Fix:
โ
hasMany(name="comments", dependent="delete")
โ
belongsTo(name="user", foreignKey="userId")
โ
validatesPresenceOf(property="title", message="Required")
โ
findByKey(key=params.key, include="comments")
โ
findAll(order="id DESC", where="active = 1") // No positional args, OK
Error Message:
โ ๏ธ ANTI-PATTERN DETECTED: Mixed argument styles
Line: 5
Found: hasMany("comments", dependent="delete")
Fix: hasMany(name="comments", dependent="delete")
Wheels requires consistent parameter syntax - either ALL positional OR ALL named.
When using options like 'dependent', you MUST use named arguments for ALL parameters.
Detection Pattern:
ArrayLen\s*\(\s*\w+\.(comments|posts|tags|users|[a-z]+)\(\s*\)\s*\)
Examples:
โ <cfset count = ArrayLen(post.comments())>
โ <cfloop array="#post.comments()#" index="comment">
โ <cfif ArrayIsEmpty(user.posts())>
Auto-Fix:
โ
<cfset count = post.comments().recordCount>
โ
<cfloop query="comments"> // After: comments = post.comments()
โ
<cfif user.posts().recordCount == 0>
Error Message:
โ ๏ธ ANTI-PATTERN DETECTED: ArrayLen() on query object
Line: 12
Found: ArrayLen(post.comments())
Fix: post.comments().recordCount
Wheels associations return QUERIES, not arrays. Use .recordCount for count.
Detection Pattern:
<cfloop\s+query="[^"]+">[\s\S]*?\.\w+\(\)\.recordCount
Examples:
โ <cfloop query="posts">
<p>#posts.comments().recordCount# comments</p>
</cfloop>
Auto-Fix:
โ
<cfloop query="posts">
<cfset postComments = model("Post").findByKey(posts.id).comments()>
<p>#postComments.recordCount# comments</p>
</cfloop>
Error Message:
โ ๏ธ ANTI-PATTERN DETECTED: Association access inside query loop
Line: 15
Found: posts.comments().recordCount inside <cfloop query="posts">
Fix: Load association separately: postComments = model("Post").findByKey(posts.id).comments()
Cannot access associations directly on query objects inside loops.
Must reload the model object first.
Detection Pattern:
(emailField|passwordField|numberField|dateField|timeField|urlField|telField)\s*\(
Examples:
โ #emailField(objectName="user", property="email")#
โ #passwordField(objectName="user", property="password")#
โ #numberField(objectName="product", property="price")#
โ #urlField(objectName="company", property="website")#
Auto-Fix:
โ
#textField(objectName="user", property="email", type="email")#
โ
#textField(objectName="user", property="password", type="password")#
โ
#textField(objectName="product", property="price", type="number")#
โ
#textField(objectName="company", property="website", type="url")#
Error Message:
โ ๏ธ ANTI-PATTERN DETECTED: Non-existent form helper
Line: 23
Found: emailField(objectName="user", property="email")
Fix: textField(objectName="user", property="email", type="email")
Wheels doesn't have specialized field helpers like emailField().
Use textField() with the 'type' attribute instead.
Detection Pattern:
resources\s*\([^)]+,\s*(nested|namespace)\s*=
Examples:
โ resources("posts", nested=resources("comments"))
โ resources("users", namespace="admin")
Auto-Fix:
โ
resources("posts")
โ
resources("comments")
// Define separately, not nested
Error Message:
โ ๏ธ ANTI-PATTERN DETECTED: Rails-style nested routing
Line: 8
Found: resources("posts", nested=resources("comments"))
Fix: resources("posts") and resources("comments") as separate declarations
Wheels doesn't support Rails-style nested resources.
Define resources separately and handle nesting in controllers.
Detection Pattern:
(DATE_SUB|DATE_ADD|NOW|CURDATE|CURTIME|DATEDIFF|INTERVAL)\s*\(
Examples:
โ execute("INSERT INTO posts (publishedAt) VALUES (DATE_SUB(NOW(), INTERVAL 1 DAY))")
โ execute("SELECT * FROM posts WHERE createdAt > CURDATE()")
โ execute("UPDATE posts SET modifiedAt = NOW()")
Auto-Fix:
โ
var pastDate = DateAdd("d", -1, Now());
execute("INSERT INTO posts (publishedAt) VALUES (TIMESTAMP '#DateFormat(pastDate, "yyyy-mm-dd")# #TimeFormat(pastDate, "HH:mm:ss")#')")
โ
var today = Now();
execute("SELECT * FROM posts WHERE createdAt > TIMESTAMP '#DateFormat(today, "yyyy-mm-dd")# 00:00:00'")
โ
var now = Now();
execute("UPDATE posts SET modifiedAt = TIMESTAMP '#DateFormat(now, "yyyy-mm-dd")# #TimeFormat(now, "HH:mm:ss")#'")
Error Message:
โ ๏ธ ANTI-PATTERN DETECTED: Database-specific SQL function
Line: 34
Found: DATE_SUB(NOW(), INTERVAL 1 DAY)
Fix: Use CFML DateAdd() + TIMESTAMP formatting
MySQL-specific date functions won't work across all databases.
Use CFML date functions (DateAdd, DateFormat, TimeFormat) for compatibility.
Detection Pattern:
<form[^>]*method\s*=\s*["']post["'][^>]*>(?![\s\S]*csrf)
Examples:
โ <form method="post" action="/users/create">
<!--- No CSRF token --->
</form>
Auto-Fix:
โ
#startFormTag(action="create", method="post")#
<!--- CSRF token automatically included --->
#endFormTag()#
Error Message:
โ ๏ธ ANTI-PATTERN DETECTED: Form without CSRF protection
Line: 45
Found: <form method="post"> without CSRF token
Fix: Use #startFormTag()# which includes CSRF automatically
Wheels provides built-in CSRF protection.
Use startFormTag() instead of raw <form> tags.
Detection Pattern: Check for mixing positional and named arguments within same config() function
Examples:
โ function config() {
hasMany("comments"); // Positional
belongsTo(name="user"); // Named
}
Auto-Fix:
โ
function config() {
hasMany(name="comments"); // All named
belongsTo(name="user"); // All named
}
Error Message:
โ ๏ธ ANTI-PATTERN DETECTED: Inconsistent parameter style in config()
Lines: 5-6
Found: Mixed positional and named arguments
Fix: Use SAME style for ALL association/validation declarations
config() function should use consistent argument style throughout.
Either ALL positional OR ALL named - never mix them.
๐ Validating generated code...
โ ๏ธ 3 anti-patterns detected and auto-fixed:
1. [Line 8] Mixed argument styles
Before: hasMany("comments", dependent="delete")
After: hasMany(name="comments", dependent="delete")
2. [Line 15] Query/Array confusion
Before: ArrayLen(post.comments())
After: post.comments().recordCount
3. [Line 23] Non-existent helper
Before: emailField(objectName="user", property="email")
After: textField(objectName="user", property="email", type="email")
โ
All anti-patterns fixed. Writing file...
Model Generation (wheels-model-generator)
Controller Generation (wheels-controller-generator)
View Generation (wheels-view-generator)
Migration Generation (wheels-migration-generator)
// Test Case 1: Should detect mixed arguments
Input: hasMany("comments", dependent="delete")
Detect: โ
YES
Fix: hasMany(name="comments", dependent="delete")
// Test Case 2: Should allow consistent named arguments
Input: hasMany(name="comments", dependent="delete")
Detect: โ NO (Correct pattern)
// Test Case 3: Should allow consistent positional (no options)
Input: hasMany("comments")
Detect: โ NO (Correct pattern)
// Test Case 4: Should detect ArrayLen on association
Input: ArrayLen(post.comments())
Detect: โ
YES
Fix: post.comments().recordCount
// Test Case 5: Should detect non-existent helper
Input: emailField(objectName="user", property="email")
Detect: โ
YES
Fix: textField(objectName="user", property="email", type="email")
// Test Case 6: Should detect database-specific SQL
Input: "INSERT INTO posts (date) VALUES (NOW())"
Detect: โ
YES
Fix: Use CFML Now() with formatting
// Test Case 7: Should detect inconsistent config styles
Input: hasMany("comments") + belongsTo(name="user")
Detect: โ
YES
Fix: Both use named arguments
// .claude/anti-pattern-config.json
{
"checks": {
"mixedArguments": true,
"queryArrayConfusion": true,
"nonExistentHelpers": true,
"railsRouting": true,
"databaseSpecificSQL": true,
"csrfProtection": true,
"inconsistentConfig": true
},
"autoFix": true,
"reportLevel": "warning"
}
All Wheels generator skills depend on this skill for validation.
Generated by: Wheels Anti-Pattern Detector Skill v1.0 Framework: CFWheels 3.0+ Last Updated: 2025-10-20