wheels generate snippets
This command works correctly without options (parameters). Option support is under development and will be available soon.
Generate code snippets and boilerplate code for common patterns.
Synopsis
wheels generate snippets [pattern] [options]
wheels g snippets [pattern] [options]
Description
The wheels generate snippets
command creates code snippets for common Wheels patterns and best practices. It provides ready-to-use code blocks that can be customized for your specific needs, helping you implement standard patterns quickly and consistently.
Arguments
pattern
Snippet pattern to generate
Shows available patterns
Options
--list
List all available snippets
false
--category
Filter by category
All categories
--output
Output format (console, file, clipboard)
console
--customize
Interactive customization
false
--force
Overwrite existing files
false
--help
Show help information
Available Snippets
List All Snippets
wheels generate snippets --list
Output:
Available Snippets:
━━━━━━━━━━━━━━━━━━━
Authentication:
- login-form Login form with remember me
- auth-filter Authentication filter
- password-reset Password reset flow
- user-registration User registration with validation
Model Patterns:
- soft-delete Soft delete implementation
- audit-trail Audit trail with timestamps
- sluggable URL-friendly slugs
- versionable Version tracking
- searchable Full-text search
Controller Patterns:
- crud-actions Complete CRUD actions
- api-controller JSON API controller
- nested-resource Nested resource controller
- admin-controller Admin area controller
View Patterns:
- form-with-errors Form with error handling
- pagination-links Pagination navigation
- search-form Search form with filters
- ajax-form AJAX form submission
Database:
- migration-indexes Common index patterns
- seed-data Database seeding
- constraints Foreign key constraints
Authentication Snippets
Login Form
wheels generate snippets login-form
Generates:
<!--- views/sessions/new.cfm --->
<h1>Login</h1>
#errorMessagesFor("user")#
#startFormTag(action="create", class="login-form")#
<div class="form-group">
#textField(
objectName="user",
property="email",
label="Email",
class="form-control",
placeholder="[email protected]",
required=true,
autofocus=true
)#
</div>
<div class="form-group">
#passwordField(
objectName="user",
property="password",
label="Password",
class="form-control",
required=true
)#
</div>
<div class="form-group">
#checkBox(
objectName="user",
property="rememberMe",
label="Remember me",
value="1"
)#
</div>
<div class="form-group">
#submitTag(value="Login", class="btn btn-primary")#
#linkTo(text="Forgot password?", route="forgotPassword", class="btn btn-link")#
</div>
#endFormTag()#
<!--- controllers/Sessions.cfc --->
component extends="Controller" {
function new() {
user = model("User").new();
}
function create() {
user = model("User").findOne(where="email='#params.user.email#'");
if (IsObject(user) && user.authenticate(params.user.password)) {
session.userId = user.id;
if (params.user.rememberMe == 1) {
cookie.rememberToken = user.generateRememberToken();
cookie.userId = encrypt(user.id, application.encryptionKey);
}
flashInsert(success="Welcome back, #user.firstName#!");
redirectTo(route="dashboard");
} else {
user = model("User").new(email=params.user.email);
flashInsert(error="Invalid email or password.");
renderView(action="new");
}
}
function delete() {
StructDelete(session, "userId");
StructDelete(cookie, "rememberToken");
StructDelete(cookie, "userId");
flashInsert(success="You have been logged out.");
redirectTo(route="home");
}
}
Authentication Filter
wheels generate snippets auth-filter
Generates:
// In Controller.cfc or specific controller
function init() {
filters(through="authenticate", except="new,create");
filters(through="rememberUser", only="new");
}
private function authenticate() {
if (!isLoggedIn()) {
storeLocation();
flashInsert(notice="Please log in to continue.");
redirectTo(route="login");
}
}
private function isLoggedIn() {
return StructKeyExists(session, "userId") && session.userId > 0;
}
private function currentUser() {
if (!StructKeyExists(request, "currentUser")) {
if (isLoggedIn()) {
request.currentUser = model("User").findByKey(session.userId);
} else {
request.currentUser = false;
}
}
return request.currentUser;
}
private function rememberUser() {
if (!isLoggedIn() && StructKeyExists(cookie, "rememberToken")) {
user = model("User").findOne(
where="rememberToken='#cookie.rememberToken#' AND rememberTokenExpiresAt > NOW()"
);
if (IsObject(user)) {
session.userId = user.id;
user.updateRememberToken();
}
}
}
private function storeLocation() {
if (request.method == "GET") {
session.returnTo = request.cgi.path_info;
if (Len(request.cgi.query_string)) {
session.returnTo &= "?" & request.cgi.query_string;
}
}
}
private function redirectBackOrTo(required string route) {
if (StructKeyExists(session, "returnTo")) {
local.returnTo = session.returnTo;
StructDelete(session, "returnTo");
redirectTo(location=local.returnTo);
} else {
redirectTo(route=arguments.route);
}
}
Model Patterns
Soft Delete
wheels generate snippets soft-delete
Generates:
// In Model init()
property(name="deletedAt", sql="deleted_at");
// Soft delete callbacks
beforeDelete("softDelete");
afterFind("excludeDeleted");
// Default scope
function excludeDeleted() {
if (!StructKeyExists(arguments, "includeSoftDeleted") || !arguments.includeSoftDeleted) {
if (StructKeyExists(this, "deletedAt") && !IsNull(this.deletedAt)) {
return false; // Exclude from results
}
}
}
// Soft delete implementation
private function softDelete() {
this.deletedAt = Now();
this.save(validate=false, callbacks=false);
return false; // Prevent actual deletion
}
// Scopes
function active() {
return this.findAll(where="deleted_at IS NULL", argumentCollection=arguments);
}
function deleted() {
return this.findAll(where="deleted_at IS NOT NULL", argumentCollection=arguments);
}
function withDeleted() {
return this.findAll(includeSoftDeleted=true, argumentCollection=arguments);
}
// Restore method
function restore() {
this.deletedAt = "";
return this.save(validate=false);
}
// Permanent delete
function forceDelete() {
return this.delete(callbacks=false);
}
Audit Trail
wheels generate snippets audit-trail --customize
Interactive customization:
? Include user tracking? (Y/n) › Y
? Track IP address? (y/N) › Y
? Track changes in JSON? (Y/n) › Y
Generates:
// models/AuditLog.cfc
component extends="Model" {
function init() {
belongsTo("user");
property(name="modelName", sql="model_name");
property(name="recordId", sql="record_id");
property(name="action", sql="action");
property(name="changes", sql="changes");
property(name="userId", sql="user_id");
property(name="ipAddress", sql="ip_address");
property(name="userAgent", sql="user_agent");
validatesPresenceOf("modelName,recordId,action");
}
}
// In audited model
function init() {
afterCreate("logCreate");
afterUpdate("logUpdate");
afterDelete("logDelete");
}
private function logCreate() {
createAuditLog("create", this.properties());
}
private function logUpdate() {
if (hasChanged()) {
createAuditLog("update", this.changedProperties());
}
}
private function logDelete() {
createAuditLog("delete", {id: this.id});
}
private function createAuditLog(required string action, required struct data) {
model("AuditLog").create({
modelName: ListLast(GetMetaData(this).name, "."),
recordId: this.id,
action: arguments.action,
changes: SerializeJSON(arguments.data),
userId: request.currentUser.id ?: "",
ipAddress: request.remoteAddress,
userAgent: request.userAgent
});
}
private function changedProperties() {
local.changes = {};
local.properties = this.properties();
for (local.key in local.properties) {
if (hasChanged(local.key)) {
local.changes[local.key] = {
from: this.changedFrom(local.key),
to: local.properties[local.key]
};
}
}
return local.changes;
}
// Audit log migration
component extends="wheels.migrator.Migration" {
function up() {
createTable(name="audit_logs") {
t.increments("id");
t.string("model_name", null=false);
t.integer("record_id", null=false);
t.string("action", null=false);
t.text("changes");
t.integer("user_id");
t.string("ip_address");
t.string("user_agent");
t.timestamps();
t.index(["model_name", "record_id"]);
t.index("user_id");
t.index("created_at");
};
}
function down() {
dropTable("audit_logs");
}
}
Controller Patterns
CRUD Actions
wheels generate snippets crud-actions
Generates complete CRUD controller with error handling, pagination, and filters.
API Controller
wheels generate snippets api-controller
Generates:
component extends="Controller" {
function init() {
provides("json");
filters(through="setApiHeaders");
filters(through="authenticateApi");
filters(through="logApiRequest", except="index,show");
}
private function setApiHeaders() {
header name="X-API-Version" value="1.0";
header name="X-RateLimit-Limit" value="1000";
header name="X-RateLimit-Remaining" value=getRateLimitRemaining();
}
private function authenticateApi() {
local.token = getAuthToken();
if (!Len(local.token)) {
renderUnauthorized("Missing authentication token");
}
request.apiUser = model("ApiKey").authenticate(local.token);
if (!IsObject(request.apiUser)) {
renderUnauthorized("Invalid authentication token");
}
}
private function getAuthToken() {
// Check Authorization header
if (StructKeyExists(getHttpRequestData().headers, "Authorization")) {
local.auth = getHttpRequestData().headers.Authorization;
if (Left(local.auth, 7) == "Bearer ") {
return Mid(local.auth, 8, Len(local.auth));
}
}
// Check X-API-Key header
if (StructKeyExists(getHttpRequestData().headers, "X-API-Key")) {
return getHttpRequestData().headers["X-API-Key"];
}
// Check query parameter
if (StructKeyExists(params, "api_key")) {
return params.api_key;
}
return "";
}
private function renderUnauthorized(required string message) {
renderWith(
data={
error: {
code: 401,
message: arguments.message
}
},
status=401
);
}
private function renderError(required string message, numeric status = 400) {
renderWith(
data={
error: {
code: arguments.status,
message: arguments.message
}
},
status=arguments.status
);
}
private function renderSuccess(required any data, numeric status = 200) {
renderWith(
data={
success: true,
data: arguments.data
},
status=arguments.status
);
}
private function renderPaginated(required any query) {
renderWith(
data={
success: true,
data: arguments.query,
pagination: {
page: arguments.query.currentPage,
perPage: arguments.query.perPage,
total: arguments.query.totalRecords,
pages: arguments.query.totalPages
}
}
);
}
}
View Patterns
Form with Errors
wheels generate snippets form-with-errors
AJAX Form
wheels generate snippets ajax-form
Generates:
<!--- View file --->
<div id="contact-form-container">
#startFormTag(
action="send",
id="contact-form",
class="ajax-form",
data={
remote: true,
method: "post",
success: "handleFormSuccess",
error: "handleFormError"
}
)#
<div class="form-messages" style="display: none;"></div>
<div class="form-group">
#textField(
name="name",
label="Name",
class="form-control",
required=true
)#
</div>
<div class="form-group">
#emailField(
name="email",
label="Email",
class="form-control",
required=true
)#
</div>
<div class="form-group">
#textArea(
name="message",
label="Message",
class="form-control",
rows=5,
required=true
)#
</div>
<div class="form-group">
#submitTag(
value="Send Message",
class="btn btn-primary",
data={loading: "Sending..."}
)#
</div>
#endFormTag()#
</div>
<script>
// AJAX form handler
document.addEventListener('DOMContentLoaded', function() {
const form = document.getElementById('contact-form');
form.addEventListener('submit', function(e) {
e.preventDefault();
const submitBtn = form.querySelector('[type="submit"]');
const originalText = submitBtn.value;
const loadingText = submitBtn.dataset.loading;
// Disable form
submitBtn.disabled = true;
submitBtn.value = loadingText;
// Send AJAX request
fetch(form.action, {
method: form.method,
body: new FormData(form),
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
})
.then(response => response.json())
.then(data => {
if (data.success) {
handleFormSuccess(data);
} else {
handleFormError(data);
}
})
.catch(error => {
handleFormError({message: 'Network error. Please try again.'});
})
.finally(() => {
submitBtn.disabled = false;
submitBtn.value = originalText;
});
});
});
function handleFormSuccess(data) {
const form = document.getElementById('contact-form');
const messages = form.querySelector('.form-messages');
// Show success message
messages.className = 'form-messages alert alert-success';
messages.textContent = data.message || 'Message sent successfully!';
messages.style.display = 'block';
// Reset form
form.reset();
// Hide message after 5 seconds
setTimeout(() => {
messages.style.display = 'none';
}, 5000);
}
function handleFormError(data) {
const form = document.getElementById('contact-form');
const messages = form.querySelector('.form-messages');
// Show error message
messages.className = 'form-messages alert alert-danger';
messages.textContent = data.message || 'An error occurred. Please try again.';
messages.style.display = 'block';
// Show field errors
if (data.errors) {
Object.keys(data.errors).forEach(field => {
const input = form.querySelector(`[name="${field}"]`);
if (input) {
input.classList.add('is-invalid');
const error = document.createElement('div');
error.className = 'invalid-feedback';
error.textContent = data.errors[field].join(', ');
input.parentNode.appendChild(error);
}
});
}
}
</script>
<!--- Controller action --->
function send() {
contact = model("Contact").new(params);
if (contact.save()) {
if (isAjax()) {
renderWith(data={
success: true,
message: "Thank you! We'll be in touch soon."
});
} else {
flashInsert(success="Thank you! We'll be in touch soon.");
redirectTo(route="home");
}
} else {
if (isAjax()) {
renderWith(data={
success: false,
message: "Please correct the errors below.",
errors: contact.allErrors()
}, status=422);
} else {
renderView(action="new");
}
}
}
Database Snippets
Migration Indexes
wheels generate snippets migration-indexes
Generates common index patterns:
// Performance indexes
t.index("email"); // Single column
t.index(["last_name", "first_name"]); // Composite
t.index("created_at"); // Timestamp queries
// Unique constraints
t.index("email", unique=true);
t.index(["user_id", "role_id"], unique=true);
// Foreign key indexes
t.index("user_id");
t.index("category_id");
// Full-text search
t.index("title", type="fulltext");
t.index(["title", "content"], type="fulltext");
// Partial indexes (PostgreSQL)
t.index("email", where="deleted_at IS NULL");
// Expression indexes
t.index("LOWER(email)", name="idx_email_lower");
Seed Data
wheels generate snippets seed-data
Custom Snippets
Create Custom Snippet
wheels generate snippets --create=my-pattern
Creates template in ~/.wheels/snippets/my-pattern/
:
my-pattern/
├── snippet.json
├── files/
│ ├── controller.cfc
│ ├── model.cfc
│ └── view.cfm
└── README.md
snippet.json
:
{
"name": "my-pattern",
"description": "Custom pattern description",
"category": "custom",
"author": "Your Name",
"version": "1.0.0",
"variables": [
{
"name": "modelName",
"prompt": "Model name?",
"default": "MyModel"
}
],
"files": [
{
"source": "files/controller.cfc",
"destination": "controllers/${controllerName}.cfc"
}
]
}
Output Options
Copy to Clipboard
wheels generate snippets login-form --output=clipboard
Save to File
wheels generate snippets api-controller --output=file --path=./controllers/Api.cfc
Interactive Mode
wheels generate snippets --customize
Best Practices
Review generated code: Customize for your needs
Understand the patterns: Don't blindly copy
Keep snippets updated: Maintain with framework updates
Share useful patterns: Contribute back to community
Document customizations: Note changes made
Test generated code: Ensure it works in your context
Use consistent patterns: Across your application
See Also
wheels generate controller - Generate controllers
wheels generate model - Generate models
wheels scaffold - Generate complete resources
Last updated
Was this helpful?