Generate a controller with actions and optional views.
Synopsis
Copy wheels generate controller [name] [actions] [options]
wheels g controller [name] [actions] [options]
Description
The wheels generate controller
command creates a new controller CFC file with specified actions and optionally generates corresponding view files. It supports both traditional and RESTful controller patterns.
Arguments
Argument
Description
Default
Controller name (singular or plural)
Comma-separated list of actions
Options
Generate API controller (no views)
Examples
Basic controller
Copy wheels generate controller products
Creates:
/controllers/Products.cfc
with index
action
/views/products/index.cfm
Controller with multiple actions
Copy wheels generate controller products index,show,new,create,edit,update,delete
Creates controller with all CRUD actions and corresponding views.
RESTful controller
Copy wheels generate controller products --rest
Automatically generates all RESTful actions:
index
- List all products
show
- Show single product
API controller
Copy wheels generate controller api/products --api
Creates:
/controllers/api/Products.cfc
with JSON responses
Custom actions
Copy wheels generate controller reports dashboard,monthly,yearly,export
Generated Code
Basic Controller
Copy component extends="Controller" {
function init() {
// Constructor
}
function index() {
products = model("Product").findAll();
}
}
RESTful Controller
Copy component extends="Controller" {
function init() {
// Constructor
}
function index() {
products = model("Product").findAll();
}
function show() {
product = model("Product").findByKey(params.key);
if (!IsObject(product)) {
flashInsert(error="Product not found");
redirectTo(action="index");
}
}
function new() {
product = model("Product").new();
}
function create() {
product = model("Product").new(params.product);
if (product.save()) {
flashInsert(success="Product created successfully");
redirectTo(action="index");
} else {
renderView(action="new");
}
}
function edit() {
product = model("Product").findByKey(params.key);
if (!IsObject(product)) {
flashInsert(error="Product not found");
redirectTo(action="index");
}
}
function update() {
product = model("Product").findByKey(params.key);
if (IsObject(product) && product.update(params.product)) {
flashInsert(success="Product updated successfully");
redirectTo(action="index");
} else {
renderView(action="edit");
}
}
function delete() {
product = model("Product").findByKey(params.key);
if (IsObject(product) && product.delete()) {
flashInsert(success="Product deleted successfully");
} else {
flashInsert(error="Could not delete product");
}
redirectTo(action="index");
}
}
API Controller
Copy component extends="Controller" {
function init() {
provides("json");
}
function index() {
products = model("Product").findAll();
renderWith(products);
}
function show() {
product = model("Product").findByKey(params.key);
if (IsObject(product)) {
renderWith(product);
} else {
renderWith({error: "Product not found"}, status=404);
}
}
function create() {
product = model("Product").new(params.product);
if (product.save()) {
renderWith(product, status=201);
} else {
renderWith({errors: product.allErrors()}, status=422);
}
}
function update() {
product = model("Product").findByKey(params.key);
if (IsObject(product) && product.update(params.product)) {
renderWith(product);
} else {
renderWith({errors: product.allErrors()}, status=422);
}
}
function delete() {
product = model("Product").findByKey(params.key);
if (IsObject(product) && product.delete()) {
renderWith({message: "Product deleted"});
} else {
renderWith({error: "Could not delete"}, status=400);
}
}
}
View Generation
Views are automatically generated for non-API controllers:
index.cfm
Copy <h1>Products</h1>
<p>#linkTo(text="New Product", action="new")#</p>
<table>
<thead>
<tr>
<th>Name</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<cfloop query="products">
<tr>
<td>#products.name#</td>
<td>
#linkTo(text="Show", action="show", key=products.id)#
#linkTo(text="Edit", action="edit", key=products.id)#
#linkTo(text="Delete", action="delete", key=products.id, method="delete", confirm="Are you sure?")#
</td>
</tr>
</cfloop>
</tbody>
</table>
Naming Conventions
Controller names : PascalCase, typically plural (Products, Users)
Action names : camelCase (index, show, createProduct)
File locations :
Controllers: /controllers/
Nested: /controllers/admin/Products.cfc
Views: /views/{controller}/
Routes Configuration
Add routes in /config/routes.cfm
:
Traditional Routes
Copy <cfset get(name="products", to="products##index")>
<cfset get(name="product", to="products##show")>
<cfset post(name="products", to="products##create")>
RESTful Resources
Copy <cfset resources("products")>
Nested Resources
Copy <cfset namespace("api")>
<cfset resources("products")>
</cfset>
Testing
Generate tests alongside controllers:
Copy wheels generate controller products --rest
wheels generate test controller products
Best Practices
Use plural names for resource controllers
Keep controllers focused on single resources
Use --rest
for standard CRUD operations
Implement proper error handling
Add authentication in init()
method
Use filters for common functionality
Common Patterns
Authentication Filter
Copy function init() {
filters(through="authenticate", except="index,show");
}
private function authenticate() {
if (!session.isLoggedIn) {
redirectTo(controller="sessions", action="new");
}
}
Copy function index() {
products = model("Product").findAll(
page=params.page ?: 1,
perPage=25,
order="createdAt DESC"
);
}
Search
Copy function index() {
if (StructKeyExists(params, "q")) {
products = model("Product").findAll(
where="name LIKE :search OR description LIKE :search",
params={search: "%#params.q#%"}
);
} else {
products = model("Product").findAll();
}
}
See Also