In this tutorial, we'll be writing a simple application to make sure we have Wheels installed properly and that everything is working as it should.
Testing Your Install
Okay, so you have Wheels installed and can see the Wheels "Congratulations!"
page as shown below. That wasn't that hard now, was it?
Hello World: Your First Wheels App
Okay, let's get to some example code. We know that you've been dying to get your
hands on some code!
To continue with Programming Tutorial Tradition, we'll create the ubiquitous Hello World! application. But to keep things interesting, let's add a little Wheels magic along the way.
Setting up the Controller
Let's create a controller from scratch to illustrate how easy it is to set up a
controller and plug it into the Wheels framework.
First, create a file called Say.cfc in the app/controllers directory and add the
code below to the file.
app/controllers/Say.cfc
component extends="Controller"{
}
Congratulations, you just created your first Wheels controller! What does this
controller do, you might ask? Well, to be honest, not much. It has no methods
defined, so it doesn't add any new functionality to our application. But because
it extends the base Controller component, it inherits quite a bit of powerful
functionality and is now tied into our Wheels application.
The error says "Could not find the view page for the 'index' action in the 'say'
controller." Where did "index" come from? The URL we typed in only specified a
controller name but no action. When an action is not specified in the URL,
Wheels assumes that we want the default action. Out of the box, the default
action in Wheels is set to index. So in our example, Wheels tried to find
the index action within the say controller, and it threw an error because it
couldn't find its view page.
Setting up an Action
But let's jump ahead. Now that we have the controller created, let's add an
action to it called hello. Change your say controller so it looks like the
code block below:
app/controllers/Say.cfc
component extends="Controller" {
function hello() {
}
}
As you can see, we created an empty method named hello.
Now let's call our new action in the browser and see what we get. To call the
hello action, we simply add /hello to the end of the previous URL that we
used to call our say controller:
http://127.0.0.1:60000/say/hello
Once again, we get a ColdFusion error. Although we have created the controller
and added the hello action to it, we haven't created the view.
Setting up the View
By default, when an action is called, Wheels will look for a view file with
the same name as the action. It then hands off the processing to the view to
display the user interface. In our case, Wheels tried to find a view file for
our say/hello action and couldn't find one.
Let's remedy the situation and create a view file. View files are simple CFML
pages that handle the output of our application. In most cases, views will
return HTML code to the browser. By default, the view files will have the same
name as our controller actions and will be grouped into a directory under the
view directory. This new directory will have the same name as our controller.
Find the views directory inside the app directory, located at the root of your
Wheels installation. There will be a few directories in there already. For
now, we need to create a new directory in the views directory called say.
This is the same name as the controller that we created above.
Now inside the say directory, create a file called hello.cfm. In the
hello.cfm file, add the following line of code:
app/views/say/hello.cfm
<h1>Hello World!</h1>
Save your hello.cfm file, and let's call our say/hello action once again.
You have your first working Wheels page if your browser looks like Figure 3
below.
You have just created your first functional Wheels page, albeit it is a very
simple one. Pat yourself on the back, go grab a snack, and when you're ready,
let's go on and extend the functionality of our Hello World! application a
little more.
Adding Dynamic Content to Your View
We will add some simple dynamic content to our hello action and add a second
action to the application. We'll then use some Wheels code to tie the 2
actions together. Let's get get to it!
The Dynamic Content
The first thing we are going to do is to add some dynamic content to our
say/hello action. Modify your say controller so it looks like the code block
below:
app/controllers/Say.cfc
component extends="Controller" {
function hello() {
time = Now();
}
}
All we are doing here is creating a variable called time and setting its value
to the current server time using the basic ColdFusion Now() function. When we
do this, the variable becomes immediately available to our view code.
Why not just set up this value directly in the view? If you think about it,
maybe the logic behind the value of time may eventually change. What if
eventually we want to display its value based on the user's time zone? What if
later we decide to pull it from a web service instead? Remember, the controller
is supposed to coordinate all of the data and business logic, not the view.
Displaying the Dynamic Content
Next, we will modify our say/hello.cfm view file so that it looks like the
code block below. When we do this, the value will be displayed in the browser.
call your say/hello action again in your browser. Your browser should look
like Figure 4 below.
This simple example showed that any dynamic content created in a controller
action is available to the corresponding view file. In our application, we
created a time variable in the say/hello controller action and display that
variable in our say/hello.cfm view file.
Adding a Second Action: Goodbye
Now we will expand the functionality of our application once again by adding a
second action to our say controller. If you feel adventurous, go ahead and add
a goodbye action to the say controller on your own, then create a
goodbye.cfm view file that displays a "Goodbye" message to the user. If you're
not feeling that adventurous, we'll quickly go step by step.
First, modify the the say controller file so that it looks like the code block
below.
app/controllers/Say.cfc
component extends="Controller" {
function hello() {
time = Now();
}
function goodbye() {
}
}
Now go to the app/views/say directory and create a goodbye.cfm page.
Add the following code to the goodbye.cfm page and save it.
app/views/say/goodbye.cfm
Goodbye World!
If we did everything right, we should be able to call the new say/goodbye
action using the following URL:
http://127.0.0.1:60000/say/goodbye
Your browser should look like Figure 5 below:
Linking to Other Actions
Now let's link our two actions together. We will do this by adding a link to the
bottom of each page so that it calls the other page.
Linking Hello to Goodbye
Open the say/hello.cfm view file. We are going to add a line of code to the
end of this file so our say/hello.cfm view file looks like the code block
below:
app/views/say/hello.cfm
<h1>Hello World!</h1>
<p>Current time: <cfoutput>#time#</cfoutput></p>
<p>Time to say <cfoutput>#linkTo(text="goodbye", action="goodbye")#?</cfoutput></p>
Once you have added the additional line of code to the end of the
say/hello.cfm view file, save your file and call the say/hello action from
your browser. Your browser should look like Figure 6 below.
You can see that Wheels created a link for us and added an appropriate URL for
the say/goodbye action to the link.
Linking Goodbye to Hello
Let's complete our little app and add a corresponding link to the bottom of our
say/goodbye.cfm view page.
Open your say/goodbye.cfm view page and modify it so it looks like the code
block below.
CFML: app/views/say/goodbye.cfm
app/views/say/goodbye.cfm
<h1>Goodbye World!</h1>
<p>Time to say <cfoutput>#linkTo(text="hello", action="hello")#?</cfoutput></p>
If you now call the say/goodbye action in your browser, your browser should
look like Figure 7 below.
Much More to Learn
You now know enough to be dangerous with Wheels. Look out! But there are many
more powerful features to cover. You may have noticed that we haven't even
talked about the M in MVC.
No worries. We will get there. And we think you will enjoy it.
Getting Started
Install Wheels and get a local development server running
One module that we have created is a module that extends CommandBox itself with commands and features specific to the Wheels framework. The Wheels CLI module for CommandBox is modeled after the Ruby on Rails CLI module and gives similar capabilities to the Wheels developer.
Install CommandBox
Once installed, you can either double-click on the box executable which opens the CommandBox shell window, or run box from a CMD window in Windows, Terminal window in MacOS, or shell prompt on a Linux server. Sometimes you only want to call a single CommandBox command and don't need to launch a whole CommandBox shell window to do that, for these instances you can call the CommandBox command directly from your default system terminal window by prefixing the command with the box prefix.
So to run the CommandBox version command you could run box version from the shell or you could launch the CommandBox shell and run version inside it.
box version
version
This is a good concept to grasp, cause depending on your workflow, you may find it easier to do one versus the other. Most of the commands you will see in these CLI guides will assume that you are entering the command in the actual CommandBox shell so the box prefix is left off.
Install the wheels-cli CommandBox Module
Okay, now that we have CommandBox installed, let's add the Wheels CLI module.
install wheels-cli
Installing this module will add a number of commands to your default CommandBox installation. All of these commands are prefixed by the wheels name space. There are commands to create a brand new Wheels application or scaffold out sections of your application. We'll see some of these commands in action momentarily.
Start a new Application using the Wizard
To install a new application using version 3.0, we can use the new application wizard and select Bleeding Edge when prompted to select the template to use.
wheels new
Start a New Application Using the Command Line
Now that we have CommandBox installed and extended it with the Wheels CLI module, let's start our first Wheels app from the command line. We'll look at the simplest method for creating a Wheels app and starting our development server.
wheels generate app myApp
server start
A few minutes after submitting the above commands a new browser window should open up and display the default Wheels congratulations screen.
So what just happened? Since we only passed the application name myApp to the wheels generate app command, it used default values for most of its parameters and downloaded our Base template (wheels-base-template) from ForgeBox.io, then downloaded the framework core files (wheels.dev) from ForgeBox.io and placed it in the vendor/wheels directory, then configured the application name and reload password, and started a Lucee server on a random port.
A Word About Command Aliases
CommandBox commands have the capability to be called by multiple names or aliases. The command above wheels generate app can also be initiated by typing wheels g app. In fact g is an alias for generate so wherever you see a command in the CLI documentation that has generate in it you can substitute g instead.
In addition to shortening generate to g, aliases can completely change the name space as well. A command that you haven't seen yet is the wheels generate app-wizard command. This command guides the user through a series of menu options, building up all the parameters needed to customize the start of a new Wheels project. You're likely to use the wizard when starting a new Wheels application so it's good to become familiar with it.
This command has the normal alias referenced above at wheels g app-wizard but it also has an additional alias at wheels new which is the command more prevalent in the Rails community. So the three commands wheels generate app-wizard, wheels g app-wizard, and wheels new all call the same functionality which guides the user though a set of menus, collecting details on how to configure the desired app. Once all the parameters have been gathered, this command actually calls the wheels generate app command to create the actual Wheels application.
This Getting Started guide has taken you from the very beginning and gotten you to the point where you can go into any empty directory on your local development machine and start a Wheels project by issuing a couple of CLI commands. In later guides we'll explore these options further and see what else the CLI can do for us.
Beginner Tutorial: Hello Database
A quick tutorial that demonstrates how quickly you can get database connectivity up and running with Wheels.
Wheels's built in model provides your application with some simple and powerful functionality for interacting with databases. To get started, you will make some simple configurations, call some functions within your controllers, and that's it. Best yet, you will rarely ever need to write SQL code to get those redundant CRUD tasks out of the way.
Our Sample Application: User Management
We'll learn by building part of a sample user management application. This tutorial will teach you the basics of setting up a resource that interacts with the Wheels ORM.
Download source code
Setting up the Data Source
By default, Wheels will connect to a data source wheels.dev. To change this default behavior, open the file at app/config/settings.cfm. In a fresh install of Wheels, you'll see the follwing code:
app/config/settings.cfm
<cfscript>
/*
Use this file to configure your application.
You can also use the environment specific files (e.g. app/config/production/settings.cfm) to override settings set here.
Don't forget to issue a reload request (e.g. reload=true) after making changes.
See https://guides.wheels.dev/2.5.0/v/3.0.0-snapshot/working-with-wheels/configuration-and-defaults for more info.
*/
/*
You can change the "wheels.dev" value from the two functions below to set your datasource.
You can change the the value for the "dataSourceName" to set a default datasource to be used throughout your application.
You can also change the value for the "coreTestDataSourceName" to set your testing datasource.
*/
set(coreTestDataSourceName="wheels.dev");
set(dataSourceName="wheels.dev");
// set(dataSourceUserName="");
// set(dataSourcePassword="");
/*
If you comment out the following line, Wheels will try to determine the URL rewrite capabilities automatically.
The "URLRewriting" setting can bet set to "on", "partial" or "off".
To run with "partial" rewriting, the "cgi.path_info" variable needs to be supported by the web server.
To run with rewriting set to "on", you need to apply the necessary rewrite rules on the web server first.
*/
set(URLRewriting="On");
// Reload your application with ?reload=true&password=wheels.dev
set(reloadPassword="wheels.dev");
// CLI-Appends-Here
</cfscript>
These lines provide Wheels with the necessary information about the data source, URL rewriting, and reload password for your application, and include the appropriate values. This may include values for dataSourceName, dataSourceUserName, and dataSourcePassword. More on URL rewriting and reload password later.
Wheels supports MySQL, SQL Server, PostgreSQL, and H2. It doesn't matter which DBMS you use for this tutorial; we will all be writing the same CFML code to interact with the database. Wheels does everything behind the scenes that needs to be done to work with each DBMS.
That said, here's a quick look at a table that you'll need in your database, named users:
Column Name
Data Type
Extra
id
int
auto increment
username
varchar(100)
email
varchar(255)
passwd
varchar(15)
Note a couple things about this users table:
The table name is plural.
The table has an auto-incrementing primary key named id.
Fortunately, there are ways of going outside of these conventions when you really need it. But let's learn the conventional way first. Sometimes you need to learn the rules before you can know how to break them.
Creating Routes for the users Resource
Next, open the file at app/config/routes.cfm. You will see contents similar to this:
We are going to create a section of our application for listing, creating, updating, and deleting user records. In Wheels routing, this requires a plural resource, which we'll name users.
Because a users resource is more specific than the "generic" routes provided by Wheels, we'll list it first in the chain of mapper method calls:
This will create URL endpoints for creating, reading, updating, and deleting user records:
Name
Method
URL Path
Description
users
GET
/users
Lists users
newUsers
GET
/users/new
Display a form for creating a user record
users
POST
/users
Form posts a new user record to be created
editUser
GET
/users/[id]/edit
Displays a form for editing a user record
user
PATCH
/users/[id]
Form posts an existing user record to be updated
user
DELETE
/users/[id]
Deletes a user record
Name is referenced in your code to tell Wheels where to point forms and links.
Method is the HTTP verb that Wheels listens for to match up the request.
URL Path is the URL that Wheels listens for to match up the request.
Don't forget to reload
You will need to reload your application after adding new routes!
Creating Users
First, let's create a simple form for adding a new user to the users table. To do this, we will use Wheels's form helper functions. Wheels includes a whole range of functions that simplifies all of the tasks that you need to display forms and communicate errors to the user.
Creating the Form
Now create a new file in app/views/users called new.cfm. This will contain the view code for our simple form.
What we've done here is use form helpers to generate all of the form fields necessary for creating a new user in our database. It may feel a little strange using functions to generate form elements, but it will soon become clear why we're doing this. Trust us on this one… you'll love it!
Supplying the Form with Data
All of the form helper calls in our view specify an objectName argument with a reference to a variable named user. That means that we need to supply our view code with an object called user. Because the controller is responsible for providing the views with data, we'll set it there.
Create a new ColdFusion component at app/controllers/Users.cfc.
app/controllers/Users.cfc
component extends="Controller" {
function config(){}
function new() {
user = model("user").new();
}
}
Wheels will automatically know that we're talking about the users database table when we instantiate a user model. The convention: database tables are plural and their corresponding Wheels models are singular.
Why is our model name singular instead of plural? When we're talking about a single record in the users database, we represent that with an individual model object. So the users table contains many user objects. It just works better in conversation.
The Generated Form
Now when we run the URL at http://localhost/users/new, we'll see the form with the fields that we defined.
The HTML generated by your application will look something like this:
You'll see references to EncodeForHtml in some of our examples that output data. This helps escape HTML code in data that attackers could use to embed inject harmful JavaScript. (This is commonly referred to as an "XSS attack," short for "Cross-site Scripting attack.")
A rule of thumb: you do not need to use EncodeForHtml when passing values into Wheels helpers like linkTo, buttonTo, startFormTag, textField, etc. However, you need to escape data that is displayed directly onto the page without a Wheels helper.
Editing Users
We'll now show another cool aspect of form helpers by creating a screen for editing users.
Coding the Edit Form
You probably noticed in the code listed above that we'll have an action for editing a single users record. We used the linkTo() form helper function to add an "Edit" button to the form. This action expects a key as well.
Because in the linkTo() form helper function we specified the parameter key, Wheels adds this parameter into the URL when generating the route.
Wheels will automatically add the provided 'key' from the URL to the params struct in the controllers edit() function.
Given the provided key, we'll have the action load the appropriate user object to pass on to the view:
app/controllers/Users.cfc
function edit() {
user = model("user").findByKey(params.key);
}
The view at app/views/users/edit.cfm looks almost exactly the same as the view for creating a user:
But an interesting thing happens. Because the form fields are bound to the user object via the form helpers' objectName arguments, the form will automatically provide default values based on the object's properties.
With the user model populated, we'll end up seeing code similar to this:
There's a lot of repetition in the new and edit forms. You'd imagine that we could factor out most of this code into a single view file. To keep this tutorial from becoming a book, we'll just continue on knowing that this could be better.
Handing the Edit Form Submission
Now we'll create the update action. This will be similar to the create action, except it will be updating the user object:
app/controllers/Users.cfc
function update() {
user = model("user").findByKey(params.key);
user.update(params.user);
redirectTo(
route="editUser",
key=user.id,
success="User updated successfully."
);
}
Deleting Users
Notice in our listing above that we have a delete action. Here's what it would look like:
app/controllers/Users.cfc
function delete() {
user = model("user").findByKey(params.key);
user.delete();
redirectTo(
route="users",
success="User deleted successfully."
);
}
Database Says Hello
We've shown you quite a few of the basics in getting a simple user database up and running. We hope that this has whet your appetite to see some of the power packed into the Wheels framework. There's plenty more.
Be sure to read on to some of the related chapters listed below to learn more about working with Wheels's ORM.
Running Local Development Servers
Starting a local development server
With CommandBox, we don't need to have Lucee or Adobe ColdFusion installed locally. With a simple command, we can make CommandBox go and get the CFML engine we've requested, and quickly create a server running on Undertow. Make sure you're in the root of your website, and then run:
server start
The server will then start on a random port on 127.0.0.1 based the configuration from the server.json file that is in the root of your application that comes with wheels. We can add various options to server.json to customize our server. Your default server.json will look something like this:
In this server.json file, the server name is set to wheels, meaning I can now start the server from any directory by simply calling start myApp. We don't have any port specified, but you can specify any port you want. Lastly, we have URL rewriting enabled and pointed the URL rewrite configuration file to public/urlrewrite.xml, which is included starting from Wheels 2.x.
Using custom host names
Controlling local servers
Obviously, anything you start, you might want to stop. Servers can be stopped either via right/ctrl clicking on the icon in the taskbar, or by the stop command. To stop a server running in the current directory issue the following:
server stop
You can also stop a server from anywhere by using its name:
server stop myApp
If you want to see what server configurations exist on your system and their current status, simply do server list
To remove a server configuration from the list, you can use server forget myapp. Note the status of the servers on the list are somewhat unreliable, as it only remembers the last known state of the server: so if you start a server and then turn off your local machine, it may still remember it as running when you turn your local machine back on, which is why we recommend the use of force: true in the server.json file.
Specifying different CF engines
By default, CommandBox will run Lucee (version 6.x at time of writing). You may wish to specify an exact version of Lucee, or use Adobe ColdFusion. We can do this via either setting the appropriate cfengine setting in server.json, or at runtime with the cfengine= argument.
Start the default engine
CommandBox> start
__
Start the latest stable Lucee 5.x engine
CommandBox> start cfengine=lucee@5
__
Start a specific engine and version
CommandBox> start cfengine=adobe@10.0.12
__
Start the most recent Adobe server that starts with version "11"
CommandBox> start cfengine=adobe@11
__
Start the most recent adobe engine that matches the range
The default username and password for all administrators is admin & commandbox
You can of course run multiple servers, so if you need to test your app on Lucee 5.x, Lucee 6.x and Adobe 2018, you can just start three servers with different cfengine= arguments!
Watch out
CommandBox 5.1 required to install dependencies easily
By default, the Lucee server that CommandBox starts includes all the essential Lucee extensions you need, but if need to minimize the size of the Lucee instance you launch, then you can use Lucee-Light by specifying cfengine=lucee-light in your server.json file. Wheels can run just fine on lucee-light (which is after all, Lucee, minus all the extensions) but at a minimum, requires the following extensions to be installed as dependencies in your box.json. Please note you may have to add any drivers you need for your database to this list as well.
Once added to your box.json file, while the server is running, just do box install, which will install the dependencies, and load them into the running server within 60 seconds.
Alternatively you can download the extensions and add them manually to your server root's deploy folder (i.e \WEB-INF\lucee-server\deploy)
Tutorial: Wheels, AJAX, and You
Using Wheels to develop web applications with AJAX features is a breeze. You have several options and tools at your disposal, which we'll cover in this chapter.
Wheels was designed to be as lightweight as possible, so this keeps your options fairly open for developing AJAX features into your application.
For this tutorial, let's create the simplest example of all: a link that will render a message back to the user without refreshing the page.
A Simple Example
In this example, we'll wire up some simple JavaScript code that calls a Wheels action asynchronously. All of this will be done with basic jQuery code and built-in Wheels functionality.
First, let's make sure we've got an appropriate route setup. It might be you're still using the default wildcard() route which will create some default GET routes for the controller/action pattern, but we'll add a new route here just for practice. We are going to create a route named sayHello and direct it to the hello action of the say controller. There are two ways you could write this code a long hand method specifying the controller and action separately as well as a short hand method that combines the two into a single parameter.
That piece of code by itself will work just like you expect it to. When you click the link, you will load the hello action inside the say controller.
JavaScript
(function($) {
// Listen to the "click" event of the "alert-button" link and make an AJAX request
$("#alert-button").on("click", function(event) {
$.ajax({
type: "GET",
// References "/say/hello?format=json"
url: $(this).attr("href") + "?format=json",
dataType: "json",
success: function(response) {
$("h1").html(response.message);
$("p").html(response.time);
}
});
event.preventDefault();
});
})(jQuery);
With that code, we are listening to the click event of the hyperlink, which will make an asynchronous request to the hello action in the say controller. Additionally, the JavaScript call is passing a URL parameter called format set to json.
Note that the success block inserts keys from the response into the empty h1 and p blocks in the calling view. (You may have been wondering about those when you saw the first example. Mystery solved.)
app/controllers/Say.cfc
component extends="Controller" {
function config() {
provides("html,json");
}
function hello() {
// Prepare the message for the user.
greeting = {};
greeting["message"] = "Hi there";
greeting["time"] = Now();
// Respond to all requests with `renderWith()`.
renderWith(greeting);
}
}
Lastly, notice the lines where we're setting greeting["message"] and greeting["time"]. Due to the case-insensitive nature of ColdFusion, we recommend setting variables to be consumed by JavaScript using bracket notation like that. If you do not use that notation (i.e., greetings.message and greetings.time instead), your JavaScript will need to reference those keys from the JSON as MESSAGE and TIME (all caps). Unless you like turning caps lock on and off, you can see how that would get annoying after some time.
Assuming you already included jQuery in your application and you followed the code examples above, you now have a simple AJAX-powered web application built on Wheels. After clicking that Alert me! link, your say controller will respond back to you the serialized message via AJAX. jQuery will parse the JSON object and populate the h1 and pwith the appropriate data.
AJAX in Wheels Explained
That is it! Hopefully now you have a clearer picture on how to create AJAX-based features for your web applications.
Requirements
What you need to know and have installed before you start programming in Wheels.
We can identify 3 different types of requirements that you should be aware of:
Project Requirements. Is Wheels a good fit for your project?
Developer Requirements. Do you have the knowledge and mindset to program effectively in Wheels?
System Requirements. Is your server ready for Wheels?
Project Requirements
Before you start learning Wheels and making sure all the necessary software is installed on your computer, you really need to take a moment and think about the project you intend to use Wheels on. Is it a ten page website that won't be updated very often? Is it a space flight simulator program for NASA? Is it something in between?
Most websites are, at their cores, simple data manipulation applications. You fetch a row, make some updates to it, stick it back in the database and so on. This is the "target market" for Wheels--simple CRUD (create, read, update, delete) website applications.
A simple ten page website won't do much data manipulation, so you don't need Wheels for that (or even ColdFusion in some cases). A flight simulator program will do so much more than simple CRUD work, so in that case, Wheels is a poor match for you (and so perhaps, is ColdFusion).
If your website falls somewhere in between these two extreme examples, then read on. If not, go look for another programming language and framework. ;)
Another thing worth noting right off the bat (and one that ties in with the simple CRUD reasoning above) is that Wheels takes a very data-centric approach to the development process. What we mean by that is that it should be possible to visualize and implement the database design early on in the project's life cycle. So, if you're about to embark on a project with an extensive period of object oriented analysis and design which, as a last step almost, looks at how to persist objects, then you should probably also look for another framework.
Still reading?
Good!
Moving on...
Developer Requirements
Yes, there are actually some things you should familiarize yourself with before starting to use Wheels. Don't worry though. You don't need to be an expert on any on of them. A basic understanding is good enough.
CFML. You should know CFML, the ColdFusion programming language. (Surprise!)
Object Oriented Programming. You should grasp the concept of object oriented programming and how it applies to CFML.
Model-View-Controller. You should know the theory behind the Model-View-Controller development pattern.
CFML
Object Oriented Programming (OOP)
This is a programming methodology that uses constructs called objects to design applications. Objects model real world entities in your application. OOP is based on several techniques including inheritance, modularity, polymorphism, and encapsulation. Most of these techniques are supported in CFML, making it a fairly functional object oriented language. At the most basic level, a .cfc file in CFML is a class, and you create an instance of a class by using the CreateObject function or the <cfobject> tag.
Trying to squeeze an explanation of object oriented programming and how it's used in CFML into a few sentences is impossible, and a detailed overview of it is outside the scope of this chapter. There is lots of high quality information online, so go ahead and Google it.
Model-View-Controller
Model-View-Controller, or MVC for short, is a way to structure your code so that it is broken down into 3 easy-to-manage pieces:
Model. Just another name for the representation of data, usually a database table.
View. What the user sees and interacts with (a web page in our case).
Controller. The behind-the-scenes guy that's coordinating everything.
MVC is how Wheels structures your code for you. As you start working with Wheels applications, you'll see that most of the code you write is very nicely separated into one of these 3 categories.
System Requirements
Wheels requires that you use one of these CFML engines:
Operating Systems
Your ColdFusion or Lucee engine can be installed on Windows, Mac, UNIX, or Linux—they all work just fine.
Web Servers
Database Engines
Finally, to build any kind of meaningful website application, you will likely interact with a database. These are the currently supported databases:
SQL Server 7+
MySQL 5+ *
PostgreSQL 8.4+
H2 1.4+
MySQL
Wheels maybe incompatible with newer MySQL JDBC drivers. It is recommended you downgrade the driver to version 5.1.x for full ORM functionality.
MySQL 4 is not supported.
OK, hopefully this chapter didn't scare you too much. You can move on knowing that you have the basic knowledge needed, the software to run Wheels, and a suitable project to start with.
Manual Installation
Instructions for installing Wheels on your system.
Installing Wheels is so simple that there is barely a need for a chapter devoted to it. But we figured we'd better make one anyway in case anyone is specifically looking for a chapter about installation.
So, here are the simple steps you need to follow to get rolling on Wheels...
Manual Installation
1. Download Wheels
You have 2 choices when downloading Wheels. You can either use the latest official release of Wheels, or you can take a walk on the wild side and go with the latest committed source code in our Git repository.
In most cases, we recommend going with the official release because it's well documented and has been through a lot of bug testing. Only if you're in desperate need of a feature that has not been released yet would we advise you to go with the version stored in the Git master branch.
Let's assume you have downloaded the latest official release. (Really, you should go with this option.) You now have a .zip file saved somewhere on your computer. On to the next step...
2. Setup the Website
Getting an empty website running with Wheels installed is an easy process if you already know your way around IIS or Apache. Basically, you need to create a new website in your web server of choice and unzip the contents of the file into the root of it.
Create a new folder under your web root (usually C:\Inetpub\wwwroot) named wheels_site and unzip the Wheels .zip file into the root of it.
Create a new website using IIS called Wheels Site with localhost as the host header name and C:\Inetpub\wwwroot\mysite as the path to your home directory.
3. Setup the Database (Optional)
Create a new database in MySQL, PostgreSQL, Microsoft SQL Server, or H2 and add a new data source for it in the ColdFusion/Lucee Administrator, just as you'd normally do. Now open up app/config/settings.cfm and call set(dataSourceName="") with the name you chose for the data source.
4. Test It
When you've followed the steps above, you can test your installation by typing http://localhost/ (or whatever you set as the host header name) in your web browser. You should get a "Congratulations!" page.
That's it. You're done. This is where the fun begins!
Screencasts
Tutorials, demonstrations, and presentations about the ColdFusion on Wheels framework.
Wheels 2.x
Wheels 1.x
Please note that all the webcasts below were created with Wheels 1.x in mind, and are listed here as they might still be useful to those starting out.
CRUD series
"Building a Social Network"
Chris Peters adds data validation to the user registration form
Other
Upgrading
Instructions for upgrading Wheels applications
Generally speaking, upgrading Wheels is as easy as replacing the wheels folder, especially for those small maintenance releases: however, there are usually exceptions in minor point releases (i.e, 1.1 to 1.3 required replacing other files outside the wheels folder). The notes below detail those changes.
Upgrading to 3.0.0
Compatibility Changes
Adobe Coldfusion 2016 and below are no longer compatible with Wheels going forward. Consequently, these versions have been removed from the Wheels Internal Test Suites.
Code changes
After installing Wheels 3.x, you'll have to run box install to intall testbox and wirebox in your application as they are not shipped with Wheels but are rather listed in box.json file as dependencies to be installed.
Added Mappings for the app, vendor, wheels, wirebox, testbox and tests directories.
root.cfm and rewrite.cfm have been removed. All the requests are now being redirected only through public/index.cfm.
Changes to the wheels folder
Replace the wheels folder with the new one from the 3.0.0 download.
Move the wheels folder inside the vendor folder.
Changes outside the wheels folder
Moved the config, controllers, events, global, lib, migrator, models, plugins, snippets and views directories inside the app directory.
Moved the files, images, javascripts, miscellaneous, stylesheets directories and Application.cfc, index.cfm and urlrewrite.xml files into the public folder.
Upgrading to 2.3.x
Replace the wheels folder with the new one from the 2.3.0 download.
Upgrading to 2.2.x
Replace the wheels folder with the new one from the 2.2.0 download.
Upgrading to 2.1.x
Replace the wheels folder with the new one from the 2.1.0 download.
Code changes
Rename any instances of findLast() to findLastOne()
Changes outside the wheels folder
Create /events/onabort.cfm to support the onAbort method
Upgrading to 2.0.x
As always, the first step is to replace the wheels folder with the new one from the 2.0 download.
Other major changes required to upgrade your application are listed in the following sections.
Supported CFML Engines
Wheels 2.0 requires one of these CFML engines:
Lucee 4.5.5.006 + / 5.2.1.9+
Adobe ColdFusion 10.0.23 / 11.0.12+ / 2016.0.4+
We've updated our minimum requirements to match officially supported versions from the vendors. (For example, Adobe discontinued support for ColdFusion 10 in May 2017, which causes it to be exposed to security exploits in the future. We've included it in 2.0 but it may be discontinued in a future version)
Changes outside the wheels folder
The events/functions.cfm file has been moved to global/functions.cfm.
The models/Model.cfc file should extend wheels.Model instead of Wheels (models/Wheels.cfc can be deleted).
The controllers/Controller.cfc file should extend wheels.Controller instead of Wheels(controllers/Wheels.cfc can be deleted).
The init function of controllers and models must be renamed to config.
The global setting modelRequireInit has been renamed to modelRequireConfig.
The global setting cacheControllerInitialization has been renamed to cacheControllerConfig.
The global setting cacheModelInitialization has been renamed to cacheModelConfig.
The global setting clearServerCache has been renamed to clearTemplateCache.
The updateProperties() method has been removed, use update() instead.
The renderPage function has been renamed to renderView
includePartial() now requires the partial and query arguments to be set (if using a query)
Routing
By default, this is limited to GET requests for security reasons.
Cross-Site Request Forgery (CSRF) Protection
It is strongly recommended that you enable Wheels 2.0's built-in CSRF protection.
For many applications, you need to follow these steps:
Update your route definitions to enforce HTTP verbs on actions that manipulate data (get, post, patch, delete, etc.)
Make sure that forms within the application are POSTing data to the actions that require post, patch, and delete verbs.
Database Migrations
If you have previously been using the dbmigrate plugin, you can now use the inbuilt version within the Wheels 2 core.
Database Migration files in /db/migrate/ should now be moved to /migrator/migrations and extend wheels.migrator.Migration, not plugins.dbmigrate.Migration which can be changed with a simple find and replace. Note: Oracle is not currently supported for Migrator.
Upgrading to 1.4.x
Replace the wheels folder with the new one from the 1.4 download.
In addition, if you're upgrading from an earlier version of Wheels, we recommend reviewing the instructions from earlier reference guides below.
Upgrading to 1.3.x
If you are upgrading from Wheels 1.1.0 or newer, follow these steps:
Replace the wheels folder with the new one from the 1.3 download.
Replace the root root.cfm file with the new one from the 1.3 download.
Remove the <cfheader> calls from the following files:
events/onerror.cfm
events/onmaintenance.cfm
events/onmissingtemplate.cfm
In addition, if you're upgrading from an earlier version of Wheels, we recommend reviewing the instructions from earlier reference guides below.
Note: To accompany the newest 1.1.x releases, we've highlighted the changes that are affected by each release in this cycle.
Upgrading to 1.1.x
If you are upgrading from Wheels 1.0 or newer, the easiest way to upgrade is to replace the wheels folder with the new one from the 1.1 download. If you are upgrading from an earlier version, we recommend reviewing the steps outlined in Upgrading to Wheels 1.0.
Note: To accompany the newest 1.1.x releases, we've highlighted the changes that are affected by each release in this cycle.
Plugin Compatibility
Be sure to review your plugins and their compatibility with your newly-updated version of Wheels. Some plugins may stop working, throw errors, or cause unexpected behavior in your application.
Supported System Changes
1.1: The minimum Adobe ColdFusion version required is now 8.0.1.
1.1: The minimum Railo version required is now 3.1.2.020.
1.1: The H2 database engine is now supported.
File System Changes
1.1: The .htaccess file has been changed. Be sure to copy over the new one from the new version 1.1 download and copy any addition changes that you may have also made to the original version.
Database Structure Changes
1.1: By default, Wheels 1.1 will wrap database queries in transactions. This requires that your database engine supports transactions. For MySQL in particular, you can convert your MyISAM tables to InnoDB to be compatible with this new functionality. Otherwise, to turn off automatic transactions, place a call to set(transactionMode="none").
1.1: Binary data types are now supported.
CFML Code Changes
Model Code
1.1: Validations will be applied to some model properties automatically. This may cause unintended behavior with your validations. To turn this setting off, call set(automaticValidations=false) in config/settings.cfm.
1.1: The class argument in hasOne(), hasMany(), and belongsTo() has been deprecated. Use the modelName argument instead.
1.1: afterFind() callbacks no longer require special logic to handle the setting of properties in objects and queries. (The "query way" works for both cases now.) Because arguments will always be passed in to the method, you can't rely on StructIsEmpty() to determine if you're dealing with an object or not. In the rare cases that you need to know, you can now call isInstance() or isClass() instead.
1.1: On create, a model will now set the updatedAt auto-timestamp to the same value as the createdAt timestamp. To override this behavior, call set(setUpdatedAtOnCreate=false) in config/settings.cfm.
View Code
1.1: Object form helpers (e.g. textField() and radioButton()) now automatically display a label based on the property name. If you left the label argument blank while using an earlier version of Wheels, some labels may start appearing automatically, leaving you with unintended results. To stop a label from appearing, use label=false instead.
1.1: The contentForLayout() helper to be used in your layout files has been deprecated. Use the includeContent() helper instead.
1.1: In production mode, query strings will automatically be added to the end of all asset URLs (which includes JavaScript includes, stylesheet links, and images). To turn off this setting, call set(assetQueryString=false) in config/settings.cfm.
1.1: stylesheetLinkTag() and javaScriptIncludeTag() now accept external URLs for the source/sources argument. If you manually typed out these tags in previous releases, you can now use these helpers instead.
1.1: flashMessages(), errorMessageOn(), and errorMessagesFor() now create camelCased class attributes instead (for example error-messages is now errorMessages). The same goes for the class attribute on the tag that wraps form elements with errors: it is now fieldWithErrors.
Controller Code
1.1.1: The if argument in all validation functions is now deprecated. Use the condition argument instead.
Upgrading to 1.0.x
Our listing of steps to take while upgrading your Wheels application from earlier versions to 1.0.x.
Upgrading from an earlier version of 1.x? Then the upgrade path is simple. All you need to do is replace the wheels folder with the new wheels folder from the download.
The easiest way to upgrade is to setup an empty website, deploy a fresh copy of Wheels 1.0, and then transfer your application code to it. When transferring, please make note of the following changes and make the appropriate changes to your code.
Note: To accompany the newest 1.0 release, we've highlighted the changes that are affected by that release.
Supported System Changes
1.0: URL rewriting with IIS 7 is now supported.
1.0: URL rewriting in a sub folder on Apache is now supported.
ColdFusion 9 is now supported.
Oracle 10g or later is now supported.
PostgreSQL is now supported.
Railo 3.1 is now supported.
File System Changes
1.0: There is now an app.cfm file in the config folder. Use it to set variables that you'd normally set in Application.cfc (i.e., this.name, this.sessionManagement, this.customTagPaths, etc.)
1.0: There is now a web.config file in the root.
1.0: There is now a Wheels.cfc file in the models folder.
1.0: The Wheels.cfc file in the controllers folder has been updated.
1.0: The IsapiRewrite4.ini and .htaccess files in the root have both been updated.
The controller folder has been changed to controllers.
The model folder has been changed to models.
The view folder has been changed to views.
Rename all of your CFCs in models and controllers to UpperCamelCase. So controller.cfc will become Controller.cfc, adminUser.cfc will become AdminUser.cfc, and so on.
All images must now be stored in the images folder, files in the files folder, JavaScript files in the javascripts folder, and CSS files in the stylesheets folder off of the root.
Database Structure Changes
deletedOn, updatedOn, and createdOn are no longer available as auto-generated fields. Please change the names to deletedAt, updatedAt, and createdAt instead to get similar functionality, and make sure that they are of type datetime, timestamp, or equivalent.
CFML Code Changes
Config Code
1.0: The action of the default route (home) has changed to wheels. The way configuration settings are done has changed quite a bit. To change a Wheels application setting, use the new set() function with the name of the Wheels property to change. (For example, <cfset set(dataSourceName="mydatasource")>.) To see a list of available Wheels settings, refer to the Configuration and Defaults chapter.
Model Code
1.0: The extends attribute in models/Model.cfc should now be Wheels.
findById() is now called findByKey(). Additionally, its id argument is now named key instead. For composite keys, this argument will accept a comma-delimited list.
When using a model's findByKey() or findOne() functions, the found property is no longer available. Instead, the functions return false if the record was not found.
A model's errorsOn() function now always returns an array, even if there are no errors on the field. When there are errors for the field, the array elements will contain a struct with name, fieldName, and message elements.
The way callbacks are created has changed. There is now a method for each callback event ( beforeValidation(), beforeValidationOnCreate(), etc.) that should be called from your model's init() method. These methods take a single argument: the method within your model that should be invoked during the callback event. See the chapter on Object Callbacks for an example.
View Code
1.0: The contents of the views/wheels folder have been changed.
The wrapLabel argument in form helpers is now replaced with labelPlacement. Valid values for labelPlacement are before, after, and around.
The first argument for includePartial() has changed from name to partial. If you're referring to it through a named argument, you'll need to replace all instances with partial.
The variable that keeps a counter of the current record when using includePartial() with a query has been renamed from currentRow to current.
There is now an included wheels view folder in views. Be sure to copy that into your existing Wheels application if you're upgrading.
The location of the default layout has changed. It is now stored at /views/layout.cfm. Now controller-specific layouts are stored in their respective view folder as layout.cfm. For example, a custom layout for www.domain.com/about would be stored at /views/about/layout.cfm.
In linkTo(), the id argument is now called key. It now accepts a comma-delimited list in the case of composite keys.
The linkTo() function also accepts an object for the key argument, in which case it will automatically extract the keys from it for use in the hyperlink.
The linkTo() function can be used only for controller-, action-, and route-driven links now. * The url argument has been removed, so now all static links should be coded using a standard "a" tag.
Controller Code
URL/Routing
The default route for Wheels has changed from [controller]/[action]/[id] to [controller]/[action]/[key]. This is to support composite keys. The params.id value will now only be available as params.key.
You can now pass along composite keys in the URL. Delimit multiple keys with a comma. (If you want to use this feature, then you can't have a comma in the key value itself).
Let's make sure we're all on the same page. I'm going to assume that you've followed the guide and have CommandBox all setup. If you haven't done that, stop and read that guide get everything setup. It's okay, this web page will wait for you.
So what happens if we try to call our new controller right now? Lets take a
look. Open your browser and point your browser to the new controller. Because my
local server is installed on port 60000, my URL is http://127.0.0.1:60000/say.
You may need to enter a different URL, depending on how your web server is
configured. In my case, I'm using .
Figure 2: Wheels error after setting up your blank say controller
Figure 3: Your first working Wheels action.
Figure 4: Hello World with the current date and time
Figure 5: Your new goodbye action
The function is a built-in Wheels function. In this case, we are passing 2 named parameters to it. The first parameter, text, is the text
that will be displayed in the hyperlink. The second parameter, action, defines the action to point the link to. By using this built-in function, your application's main URL may change, and even controllers and actions may get shifted around, but you won't suffer from the dreaded dead link. Wheels will
always create a valid link for you as long as you configure it correctly when you make infrastructure changes to your application.
Figure 6: Your say/hello action with a link to the goodbye action
Figure 7: Your say/goodbye action with a link to the hello action
By far the quickest way to get started with Wheels is via . CommandBox brings a whole host of command line capabilities to the CFML developer. It allows you to write scripts that can be executed at the command line written entirely in CFML. It allows you to start a CFML server from any directory on your machine and wire up the code in that directory as the web root of the server. What's more is, those servers can be either Lucee servers or Adobe ColdFusion servers. You can even specify what version of each server to launch. Lastly, CommandBox is a package manager for CFML. That means you can take some CFML code and package it up into a module, host it on ForgeBox.io, and make it available to other CFML developers. In fact we make extensive use of these capabilities to distribute Wheels plugins and templates. More on that later.
The first step is to get downloaded and running. CommandBox is available for Windows, Mac & Linux, and can be installed manually or using one of the respective package managers for each OS. You can use on Windows, on MacOS, or Yum/Apt on Linux depending on your flavor of Linux. Please follow the instructions on how to install CommandBox on your particular operating system. At the end of the installation process you want to make sure the box command is part of your system path so you can call the command from any directory on your system.
You can download all the source code for this sample application from
These are database used by Wheels. This framework strongly encourages that everyone follow convention over configuration. That way everyone is doing things mostly the same way, leading to less maintenance and training headaches down the road.
To generate the form tag's action attribute, the function takes parameters similar to the function that we introduced in the Beginner Tutorial: Hello World tutorial. We can pass in controller, action, key, and other route- and parameter-defined URLs just like we do with .
To end the form, we use the function. Easy enough.
The and helpers are similar. As you probably guessed, they create <input> elements with type="text" and type="password", respectively. And the function creates an <input type="submit" /> element.
One thing you'll notice is the and functions accept arguments called objectName and property. As it turns out, this particular view code will throw an error because these functions are expecting an object named user. Let's fix that.
As it turns out, our controller needs to provide the view with a blank user object (whose instance variable will also be called user in this case). In our new action, we will use the function to generate a new instance of the user model.
To get a blank set of properties in the model, we'll also call the generated model's method.
A basic way of doing this is using the model object's method:
Notice that our create action above redirects the user to the users index route using the function. We'll use this action to list all users in the system with "Edit" links. We'll also provide a link to the "New User" form that we just coded.
This call to the model's method will return a query object of all users in the system. By using the method's order argument, we're also telling the database to order the records by username.
To update the user, simply call its method with the user struct passed from the form via params. It's that simple.
After the update, we'll add a success message and send the end user back to the edit form in case they want to make more changes.
We simply load the user using the model's method and then call the object's method. That's all there is to it.
You can also specify hosts other than localhost: there's a useful CommandBox module to do that () which will automatically create entries in your hosts file to allow for domains such as myapp.local running on port 80. You can install it via install commandbox-hostupdater when running the box shell with administrator privileges.
While there are several flavors of JavaScript libraries out there with AJAX support, we will be using the in this tutorial. Let's assume that you are fairly familiar with the basics of jQuery and know how to set it up.
But let's make it into an asynchronous request. Add this JavaScript (either on the page inside script tags or in a separate .js file included via ):
The last thing that we need to do is implement the say/hello action. Note that the request expects a dataType of JSON. By default, Wheels controllers only generate HTML responses, but there is an easy way to generate JSON instead using Wheels's and functions:
In this controller's config() method, we use the function to indicate that we want all actions in the controller to be able to respond with the data in HTML or JSON formats. Note that the client calling the action can request the type by passing a URL parameter named format or by sending the format in the request header.
The call to in the hello action takes care of the translation to the requested format. Our JavaScript is requesting JSON, so Wheels will format the greeting struct as JSON automatically and send it back to the client. If the client requested HTML or the default of none, Wheels will process and serve the view template at app/views/say/hello.cfm. For more information about and , reference the chapter on .
Simply the best web development language in the world! The best way to learn it, in our humble opinion, is to get the free developer edition of Adobe ColdFusion, buy Ben Forta's ColdFusion Web Application Construction Kit series, and start coding using your programming editor of choice. Remember it's not just the commercial Adobe offering that's available; offers an excellent open source alternative. Using is a great and simple way to get a local development environment of your choice up and running quickly.
2018 / 2021 / 2023
5.2.1.9+ / 6
You also need a web server. Wheels runs on all popular web servers, including Apache, Microsoft IIS, Jetty, and the JRun or Tomcat web server that ships with Adobe ColdFusion. Some web servers support URL rewriting out of the box, some support the cgi.PATH_INFO variable which is used to achieve partial rewriting, and some don't have support for either. For local development, we strongly encourage the use of .
Don't worry though. Wheels will adopt to your setup and run just fine, but the URLs that it creates might differ a bit. You can read more about this in the chapter.
If you're using MySQL 5.7.5+ you should be aware that the ONLY_FULL_GROUP_BY setting is enabled by default and it's currently not compatible with the Wheels ORM. However, you can work around this by either disabling the ONLY_FULL_GROUP_BY setting or using ANY_VALUE() in a calculated property. You can read more about it .
We also recommend using the InnoDB engine if you want to work.
The latest official releases can always be found in the section of GitHub, and the Git repository is available at our .
In case you're not sure, here are the instructions for setting up an empty Wheels site that can be accessed when typing localhost in your browser. The instructions refer to a system running Windows Server 2003 and IIS, but you should be able to follow along and apply the instructions with minor modifications to your system. (See for a list of tested systems).
If you want to run a Wheels-powered application from a subfolder in an existing website, this is entirely possible, but you may need to get a little creative with your URL rewrite rules if you want to get pretty URLs--it will only work out of the box on recent versions of Apache. (Read more about this in the chapter.)
If you don't want to be bothered by opening up a Wheels configuration file at all, there is a nice convention you can follow for the naming. Just name your data source with the same name as the folder you are running your website from (mysite in the example above), and Wheels will use that when you haven't set the dataSourceName setting using the function.
Create a basic CRUD interface in Wheels 2.x
Create a basic JSON API in Wheels 2.x
Routing in Wheels 2.x - Part 1
Routing in Wheels 2.x - Part 2
Introduction to Unit Testing in Wheels 2.x
Unit Testing Controllers in Wheels 2.x
Learn about basic create operations when building standard CRUD functionality in Wheels
Learn about basic read operations when building standard CRUD functionality in Wheels
Chris Peters demonstrates updating data in a simple CRUD Wheels application
Learn how simple it is to delete records in a basic CRUD application using Wheels
Chris Peters starts the webcast series by demonstrating how to set up ColdFusion on Wheels
Chris Peters demonstrates how to bind a Wheels model object to a form through the use of form helpers
Chris Peters finishes the "success" portion of the registration functionality by adding a success message to the Flash and redirecting the user to their home screen
Chris Peters teaches you about more validation options and how you can add them to the registration form quickly and easily
Chris Peters stylizes form markup globally using a Wheels feature called global helpers
Learn how to set up simple user authentication on a website by using a Wheels feature called filters
Learn the mechanics of reading a single record from the database and displaying its data in the view
Creating custom URL patterns is a breeze in ColdFusion on Wheels
Learn how to fetch multiple records from your model with findAll() and then display them to the user using ColdFusion on Wheels
Learn how to factor out logic in your view templates into custom helper functions in ColdFusion on Wheels
Chris Peters demonstrates joining data together with model associations using ColdFusion on Wheels
All it takes to offer pagination is two extra arguments to findAll() and a call to a view helper called paginationLinks()
Learn how to use the provides() and renderWith() functions to automatically serialize data into XML, JSON, and more
Peter Amiri walks you through setting up a "Hello World" application using the ColdFusion on Wheels framework
Chris Peters gives a high level overview of the ORM included with ColdFusion on Wheels
Chris Peters from Liquifusion demonstrates the ColdRoute plugin for Wheels
Doug Boude demonstrates using his new Wirebox plugin for Wheels
Chris Peters from Liquifusion demonstrates creating tables and records using database migrations in ColdFusion on Wheels
Online ColdFusion Meetup (coldfusionmeetup.com) session for March 10 2011, "What's New in Wheels 1.1", with Chris Peters:
A quick demo of the Wheels Textmate bundle by Russ Johnson
Wheels follows Semantic Versioning () so large version changes (e.g, 1.x.x -> 2.x.x) will most likely contain breaking changes which will require evaluation of your codebase. Minor version changes (e.g, 1.3.x->1.4.x) will often contain new functionality, but in a backwards-compatible manner, and maintenance releases (e.g 1.4.4 -> 1.4.5) will just be trying to fix bugs.
Migrate your tests from the tests directory which are written with rocketUnit and rewrite them into in the tests/Testbox directory. Starting with Wheels 3.x, will replace RocketUnit as the default testing framework.
Starting with Wheels 3.x, will be used as the default dependency injector.
A .env file has been added in the root of the application which adds the H2 database extension for lucee and sets the cfadmin password to commandbox for both and .
JavaScript arguments like confirm and disable have been removed from the link and form helper functions (use the and plugins to reinstate the old behavior).
The function has been removed in Wheels 2.0 in favor of a new routing API. See the chapter for information about the new RESTful routing system.
A limited version of the "wildcard" route ([controller]/[action]/[key]) is available as [controller]/[action]) if you use the new mapper method:
In controllers/Controller.cfc, add a call to to the config method.
Add a call to the helper in your layouts' <head> sections.
Configure any AJAX calls that POST data to your application to pass the authenticityToken from the <meta>tags generated by as an X-CSRF-TOKEN HTTP header.
See documentation for the for more information.
Note: If you had previously installed the , you may remove it and rely on the functionality included in the Wheels 2 core.
1.0: The extends attribute in controllers/Controller.cfc should now be Wheels.
Multiple-word controllers and actions are now word-delimited by hyphens in the URL. For example, if your controller is called SiteAdmin and the action is called editLayout, the URL to access that action would be .
Plugin Management - Install and manage Wheels plugins
Environment Management - Switch between development, testing, and production
Documentation Structure
Complete reference for all CLI commands organized by category:
Core Commands - Essential commands like init, reload, watch
Get up and running with Wheels CLI in minutes. Learn how to:
Install Wheels CLI
Create your first application
Generate CRUD scaffolding
Run tests and migrations
📖 Guides
Development Guides
Best Practices
📋 Reference
Key Features
🛠️ Code Generation
Generate complete applications or individual components:
# Create new application
wheels new blog
# Generate complete CRUD scaffolding
wheels scaffold post --properties="title:string,content:text,published:boolean"
# Generate individual components
wheels generate model user
wheels generate controller users --rest
wheels generate view users index
🗄️ Database Migrations
Manage database schema changes:
# Create migration
wheels dbmigrate create table posts
# Run migrations
wheels dbmigrate latest
# Check status
wheels dbmigrate info
🧪 Testing
Comprehensive testing support:
# Run all tests
wheels test run
# Watch mode
wheels test run --watch
# Generate coverage
wheels test coverage
wheels g controller users # Same as: wheels generate controller users
wheels g model user # Same as: wheels generate model user
wheels new myapp # Same as: wheels generate app myapp
Common Workflows
Creating a new feature:
wheels scaffold product --properties="name:string,price:decimal"
wheels dbmigrate latest
wheels test run
Starting development:
wheels watch # Terminal 1
box server start # Terminal 2
wheels test run --watch # Terminal 3
Deployment preparation:
wheels test run
wheels security scan
wheels optimize performance
wheels dbmigrate info
Environment Variables
Variable
Description
Default
WHEELS_ENV
Environment mode
development
WHEELS_DATASOURCE
Database name
From config
WHEELS_RELOAD_PASSWORD
Reload password
From config
Exit Codes
Code
Description
0
Success
1
General error
2
Invalid arguments
3
File not found
4
Permission denied
5
Database error
Command Status Notes
Some commands in the Wheels CLI are currently in various states of development or maintenance:
Broken Commands
wheels docs - Base documentation command is currently broken
wheels generate api-resource - API resource generation is currently broken
Disabled Commands
The following commands exist in the codebase but are currently disabled:
Some CI and Docker commands have disabled variants in the codebase
These commands may be re-enabled in future versions of Wheels.
See Also
wheels reload
Reload the Wheels application in different modes.
Synopsis
wheels reload [mode] [password]
Description
The wheels reload command reloads your Wheels application, clearing caches and reinitializing the framework. This is useful during development when you've made changes to configuration, routes, or framework settings.
Arguments
Argument
Description
Default
mode
Reload mode: development, testing, maintenance, production
development
password
Reload password (overrides configured password)
From .wheels-cli.json
Options
Option
Description
--help
Show help information
Reload Modes
Development Mode
wheels reload development
Enables debugging
Shows detailed error messages
Disables caching
Ideal for active development
Testing Mode
wheels reload testing
Optimized for running tests
Consistent environment
Predictable caching
Maintenance Mode
wheels reload maintenance
Shows maintenance page to users
Allows admin access
Useful for deployments
Production Mode
wheels reload production
Full caching enabled
Minimal error information
Optimized performance
Examples
Basic reload (development mode)
wheels reload
Reload in production mode
wheels reload production
Reload with custom password
wheels reload development mySecretPassword
Reload for testing
wheels reload testing
Security
The reload password must match the one configured in your Wheels application
Default password from .wheels-cli.json is used if not specified
Password is sent securely to the application
Configuration
Set the default reload password in .wheels-cli.json:
{
"reload": "mySecretPassword"
}
Or in your Wheels settings.cfm:
set(reloadPassword="mySecretPassword");
Notes
Reload clears all application caches
Session data may be lost during reload
Database connections are refreshed
All singletons are recreated
Common Issues
Invalid password: Check password in settings
Timeout: Large applications may take time to reload
Memory issues: Monitor JVM heap during reload
See Also
wheels init
Bootstrap an existing Wheels application for CLI usage.
Synopsis
Description
The wheels init command initializes an existing Wheels application to work with the Wheels CLI. It sets up the necessary configuration files and creates a .wheels-cli.json file in your project root.
Arguments
Options
Examples
Initialize current directory
Initialize with custom name
Initialize specific directory
Initialize with custom reload password
Initialize without creating folders
What It Does
Creates .wheels-cli.json configuration file
Sets up application name and version
Configures reload password
Optionally creates standard Wheels directory structure:
/config
/controllers
/models
/views
/tests
/plugins
Configuration File
The generated .wheels-cli.json contains:
Notes
Run this command in the root directory of your Wheels application
The reload password is used for the wheels reload command
If folders already exist, they won't be overwritten
See Also
wheels info
Display CLI and Wheels framework version information.
Synopsis
Description
The wheels info command displays detailed information about your Wheels CLI installation, the current Wheels application, and the environment.
Options
Output
The command displays:
CLI Information
Wheels CLI version
CommandBox version
Installation path
Application Information (if in a Wheels app directory)
Application name
Wheels framework version
Reload password status
Configuration file location
Environment Information
CFML engine and version
Operating system
Java version
Example Output
Use Cases
Verify CLI installation
Check Wheels framework version
Troubleshoot environment issues
Confirm application configuration
Notes
Run from within a Wheels application directory for full information
Application information only appears when .wheels-cli.json exists
Sensitive information like passwords are masked
See Also
wheels destroy
Remove generated code and files.
Synopsis
Description
The wheels destroy command reverses the actions of generator commands, removing files and code that were previously generated. It's useful for cleaning up mistakes or removing features.
Arguments
Resource Types
controller - Remove a controller and its views
model - Remove a model
view - Remove a specific view
scaffold - Remove entire scaffolding
migration - Remove a migration file
test - Remove test files
Options
Examples
Remove a controller
Removes:
/controllers/Users.cfc
/views/users/ directory and all views
Remove a model
Removes:
/models/User.cfc
Related test files
Remove scaffolding
Removes:
/models/Product.cfc
/controllers/Products.cfc
/views/products/ directory
All CRUD views
Test files
Remove a specific view
Removes:
/views/users/edit.cfm
Remove a migration
Removes:
Migration file from /db/migrate/
Dry run to preview
Shows what would be removed without deleting
Force removal without confirmation
Confirmation
By default, the command asks for confirmation:
Safety Features
Confirmation Required: Always asks unless --force is used
Dry Run Mode: Preview changes with --dry-run
No Database Changes: Only removes files, not database tables
Git Awareness: Warns if files have uncommitted changes
What's NOT Removed
Database tables or columns
Routes (must be manually removed)
References in other files
Git history
Best Practices
Always use --dry-run first
Commit changes before destroying
Check for file dependencies
Update routes manually
Remove database tables separately
Common Workflows
Undo a scaffold
Clean up a mistake
Notes
Cannot be undone - files are permanently deleted
Does not remove custom code added to generated files
Works only with files created by generators
See Also
wheels deps
Manage application dependencies.
Synopsis
Description
The wheels deps command helps manage your Wheels application's dependencies, including CFML modules, Wheels plugins, and JavaScript packages.
Options
Features
Dependency Analysis
Scans box.json for CFML dependencies
Checks package.json for Node.js dependencies
Identifies Wheels plugins
Version Checking
Compares installed vs required versions
Identifies outdated packages
Shows available updates
Dependency Installation
Installs missing dependencies
Updates outdated packages
Resolves version conflicts
Output Example
Dependency Sources
CFML Dependencies (box.json)
Wheels Plugins
Located in /plugins directory or installed via ForgeBox.
Node Dependencies (package.json)
Commands Executed
Behind the scenes, wheels deps runs:
Use Cases
Check dependency status before deployment
Ensure all team members have same dependencies
Update dependencies safely
Troubleshoot missing functionality
Best Practices
Run after pulling new code
Check before deployments
Update dependencies incrementally
Test after updates
Notes
Requires box.json for CFML dependencies
Optional package.json for Node dependencies
Some plugins may require manual configuration
See Also
wheels watch
Watch for file changes and automatically reload the application.
Synopsis
Description
The wheels watch command monitors your application files for changes and automatically triggers actions like reloading the application or running tests. This provides a smooth development workflow with instant feedback.
Arguments
Options
Examples
Basic file watching
Watches current directory for .cfc, .cfm, and .json changes
Watch specific directory
Watch additional file types
Exclude directories
Auto-run tests
Custom debounce timing
Default Behavior
When a file change is detected:
CFC Files (models, controllers)
Triggers application reload
Clears relevant caches
Runs tests if --test enabled
CFM Files (views)
Clears view cache
No full reload needed
Config Files (.json, settings.cfm)
Full application reload
Re-reads configuration
Output Example
Advanced Configuration
Create .wheels-watch.json for project-specific settings:
Performance Considerations
Large directories may slow down watching
Use --exclude to skip unnecessary paths
Increase --debounce for grouped changes
Consider watching specific subdirectories
Integration with Editors
VS Code
Add to .vscode/tasks.json:
Sublime Text
Create build system:
Common Patterns
Development Workflow
Frontend + Backend
Test-Driven Development
Troubleshooting
Too many file descriptors: Increase system limits or exclude more directories
Changes not detected: Check file extensions and excluded paths
Slow response: Increase debounce time or watch specific directories
Tests failing: Ensure test environment is properly configured
Notes
Requires file system events support
Some network drives may not support watching
Symbolic links are followed by default
See Also
wheels generate app-wizard
Interactive wizard for creating a new Wheels application.
Synopsis
Description
The wheels generate app-wizard command provides an interactive, step-by-step wizard for creating a new Wheels application. It guides you through all configuration options with helpful prompts and explanations, making it ideal for beginners or when you want to explore all available options.
Options
Interactive Process
Step 1: Application Name
Must be alphanumeric with hyphens/underscores
Used for directory and configuration names
Step 2: Template Selection
Step 3: Target Directory
Defaults to ./{app-name}
Can specify absolute or relative path
Step 4: Database Configuration
Step 5: Additional Features
Step 6: CFML Engine
Step 7: Security Settings
Step 8: Review & Confirm
Wizard Flow
Expert Mode
Enable expert mode for additional options:
Additional prompts in expert mode:
Custom server ports
JVM settings
Environment-specific configurations
Advanced routing options
Custom plugin repositories
Build tool integration
Configuration Profiles
Save and reuse configurations:
Save Profile
Use Profile
List Profiles
Feature Descriptions
Bootstrap CSS
Includes Bootstrap 5.x
Responsive grid system
Pre-styled components
Example layouts
jQuery Library
Latest jQuery version
AJAX helpers configured
Example usage in views
Sample Authentication
User model with secure passwords
Login/logout controllers
Session management
Protected routes example
API Documentation
OpenAPI/Swagger setup
Auto-generated documentation
Interactive API explorer
Docker Configuration
Multi-stage Dockerfile
docker-compose.yml
Development & production configs
Database containers
Post-Creation Steps
After successful creation, the wizard displays:
Error Handling
The wizard handles common issues:
Invalid names: Suggests valid alternatives
Existing directories: Offers to overwrite or choose new location
Configuration errors: Allows editing before creation
Validation Rules
Application Name
Start with letter
Alphanumeric plus - and _
No spaces or special characters
Not a reserved word
Directory Path
Must be writable
Cannot be system directory
Warns if not empty
Passwords
Minimum 6 characters
Strength indicator
Confirmation required
Customization
Custom Templates
Add templates to ~/.wheels/templates/:
Template Configuration
template.json:
Integration
CI/CD Pipeline
Generate with CI configuration:
Includes:
.github/workflows/test.yml
Build configuration
Deployment scripts
IDE Configuration
Generate with IDE files:
Includes:
.vscode/settings.json
.vscode/launch.json
.editorconfig
Best Practices
Run wizard in empty directory
Choose descriptive application names
Configure database early
Enable security features for production
Save profiles for team consistency
Review all settings before confirming
Common Use Cases
API-Only Application
Choose Base@BE template
Skip Bootstrap/jQuery
Enable API documentation
Configure CORS settings
Traditional Web Application
Choose Base template
Include Bootstrap/jQuery
Add sample authentication
Configure session management
Microservice
Choose Base@BE template
Configure Docker
Set specific ports
Minimal dependencies
Troubleshooting
Wizard Freezes
Check terminal compatibility
Try --no-interactive mode
Check system resources
Installation Fails
Verify internet connection
Check CommandBox version
Try --skip-install and install manually
Configuration Issues
Review generated .wheels-cli.json
Check server.json settings
Verify file permissions
See Also
wheels generate app
Create a new Wheels application from templates.
Synopsis
Description
The wheels generate app command creates a new Wheels application with a complete directory structure, configuration files, and optionally sample code. It supports multiple templates for different starting points.
Arguments
Options
Available Templates
Base (Default)
Minimal Wheels application
Basic directory structure
Essential configuration files
Base@BE (Backend Edition)
Backend-focused template
No view files
API-ready configuration
HelloWorld
Simple "Hello World" example
One controller and view
Great for learning
HelloDynamic
Dynamic content example
Database interaction
Form handling
HelloPages
Static pages example
Layout system
Navigation structure
Examples
Create basic application
Create with custom template
Create in specific directory
Create with Bootstrap
Create with H2 database
Create with all options
Generated Structure
Configuration Files
box.json
server.json
.wheels-cli.json
Database Setup
With H2 (Embedded)
No external database needed
Perfect for development
Auto-configured datasource
With External Database
Create application:
Configure in CommandBox:
Post-Generation Steps
Navigate to directory
Install dependencies
Start server
Open browser
Template Development
Create custom templates in ~/.commandbox/cfml/modules/wheels-cli/templates/apps/:
Best Practices
Use descriptive application names
Choose appropriate template for project type
Set secure reload password for production
Configure datasource before starting
Run tests after generation
Common Issues
Directory exists: Use --force or choose different name
Template not found: Check available templates with wheels info
Datasource errors: Configure database connection
Port conflicts: Change port in server.json
See Also
wheels generate controller
Generate a controller with actions and optional views.
Synopsis
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
Options
Examples
Basic controller
Creates:
/controllers/Products.cfc with index action
/views/products/index.cfm
Controller with multiple actions
Creates controller with all CRUD actions and corresponding views.
RESTful controller
Automatically generates all RESTful actions:
index - List all products
show - Show single product
new - New product form
create - Create product
edit - Edit product form
update - Update product
delete - Delete product
API controller
Creates:
/controllers/api/Products.cfc with JSON responses
No view files
Custom actions
Generated Code
Basic Controller
RESTful Controller
API Controller
View Generation
Views are automatically generated for non-API controllers:
index.cfm
Naming Conventions
Controller names: PascalCase, typically plural (Products, Users)
The wheels generate view command creates view files for specified controller actions. It can generate individual views, sets of views for RESTful actions, or custom view templates with various layout options.
wheels generate app-wizard [options]
wheels g app-wizard [options]
--expert
Show advanced options
false
--skip-install
Skip dependency installation
false
--help
Show help information
? What is the name of your application? › myapp
? Which template would you like to use? ›
❯ Base - Minimal Wheels application
Base@BE - Backend-only (no views)
HelloWorld - Simple example application
HelloDynamic - Database-driven example
HelloPages - Static pages example
? Where should the application be created? › ./myapp
? Would you like to configure a database? (Y/n) › Y
? Database type? ›
❯ H2 (Embedded)
MySQL
PostgreSQL
SQL Server
Custom
? Which CFML engine will you use? ›
❯ Lucee 5
Lucee 6
Adobe ColdFusion 2018
Adobe ColdFusion 2021
Adobe ColdFusion 2023
? Set reload password (leave blank for 'wheels'): › ****
? Enable CSRF protection? (Y/n) › Y
? Enable secure cookies? (y/N) › N
Application Configuration:
─────────────────────────
Name: myapp
Template: Base
Directory: ./myapp
Database: H2 (Embedded)
Features: Bootstrap, jQuery
Engine: Lucee 5
Reload PWD: ****
? Create application with these settings? (Y/n) › Y
graph TD
A[Start Wizard] --> B[Enter App Name]
B --> C[Select Template]
C --> D[Choose Directory]
D --> E[Configure Database]
E --> F[Select Features]
F --> G[Choose CFML Engine]
G --> H[Security Settings]
H --> I[Review Configuration]
I --> J{Confirm?}
J -->|Yes| K[Create Application]
J -->|No| B
K --> L[Install Dependencies]
L --> M[Show Next Steps]
wheels generate app-wizard --expert
? Save this configuration as a profile? (y/N) › Y
? Profile name: › enterprise-api
✓ Application created successfully!
Next steps:
1. cd myapp
2. box install (or run manually if skipped)
3. box server start
4. Visit http://localhost:3000
Additional commands:
- wheels test Run tests
- wheels dbmigrate up Run migrations
- wheels generate Generate code
- wheels help Show all commands
Generate a model with properties, validations, and associations.
Synopsis
wheels generate model [name] [options]
wheels g model [name] [options]
Description
The wheels generate model command creates a new model CFC file with optional properties, associations, and database migrations. Models represent database tables and contain business logic, validations, and relationships.
Arguments
Argument
Description
Default
name
Model name (singular)
Required
Options
Option
Description
Default
--migration
Generate migration file
true
--properties
Properties list (name:type)
--belongs-to
Belongs to associations
--has-many
Has many associations
--has-one
Has one associations
--force
Overwrite existing files
false
--help
Show help information
Examples
Basic model
wheels generate model user
Creates:
/models/User.cfc
Migration file (if enabled)
Model with properties
wheels generate model user --properties="firstName:string,lastName:string,email:string,age:integer"
Model with associations
wheels generate model post --belongs-to="user" --has-many="comments"
Model without migration
wheels generate model setting --migration=false
Complex model
wheels generate model product \
--properties="name:string,price:decimal,stock:integer,active:boolean" \
--belongs-to="category,brand" \
--has-many="reviews,orderItems"
Property Types
Type
Database Type
CFML Type
string
VARCHAR(255)
string
text
TEXT
string
integer
INTEGER
numeric
biginteger
BIGINT
numeric
float
FLOAT
numeric
decimal
DECIMAL(10,2)
numeric
boolean
BOOLEAN
boolean
date
DATE
date
datetime
DATETIME
date
timestamp
TIMESTAMP
date
binary
BLOB
binary
uuid
VARCHAR(35)
string
Generated Code
Basic Model
component extends="Model" {
function init() {
// Table name (optional if following conventions)
table("users");
// Validations
validatesPresenceOf("email");
validatesUniquenessOf("email");
validatesFormatOf("email", regex="^[^@]+@[^@]+\.[^@]+$");
// Callbacks
beforeCreate("setDefaultValues");
}
private function setDefaultValues() {
if (!StructKeyExists(this, "createdAt")) {
this.createdAt = Now();
}
}
}
function init() {
beforeCreate("setDefaults");
}
private function setDefaults() {
if (!StructKeyExists(this, "status")) {
this.status = "pending";
}
if (!StructKeyExists(this, "priority")) {
this.priority = 5;
}
}
Testing
Generate model tests:
wheels generate model user --properties="email:string,name:string"
wheels generate test model user
See Also
wheels generate route
Generate route definitions for your application.
Synopsis
wheels generate route [pattern] [options]
wheels g route [pattern] [options]
Description
The wheels generate route command helps you create route definitions in your Wheels application. It can generate individual routes, RESTful resources, nested routes, and complex routing patterns while maintaining proper syntax and organization in your routes file.
The wheels generate property command adds new properties to existing model files. It can add simple properties, associations, calculated properties, and validations while maintaining proper code formatting and structure.
Arguments
Argument
Description
Default
model
Model name to add properties to
Required
properties
Property definitions (name:type:options)
Required
Options
Option
Description
Default
--migrate
Generate migration for database changes
true
--validate
Add validation rules
true
--defaults
Include default values
false
--callbacks
Generate property callbacks
false
--force
Overwrite without confirmation
false
--help
Show help information
Property Syntax
Basic Format
propertyName:type:option1:option2
Supported Types
string - VARCHAR(255)
text - TEXT/CLOB
integer - INT
float - DECIMAL
boolean - BIT/BOOLEAN
date - DATE
datetime - DATETIME
timestamp - TIMESTAMP
binary - BLOB
Property Options
required - Not null
unique - Unique constraint
index - Create index
default=value - Default value
limit=n - Character limit
precision=n - Decimal precision
scale=n - Decimal scale
Examples
Add single property
wheels generate property user email:string:required:unique
wheels generate property order "status:string:default=pending statusChangedAt:timestamp"
Audit Fields
wheels generate property model "createdBy:integer:belongsTo=user updatedBy:integer:belongsTo=user"
Testing
After adding properties:
# Run migration
wheels dbmigrate up
# Generate property tests
wheels generate test model user
# Run tests
wheels test
See Also
wheels generate api-resource
Generate a complete API resource with model, API controller, and routes.
⚠️ Note: This command is currently marked as broken/disabled in the codebase. The documentation below represents the intended functionality when the command is restored.
The wheels generate api-resource command creates a complete RESTful API resource including model, API-specific controller (no views), routes, and optionally database migrations and tests. It's optimized for building JSON APIs following REST conventions.
Current Status
This command is temporarily disabled. Use alternative approaches:
# Option 1: Use regular resource with --api flag
wheels generate resource product name:string price:float --api
# Option 2: Generate components separately
wheels generate model product name:string price:float
wheels generate controller api/products --api
wheels generate route products --api --namespace=api
Generate frontend code including JavaScript, CSS, and interactive components.
⚠️ Note: This command is currently marked as disabled in the codebase. The documentation below represents the intended functionality when the command is restored.
The wheels generate frontend command creates frontend assets including JavaScript modules, CSS stylesheets, and interactive components. It supports various frontend frameworks and patterns while integrating seamlessly with Wheels views.
Current Status
This command is temporarily disabled. Use manual approaches:
Follow conventions: Consistent naming and structure
Progressive enhancement: Work without JavaScript
Optimize performance: Minimize and bundle assets
Test components: Unit and integration tests
Document APIs: Clear component documentation
Handle errors: Graceful error handling
Accessibility: ARIA labels and keyboard support
Security: Validate inputs, use CSRF tokens
See Also
Quick Start Guide
Get up and running with Wheels CLI in minutes.
Prerequisites
CommandBox 5.0+
Java 8+
Database (MySQL, PostgreSQL, SQL Server, or H2)
Installation
Install CommandBox
# macOS/Linux
curl -fsSl https://downloads.ortussolutions.com/debs/gpg | sudo apt-key add -
echo "deb https://downloads.ortussolutions.com/debs/noarch /" | sudo tee -a /etc/apt/sources.list.d/commandbox.list
sudo apt-get update && sudo apt-get install commandbox
# Windows (PowerShell as Admin)
iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))
choco install commandbox
Install Wheels CLI
box install wheels-cli
Creating Your First Application
1. Generate Application
wheels new blog
cd blog
This creates a new Wheels application with:
Complete directory structure
Configuration files
Sample code
2. Configure Database
Edit /config/settings.cfm:
<cfset set(dataSourceName="blog_development")>
Or use H2 embedded database:
wheels new blog --setupH2
3. Start Server
box server start
Visit http://localhost:3000
Creating Your First Feature
Let's create a blog post feature:
1. Generate Scaffold
wheels scaffold post --properties="title:string,content:text,published:boolean"
This generates:
Model with validations
Controller with CRUD actions
Views for all actions
Database migration
Test files
2. Run Migration
wheels dbmigrate latest
3. Add Routes
Edit /config/routes.cfm:
<cfscript>
// Add this line
resources("posts");
</cfscript>
4. Reload Application
wheels reload
5. Test Your Feature
Visit http://localhost:3000/posts
You now have a fully functional blog post management system!
Development Workflow
File Watching
In a new terminal:
wheels watch
Now changes to .cfc and .cfm files trigger automatic reloads.
Running Tests
# Run all tests
wheels test run
# Watch mode
wheels test run --watch
# Specific tests
wheels test run tests/models/PostTest.cfc
Adding Relationships
Let's add comments to posts:
# Generate comment model
wheels generate model comment --properties="author:string,content:text,postId:integer" \
--belongs-to="post"
# Update post model
wheels generate property post comments --has-many
# Generate comments controller
wheels generate controller comments --rest
# Run migration
wheels dbmigrate latest
Common Tasks
Adding Authentication
# Generate user model
wheels scaffold user --properties="email:string,password:string,admin:boolean"
# Generate session controller
wheels generate controller sessions new,create,delete
# Run migrations
wheels dbmigrate latest
Adding API Endpoints
# Generate API resource
wheels generate api-resource product --properties="name:string,price:decimal"
# Or convert existing to API
wheels generate controller api/posts --api
The wheels generate resource command creates a complete RESTful resource including model, controller with all CRUD actions, views, routes, and optionally database migrations and tests. It's a comprehensive generator that sets up everything needed for a functioning resource.
Generate test files for models, controllers, views, and other components.
Synopsis
wheels generate test [type] [name] [options]
wheels g test [type] [name] [options]
Description
The wheels generate test command creates test files for various components of your Wheels application. It generates appropriate test scaffolding based on the component type and includes common test cases to get you started.
Arguments
Argument
Description
Default
type
Type of test (model, controller, view, helper, route)
Required
name
Name of the component to test
Required
Options
Option
Description
Default
--methods
Specific methods to test
All methods
--integration
Generate integration tests
false
--coverage
Include coverage setup
false
--fixtures
Generate test fixtures
true
--force
Overwrite existing files
false
--help
Show help information
Examples
Model Test
wheels generate test model product
Generates /tests/models/ProductTest.cfc:
component extends="wheels.Test" {
function setup() {
super.setup();
// Clear test data
model("Product").deleteAll();
// Setup test fixtures
variables.validProduct = {
name: "Test Product",
price: 19.99,
description: "Test product description"
};
}
function teardown() {
super.teardown();
// Clean up after tests
model("Product").deleteAll();
}
// Validation Tests
function test_valid_product_saves_successfully() {
// Arrange
product = model("Product").new(variables.validProduct);
// Act
result = product.save();
// Assert
assert(result, "Product should save successfully");
assert(product.id > 0, "Product should have an ID after saving");
}
function test_product_requires_name() {
// Arrange
product = model("Product").new(variables.validProduct);
product.name = "";
// Act
result = product.save();
// Assert
assert(!result, "Product should not save without name");
assert(ArrayLen(product.errorsOn("name")) > 0, "Should have error on name");
}
function test_product_requires_positive_price() {
// Arrange
product = model("Product").new(variables.validProduct);
product.price = -10;
// Act
result = product.save();
// Assert
assert(!result, "Product should not save with negative price");
assert(ArrayLen(product.errorsOn("price")) > 0, "Should have error on price");
}
function test_product_name_must_be_unique() {
// Arrange
product1 = model("Product").create(variables.validProduct);
product2 = model("Product").new(variables.validProduct);
// Act
result = product2.save();
// Assert
assert(!result, "Should not save duplicate product name");
assert(ArrayLen(product2.errorsOn("name")) > 0, "Should have uniqueness error");
}
// Association Tests
function test_product_has_many_reviews() {
// Arrange
product = model("Product").create(variables.validProduct);
review = product.createReview(rating=5, comment="Great product!");
// Act
reviews = product.reviews();
// Assert
assert(reviews.recordCount == 1, "Product should have one review");
assert(reviews.rating == 5, "Review rating should be 5");
}
// Callback Tests
function test_before_save_sanitizes_input() {
// Arrange
product = model("Product").new(variables.validProduct);
product.name = " Test Product ";
// Act
product.save();
// Assert
assert(product.name == "Test Product", "Name should be trimmed");
}
// Scope Tests
function test_active_scope_returns_only_active_products() {
// Arrange
activeProduct = model("Product").create(
variables.validProduct & {isActive: true}
);
inactiveProduct = model("Product").create(
name="Inactive Product",
price=29.99,
isActive=false
);
// Act
activeProducts = model("Product").active();
// Assert
assert(activeProducts.recordCount == 1, "Should have one active product");
assert(activeProducts.id == activeProduct.id, "Should return active product");
}
// Method Tests
function test_calculate_discount_price() {
// Arrange
product = model("Product").create(variables.validProduct);
// Act
discountPrice = product.calculateDiscountPrice(0.20); // 20% discount
// Assert
expected = product.price * 0.80;
assert(discountPrice == expected, "Discount price should be 80% of original");
}
// Integration Tests
function test_product_lifecycle() {
transaction {
// Create
product = model("Product").new(variables.validProduct);
assert(product.save(), "Should create product");
productId = product.id;
// Read
foundProduct = model("Product").findByKey(productId);
assert(IsObject(foundProduct), "Should find product");
assert(foundProduct.name == variables.validProduct.name, "Should have correct name");
// Update
foundProduct.price = 24.99;
assert(foundProduct.save(), "Should update product");
// Verify update
updatedProduct = model("Product").findByKey(productId);
assert(updatedProduct.price == 24.99, "Price should be updated");
// Delete
assert(updatedProduct.delete(), "Should delete product");
// Verify deletion
deletedProduct = model("Product").findByKey(productId);
assert(!IsObject(deletedProduct), "Product should not exist");
// Rollback transaction
transaction action="rollback";
}
}
}
Controller Test
wheels generate test controller products
Generates /tests/controllers/ProductsTest.cfc:
component extends="wheels.Test" {
function setup() {
super.setup();
// Setup test data
model("Product").deleteAll();
variables.testProducts = [];
for (i = 1; i <= 3; i++) {
ArrayAppend(variables.testProducts,
model("Product").create(
name="Product #i#",
price=19.99 * i,
description="Description #i#"
)
);
}
}
function teardown() {
super.teardown();
model("Product").deleteAll();
}
// Action Tests
function test_index_returns_all_products() {
// Act
result = processRequest(route="products", method="GET");
// Assert
assert(result.status == 200, "Should return 200 status");
assert(Find("<h1>Products</h1>", result.body), "Should have products heading");
for (product in variables.testProducts) {
assert(Find(product.name, result.body), "Should display product: #product.name#");
}
}
function test_show_displays_product_details() {
// Arrange
product = variables.testProducts[1];
// Act
result = processRequest(route="product", key=product.id, method="GET");
// Assert
assert(result.status == 200, "Should return 200 status");
assert(Find(product.name, result.body), "Should display product name");
assert(Find(DollarFormat(product.price), result.body), "Should display formatted price");
}
function test_show_returns_404_for_invalid_product() {
// Act
result = processRequest(route="product", key=99999, method="GET");
// Assert
assert(result.status == 302, "Should redirect");
assert(result.flash.error == "Product not found.", "Should have error message");
}
function test_new_displays_form() {
// Act
result = processRequest(route="newProduct", method="GET");
// Assert
assert(result.status == 200, "Should return 200 status");
assert(Find("<form", result.body), "Should have form");
assert(Find('name="product[name]"', result.body), "Should have name field");
assert(Find('name="product[price]"', result.body), "Should have price field");
}
function test_create_with_valid_data() {
// Arrange
params = {
product: {
name: "New Test Product",
price: 39.99,
description: "New product description"
}
};
// Act
result = processRequest(route="products", method="POST", params=params);
// Assert
assert(result.status == 302, "Should redirect after creation");
assert(result.flash.success == "Product was created successfully.", "Should have success message");
// Verify product was created
newProduct = model("Product").findOne(where="name='New Test Product'");
assert(IsObject(newProduct), "Product should be created");
assert(newProduct.price == 39.99, "Should have correct price");
}
function test_create_with_invalid_data() {
// Arrange
params = {
product: {
name: "",
price: -10,
description: "Invalid product"
}
};
// Act
result = processRequest(route="products", method="POST", params=params);
// Assert
assert(result.status == 200, "Should render form again");
assert(Find("error", result.body), "Should display errors");
assert(model("Product").count(where="description='Invalid product'") == 0,
"Should not create invalid product");
}
function test_edit_displays_form_with_product_data() {
// Arrange
product = variables.testProducts[1];
// Act
result = processRequest(route="editProduct", key=product.id, method="GET");
// Assert
assert(result.status == 200, "Should return 200 status");
assert(Find('value="#product.name#"', result.body), "Should pre-fill name");
assert(Find(ToString(product.price), result.body), "Should pre-fill price");
}
function test_update_with_valid_data() {
// Arrange
product = variables.testProducts[1];
params = {
product: {
name: "Updated Product Name",
price: 49.99
}
};
// Act
result = processRequest(route="product", key=product.id, method="PUT", params=params);
// Assert
assert(result.status == 302, "Should redirect after update");
assert(result.flash.success == "Product was updated successfully.", "Should have success message");
// Verify update
updatedProduct = model("Product").findByKey(product.id);
assert(updatedProduct.name == "Updated Product Name", "Name should be updated");
assert(updatedProduct.price == 49.99, "Price should be updated");
}
function test_delete_removes_product() {
// Arrange
product = variables.testProducts[1];
initialCount = model("Product").count();
// Act
result = processRequest(route="product", key=product.id, method="DELETE");
// Assert
assert(result.status == 302, "Should redirect after deletion");
assert(result.flash.success == "Product was deleted successfully.", "Should have success message");
assert(model("Product").count() == initialCount - 1, "Should have one less product");
assert(!IsObject(model("Product").findByKey(product.id)), "Product should be deleted");
}
// Filter Tests
function test_authentication_required_for_protected_actions() {
// Test that certain actions require authentication
protectedRoutes = [
{route: "newProduct", method: "GET"},
{route: "products", method: "POST"},
{route: "editProduct", key: variables.testProducts[1].id, method: "GET"},
{route: "product", key: variables.testProducts[1].id, method: "PUT"},
{route: "product", key: variables.testProducts[1].id, method: "DELETE"}
];
for (route in protectedRoutes) {
// Act without authentication
result = processRequest(argumentCollection=route);
// Assert
assert(result.status == 302, "Should redirect unauthenticated user");
assert(result.redirectUrl contains "login", "Should redirect to login");
}
}
// Helper method for processing requests
private function processRequest(
required string route,
string method = "GET",
struct params = {},
numeric key = 0
) {
local.args = {
route: arguments.route,
method: arguments.method,
params: arguments.params
};
if (arguments.key > 0) {
local.args.key = arguments.key;
}
return $processRequest(argumentCollection=local.args);
}
}
View Test
wheels generate test view products/index
Generates /tests/views/products/IndexTest.cfc:
component extends="wheels.Test" {
function setup() {
super.setup();
// Create test data
variables.products = QueryNew(
"id,name,price,createdAt",
"integer,varchar,decimal,timestamp"
);
for (i = 1; i <= 3; i++) {
QueryAddRow(variables.products, {
id: i,
name: "Product #i#",
price: 19.99 * i,
createdAt: Now()
});
}
}
function test_index_view_renders_product_list() {
// Act
result = $renderView(
view="/products/index",
products=variables.products,
layout=false
);
// Assert
assert(Find("<h1>Products</h1>", result), "Should have products heading");
assert(Find("<table", result), "Should have products table");
assert(Find("Product 1", result), "Should display first product");
assert(Find("Product 2", result), "Should display second product");
assert(Find("Product 3", result), "Should display third product");
}
function test_index_view_shows_empty_state() {
// Arrange
emptyQuery = QueryNew("id,name,price,createdAt");
// Act
result = $renderView(
view="/products/index",
products=emptyQuery,
layout=false
);
// Assert
assert(Find("No products found", result), "Should show empty state message");
assert(Find("Create one now", result), "Should have create link");
assert(!Find("<table", result), "Should not show table when empty");
}
function test_index_view_formats_prices_correctly() {
// Act
result = $renderView(
view="/products/index",
products=variables.products,
layout=false
);
// Assert
assert(Find("$19.99", result), "Should format first price");
assert(Find("$39.98", result), "Should format second price");
assert(Find("$59.97", result), "Should format third price");
}
function test_index_view_includes_action_links() {
// Act
result = $renderView(
view="/products/index",
products=variables.products,
layout=false
);
// Assert
assert(Find("New Product", result), "Should have new product link");
assert(FindNoCase("href=""/products/new""", result), "New link should be correct");
// Check action links for each product
for (row in variables.products) {
assert(Find("View</a>", result), "Should have view link");
assert(Find("Edit</a>", result), "Should have edit link");
assert(Find("Delete</a>", result), "Should have delete link");
}
}
function test_index_view_with_pagination() {
// Arrange
paginatedProducts = Duplicate(variables.products);
paginatedProducts.currentPage = 2;
paginatedProducts.totalPages = 5;
paginatedProducts.totalRecords = 50;
// Act
result = $renderView(
view="/products/index",
products=paginatedProducts,
layout=false
);
// Assert
assert(Find("class=""pagination""", result), "Should have pagination");
assert(Find("Previous", result), "Should have previous link");
assert(Find("Next", result), "Should have next link");
assert(Find("Page 2 of 5", result), "Should show current page");
}
function test_index_view_escapes_html() {
// Arrange
productsWithHtml = QueryNew("id,name,price,createdAt");
QueryAddRow(productsWithHtml, {
id: 1,
name: "<script>alert('XSS')</script>",
price: 19.99,
createdAt: Now()
});
// Act
result = $renderView(
view="/products/index",
products=productsWithHtml,
layout=false
);
// Assert
assert(!Find("<script>alert('XSS')</script>", result),
"Should not have unescaped script tag");
assert(Find("<script>", result), "Should have escaped HTML");
}
}
Integration Test
wheels generate test controller products --integration
Generates additional integration tests:
component extends="wheels.Test" {
function test_complete_product_workflow() {
transaction {
// 1. View product list (empty)
result = $visit(route="products");
assert(result.status == 200);
assert(Find("No products found", result.body));
// 2. Navigate to new product form
result = $click("Create one now");
assert(result.status == 200);
assert(Find("<form", result.body));
// 3. Submit new product form
result = $submitForm({
"product[name]": "Integration Test Product",
"product[price]": "29.99",
"product[description]": "Test description"
});
assert(result.status == 302);
assert(result.flash.success);
// 4. View created product
product = model("Product").findOne(order="id DESC");
result = $visit(route="product", key=product.id);
assert(result.status == 200);
assert(Find("Integration Test Product", result.body));
// 5. Edit product
result = $click("Edit");
assert(Find('value="Integration Test Product"', result.body));
result = $submitForm({
"product[name]": "Updated Product",
"product[price]": "39.99"
});
assert(result.status == 302);
// 6. Verify update
result = $visit(route="product", key=product.id);
assert(Find("Updated Product", result.body));
assert(Find("$39.99", result.body));
// 7. Delete product
result = $click("Delete", confirm=true);
assert(result.status == 302);
assert(result.flash.success contains "deleted");
// 8. Verify deletion
assert(!IsObject(model("Product").findByKey(product.id)));
transaction action="rollback";
}
}
}
// In test file
function assertProductValid(required any product) {
assert(IsObject(arguments.product), "Product should be an object");
assert(arguments.product.id > 0, "Product should have valid ID");
assert(Len(arguments.product.name), "Product should have name");
assert(arguments.product.price > 0, "Product should have positive price");
}
function assertHasError(required any model, required string property) {
local.errors = arguments.model.errorsOn(arguments.property);
assert(ArrayLen(local.errors) > 0,
"Expected error on #arguments.property# but found none");
}
wheels test app tests/models/ProductTest.cfc::test_product_requires_name
Run with coverage
wheels test --coverage
Best Practices
Test in isolation: Each test should be independent
Use descriptive names: Test names should explain what they test
Follow AAA pattern: Arrange, Act, Assert
Clean up data: Use setup/teardown or transactions
Test edge cases: Empty data, nulls, extremes
Mock external services: Don't rely on external APIs
Keep tests fast: Optimize slow tests
Test one thing: Each test should verify one behavior
Use fixtures wisely: Share common test data
Run tests frequently: Before commits and in CI
Common Testing Patterns
Testing Private Methods
function test_private_method_through_public_interface() {
// Don't test private methods directly
// Test them through public methods that use them
product = model("Product").new(name: " Test ");
product.save(); // Calls private sanitize method
assert(product.name == "Test");
}
Testing Time-Dependent Code
function test_expiration_date() {
// Use specific dates instead of Now()
testDate = CreateDate(2024, 1, 1);
product = model("Product").new(
expiresAt: DateAdd("d", 30, testDate)
);
// Test with mocked current date
request.currentDate = testDate;
assert(!product.isExpired());
request.currentDate = DateAdd("d", 31, testDate);
assert(product.isExpired());
}
Testing Randomness
function test_random_discount() {
// Test the range, not specific values
product = model("Product").new(price: 100);
for (i = 1; i <= 100; i++) {
discount = product.getRandomDiscount();
assert(discount >= 0.05 && discount <= 0.25,
"Discount should be between 5% and 25%");
}
}
See Also
wheels generate snippets
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
Argument
Description
Default
pattern
Snippet pattern to generate
Shows available patterns
Options
Option
Description
Default
--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
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 dbmigrate info
Display database migration status and information.
Synopsis
wheels dbmigrate info
Description
The wheels dbmigrate info command shows the current state of database migrations, including which migrations have been run, which are pending, and the current database version.
Options
Option
Description
--help
Show help information
Output
The command displays:
Current Version: The latest migration that has been run
Available Migrations: All migration files found
Migration Status: Which migrations are completed vs pending
Database Details: Connection information
Example Output
╔═══════════════════════════════════════════════╗
║ Database Migration Status ║
╚═══════════════════════════════════════════════╝
Current Version: 20240115120000
Database: myapp_development
Connection: MySQL 8.0
╔═══════════════════════════════════════════════╗
║ Migration History ║
╚═══════════════════════════════════════════════╝
✓ 20240101100000_create_users_table.cfc
✓ 20240105150000_create_products_table.cfc
✓ 20240110090000_add_email_to_users.cfc
✓ 20240115120000_create_orders_table.cfc
○ 20240120140000_add_status_to_orders.cfc (pending)
○ 20240125160000_create_categories_table.cfc (pending)
Status: 4 completed, 2 pending
To run pending migrations, use: wheels dbmigrate latest
Migration Files Location
Migrations are stored in /db/migrate/ and follow the naming convention:
[timestamp]_[description].cfc
Example:
20240125160000_create_users_table.cfc
Understanding Version Numbers
Version numbers are timestamps in format: YYYYMMDDHHmmss
Higher numbers are newer migrations
Migrations run in chronological order
Database Schema Table
Migration status is tracked in schema_migrations table:
#!/bin/bash
# Check migration status
wheels dbmigrate info
# Run if needed
if [[ $(wheels dbmigrate info | grep "pending") ]]; then
echo "Running pending migrations..."
wheels dbmigrate latest
fi
Best Practices
Always check info before running migrations
Review pending migrations before deployment
Keep migration files in version control
Don't modify completed migration files
Use info to verify production deployments
See Also
wheels scaffold
Generate complete CRUD scaffolding for a resource.
Synopsis
wheels scaffold [name] [options]
Description
The wheels scaffold command generates a complete CRUD (Create, Read, Update, Delete) implementation including model, controller, views, tests, and database migration. It's the fastest way to create a fully functional resource.
component extends="Controller" {
function init() {
// Filters
}
function index() {
products = model("Product").findAll(order="name");
}
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 was created successfully.");
redirectTo(action="index");
} else {
flashInsert(error="There was an error creating the product.");
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 was updated successfully.");
redirectTo(action="index");
} else {
flashInsert(error="There was an error updating the product.");
renderView(action="edit");
}
}
function delete() {
product = model("Product").findByKey(params.key);
if (IsObject(product) && product.delete()) {
flashInsert(success="Product was deleted successfully.");
} else {
flashInsert(error="Product could not be deleted.");
}
redirectTo(action="index");
}
}
function init() {
filters(through="authenticate", except="index,show");
}
Best Practices
Properties: Define all needed properties upfront
Associations: Include relationships in initial scaffold
Validation: Add custom validations after generation
Testing: Always generate and run tests
Routes: Use RESTful resources when possible
Security: Add authentication/authorization
Comparison with Individual Generators
Scaffold generates everything at once:
# Scaffold does all of this:
wheels generate model product --properties="name:string,price:decimal"
wheels generate controller products --rest
wheels generate view products index,show,new,edit,_form
wheels generate test model product
wheels generate test controller products
wheels dbmigrate create table products
See Also
wheels dbmigrate latest
Run all pending database migrations to bring database to latest version.
Synopsis
wheels dbmigrate latest
Description
The wheels dbmigrate latest command runs all pending migrations in chronological order, updating your database schema to the latest version. This is the most commonly used migration command.
→ Running 20240120140000_add_status_to_orders.cfc
Adding column: status to orders
✗ ERROR: Column 'status' already exists
Migration failed at version 20240115120000
Error: Column 'status' already exists in table 'orders'
To retry: Fix the migration file and run 'wheels dbmigrate latest' again
To skip: Run 'wheels dbmigrate up' to run one at a time
Best Practices
Test migrations locally first
# Test on development database
wheels dbmigrate latest
# Verify
wheels dbmigrate info
function up() {
transaction {
// All changes here
}
}
Make migrations reversible
function down() {
transaction {
dropTable("products");
}
}
Environment-Specific Migrations
Migrations can check environment:
function up() {
transaction {
// Always run
addColumn(table="users", column="lastLogin", type="datetime");
// Development only
if (get("environment") == "development") {
// Add test data
sql("INSERT INTO users (email) VALUES ('test@example.com')");
}
}
}
Dry Run
Preview migrations without running:
# Check what would run
wheels dbmigrate info
# Review migration files
ls db/migrate/
Performance Considerations
For large tables:
function up() {
transaction {
// Add index concurrently (if supported)
if (get("databaseType") == "postgresql") {
sql("CREATE INDEX CONCURRENTLY idx_users_email ON users(email)");
} else {
addIndex(table="users", columns="email");
}
}
}
Continuous Integration
Add to CI/CD pipeline:
# .github/workflows/deploy.yml
- name: Run migrations
run: |
wheels dbmigrate latest
wheels test app
Rollback Strategy
If issues occur after migration:
Use down migrations
wheels dbmigrate down
wheels dbmigrate down
Restore from backup
mysql myapp_production < backup.sql
Fix and retry
Fix migration file
Run wheels dbmigrate latest
Common Issues
Timeout on Large Tables
function up() {
// Increase timeout for large operations
setting requestTimeout="300";
transaction {
// Long running operation
}
}
The dbmigrate up command executes the next pending migration in your database migration queue. This command is used to incrementally apply database changes one migration at a time, allowing for controlled and reversible database schema updates.
Options
--env
Type: String
Default:development
Description: The environment to run the migration in (development, testing, production)
--datasource
Type: String
Default: Application default
Description: Specify a custom datasource for the migration
--verbose
Type: Boolean
Default:false
Description: Display detailed output during migration execution
--dry-run
Type: Boolean
Default:false
Description: Preview the migration without executing it
Examples
Run the next pending migration
wheels dbmigrate up
Run migration in production environment
wheels dbmigrate up --env=production
Preview migration without executing
wheels dbmigrate up --dry-run --verbose
Use a specific datasource
wheels dbmigrate up --datasource=myCustomDB
Use Cases
Incremental Database Updates
When you want to apply database changes one at a time rather than all at once:
# Check pending migrations
wheels dbmigrate info
# Apply next migration
wheels dbmigrate up
# Verify the change
wheels dbmigrate info
Testing Individual Migrations
Test migrations individually before applying all pending changes:
# Run in test environment first
wheels dbmigrate up --env=testing
# If successful, apply to development
wheels dbmigrate up --env=development
Controlled Production Deployments
Apply migrations incrementally in production for better control:
# Preview the migration
wheels dbmigrate up --dry-run --env=production
# Apply if preview looks good
wheels dbmigrate up --env=production
# Monitor application
# If issues arise, can rollback with 'wheels dbmigrate down'
Notes
Migrations are executed in chronological order based on their timestamps
Each migration is tracked in the database to prevent duplicate execution
Use wheels dbmigrate info to see pending and completed migrations
Always backup your database before running migrations in production
Related Commands
wheels dbmigrate down
Rollback the last executed database migration.
Synopsis
wheels dbmigrate down [options]
Description
The dbmigrate down command reverses the last executed migration by running its down() method. This is useful for undoing database changes when issues are discovered or when you need to modify a migration. The command ensures safe rollback of schema changes while maintaining database integrity.
Options
--env
Type: String
Default:development
Description: The environment to rollback the migration in
--datasource
Type: String
Default: Application default
Description: Specify a custom datasource for the rollback
--verbose
Type: Boolean
Default:false
Description: Display detailed output during rollback
--force
Type: Boolean
Default:false
Description: Force rollback even if there are warnings
--dry-run
Type: Boolean
Default:false
Description: Preview the rollback without executing it
Examples
Rollback the last migration
wheels dbmigrate down
Rollback in production with confirmation
wheels dbmigrate down --env=production --verbose
Preview rollback without executing
wheels dbmigrate down --dry-run
Force rollback with custom datasource
wheels dbmigrate down --datasource=legacyDB --force
Use Cases
Fixing Migration Errors
When a migration contains errors or needs modification:
# Run the migration
wheels dbmigrate up
# Discover an issue
# Rollback the migration
wheels dbmigrate down
# Edit the migration file
# Re-run the migration
wheels dbmigrate up
Development Iteration
During development when refining migrations:
# Apply migration
wheels dbmigrate up
# Test the changes
# Need to modify? Rollback
wheels dbmigrate down
# Make changes to migration
# Apply again
wheels dbmigrate up
Emergency Production Rollback
When a production migration causes issues:
# Check current migration status
wheels dbmigrate info --env=production
# Rollback the problematic migration
wheels dbmigrate down --env=production --verbose
# Verify rollback
wheels dbmigrate info --env=production
Important Considerations
Data Loss Warning
Rolling back migrations that drop columns or tables will result in data loss. Always ensure you have backups before rolling back destructive migrations.
Down Method Requirements
For a migration to be rolled back, it must have a properly implemented down() method that reverses the changes made in the up() method.
Migration Dependencies
Be cautious when rolling back migrations that other migrations depend on. This can break the migration chain.
Best Practices
Always implement down() methods: Even if you think you'll never need to rollback
Test rollbacks: In development, always test that your down() method works correctly
Backup before rollback: Especially in production environments
Document destructive operations: Clearly indicate when rollbacks will cause data loss
Notes
Only the last executed migration can be rolled back with this command
To rollback multiple migrations, run the command multiple times
The migration version is removed from the database tracking table upon successful rollback
Some operations (like dropping columns with data) cannot be fully reversed
Related Commands
wheels dbmigrate create blank
Create an empty database migration file with up and down methods.
Synopsis
Description
The dbmigrate create blank command generates a new empty migration file with the basic structure including up() and down() methods. This provides a starting point for custom migrations where you need full control over the migration logic.
Options
--name
Type: String
Required: Yes
Description: The name of the migration (will be prefixed with timestamp)
--datasource
Type: String
Default: Application default
Description: Specify the datasource this migration targets
--description
Type: String
Default: Empty
Description: Add a description comment to the migration file
--template
Type: String
Default:blank
Description: Use a custom template for the migration
Examples
Create a basic empty migration
Create migration with description
Create migration for specific datasource
Generated File Structure
The command creates a file named YYYYMMDDHHmmss_<name>.cfc with the following structure:
Use Cases
Custom Database Operations
For complex operations not covered by other generators:
Data Migrations
When you need to migrate data, not just schema:
Multi-Step Operations
For migrations requiring multiple coordinated changes:
Database-Specific Features
For database-specific features not abstracted by CFWheels:
Best Practices
1. Descriptive Names
Use clear, descriptive names that indicate the migration's purpose:
2. Implement Both Methods
Always implement both up() and down() methods:
3. Use Transactions
Wrap operations in transactions for atomicity:
4. Add Comments
Document complex operations:
Available Migration Methods
Within your blank migration, you can use these helper methods:
createTable(name, options) - Create a new table
dropTable(name) - Drop a table
addColumn(table, column, type, options) - Add a column
removeColumn(table, column) - Remove a column
changeColumn(table, column, type, options) - Modify a column
addIndex(table, column, options) - Add an index
removeIndex(table, column) - Remove an index
execute(sql) - Execute raw SQL
announce(message) - Output a message during migration
Notes
Migration files are created in /db/migrate/ or your configured migration path
The timestamp ensures migrations run in the correct order
Always test migrations in development before production
Keep migrations focused on a single purpose
Related Commands
wheels dbmigrate create table
Generate a migration file for creating a new database table.
Synopsis
Description
The dbmigrate create table command generates a migration file that creates a new database table with specified columns. It automatically includes timestamp columns (createdAt, updatedAt) and provides a complete table structure following CFWheels conventions.
Arguments
<table_name>
Type: String
Required: Yes
Description: The name of the table to create (singular form recommended)
[columns...]
Type: String (multiple)
Required: No
Format:name:type:options
Description: Column definitions in the format name:type:options
Options
--id
Type: String
Default:id
Description: Name of the primary key column (use --no-id to skip)
--no-id
Type: Boolean
Default:false
Description: Skip creating a primary key column
--timestamps
Type: Boolean
Default:true
Description: Include createdAt and updatedAt columns
--no-timestamps
Type: Boolean
Default:false
Description: Skip creating timestamp columns
--datasource
Type: String
Default: Application default
Description: Target datasource for the migration
--force
Type: Boolean
Default:false
Description: Overwrite existing migration file
Column Types
Supported column types:
string - VARCHAR(255)
text - TEXT/CLOB
integer - INTEGER
biginteger - BIGINT
float - FLOAT
decimal - DECIMAL
boolean - BOOLEAN/BIT
date - DATE
time - TIME
datetime - DATETIME/TIMESTAMP
timestamp - TIMESTAMP
binary - BLOB/BINARY
Column Options
Column options are specified after the type with colons:
:null - Allow NULL values
:default=value - Set default value
:limit=n - Set column length/size
:precision=n - Set decimal precision
:scale=n - Set decimal scale
Examples
Create a basic table
Create table with columns
Create table with column options
Create table without timestamps
Create join table without primary key
Generated Migration Example
For the command:
Generates:
Use Cases
Standard Entity Table
Create a typical entity table:
Join Table for Many-to-Many
Create a join table for relationships:
Configuration Table
Create a settings/configuration table:
Audit Log Table
Create an audit trail table:
Best Practices
1. Use Singular Table Names
CFWheels conventions expect singular table names:
2. Include Foreign Keys
Add foreign key columns for relationships:
3. Set Appropriate Defaults
Provide sensible defaults where applicable:
4. Consider Indexes
Plan for indexes (add them in separate migrations):
Advanced Options
Custom Primary Key
Specify a custom primary key name:
Composite Keys
For composite primary keys, use blank migration:
Notes
Table names should follow your database naming conventions
The migration automatically handles rollback with dropTable()
Column order in the command is preserved in the migration
Use wheels dbmigrate up to run the generated migration
Related Commands
wheels dbmigrate exec
Execute a specific database migration by version number.
Synopsis
Description
The dbmigrate exec command allows you to run a specific migration identified by its version number, regardless of the current migration state. This is useful for applying individual migrations out of sequence during development or for special maintenance operations.
Arguments
<version>
Type: String
Required: Yes
Description: The version number of the migration to execute (e.g., 20240115123456)
Options
--env
Type: String
Default:development
Description: The environment to run the migration in
--datasource
Type: String
Default: Application default
Description: Specify a custom datasource for the migration
--direction
Type: String
Default:up
Values:up, down
Description: Direction to run the migration (up or down)
--force
Type: Boolean
Default:false
Description: Force execution even if migration is already run
--verbose
Type: Boolean
Default:false
Description: Display detailed output during execution
--dry-run
Type: Boolean
Default:false
Description: Preview the migration without executing it
Examples
Execute a specific migration
Execute migration's down method
Force re-execution of a migration
Execute in production environment
Preview migration execution
Use Cases
Applying Hotfix Migrations
Apply a critical fix out of sequence:
Re-running Failed Migrations
When a migration partially fails:
Testing Specific Migrations
Test individual migrations during development:
Selective Migration Application
Apply only certain migrations from a set:
Important Considerations
Migration Order
Executing migrations out of order can cause issues if migrations have dependencies. Always ensure that any required preceding migrations have been run.
Version Tracking
The command updates the migration tracking table to reflect the execution status. Using --force will update the timestamp of execution.
Down Direction
When running with --direction=down, the migration must have already been executed (unless --force is used).
Best Practices
Check Dependencies: Ensure required migrations are already applied
Test First: Run in development/testing before production
Use Sparingly: Prefer normal migration flow with up/latest
Document Usage: Record when and why specific executions were done
Verify State: Check migration status before and after execution
Version Number Format
Migration versions are typically timestamps in the format:
YYYYMMDDHHmmss (e.g., 20240115123456)
Year: 2024
Month: 01
Day: 15
Hour: 12
Minute: 34
Second: 56
Notes
The migration file must exist in the migrations directory
Using --force bypasses normal safety checks
The command will fail if the migration file has syntax errors
Both up() and down() methods should be defined in the migration
Related Commands
wheels dbmigrate reset
Reset all database migrations by rolling back all executed migrations and optionally re-running them.
Synopsis
Description
The dbmigrate reset command provides a way to completely reset your database migrations. It rolls back all executed migrations in reverse order and can optionally re-run them all. This is particularly useful during development when you need to start fresh or when testing migration sequences.
Options
--env
Type: String
Default:development
Description: The environment to reset migrations in
--datasource
Type: String
Default: Application default
Description: Specify a custom datasource for the reset
--remigrate
Type: Boolean
Default:false
Description: After rolling back all migrations, run them all again
--verbose
Type: Boolean
Default:false
Description: Display detailed output during reset
--force
Type: Boolean
Default:false
Description: Skip confirmation prompts (use with caution)
--dry-run
Type: Boolean
Default:false
Description: Preview the reset without executing it
Examples
Reset all migrations (rollback only)
Reset and re-run all migrations
Reset in testing environment with verbose output
Force reset without confirmation
Preview reset operation
Use Cases
Fresh Development Database
Start with a clean slate during development:
Testing Migration Sequence
Verify that all migrations run correctly from scratch:
Fixing Migration Order Issues
When migrations have dependency problems:
Continuous Integration Setup
Reset database for each test run:
Important Warnings
Data Loss
WARNING: This command will result in complete data loss as it rolls back all migrations. Always ensure you have proper backups before running this command, especially in production environments.
Production Usage
Using this command in production is strongly discouraged. If you must use it in production:
Take a complete database backup
Put the application in maintenance mode
Use the confirmation prompts (don't use --force)
Have a rollback plan ready
Migration Dependencies
The reset process rolls back migrations in reverse chronological order. Ensure all your down() methods are properly implemented.
Best Practices
Development Only: Primarily use this command in development environments
Backup First: Always backup your database before resetting
Test Down Methods: Ensure all migrations have working down() methods
Use Confirmation: Don't use --force unless in automated environments
Document Usage: If used in production, document when and why
Process Flow
Confirms the operation (unless --force is used)
Retrieves all executed migrations
Rolls back each migration in reverse order
Clears the migration tracking table
If --remigrate is specified, runs all migrations again
Notes
The command will fail if any migration's down() method fails
Migration files must still exist for rollback to work
The migration tracking table itself is preserved
Use wheels dbmigrate info after reset to verify status
Related Commands
wheels dbmigrate remove table
Generate a migration file for dropping a database table.
Synopsis
Description
The dbmigrate remove table command generates a migration file that drops an existing database table. The generated migration includes both the drop operation and a reversible up method that recreates the table structure, making the migration fully reversible.
Arguments
<table_name>
Type: String
Required: Yes
Description: The name of the table to drop
Options
--datasource
Type: String
Default: Application default
Description: Target datasource for the migration
--force
Type: Boolean
Default:false
Description: Skip safety prompts and generate migration immediately
--no-backup
Type: Boolean
Default:false
Description: Don't include table structure backup in the down() method
--cascade
Type: Boolean
Default:false
Description: Include CASCADE option to drop dependent objects
Examples
Basic table removal
Remove table with cascade
Force removal without prompts
Remove without backup structure
Generated Migration Example
For the command:
Generates:
Use Cases
Removing Temporary Tables
Clean up temporary or staging tables:
Refactoring Database Schema
Remove tables during schema refactoring:
Cleaning Up Failed Features
Remove tables from cancelled features:
Archive Table Cleanup
Remove old archive tables:
Safety Considerations
Data Loss Warning
CRITICAL: Dropping a table permanently deletes all data. Always:
Backup the table data before removal
Verify data has been migrated if needed
Test in development/staging first
Have a rollback plan
Dependent Objects
Consider objects that depend on the table:
Foreign key constraints
Views
Stored procedures
Triggers
Application code
Using CASCADE
The --cascade option drops dependent objects:
Best Practices
1. Document Removals
Add clear documentation about why the table is being removed:
2. Backup Data First
Before removing tables, create data backups:
3. Staged Removal
For production systems, consider staged removal:
4. Check Dependencies
Verify no active dependencies before removal:
Migration Structure Details
With Backup (Default)
The generated down() method includes table structure:
Without Backup
With --no-backup, down() is simpler:
Recovery Strategies
If Removal Was Mistake
Don't run the migration in production
Use wheels dbmigrate down if already run
Restore from backup if down() fails
Preserving Table Structure
Before removal, capture structure:
Notes
The command analyzes table structure before generating migration
Foreign key constraints must be removed before table removal
The migration is reversible if table structure is preserved
Always review generated migration before running
Related Commands
wheels dbmigrate create column
Generate a migration file for adding columns to an existing database table.
Synopsis
Description
The dbmigrate create column command generates a migration file that adds one or more columns to an existing database table. It supports all standard column types and options, making it easy to evolve your database schema incrementally.
Arguments
<table_name>
Type: String
Required: Yes
Description: The name of the table to add columns to
<column_name>:<type>[:options]
Type: String
Required: Yes (at least one)
Format:name:type:option1:option2=value
Description: Column definition(s) to add
Options
--datasource
Type: String
Default: Application default
Description: Target datasource for the migration
--after
Type: String
Default: None
Description: Position new column(s) after specified column
--force
Type: Boolean
Default:false
Description: Overwrite existing migration file
Column Types
string - VARCHAR(255)
text - TEXT/CLOB
integer - INTEGER
biginteger - BIGINT
float - FLOAT
decimal - DECIMAL
boolean - BOOLEAN/BIT
date - DATE
time - TIME
datetime - DATETIME/TIMESTAMP
timestamp - TIMESTAMP
binary - BLOB/BINARY
Column Options
:null - Allow NULL values
:default=value - Set default value
:limit=n - Set column length
:precision=n - Set decimal precision
:scale=n - Set decimal scale
:index - Create an index on this column
:unique - Add unique constraint
Examples
Add a single column
Add multiple columns
Add column with positioning
Add columns with indexes
Generated Migration Example
For the command:
Generates:
Use Cases
Adding User Preferences
Add preference columns to user table:
Adding Audit Fields
Add tracking columns to any table:
Adding Calculated Fields
Add columns for denormalized/cached data:
Adding Search Columns
Add columns optimized for searching:
Best Practices
1. Consider NULL Values
For existing tables with data, make new columns nullable or provide defaults:
2. Use Appropriate Types
Choose the right column type for your data:
3. Plan for Indexes
Add indexes for columns used in queries:
4. Group Related Changes
Add related columns in a single migration:
Advanced Scenarios
Adding Foreign Keys
Add foreign key columns with appropriate types:
Adding JSON Columns
For databases that support JSON:
Positional Columns
Control column order in table:
Common Pitfalls
1. Non-Nullable Without Default
2. Changing Column Types
This command adds columns, not modifies them:
Notes
The migration includes automatic rollback with removeColumn()
Column order in down() is reversed for proper rollback
component extends="wheels.migrator.Migration" hint="<description>" {
function up() {
transaction {
// Add your migration code here
}
}
function down() {
transaction {
// Add code to reverse the migration
}
}
}
# Create migration for custom stored procedure
wheels dbmigrate create blank --name=create_reporting_procedures
# Edit the file to add:
# - CREATE PROCEDURE statements
# - Complex SQL operations
# - Multiple related changes
# Create data migration
wheels dbmigrate create blank --name=normalize_user_emails
# Edit to add data transformation logic
# Example: lowercase all email addresses
# Create complex migration
wheels dbmigrate create blank --name=refactor_order_system
# Edit to include:
# - Create new tables
# - Migrate data
# - Drop old tables
# - Update foreign keys
# Create migration for PostgreSQL-specific features
wheels dbmigrate create blank --name=add_json_columns
# Edit to use PostgreSQL JSON operations
# Good
wheels dbmigrate create blank --name=add_user_authentication_tokens
# Bad
wheels dbmigrate create blank --name=update1
function up() {
transaction {
execute("CREATE INDEX idx_users_email ON users(email)");
}
}
function down() {
transaction {
execute("DROP INDEX idx_users_email");
}
}
function up() {
transaction {
// All operations succeed or all fail
createTable("new_table");
execute("INSERT INTO new_table SELECT * FROM old_table");
dropTable("old_table");
}
}
function up() {
transaction {
// Create composite index for query optimization
// This supports the findActiveUsersByRegion() query
execute("
CREATE INDEX idx_users_active_region
ON users(is_active, region_id)
WHERE is_active = 1
");
}
}
# Check migration status
wheels dbmigrate info
# Re-run the failed migration
wheels dbmigrate exec 20240115123456 --force --verbose
# Run specific migration up
wheels dbmigrate exec 20240115123456
# Test the changes
# Run it down
wheels dbmigrate exec 20240115123456 --direction=down
# List all migrations
wheels dbmigrate info
# Execute only the ones you need
wheels dbmigrate exec 20240115123456
wheels dbmigrate exec 20240115134567
wheels dbmigrate reset [options]
wheels dbmigrate reset
wheels dbmigrate reset --remigrate
wheels dbmigrate reset --env=testing --verbose
wheels dbmigrate reset --force --remigrate
wheels dbmigrate reset --dry-run --verbose
# Reset and rebuild database schema
wheels dbmigrate reset --remigrate --verbose
# Seed with test data
wheels db seed
# Reset all migrations
wheels dbmigrate reset
# Run migrations one by one to test
wheels dbmigrate up
wheels dbmigrate up
# ... continue as needed
# Reset all migrations
wheels dbmigrate reset
# Manually fix migration files
# Re-run all migrations
wheels dbmigrate latest
# CI script
wheels dbmigrate reset --env=testing --force --remigrate
wheels test run --env=testing
-- Check foreign keys
SELECT * FROM information_schema.referential_constraints
WHERE referenced_table_name = 'table_name';
-- Check views
SELECT * FROM information_schema.views
WHERE table_schema = DATABASE()
AND view_definition LIKE '%table_name%';
# Good - nullable
wheels dbmigrate create column user bio:text:null
# Good - with default
wheels dbmigrate create column user status:string:default='active'
# Bad - will fail if table has data
wheels dbmigrate create column user required_field:string
# For short text
wheels dbmigrate create column user username:string:limit=50
# For long text
wheels dbmigrate create column post content:text
# For money
wheels dbmigrate create column invoice amount:decimal:precision=10:scale=2
# Add indexed column
wheels dbmigrate create column order customer_email:string:index
# Or create separate index migration
wheels dbmigrate create blank --name=add_order_customer_email_index
# Add foreign key column
wheels dbmigrate create column order customer_id:integer:index
# Then create constraint in blank migration
wheels dbmigrate create blank --name=add_order_customer_foreign_key
# Create blank migration for JSON column
wheels dbmigrate create blank --name=add_user_preferences_json
# Then manually add JSON column type
# Add after specific column
wheels dbmigrate create column user display_name:string --after=username
# This will fail if table has data
wheels dbmigrate create column user required_field:string
# Do this instead
wheels dbmigrate create column user required_field:string:default='TBD'
# Wrong - trying to change type
wheels dbmigrate create column user age:integer
# Right - use blank migration for modifications
wheels dbmigrate create blank --name=change_user_age_to_integer
Generate code coverage reports for your test suite.
Synopsis
wheels test coverage [options]
Description
The wheels test coverage command runs your test suite while collecting code coverage metrics. It generates detailed reports showing which parts of your code are tested and identifies areas that need more test coverage.
Options
Option
Description
Default
--bundles
Specific test bundles to include
All bundles
--specs
Specific test specs to run
All specs
--format
Report format (html, json, xml, console)
html
--output
Output directory for reports
./coverage
--threshold
Minimum coverage percentage required
0
--include
Paths to include in coverage
app/**
--exclude
Paths to exclude from coverage
tests/**,vendor/**
--fail-on-low
Fail if coverage below threshold
false
--open
Open HTML report after generation
true
--verbose
Show detailed output
false
--help
Show help information
Examples
Generate basic coverage report
wheels test coverage
Set coverage threshold
wheels test coverage --threshold=80 --fail-on-low
Generate multiple formats
wheels test coverage --format=html,json,xml
Coverage for specific bundles
wheels test coverage --bundles=models,controllers
Custom output directory
wheels test coverage --output=./reports/coverage
Exclude specific paths
wheels test coverage --exclude="app/legacy/**,app/temp/**"
What It Does
Instruments Code: Adds coverage tracking to your application
Runs Tests: Executes all specified tests
Collects Metrics: Tracks which lines are executed
Generates Reports: Creates coverage reports in requested formats
Analyzes Results: Provides insights and recommendations
Focus on Quality: 100% coverage doesn't mean bug-free
Test Business Logic: Prioritize critical code
Regular Monitoring: Track coverage trends
Performance Considerations
Coverage collection adds overhead:
Slower test execution
Increased memory usage
Larger test artifacts
Tips:
Run coverage in CI/CD, not every test run
Use incremental coverage for faster feedback
Exclude third-party code
Troubleshooting
Low Coverage
Check if tests are actually running
Verify include/exclude patterns
Look for untested files
Coverage Not Collected
Ensure code is instrumented
Check file path patterns
Verify test execution
Report Generation Failed
Check output directory permissions
Verify report format support
Review error logs
Advanced Usage
Incremental Coverage
# Coverage for changed files only
wheels test coverage --since=HEAD~1
Coverage Trends
# Generate trend data
wheels test coverage --save-baseline
# Compare with baseline
wheels test coverage --compare-baseline
Merge Coverage
# From multiple test runs
wheels test coverage --merge coverage1.json coverage2.json
Notes
Coverage data is collected during test execution
Some code may be unreachable and shouldn't count
Focus on meaningful coverage, not just percentages
Different metrics provide different insights
See Also
wheels test
Run Wheels framework tests (core, app, or plugin tests).
Synopsis
wheels test [type] [servername] [options]
Description
The wheels test command runs the built-in Wheels framework test suite. This is different from wheels test run which runs your application's TestBox tests. Use this command to verify framework integrity or test Wheels plugins.
Arguments
Argument
Description
Default
type
Test type: core, app, or plugin
app
servername
CommandBox server name
Current server
Options
Option
Description
Default
--reload
Reload before running tests
true
--debug
Show debug output
false
--format
Output format
simple
--adapter
Test adapter
testbox
--help
Show help information
Test Types
Core Tests
wheels test core
Tests Wheels framework functionality
Verifies framework integrity
Useful after framework updates
App Tests
wheels test app
Runs application-level framework tests
Tests Wheels configuration
Verifies app-specific framework features
Plugin Tests
wheels test plugin
Tests installed Wheels plugins
Verifies plugin compatibility
Checks plugin functionality
Examples
Run app tests (default)
wheels test
Run core framework tests
wheels test core
Run tests on specific server
wheels test app myserver
Run with debug output
wheels test --debug
Skip reload
wheels test --reload=false
Output Example
╔═══════════════════════════════════════════════╗
║ Running Wheels Tests ║
╚═══════════════════════════════════════════════╝
Test Type: app
Server: default
Reloading: Yes
Initializing test environment...
✓ Environment ready
Running tests...
Model Tests
✓ validations work correctly (15ms)
✓ associations load properly (23ms)
✓ callbacks execute in order (8ms)
Controller Tests
✓ filters apply correctly (12ms)
✓ caching works as expected (45ms)
✓ provides correct formats (5ms)
View Tests
✓ helpers render correctly (18ms)
✓ partials include properly (9ms)
✓ layouts apply correctly (11ms)
Plugin Tests
✓ DBMigrate plugin loads (7ms)
✓ Scaffold plugin works (22ms)
╔═══════════════════════════════════════════════╗
║ Test Summary ║
╚═══════════════════════════════════════════════╝
Total Tests: 11
Passed: 11
Failed: 0
Errors: 0
Time: 173ms
✓ All tests passed!
# Check server is running
box server status
# Verify test URL
curl http://localhost:3000/wheels/tests
Reload issues
# Manual reload first
wheels reload
# Then run tests
wheels test --reload=false
Memory issues
# Increase heap size
box server set jvm.heapSize=512
box server restart
Best Practices
Run before deployment
Test after framework updates
Verify plugin compatibility
Use CI/CD integration
Keep test database clean
Difference from TestBox Tests
Feature
wheels test
wheels test run
Purpose
Framework tests
Application tests
Framework
Wheels Test
TestBox
Location
/wheels/tests/
/tests/
Use Case
Framework integrity
App functionality
See Also
wheels reload - Reload application
wheels db schema
Export and import database schema definitions.
Synopsis
wheels db schema [command] [options]
Description
The db schema command provides tools for exporting database schemas to files and importing them back. This is useful for version control, documentation, sharing database structures, and setting up new environments.
Subcommands
export
Export database schema to a file
import
Import database schema from a file
diff
Compare two schema files or database states
validate
Validate a schema file without importing
Global Options
--env
Type: String
Default:development
Description: The environment to work with
--datasource
Type: String
Default: Application default
Description: Specific datasource to use
--format
Type: String
Default:sql
Options:sql, json, xml, cfm
Description: Output format for schema
Export Command
Synopsis
wheels db schema export [options]
Options
--output
Type: String
Default:db/schema.sql
Description: Output file path
--tables
Type: String
Default: All tables
Description: Comma-separated list of tables to export
--no-data
Type: Boolean
Default:true
Description: Export structure only, no data
--include-drops
Type: Boolean
Default:false
Description: Include DROP statements
--include-indexes
Type: Boolean
Default:true
Description: Include index definitions
--include-constraints
Type: Boolean
Default:true
Description: Include foreign key constraints
Examples
# Export entire schema
wheels db schema export
# Export specific tables
wheels db schema export --tables=user,order,product
# Export as JSON
wheels db schema export --format=json --output=db/schema.json
# Export with DROP statements
wheels db schema export --include-drops --output=db/recreate-schema.sql
Import Command
Synopsis
wheels db schema import [options]
Options
--input
Type: String
Default:db/schema.sql
Description: Input file path
--dry-run
Type: Boolean
Default:false
Description: Preview import without executing
--force
Type: Boolean
Default:false
Description: Drop existing objects before import
--skip-errors
Type: Boolean
Default:false
Description: Continue on errors
Examples
# Import schema
wheels db schema import
# Import from specific file
wheels db schema import --input=db/production-schema.sql
# Preview import
wheels db schema import --dry-run
# Force recreate schema
wheels db schema import --force
Diff Command
Synopsis
wheels db schema diff [source] [target] [options]
Options
--output
Type: String
Default: Console output
Description: Save diff to file
--format
Type: String
Default:text
Options:text, sql, html
Description: Diff output format
Examples
# Compare dev and production
wheels db schema diff --env=development --env=production
# Compare schema files
wheels db schema diff db/schema-v1.sql db/schema-v2.sql
# Generate migration SQL
wheels db schema diff --env=development --env=production --format=sql
# Clone repository
git clone repo-url
# Import schema
wheels db schema import
# Run any pending migrations
wheels dbmigrate latest
Database Documentation
Generate schema documentation:
# Export as JSON for documentation tools
wheels db schema export --format=json --output=docs/database-schema.json
# Export with comments
wheels db schema export --include-comments --output=docs/schema-annotated.sql
Continuous Integration
Validate schema in CI:
# Export current schema
wheels db schema export --output=current-schema.sql
# Compare with committed schema
wheels db schema diff db/schema.sql current-schema.sql
Backup and Recovery
Create schema backups:
# Backup before major changes
wheels db schema export --output=backups/schema-$(date +%Y%m%d).sql
# Include drops for full recreate
wheels db schema export --include-drops --output=backups/recreate-$(date +%Y%m%d).sql
Schema File Formats
SQL Format (Default)
-- Generated by CFWheels
-- Date: 2024-01-15 10:30:00
CREATE TABLE user (
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL,
email VARCHAR(150) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
CREATE UNIQUE INDEX idx_user_email ON user(email);
# In deployment script
wheels dbmigrate latest
wheels db schema export
git add db/schema.sql
git commit -m "Update schema after migrations"
2. Environment Comparison
Regularly compare environments:
# Weekly check
wheels db schema diff --env=development --env=production
3. Schema Validation
Validate before deployment:
# In CI pipeline
wheels db schema validate --input=db/schema.sql
4. Backup Strategy
Maintain schema history:
# Before major updates
wheels db schema export --output=db/history/schema-$(git rev-parse --short HEAD).sql
Integration with Migrations
Schema vs Migrations
Migrations: Incremental changes
Schema: Current state snapshot
Workflow
Run migrations: wheels dbmigrate latest
Export schema: wheels db schema export
Commit both: git add db/migrate/* db/schema.sql
Notes
Schema export captures current database state
Some database-specific features may not export perfectly
Always review exported schemas before importing
Use migrations for incremental changes, schemas for full setup
Related Commands
wheels db seed
Populate the database with seed data for development and testing.
Synopsis
wheels db seed [options]
Description
The db seed command populates your database with predefined data sets. This is essential for development environments, testing scenarios, and demo installations. Seed data provides a consistent starting point for application development and testing.
Options
--env
Type: String
Default:development
Description: Environment to seed (development, testing, staging)
--file
Type: String
Default:db/seeds.cfm
Description: Path to seed file or directory
--datasource
Type: String
Default: Application default
Description: Specific datasource to seed
--clean
Type: Boolean
Default:false
Description: Clear existing data before seeding
--only
Type: String
Default: All seeds
Description: Run only specific seed files (comma-separated)
--except
Type: String
Default: None
Description: Skip specific seed files (comma-separated)
--verbose
Type: Boolean
Default:false
Description: Show detailed output during seeding
--dry-run
Type: Boolean
Default:false
Description: Preview seed operations without executing
Examples
Basic seeding
# Run default seeds
wheels db seed
# Seed specific environment
wheels db seed --env=testing
Clean and seed
# Clear data and reseed
wheels db seed --clean
# Clean seed with confirmation
wheels db seed --clean --verbose
Selective seeding
# Run specific seeds
wheels db seed --only=users,products
# Skip certain seeds
wheels db seed --except=large_dataset,temporary_data
Custom seed files
# Use custom seed file
wheels db seed --file=db/seeds/demo_data.cfm
# Use seed directory
wheels db seed --file=db/seeds/development/
Seed File Structure
Basic Seed File (db/seeds.cfm)
<cfscript>
// db/seeds.cfm
component extends="wheels.Seeder" {
function run() {
// Create admin user
user = model("user").create(
username = "admin",
email = "admin@example.com",
password = "password123",
role = "admin"
);
// Create sample categories
categories = [
{name: "Electronics", slug: "electronics"},
{name: "Books", slug: "books"},
{name: "Clothing", slug: "clothing"}
];
for (category in categories) {
model("category").create(category);
}
// Create sample products
electronicsCategory = model("category").findOne(where="slug='electronics'");
products = [
{
name: "Laptop",
price: 999.99,
category_id: electronicsCategory.id,
in_stock: true
},
{
name: "Smartphone",
price: 699.99,
category_id: electronicsCategory.id,
in_stock: true
}
];
for (product in products) {
model("product").create(product);
}
announce("Seed data created successfully!");
}
function clean() {
// Clean in reverse order of dependencies
model("product").deleteAll();
model("category").deleteAll();
model("user").deleteAll();
announce("Database cleaned!");
}
}
</cfscript>
Modular Seed Files
// db/seeds/users.cfm
component extends="wheels.Seeder" {
function run() {
// Admin users
createAdminUsers();
// Regular users
createSampleUsers(count=50);
// Test users
createTestUsers();
}
private function createAdminUsers() {
admins = [
{username: "admin", email: "admin@example.com", role: "admin"},
{username: "moderator", email: "mod@example.com", role: "moderator"}
];
for (admin in admins) {
admin.password = hash("password123");
model("user").create(admin);
}
}
private function createSampleUsers(required numeric count) {
for (i = 1; i <= arguments.count; i++) {
model("user").create(
username = "user#i#",
email = "user#i#@example.com",
password = hash("password123"),
created_at = dateAdd("d", -randRange(1, 365), now())
);
}
}
}
Use Cases
Development Environment Setup
Create consistent development data:
# Reset and seed development database
wheels dbmigrate reset --remigrate
wheels db seed --clean
Testing Data
Prepare test database:
# Seed test environment
wheels db seed --env=testing --clean
# Run tests
wheels test run
Demo Data
Create demonstration data:
# Load demo dataset
wheels db seed --file=db/seeds/demo.cfm --clean
Performance Testing
Generate large datasets:
# Create performance test data
wheels db seed --file=db/seeds/performance_test.cfm
Advanced Seeding Patterns
Faker Integration
component extends="wheels.Seeder" {
function run() {
faker = new lib.Faker();
// Generate realistic data
for (i = 1; i <= 100; i++) {
model("customer").create(
first_name = faker.firstName(),
last_name = faker.lastName(),
email = faker.email(),
phone = faker.phoneNumber(),
address = faker.streetAddress(),
city = faker.city(),
state = faker.state(),
zip = faker.zipCode()
);
}
}
}
Relationship Seeding
component extends="wheels.Seeder" {
function run() {
// Create users
users = [];
for (i = 1; i <= 10; i++) {
users.append(model("user").create(
username = "user#i#",
email = "user#i#@example.com"
));
}
// Create posts for each user
for (user in users) {
postCount = randRange(5, 15);
for (j = 1; j <= postCount; j++) {
post = model("post").create(
user_id = user.id,
title = "Post #j# by #user.username#",
content = generateContent(),
published_at = dateAdd("d", -randRange(1, 30), now())
);
// Add comments
addCommentsToPost(post, users);
}
}
}
private function addCommentsToPost(post, users) {
commentCount = randRange(0, 10);
for (i = 1; i <= commentCount; i++) {
randomUser = users[randRange(1, arrayLen(users))];
model("comment").create(
post_id = post.id,
user_id = randomUser.id,
content = "Comment #i# on post",
created_at = dateAdd("h", i, post.published_at)
);
}
}
}
Conditional Seeding
component extends="wheels.Seeder" {
function run() {
// Only seed if empty
if (model("user").count() == 0) {
seedUsers();
}
// Environment-specific seeding
if (application.environment == "development") {
seedDevelopmentData();
} else if (application.environment == "staging") {
seedStagingData();
}
}
}
Best Practices
1. Idempotent Seeds
Make seeds safe to run multiple times:
function run() {
// Check before creating
if (!model("user").exists(username="admin")) {
model("user").create(
username = "admin",
email = "admin@example.com"
);
}
}
function run() {
try {
user = model("user").create(data);
if (user.hasErrors()) {
announce("Failed to create user: #user.allErrors()#");
}
} catch (any e) {
announce("Error: #e.message#", "error");
}
}
Dependency Handling
function run() {
// Check dependencies
if (model("category").count() == 0) {
throw("Categories must be seeded first!");
}
// Continue with seeding
}
Notes
Seed files are typically not run in production
Always use transactions for data integrity
Consider performance for large seed operations
Keep seed data realistic and useful
Related Commands
wheels test debug
Debug test execution with detailed diagnostics and troubleshooting tools.
Synopsis
wheels test debug [spec] [options]
Description
The wheels test debug command provides advanced debugging capabilities for your test suite. It helps identify why tests are failing, diagnose test environment issues, and provides detailed execution traces for troubleshooting complex test problems.
Arguments
Argument
Description
Default
spec
Specific test spec to debug
All tests
Options
Option
Description
Default
--bundles
Test bundles to debug
All bundles
--labels
Filter by test labels
None
--breakpoint
Set breakpoint at test
None
--step
Step through test execution
false
--trace
Show execution trace
false
--verbose
Verbose output level (1-3)
1
--slow
Highlight slow tests (ms)
1000
--dump-context
Dump test context
false
--inspect
Enable remote debugging
false
--port
Debug port for inspection
9229
--pause-on-failure
Pause when test fails
false
--replay
Replay failed tests
false
--help
Show help information
Examples
Debug specific test
wheels test debug UserModelTest
Debug with execution trace
wheels test debug --trace
Step through test execution
wheels test debug UserModelTest.testValidation --step
Debug with breakpoints
wheels test debug --breakpoint=UserModelTest.testLogin:15
Enable remote debugging
wheels test debug --inspect --port=9229
Debug slow tests
wheels test debug --slow=500 --verbose=2
Debug Output
Basic Debug Info
🔍 Test Debug Session Started
================================
Environment: testing
Debug Level: 1
Test Framework: TestBox 5.0.0
CFML Engine: Lucee 5.3.9.141
Running: UserModelTest.testValidation
Status: RUNNING
[DEBUG] Setting up test case...
[DEBUG] Creating test user instance
[DEBUG] Validating empty user
[DEBUG] Assertion: user.hasErrors() = true ✓
[DEBUG] Test completed in 45ms
Verbose Trace Output
With --trace --verbose=3:
🔍 Test Execution Trace
======================
▶ UserModelTest.setup()
└─ [0.5ms] Creating test database transaction
└─ [1.2ms] Loading test fixtures
└─ [0.3ms] Initializing test context
▶ UserModelTest.testValidation()
├─ [0.1ms] var user = model("User").new()
│ └─ [2.1ms] Model instantiation
│ └─ [0.5ms] Property initialization
├─ [0.2ms] user.validate()
│ └─ [5.3ms] Running validations
│ ├─ [1.2ms] Checking required fields
│ ├─ [2.1ms] Email format validation
│ └─ [2.0ms] Custom validations
├─ [0.1ms] expect(user.hasErrors()).toBe(true)
│ └─ [0.3ms] Assertion passed ✓
└─ [0.1ms] Test completed
Total Time: 10.2ms
Memory Used: 2.3MB
Interactive Debugging
Step Mode
With --step:
▶ Entering step mode for UserModelTest.testLogin
[1] user = model("User").findOne(where="email='test@example.com'")
> (n)ext, (s)tep into, (c)ontinue, (v)ariables, (q)uit: v
Variables:
- arguments: {}
- local: { user: [undefined] }
- this: UserModelTest instance
> n
[2] expect(user.authenticate("password123")).toBe(true)
> v
Variables:
- arguments: {}
- local: { user: User instance {id: 1, email: "test@example.com"} }
> s
[2.1] Entering: user.authenticate("password123")
Parameters: { password: "password123" }
Breakpoints
Set breakpoints in code:
// In test file
function testComplexLogic() {
var result = complexCalculation(data);
debugBreak(); // Execution pauses here
expect(result).toBe(expectedValue);
}
Or via command line:
wheels test debug --breakpoint=OrderTest.testCalculateTotal:25
✗ Test Failed: UserModelTest.testUniqueEmail
Test paused at failure point.
Failure Details:
- Expected: true
- Actual: false
- Location: UserModelTest.cfc:45
Debug Options:
(i) Inspect variables
(s) Show stack trace
(d) Dump database state
(r) Retry test
(c) Continue
(q) Quit
> i
Local Variables:
- user1: User { email: "test@example.com", id: 1 }
- user2: User { email: "test@example.com", errors: ["Email already exists"] }
Stack Trace Analysis
Stack Trace:
-----------
1. TestBox.expectation.toBe() at TestBox/system/Expectation.cfc:123
2. UserModelTest.testUniqueEmail() at tests/models/UserModelTest.cfc:45
3. TestBox.runTest() at TestBox/system/BaseSpec.cfc:456
4. Model.validate() at wheels/Model.cfc:789
5. Model.validatesUniquenessOf() at wheels/Model.cfc:1234
Test Replay
Replay Failed Tests
wheels test debug --replay
Replays last failed tests with debug info:
Replaying 3 failed tests from last run...
1/3 UserModelTest.testValidation
- Original failure: Assertion failed at line 23
- Replay status: PASSED ✓
- Possible flaky test
2/3 OrderControllerTest.testCheckout
- Original failure: Database connection timeout
- Replay status: FAILED ✗
- Consistent failure
# Run only the failing test
wheels test debug UserModelTest.testValidation --trace
2. Check Test Environment
# Dump environment and context
wheels test debug --dump-context > test-context.txt
3. Step Through Execution
# Interactive debugging
wheels test debug FailingTest --step --pause-on-failure
4. Compare Working vs Failing
# Debug working test
wheels test debug WorkingTest --trace > working.log
# Debug failing test
wheels test debug FailingTest --trace > failing.log
# Compare outputs
diff working.log failing.log
Common Issues
Test Pollution
Debug test isolation:
wheels test debug --trace --verbose=3 | grep -E "(setup|teardown|transaction)"
Race Conditions
Debug timing issues:
wheels test debug --slow=100 --trace
Database State
wheels test debug --dump-context | grep -A 20 "Database state"
Best Practices
Start Simple: Use basic debug before advanced options
Isolate Issues: Debug one test at a time
Use Breakpoints: Strategic breakpoints save time
Check Environment: Ensure test environment is correct
Save Debug Logs: Keep logs for complex issues
Notes
Debug mode affects test performance
Some features require specific CFML engine support
Remote debugging requires network access
Verbose output can be overwhelming - filter as needed
See Also
wheels env setup
Setup a new environment configuration for your Wheels application.
Synopsis
wheels env setup [name] [options]
Description
The wheels env setup command creates and configures new environments for your Wheels application. It generates environment-specific configuration files, database settings, and initializes the environment structure.
Set configuration values for your Wheels application.
Synopsis
wheels config set [key] [value] [options]
Description
The wheels config set command updates configuration settings in your Wheels application. It can modify settings in configuration files, set environment-specific values, and manage runtime configurations.
Arguments
Argument
Description
Required
key
Configuration key to set
Yes
value
Value to set
Yes (unless --delete)
Options
Option
Description
Default
--environment
Target environment
Current
--global
Set globally across all environments
false
--file
Configuration file to update
Auto-detect
--type
Value type (string, number, boolean, json)
Auto-detect
--encrypt
Encrypt sensitive values
Auto for passwords
--delete
Delete the configuration key
false
--force
Overwrite without confirmation
false
--help
Show help information
Examples
Set basic configuration
wheels config set dataSourceName wheels_production
Set with specific type
wheels config set cacheQueries true --type=boolean
wheels config set sessionTimeout 3600 --type=number
Set for specific environment
wheels config set showDebugInformation false --environment=production
Set complex value
wheels config set cacheSettings '{"queries":true,"pages":false}' --type=json
Delete configuration
wheels config set oldSetting --delete
Set encrypted value
wheels config set apiKey sk_live_abc123 --encrypt
Configuration Types
String Values
wheels config set appName "My Wheels App"
wheels config set emailFrom "noreply@example.com"
Boolean Values
wheels config set showDebugInformation true
wheels config set cacheQueries false
Numeric Values
wheels config set sessionTimeout 1800
wheels config set maxUploadSize 10485760
JSON/Complex Values
wheels config set mailSettings '{"server":"smtp.example.com","port":587}'
wheels config set allowedDomains '["example.com","app.example.com"]'
wheels config set environment production
# Also updates: debug settings, cache settings
Environment Variables
Set as Environment Variable
wheels config set WHEELS_DATASOURCE wheels_prod --env-var
Export Format
wheels config set --export-env > .env
Validation Rules
Key Naming
Alphanumeric and underscores
No spaces or special characters
Case-sensitive
Value Constraints
# Validates port range
wheels config set port 80000 --type=number
# Error: Port must be between 1-65535
# Validates boolean
wheels config set cacheQueries maybe --type=boolean
# Error: Value must be true or false
Best Practices
Use Correct Types: Specify type for clarity
Environment-Specific: Don't set production values globally
Encrypt Secrets: Always encrypt sensitive data
Backup First: Create backups before changes
Document Changes: Add comments in config files
Advanced Usage
Conditional Setting
# Set only if not exists
wheels config set apiUrl "https://api.example.com" --if-not-exists
# Set only if current value matches
wheels config set cacheQueries true --if-value=false
Template Variables
wheels config set dbName "wheels_${ENVIRONMENT}" --parse-template
Troubleshooting
Permission Denied
Check file write permissions
Run with appropriate user
Verify directory ownership
Setting Not Taking Effect
Restart application
Clear caches
Check precedence order
Invalid Value
Verify type compatibility
Check for typos
Review validation rules
Integration
CI/CD Pipeline
- name: Configure production
run: |
wheels config set environment production
wheels config set dataSourceName ${{ secrets.DB_NAME }}
wheels config set reloadPassword ${{ secrets.RELOAD_PASS }}
Docker
RUN wheels config set dataSourceName ${DB_NAME} \
&& wheels config set cacheQueries true
Notes
Some settings require application restart
Encrypted values can't be read back
Changes are logged for audit
Use environment variables for containers
See Also
wheels config list
List all configuration settings for your Wheels application.
Synopsis
wheels config list [options]
Description
The wheels config list command displays all configuration settings for your Wheels application. It shows current values, defaults, and helps you understand your application's configuration state.
Options
Option
Description
Default
--filter
Filter settings by name or pattern
Show all
--category
Filter by category (database, cache, security, etc.)
All
--format
Output format (table, json, yaml, env)
table
--show-defaults
Include default values
false
--show-source
Show where setting is defined
false
--environment
Show for specific environment
Current
--verbose
Show detailed information
false
--help
Show help information
Examples
List all settings
wheels config list
Filter by pattern
wheels config list --filter=cache
wheels config list --filter="database*"
# All cache settings
wheels config list --filter=cache*
# Settings containing "database"
wheels config list --filter=*database*
# Specific setting
wheels config list --filter=reloadPassword
By Category
# Database settings only
wheels config list --category=database
# Security settings
wheels config list --category=security
# Multiple categories
wheels config list --category=database,cache
# Compare dev and production
wheels config list --environment=development > dev.json
wheels config list --environment=production > prod.json
diff dev.json prod.json
# Check for changes
wheels config list --format=json > config-current.json
diff config-baseline.json config-current.json
Special Values
Hidden Values
Sensitive settings show as asterisks:
Passwords: ********
Keys: ****...****
Secrets: [hidden]
Complex Values
Arrays: ["item1", "item2"]
Structs: {key: "value"}
Functions: [function]
Best Practices
Regular Audits: Check configuration regularly
Document Changes: Track setting modifications
Environment Parity: Keep environments similar
Secure Secrets: Don't expose sensitive data
Version Control: Track configuration files
Troubleshooting
Missing Settings
Check environment-specific files
Verify file permissions
Look for syntax errors
Incorrect Values
Check source precedence
Verify environment variables
Review recent changes
Notes
Some settings require restart to take effect
Sensitive values are automatically hidden
Custom settings from plugins included
Performance impact minimal
See Also
wheels env list
List all available environments for your Wheels application.
Synopsis
wheels env list [options]
Description
The wheels env list command displays all configured environments in your Wheels application. It shows environment details, current active environment, and configuration status.
Options
Option
Description
Default
--format
Output format (table, json, yaml)
table
--verbose
Show detailed configuration
false
--check
Validate environment configurations
false
--filter
Filter by environment type
All
--sort
Sort by (name, type, modified)
name
--help
Show help information
Examples
List all environments
wheels env list
Show detailed information
wheels env list --verbose
Output as JSON
wheels env list --format=json
Check environment validity
wheels env list --check
Filter production environments
wheels env list --filter=production
Output Example
Basic Output
Available Environments
=====================
NAME TYPE DATABASE STATUS
development * Development wheels_dev ✓ Active
testing Testing wheels_test ✓ Valid
staging Staging wheels_staging ✓ Valid
production Production wheels_prod ✓ Valid
qa Custom wheels_qa ⚠ Issues
* = Current environment
# Production environments only
wheels env list --filter=production
# Development environments
wheels env list --filter=development
By Status
# Valid environments only
wheels env list --filter=valid
# Environments with issues
wheels env list --filter=issues
By Pattern
# Environments containing "prod"
wheels env list --filter="*prod*"
Sorting Options
By Name
wheels env list --sort=name
By Type
wheels env list --sort=type
By Last Modified
wheels env list --sort=modified
Integration
Script Usage
# Get current environment
current=$(wheels env list --format=json | jq -r '.current')
# List all environment names
wheels env list --format=json | jq -r '.environments[].name'
CI/CD Usage
# Verify environment exists
if wheels env list | grep -q "staging"; then
wheels env switch staging
fi
Environment Details
When using --verbose, shows:
Configuration:
Config file path
Last modified date
File size
Database:
Database name
Datasource name
Connection status
Settings:
Debug mode
Cache settings
Custom configurations
Validation:
Syntax check
Connection test
Dependencies
Troubleshooting
No Environments Listed
Check /config/ directory
Verify environment.cfm exists
Run wheels env setup to create
Invalid Environment
Check configuration syntax
Verify database credentials
Test database connection
Missing Current Environment
Check WHEELS_ENV variable
Verify environment.cfm logic
Set environment explicitly
Export Capabilities
Export Configuration
# Export all environments
wheels env list --format=json > environments.json
# Export for documentation
wheels env list --format=markdown > ENVIRONMENTS.md
Environment Comparison
# Compare environments
wheels env list --compare=development,production
The wheels config env command provides specialized tools for managing environment-specific configurations. It helps you create, compare, sync, and validate configurations across different environments.
Actions
Action
Description
show
Display environment configuration
compare
Compare configurations between environments
sync
Synchronize settings between environments
validate
Validate environment configuration
export
Export environment configuration
import
Import environment configuration
Arguments
Argument
Description
Required
action
Action to perform
Yes
environment
Target environment name
Depends on action
Options
Option
Description
Default
--format
Output format (table, json, yaml, diff)
table
--output
Output file path
Console
--filter
Filter settings by pattern
Show all
--include-defaults
Include default values
false
--safe
Hide sensitive values
true
--force
Force operation without confirmation
false
--help
Show help information
Examples
Show environment config
wheels config env show production
Compare environments
wheels config env compare development production
Sync configurations
wheels config env sync development staging
Validate configuration
wheels config env validate production
Export configuration
wheels config env export production --format=json > prod-config.json
Comparing: development → production
Setting Development Production
--------------------- ------------------- -------------------
environment development production
dataSourceName wheels_dev wheels_prod
showDebugInformation true false
showErrorInformation true false
cacheQueries false true
cacheActions false true
Differences: 6
Only in development: 2
Only in production: 1
Detailed Diff
wheels config env compare development production --format=diff
Output:
--- development
+++ production
@@ -1,6 +1,6 @@
-environment=development
-dataSourceName=wheels_dev
-showDebugInformation=true
-showErrorInformation=true
-cacheQueries=false
-cacheActions=false
+environment=production
+dataSourceName=wheels_prod
+showDebugInformation=false
+showErrorInformation=false
+cacheQueries=true
+cacheActions=true
Synchronize Environments
Copy Settings
wheels config env sync production staging
Prompts:
Sync configuration from production to staging?
Settings to copy:
- cacheQueries: false → true
- cacheActions: false → true
- sessionTimeout: 1800 → 3600
Settings to preserve:
- dataSourceName: wheels_staging
- environment: staging
Continue? (y/N)
Selective Sync
wheels config env sync production staging --filter=cache*
# Include descriptions
wheels config env export production --verbose
# Exclude sensitive data
wheels config env export production --safe
# Filter specific settings
wheels config env export production --filter=cache*
Run TestBox tests for your application with advanced features.
Synopsis
wheels test run [spec] [options]
Description
The wheels test run command executes your application's TestBox test suite with support for watching, filtering, and various output formats. This is the primary command for running your application tests (as opposed to framework tests).
Arguments
Argument
Description
Default
spec
Specific test spec or directory
All tests
Options
Option
Description
Default
--watch
Watch for changes and rerun
false
--reporter
TestBox reporter
simple
--recurse
Recurse directories
true
--bundles
Test bundles to run
--labels
Filter by labels
--excludes
Patterns to exclude
--filter
Test name filter
--outputFile
Output results to file
--verbose
Verbose output
false
--help
Show help information
Examples
Run all tests
wheels test run
Run specific test file
wheels test run tests/models/UserTest.cfc
Run tests in directory
wheels test run tests/controllers/
Watch mode
wheels test run --watch
Run specific bundles
wheels test run --bundles=models,controllers
Filter by labels
wheels test run --labels=unit,critical
Use different reporter
wheels test run --reporter=json
wheels test run --reporter=junit
wheels test run --reporter=text
Save results to file
wheels test run --reporter=junit --outputFile=test-results.xml
function beforeAll() {
transaction action="begin";
}
function afterAll() {
transaction action="rollback";
}
Database Cleaner
function beforeEach() {
queryExecute("DELETE FROM users");
queryExecute("DELETE FROM products");
}
Fixtures
function loadFixtures() {
var users = deserializeJSON(
fileRead("/tests/fixtures/users.json")
);
for (var userData in users) {
model("User").create(userData);
}
}
CI/CD Integration
GitHub Actions
- name: Run tests
run: |
wheels test run --reporter=junit --outputFile=test-results.xml
- name: Upload results
uses: actions/upload-artifact@v2
with:
name: test-results
path: test-results.xml
Pre-commit Hook
#!/bin/bash
# .git/hooks/pre-commit
echo "Running tests..."
wheels test run --labels=unit
if [ $? -ne 0 ]; then
echo "Tests failed. Commit aborted."
exit 1
fi
Performance Tips
Use labels for fast feedback
wheels test run --labels=unit # Fast
wheels test run --labels=integration # Slow
Parallel execution
wheels test run --threads=4
Watch specific directories
wheels test run tests/models --watch
Skip slow tests during development
wheels test run --excludes="*integration*"
Common Issues
Out of Memory
# Increase memory
box server set jvm.heapSize=1024
box server restart
Test Pollution
Use beforeEach/afterEach
Reset global state
Use transactions
Flaky Tests
Avoid time-dependent tests
Mock external services
Use fixed test data
See Also
wheels env
Base command for environment management in Wheels applications.
Synopsis
wheels env [subcommand] [options]
Description
The wheels env command provides comprehensive environment management for Wheels applications. It handles environment configuration, switching between environments, and managing environment-specific settings.
Subcommands
Command
Description
setup
Setup a new environment
list
List available environments
switch
Switch to a different environment
Options
Option
Description
--help
Show help information
--version
Show version information
Direct Usage
When called without subcommands, displays current environment:
wheels env
Output:
Current Environment: development
Configuration File: /config/development/settings.cfm
Database: wheels_dev
Mode: development
Debug: enabled
# Uses current environment's database
wheels dbmigrate latest
# Reloads in current environment
wheels reload
# Tests run in test environment
wheels test run
--status - (Optional) Filter by status: all, active, inactive. Default: all
Description
The plugins list command displays information about all plugins installed in your CFWheels application, including:
Plugin name and version
Installation status (active/inactive)
Compatibility with current CFWheels version
Description and author information
Dependencies on other plugins
Examples
List all plugins
Show only active plugins
Export as JSON
Simple listing (names only)
Output
Table Format (Default)
JSON Format
Plugin Statuses
Active: Plugin is loaded and functioning
Inactive: Plugin is installed but not loaded
Error: Plugin failed to load (check logs)
Incompatible: Plugin requires different CFWheels version
Notes
Plugins are loaded from the /plugins directory
Plugin order matters for dependencies
Incompatible plugins may cause application errors
Use wheels plugins install to add new plugins
wheels plugins
Base command for plugin management in Wheels applications.
Synopsis
Description
The wheels plugins command provides comprehensive plugin management for Wheels applications. It handles plugin discovery, installation, configuration, and lifecycle management.
Subcommands
Options
Direct Usage
When called without subcommands, displays plugin overview:
Output:
Examples
Show plugin overview
Quick plugin check
Update all plugins
Plugin system info
Plugin System
Plugin Structure
Plugin Metadata
Each plugin contains plugin.json:
Plugin Registry
Official Registry
Default source for plugins:
Custom Registries
Configure additional sources:
Plugin Lifecycle
Discovery
Installation
Configuration
Updates
Plugin Development
Create Plugin
Plugin API
Environment Support
Environment-Specific Plugins
Conditional Loading
Plugin Commands
Plugins can register custom commands:
Usage:
Dependency Management
Automatic Resolution
Conflict Resolution
Options:
prompt: Ask for each conflict
newest: Use newest version
oldest: Keep existing version
Plugin Storage
Global Plugins
Shared across projects:
Location: ~/.wheels/plugins/
Project Plugins
Project-specific:
Location: /plugins/
Security
Plugin Verification
Permission Control
Troubleshooting
Common Issues
Plugin Not Loading
Dependency Conflicts
Version Incompatibility
Best Practices
Version Lock: Lock plugin versions for production
Test Updates: Test in development first
Backup: Backup before major updates
Documentation: Document custom plugins
Security: Verify plugin sources
Plugin Cache
Clear Cache
Rebuild Cache
Notes
Plugins are loaded in dependency order
Some plugins require application restart
Global plugins override project plugins
Plugin conflicts are resolved by load order
See Also
wheels plugins remove
Removes an installed plugin from your CFWheels application.
Usage
Parameters
plugin - (Required) Name of the plugin to remove
--backup - (Optional) Create a backup before removal. Default: true
--force - (Optional) Force removal even if other plugins depend on it
Description
The plugins remove command safely uninstalls a plugin from your CFWheels application. It:
Checks for dependent plugins
Creates a backup (by default)
Removes plugin files
Cleans up configuration
Updates plugin registry
Examples
Basic plugin removal
Remove without backup
Force removal (ignore dependencies)
Remove multiple plugins
Removal Process
Dependency Check: Ensures no other plugins depend on this one
Backup Creation: Saves plugin files to backup directory
Deactivation: Disables plugin in application
File Removal: Deletes plugin files and directories
Cleanup: Removes configuration entries
Verification: Confirms successful removal
Output
Dependency Handling
If other plugins depend on the one being removed:
Backup Management
Backups are stored in /backups/plugins/ with timestamp:
Format: [plugin-name]-[version]-[timestamp].zip
Example: authentication-2.1.0-20240115143022.zip
Restore from backup
Notes
Always restart your application after removing plugins
Backups are kept for 30 days by default
Some plugins may leave configuration files that need manual cleanup
Database tables created by plugins are not automatically removed
Use wheels plugins list to verify removal
wheels env switch
Switch to a different environment in your Wheels application.
Synopsis
Description
The wheels env switch command changes the active environment for your Wheels application. It updates configuration files, environment variables, and optionally restarts services to apply the new environment settings.
Arguments
Options
Examples
Switch to staging
Switch with application restart
Force switch without validation
Switch with backup
Quiet switch for scripts
What It Does
Validates Target Environment:
Checks if environment exists
Verifies configuration
Tests database connection
Updates Configuration:
Sets WHEELS_ENV variable
Updates .wheels-env file
Modifies environment.cfm if needed
Applies Changes:
Clears caches
Reloads configuration
Restarts services (if requested)
Verifies Switch:
Confirms environment active
Checks application health
Reports status
Output Example
Environment File Updates
.wheels-env
Before:
After:
Environment Variables
Updates system environment:
Validation Process
Before switching, validates:
Configuration:
File exists
Syntax valid
Required settings present
Database:
Connection works
Tables accessible
Migrations current
Dependencies:
Required services available
File permissions correct
Resources accessible
Switch Strategies
Safe Switch (Default)
Full validation
Graceful transition
Rollback on error
Fast Switch
Skip validation
Immediate switch
Use with caution
Zero-Downtime Switch
Prepare new environment
Switch load balancer
No service interruption
Backup and Restore
Create Backup
Restore from Backup
Manual Restore
Service Management
With Restart
Restarts:
Application server
Cache services
Background workers
Service-Specific
Pre/Post Hooks
Configure in .wheels-cli.json:
Environment-Specific Actions
Development → Production
Production → Development
Integration
CI/CD Pipeline
Deployment Scripts
Rollback
If switch fails or causes issues:
Troubleshooting
Switch Failed
Check validation errors
Verify target environment exists
Use --force if necessary
Application Not Responding
Check service status
Review error logs
Manually restart services
Database Connection Issues
Verify credentials
Check network access
Test connection manually
Best Practices
Always Validate: Don't skip checks in production
Use Backups: Enable backup for critical switches
Test First: Switch in staging before production
Monitor After: Check application health post-switch
{
"env": {
"switch": {
"pre": [
"wheels test run --quick",
"git stash"
],
"post": [
"wheels dbmigrate latest",
"wheels cache clear",
"npm run build"
]
}
}
}
wheels env switch production
# Warning: Switching from development to production
# - Debug will be disabled
# - Caching will be enabled
# - Error details will be hidden
# Continue? (y/N)
wheels env switch development
# Warning: Switching from production to development
# - Debug will be enabled
# - Caching will be disabled
# - Sensitive data may be exposed
# Continue? (y/N)
- name: Switch to staging
run: |
wheels env switch staging --check
wheels test run
wheels deploy exec staging
The analyze performance command profiles your CFWheels application to identify performance bottlenecks and provide optimization recommendations. It monitors:
--target - (Optional) Specific area to optimize: all, database, caching, assets, code. Default: all
--aggressive - (Optional) Apply aggressive optimizations that may change behavior
--backup - (Optional) Create backup before applying changes. Default: true
--dry-run - (Optional) Show what would be changed without applying
Description
The optimize performance command automatically implements performance improvements identified through analysis. It can:
Add database indexes
Implement query result caching
Optimize asset delivery
Enable application-level caching
Refactor inefficient code patterns
Configure performance settings
Examples
Basic optimization
wheels optimize performance
Optimize database only
wheels optimize performance --target=database
Preview changes
wheels optimize performance --dry-run
Aggressive optimization
wheels optimize performance --aggressive
Skip backup
wheels optimize performance --no-backup
Optimization Targets
Database
Creates missing indexes
Optimizes slow queries
Adds query hints
Implements connection pooling
Configures query caching
Caching
Enables view caching
Implements action caching
Configures cache headers
Sets up CDN integration
Optimizes cache keys
Assets
Minifies CSS/JavaScript
Implements asset fingerprinting
Configures compression
Optimizes images
Sets up asset pipeline
Code
Refactors N+1 queries
Implements lazy loading
Optimizes loops
Reduces object instantiation
Improves algorithm efficiency
Output
Performance Optimization
========================
Analyzing application... ✓
Creating backup... ✓
Applying optimizations:
Database Optimizations:
✓ Added index on users.email
✓ Added composite index on orders(user_id, created_at)
✓ Implemented query caching for User.findActive()
✓ Optimized ORDER BY clause in Product.search()
Caching Optimizations:
✓ Enabled action caching for ProductsController.index
✓ Added fragment caching to product listings
✓ Configured Redis for session storage
✓ Set cache expiration for static content
Asset Optimizations:
✓ Minified 15 JavaScript files (saved 145KB)
✓ Compressed 8 CSS files (saved 62KB)
✓ Enabled gzip compression
✓ Configured browser caching headers
Code Optimizations:
✓ Fixed N+1 query in OrdersController.index
✓ Replaced 3 array loops with cfloop query
✓ Implemented lazy loading for User.orders
~ Skipped: Complex refactoring in ReportService (requires --aggressive)
Summary:
- Applied: 18 optimizations
- Skipped: 3 (require manual review or --aggressive flag)
- Estimated improvement: 35-40% faster page loads
Next steps:
1. Run 'wheels reload' to apply changes
2. Test application thoroughly
3. Monitor performance metrics
Dry Run Mode
Preview changes without applying them:
wheels optimize performance --dry-run
Optimization Preview
====================
Would apply the following changes:
1. Database: Create index on users.email
SQL: CREATE INDEX idx_users_email ON users(email);
2. Cache: Enable action caching for ProductsController.index
File: /app/controllers/ProductsController.cfc
Add: <cfset caches(action="index", time=30)>
3. Query: Optimize Product.search()
File: /app/models/Product.cfc
Change: Add query hint for index usage
[... more changes ...]
Run without --dry-run to apply these optimizations.
Aggressive Mode
Enables optimizations that may change application behavior:
wheels optimize performance --aggressive
Additional aggressive optimizations:
✓ Converted synchronous operations to async
✓ Implemented aggressive query result caching
✓ Reduced session timeout to 20 minutes
✓ Disabled debug output in production
✓ Implemented database connection pooling
⚠️ Changed default query timeout to 10 seconds
Backup and Rollback
Backups are created automatically:
Backup created: /backups/optimize-backup-20240115-143022.zip
To rollback:
wheels optimize rollback --backup=optimize-backup-20240115-143022.zip
Configuration
Customize optimization behavior in /config/optimizer.json:
--port - (Optional) Port number to serve on. Default: 4000
--host - (Optional) Host to bind to. Default: localhost
--open - (Optional) Open browser automatically after starting
--watch - (Optional) Watch for changes and regenerate. Default: true
Description
The docs serve command starts a local web server to preview your generated documentation. It includes:
Live reload on documentation changes
Search functionality
Responsive design preview
Print-friendly styling
Offline access support
Examples
Basic documentation server
wheels docs serve
Serve on different port
wheels docs serve --port=8080
Open in browser automatically
wheels docs serve --open
Serve without watching for changes
wheels docs serve --no-watch
Bind to all interfaces
wheels docs serve --host=0.0.0.0
Server Output
Starting documentation server...
================================
Configuration:
- Documentation path: /docs/generated/
- Server URL: http://localhost:4000
- Live reload: enabled
- File watching: enabled
Server started successfully!
- Local: http://localhost:4000
- Network: http://192.168.1.100:4000
Press Ctrl+C to stop the server
[2024-01-15 14:30:22] Serving documentation...
[2024-01-15 14:30:45] GET / - 200 OK (15ms)
[2024-01-15 14:30:46] GET /models/user.html - 200 OK (8ms)
[2024-01-15 14:31:02] File changed: /app/models/User.cfc
[2024-01-15 14:31:02] Regenerating documentation...
[2024-01-15 14:31:05] Documentation updated - reloading browsers
Features
Live Reload
When --watch is enabled, the server:
Monitors source files for changes
Automatically regenerates affected documentation
Refreshes browser without manual reload
Search Functionality
Full-text search across all documentation
Instant results as you type
Keyboard navigation (Ctrl+K or Cmd+K)
Search history
Navigation
Documentation Structure:
/ # Home page with overview
/models/ # All models documentation
/models/user.html # Specific model docs
/controllers/ # Controller documentation
/api/ # API reference
/guides/ # Custom guides
/search # Search page
Print Support
Optimized CSS for printing
Clean layout without navigation
Page breaks at logical points
Print entire docs or single pages
Development Workflow
Typical usage during development:
# Terminal 1: Start the docs server
wheels docs serve --open
# Terminal 2: Make code changes
# Edit your models/controllers
# Documentation auto-updates
# Terminal 3: Generate fresh docs if needed
wheels docs generate
Review workflow:
# Generate and serve for team review
wheels docs generate --format=html
wheels docs serve --port=3000 --host=0.0.0.0
# Share URL with team
echo "Documentation available at http://$(hostname -I | awk '{print $1}'):3000"
/**
* User model for authentication and authorization
*
* @author John Doe
* @since 1.0.0
*/
component extends="Model" {
/**
* Initialize user relationships and validations
* @hint Sets up the user model configuration
*/
function config() {
// Relationships
hasMany("orders");
belongsTo("role");
// Validations
validatesPresenceOf("email,firstName,lastName");
validatesUniquenessOf("email");
}
/**
* Find active users with recent activity
*
* @param days Number of days to look back
* @return query Active users
*/
public query function findActive(numeric days=30) {
return findAll(
where="lastLoginAt >= :date",
params={date: dateAdd("d", -arguments.days, now())}
);
}
}
Controller Documentation
/**
* Handles user management operations
*
* @displayname User Controller
* @namespace /users
*/
component extends="Controller" {
/**
* Display paginated list of users
*
* @hint GET /users
* @access public
* @return void
*/
function index() {
param name="params.page" default="1";
users = model("user").findAll(
page=params.page,
perPage=20,
order="createdAt DESC"
);
}
}
⚠️ DEPRECATED: This command has been deprecated. Please use wheels security scan instead.
Migration Notice
The analyze security command has been moved to provide better organization and expanded functionality.
Old Command
wheels analyze security
New Command
wheels security scan
Why the Change?
Better command organization with dedicated security namespace
Enhanced scanning capabilities
Improved reporting options
Integration with security vulnerability databases
See Also
Deprecation Timeline
Deprecated: v1.5.0
Warning Added: v1.6.0
Removal Planned: v2.0.0
The command currently redirects to wheels security scan with a deprecation warning.
wheels optimize
Base command for application optimization.
Synopsis
wheels optimize [subcommand] [options]
Description
The wheels optimize command provides tools to improve your Wheels application's performance. It analyzes and optimizes various aspects including database queries, asset delivery, caching strategies, and code execution.
Subcommands
Command
Description
performance
Comprehensive performance optimization
Options
Option
Description
--help
Show help information
--version
Show version information
Direct Usage
When called without subcommands, runs automatic optimizations:
Base command for security management and vulnerability scanning.
Synopsis
wheels security [subcommand] [options]
Description
The wheels security command provides comprehensive security tools for Wheels applications. It scans for vulnerabilities, checks security configurations, and helps implement security best practices.
Subcommands
Command
Description
scan
Scan for security vulnerabilities
Options
Option
Description
--help
Show help information
--version
Show version information
Direct Usage
When called without subcommands, performs a quick security check:
wheels security
Output:
Wheels Security Overview
=======================
Last Scan: 2024-01-15 10:30:45
Status: 3 issues found
Critical: 0
High: 1
Medium: 2
Low: 0
Vulnerabilities:
- [HIGH] SQL Injection risk in UserModel.cfc:45
- [MEDIUM] Missing CSRF protection on /admin routes
- [MEDIUM] Outdated dependency: cfml-jwt (2.1.0 → 3.0.0)
Run 'wheels security scan' for detailed analysis
Issue: SQL Injection Risk
File: /models/User.cfc:45
Fix: Replace direct SQL with parameterized query
Current:
query = "SELECT * FROM users WHERE id = #arguments.id#";
Suggested:
queryExecute(
"SELECT * FROM users WHERE id = :id",
{ id: arguments.id }
);
Security Reports
Generate Reports
# HTML report
wheels security scan --report=html
# JSON report for tools
wheels security scan --format=json
# SARIF for GitHub
wheels security scan --format=sarif
Initialize Docker configuration for your Wheels application.
Synopsis
wheels docker init [options]
Description
The wheels docker init command creates Docker configuration files for containerizing your Wheels application. It generates a Dockerfile, docker-compose.yml, and supporting configuration files optimized for Wheels applications.
Local Development: Consistent development environment across team
Testing: Isolated test environments with different configurations
CI/CD: Containerized testing in pipelines
Deployment: Production-ready containers for cloud deployment
Environment Variables
Common environment variables configured:
Variable
Description
WHEELS_ENV
Application environment
WHEELS_DATASOURCE
Database connection name
DB_HOST
Database hostname
DB_PORT
Database port
DB_NAME
Database name
DB_USER
Database username
DB_PASSWORD
Database password
Notes
Requires Docker and Docker Compose installed
Database passwords are set to defaults in development
Production configurations should use secrets management
The command detects existing Docker files and prompts before overwriting
See Also
wheels ci init
Initialize continuous integration configuration for your Wheels application.
Synopsis
wheels ci init [provider] [options]
Description
The wheels ci init command sets up continuous integration (CI) configuration files for your Wheels application. It generates CI/CD pipeline configurations for popular CI providers like GitHub Actions, GitLab CI, Jenkins, and others.
Arguments
Argument
Description
Default
provider
CI provider to configure (github, gitlab, jenkins, travis, circle)
github
Options
Option
Description
--template
Use a specific template (basic, full, minimal)
--branch
Default branch name
--engines
CFML engines to test (lucee5, lucee6, adobe2018, adobe2021, adobe2023)
--databases
Databases to test against (h2, mysql, postgresql, sqlserver)
--force
Overwrite existing CI configuration
--help
Show help information
Examples
Initialize GitHub Actions
wheels ci init github
Initialize with specific engines
wheels ci init github --engines=lucee6,adobe2023
Initialize with multiple databases
wheels ci init github --databases=mysql,postgresql
Initialize GitLab CI
wheels ci init gitlab --branch=develop
Use full template with force overwrite
wheels ci init github --template=full --force
What It Does
Creates CI configuration files in the appropriate location:
GitHub Actions: .github/workflows/ci.yml
GitLab CI: .gitlab-ci.yml
Jenkins: Jenkinsfile
Travis CI: .travis.yml
CircleCI: .circleci/config.yml
Configures test matrix for:
Multiple CFML engines
Multiple database systems
Different operating systems (if supported)
Sets up common CI tasks:
Dependency installation
Database setup
Test execution
Code coverage reporting
Artifact generation
Generated Configuration
Example GitHub Actions configuration:
name: CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
cfengine: [lucee@5, lucee@6, adobe@2023]
database: [h2]
steps:
- uses: actions/checkout@v4
- name: Setup CommandBox
uses: ortus-solutions/setup-commandbox@v2
- name: Install dependencies
run: box install
- name: Start server
run: box server start cfengine=${{ matrix.cfengine }}
- name: Run tests
run: box testbox run
Templates
Basic Template
Single engine and database
Essential test execution
Minimal configuration
Full Template
Multiple engines and databases
Code coverage
Deployment steps
Notifications
Minimal Template
Bare minimum for CI
Quick setup
No extras
Use Cases
New Project Setup: Quickly add CI/CD to a new Wheels project
Migration: Move from one CI provider to another
Standardization: Apply consistent CI configuration across projects
Multi-Engine Testing: Ensure compatibility across CFML engines
Notes
Requires a valid Wheels application structure
Some providers may require additional authentication setup
Database services are configured as Docker containers where possible
The command respects existing .gitignore patterns
See Also
wheels docs
Base command for documentation generation and management.
Note: This command is currently broken. Use subcommands directly.
Synopsis
wheels docs [subcommand] [options]
Description
The wheels docs command provides documentation tools for Wheels applications. It generates API documentation, manages inline documentation, and serves documentation locally. While the base command is currently broken, the subcommands generate and serve work correctly.
Subcommands
Command
Description
Status
generate
Generate documentation
✓ Working
serve
Serve documentation locally
✓ Working
Options
Option
Description
--help
Show help information
--version
Show version information
Current Status
The base wheels docs command is temporarily unavailable due to a known issue. Please use the subcommands directly:
wheels docs generate - Generate documentation
wheels docs serve - Serve documentation
Expected Behavior (When Fixed)
When operational, running wheels docs without subcommands would:
/**
* User model for authentication and profile management
*
* @author John Doe
* @since 1.0.0
*/
component extends="Model" {
/**
* Authenticate user with credentials
*
* @username The user's username or email
* @password The user's password
* @return User object if authenticated, false otherwise
*
* @example
* user = model("User").authenticate("john@example.com", "secret");
* if (isObject(user)) {
* // Login successful
* }
*/
public any function authenticate(required string username, required string password) {
// Implementation
}
}
Inline Documentation
<!---
@doc
This view displays the user profile page
@requires User object in 'user' variable
@layout layouts/main
--->
Base command for deployment operations in Wheels applications.
Synopsis
Description
The wheels deploy command provides a comprehensive deployment system for Wheels applications. It manages the entire deployment lifecycle including initialization, execution, monitoring, and rollback capabilities.
Subcommands
Options
Examples
View deployment help
Initialize deployment
Execute deployment
Check deployment status
Deployment Workflow
Initialize: Set up deployment configuration
Setup: Prepare deployment environment
Configure: Set secrets and proxy settings
Deploy: Push and execute deployment
Monitor: Check status and logs
Rollback (if needed):
Configuration
Deployment configuration is stored in .wheels-deploy.json:
Deployment Strategies
Rolling Deployment
Zero-downtime deployment
Gradual rollout
Automatic rollback on failure
Blue-Green Deployment
Two identical environments
Instant switching
Easy rollback
Canary Deployment
Gradual traffic shifting
Risk mitigation
Performance monitoring
Environment Variables
Use Cases
Continuous Deployment: Automated deployments from CI/CD
Manual Releases: Controlled production deployments
Multi-Environment: Deploy to staging, production, etc.
Disaster Recovery: Quick rollback capabilities
Scheduled Deployments: Deploy during maintenance windows
Best Practices
Always run deploy audit before production deployments
Use deploy lock during critical operations
Configure proper hooks for migrations and cache clearing
Keep deployment logs for auditing
Test deployments in staging first
Use secrets management for sensitive data
Notes
Requires SSH access for remote deployments
Git repository must be properly configured
Database backups recommended before deployment
Monitor application health after deployment
See Also
wheels deploy init
Initialize deployment configuration for your Wheels application.
Synopsis
Description
The wheels deploy init command creates and configures deployment settings for your Wheels application. It generates deployment configuration files and sets up target environments.
Arguments
Options
Examples
Interactive initialization
Initialize production target
Initialize with Git deployment
Initialize Docker deployment
Initialize with specific strategy
What It Does
Creates deployment configuration:
.wheels-deploy.json in project root
Target-specific settings
Deployment credentials (encrypted)
Sets up deployment structure:
Release directories
Shared directories (uploads, logs)
Symbolic links
Configures deployment hooks:
Pre-deployment tasks
Post-deployment tasks
Rollback procedures
Validates configuration:
Tests connection to target
Verifies permissions
Checks dependencies
Configuration Structure
Generated .wheels-deploy.json:
Deployment Types
SSH Deployment
Secure shell access
Rsync for file transfer
Full control over deployment
FTP Deployment
Legacy support
Simple file transfer
Limited automation
Git Deployment
Git-based workflows
Post-receive hooks
Version control integration
Docker Deployment
Container orchestration
Image-based deployment
Scalable infrastructure
Shared Resources
Shared directories and files persist across deployments:
Directories: User uploads, logs, cache
Files: Environment configs, secrets
Deployment Hooks
Pre-deployment
Build assets
Run tests
Backup database
Post-deployment
Run migrations
Clear caches
Restart services
Send notifications
Rollback
Restore previous release
Revert database
Clear caches
Interactive Mode
When run without arguments, the command enters interactive mode:
Security Considerations
Credentials: Stored encrypted in config
SSH Keys: Recommended over passwords
Permissions: Least privilege principle
Secrets: Use environment variables
Use Cases
New Project: Set up deployment pipeline
Migration: Move from manual to automated deployment
Multi-Environment: Configure staging and production
Team Setup: Share deployment configuration
Notes
Run from project root directory
Requires appropriate server access
Test with staging environment first
Back up existing configuration before overwriting
See Also
wheels docker deploy
Deploy your Wheels application using Docker containers.
Synopsis
Description
The wheels docker deploy command deploys your containerized Wheels application to various Docker environments including Docker Swarm, Kubernetes, or cloud container services.
Arguments
Options
Examples
Deploy locally
Deploy to Docker Swarm
Deploy to Kubernetes
Deploy to AWS ECS
Dry run deployment
Deploy with resource limits
What It Does
Build and Tag:
Builds Docker image if needed
Tags with specified version
Validates image integrity
Push to Registry:
Authenticates with registry
Pushes image to registry
Verifies push success
Deploy to Target:
Generates deployment manifests
Applies configuration
Monitors deployment status
Performs health checks
Post-Deployment:
Runs database migrations
Clears caches
Sends notifications
Deployment Targets
Local
Uses docker-compose
Development/testing
No registry required
Docker Swarm
Creates/updates services
Load balancing
Rolling updates
Secrets management
Kubernetes
Creates deployments, services, ingress
ConfigMaps and Secrets
Horizontal pod autoscaling
Rolling updates
AWS ECS
Task definitions
Service updates
Load balancer configuration
Auto-scaling
Google Cloud Run
Serverless containers
Automatic scaling
HTTPS endpoints
Azure Container Instances
Container groups
Managed instances
Integration with Azure services
Configuration Files
Kubernetes Example (k8s.yml)
Docker Swarm Example (swarm.yml)
Environment Management
Environment variables can be provided via:
--env-file option
Platform-specific secrets
Configuration files
Command line overrides
Health Checks
The deployment includes health checks:
Readiness probes
Liveness probes
Startup probes
Custom health endpoints
Rollback
To rollback a deployment:
Or manually:
Use Cases
Staging Deployments: Test production configurations
Production Releases: Deploy new versions with zero downtime
Scaling: Adjust replicas based on load
Multi-Region: Deploy to multiple regions/zones
Blue-Green Deployments: Switch between environments
Notes
Ensure Docker images are built before deployment
Registry authentication must be configured
Database migrations should be handled separately or via init containers
Monitor deployment logs for troubleshooting
Use --dry-run to preview changes before applying
Troubleshooting
Common issues:
Image not found: Ensure image is pushed to registry
Auth failures: Check registry credentials
Resource limits: Adjust CPU/memory settings
Port conflicts: Check service port mappings
See Also
wheels deploy exec
Execute a deployment to the specified target environment.
Synopsis
Description
The wheels deploy exec command performs the actual deployment of your Wheels application to a configured target environment. It handles file synchronization, runs deployment hooks, and manages the deployment lifecycle.
Arguments
Options
Examples
Deploy to production
Deploy specific tag
Deploy with dry run
Force deployment
Deploy without hooks
Verbose deployment
Deployment Process
Pre-flight Checks:
Verify target configuration
Check Git status
Validate dependencies
Test connectivity
Preparation:
Create release directory
Export code from repository
Install dependencies
Compile assets
Synchronization:
Upload files to target
Exclude ignored files
Preserve shared resources
Activation:
Update symbolic links
Run database migrations
Clear caches
Reload application
Cleanup:
Remove old releases
Clean temporary files
Update deployment log
Output Example
Deployment Strategies
Rolling Deployment
Gradual rollout
Zero downtime
Automatic rollback on failure
Blue-Green Deployment
Instant switching
Full rollback capability
Requires double resources
Canary Deployment
Gradual traffic shift
Risk mitigation
Performance monitoring
Hook Execution
Pre-deployment Hooks
Executed before deployment:
Post-deployment Hooks
Executed after activation:
Rollback Handling
If deployment fails:
Automatic rollback triggered
Previous release restored
Rollback hooks executed
Notifications sent
Manual rollback:
Environment Variables
Available during deployment:
Dry Run Mode
Preview deployment without changes:
Shows:
Files to be transferred
Hooks to be executed
Resources to be created
Estimated deployment time
Error Handling
Common errors and solutions:
Connection Failed
Check SSH keys/credentials
Verify network connectivity
Confirm server accessibility
Permission Denied
Check user permissions
Verify directory ownership
Review deployment path
Hook Failed
Check hook commands
Verify dependencies
Review error logs
Disk Space
Check available space
Clean old releases
Review keep-releases setting
Performance Optimization
Use --skip-assets if assets pre-built
Enable compression for transfers
Parallelize hook execution
Use incremental deployments
Monitoring
Track deployment metrics:
Deployment duration
Transfer size
Success/failure rate
Rollback frequency
Use Cases
Automated Deployment: CI/CD pipeline integration
Scheduled Releases: Deploy during maintenance windows
Emergency Hotfix: Quick production patches
Feature Deployment: Deploy specific features
A/B Testing: Deploy variants for testing
Notes
Always test in staging first
Monitor application after deployment
Keep deployment logs for auditing
Have rollback plan ready
Coordinate with team for production deployments
See Also
wheels deploy hooks
Manage deployment hooks for custom actions during the deployment lifecycle.
Synopsis
Description
The wheels deploy hooks command allows you to manage custom hooks that execute at specific points during the deployment process. These hooks enable you to run custom scripts, notifications, or integrations at key deployment stages.
Keep hooks simple: Each hook should do one thing well
Handle failures: Always include error handling in hook scripts
Set timeouts: Prevent hooks from blocking deployments
Test thoroughly: Test hooks in staging before production
Log output: Ensure hooks provide clear logging
Use priorities: Order hooks appropriately with priorities
Document hooks: Maintain documentation for all hooks
See Also
wheels deploy audit
Audit deployment configuration and security settings to ensure compliance and best practices.
Synopsis
Description
The wheels deploy audit command performs a comprehensive security and configuration audit of your deployment setup. It checks for common misconfigurations, security vulnerabilities, and compliance issues in your deployment environment.
Options
--environment, -e - Target environment to audit (default: production)
--report-format - Output format for audit report (json, html, text) (default: text)
Configure and manage deployment proxy settings for routing traffic during deployments.
Synopsis
wheels deploy proxy <action> [options]
Description
The wheels deploy proxy command manages proxy configurations for zero-downtime deployments, traffic routing, and load balancing during deployment operations. It handles blue-green deployments, canary releases, and traffic splitting strategies.
# Deploy new version alongside current
wheels deploy exec --target green
# Verify new deployment
wheels deploy proxy health --backend green
# Switch traffic
wheels deploy proxy switch --to green
# Remove old version
wheels deploy stop --target blue
A/B testing
# Set up A/B test
wheels deploy proxy route \
--backend feature-a --weight 50 \
--backend feature-b --weight 50 \
--cookie "ab_test"
The wheels deploy secrets command provides secure management of sensitive data like API keys, database passwords, and other credentials used during deployment. Secrets are encrypted and stored separately from your codebase.
Actions
Action
Description
list
List all secrets for a target
set
Set or update a secret
get
Retrieve a secret value
delete
Remove a secret
sync
Synchronize secrets with target
rotate
Rotate secret values
export
Export secrets to file
import
Import secrets from file
Arguments
Argument
Description
Required
action
Action to perform
Yes
name
Secret name
For set/get/delete
value
Secret value
For set action
Options
Option
Description
Default
--target
Deployment target
production
--env-file
Environment file for bulk operations
--format
Output format (table, json, dotenv)
table
--force
Skip confirmation prompts
false
--encrypt
Encryption method (aes256, rsa)
aes256
--key-file
Path to encryption key
.wheels-deploy-key
--help
Show help information
Examples
Set a secret
wheels deploy secrets set DB_PASSWORD mySecretPass123 --target=production
Set secret interactively (hidden input)
wheels deploy secrets set API_KEY --target=production
# Prompts for value without displaying it
List all secrets
wheels deploy secrets list --target=production
Get a specific secret
wheels deploy secrets get DB_PASSWORD --target=production
Secrets are never logged or displayed in plain text
Use environment-specific secrets
Regular rotation improves security
Keep encryption keys secure and backed up
Monitor secret access in production
See Also
wheels deploy lock
Lock deployment state to prevent concurrent deployments and maintain deployment integrity.
Synopsis
wheels deploy lock <action> [options]
Description
The wheels deploy lock command manages deployment locks to prevent concurrent deployments, ensure deployment atomicity, and maintain system stability during critical operations. This is essential for coordinating deployments in team environments and automated systems.
Actions
acquire - Acquire a deployment lock
release - Release an existing lock
status - Check current lock status
force-release - Force release a stuck lock (use with caution)
list - List all active locks
wait - Wait for lock to become available
Options
--environment, -e - Target environment to lock (default: production)
--reason - Reason for acquiring lock (required for acquire)
--duration - Lock duration in minutes (default: 30)
--wait-timeout - Maximum time to wait for lock in seconds
--force - Force acquire lock even if already locked
--owner - Lock owner identifier (default: current user)
--metadata - Additional lock metadata as JSON
Examples
Acquire deployment lock
wheels deploy lock acquire --reason "Deploying version 2.1.0"
# Wait for lock and deploy
wheels deploy lock wait --wait-timeout 600
wheels deploy exec --auto-lock
CI/CD integration
# In CI/CD pipeline
if wheels deploy lock acquire --reason "CI/CD Deploy #${BUILD_ID}"; then
wheels deploy exec
wheels deploy lock release
else
echo "Could not acquire lock"
exit 1
fi
Lock States
Available
No active lock, deployments can proceed
Locked
Active lock in place, deployments blocked
Expired
Lock duration exceeded, can be cleaned up
Force-locked
Emergency lock overriding normal locks
Best Practices
Always provide reasons: Clear reasons help team coordination
Set appropriate durations: Don't lock longer than necessary
Release locks promptly: Release as soon as operation completes
Handle lock failures: Plan for scenarios when locks can't be acquired
Monitor stuck locks: Set up alerts for long-running locks
Use force sparingly: Only force-release when absolutely necessary
Document lock usage: Keep records of lock operations
Error Handling
Common lock errors and solutions:
Lock already exists
# Check who owns the lock
wheels deploy lock status
# Wait for it to be released
wheels deploy lock wait
# Or coordinate with lock owner
Lock expired during operation
# Extend lock duration if still needed
wheels deploy lock acquire --extend
Cannot release lock
# Verify you own the lock
wheels deploy lock status --verbose
# Force release if necessary
wheels deploy lock force-release --reason "Lock owner unavailable"
Integration
The lock system integrates with:
CI/CD pipelines for automated deployments
Monitoring systems for lock alerts
Deployment tools for automatic locking
Team communication tools for notifications
See Also
wheels deploy push
Push deployment artifacts to target environment.
Synopsis
wheels deploy push [options]
Description
The wheels deploy push command transfers deployment artifacts, configuration files, and application code to the target deployment environment. It handles file synchronization, artifact validation, and ensures secure transfer of deployment packages.
The wheels deploy rollback command reverts your application to a previous deployment release. It provides quick recovery from failed deployments or problematic releases by switching back to a known-good state.
Arguments
Argument
Description
Default
target
Deployment target to rollback
Required
Options
Option
Description
Default
--release
Specific release to rollback to
Previous release
--steps
Number of releases to rollback
1
--skip-hooks
Skip rollback hooks
false
--force
Force rollback without confirmation
false
--dry-run
Preview rollback without executing
false
--verbose
Show detailed output
false
--help
Show help information
Examples
Rollback to previous release
wheels deploy rollback production
Rollback multiple releases
wheels deploy rollback production --steps=2
Rollback to specific release
wheels deploy rollback production --release=20240114093045
Preview rollback
wheels deploy rollback production --dry-run
Force rollback without confirmation
wheels deploy rollback production --force
Rollback Process
Validation:
Verify target configuration
Check available releases
Validate rollback target
Confirmation:
Display current release
Show target release
Request confirmation (unless --force)
Execution:
Switch symbolic links
Run rollback hooks
Restore shared resources
Clear caches
Verification:
Test application health
Verify services running
Check error logs
Output Example
Rolling back production deployment...
Current Release: 20240115120000
Target Release: 20240114093045
Changes to be reverted:
- 45 files modified
- 3 database migrations
- 2 configuration changes
? Proceed with rollback? (y/N) y
✓ Switching to release 20240114093045
✓ Running rollback hooks
→ Reverting database migrations
→ Clearing application cache
→ Restarting services
✓ Verifying application health
✓ Rollback completed successfully!
Rollback Summary:
- From: 20240115120000
- To: 20240114093045
- Duration: 45s
- Status: SUCCESS
Available Releases
List available releases:
wheels deploy status production --releases
Output:
Available releases for production:
1. 20240115120000 (current)
2. 20240114093045
3. 20240113154522
4. 20240112101133
5. 20240111163421
Rollback Hooks
Configure rollback-specific hooks:
{
"hooks": {
"rollback": [
"wheels dbmigrate down --steps=1",
"wheels reload production",
"npm run cache:clear",
"curl -X POST https://api.example.com/rollback-notification"
]
}
}
Database Rollback
Handling database changes during rollback:
Automatic Migration Rollback:
wheels dbmigrate down --to=20240114093045
Manual Intervention:
Some changes may require manual rollback
Data migrations might not be reversible
Always backup before deployment
Rollback Strategies
Immediate Rollback
Quick switch to previous release:
wheels deploy rollback production
Staged Rollback
Gradual rollback with canary:
wheels deploy rollback production --strategy=canary --percentage=10
Blue-Green Rollback
Instant switch between environments:
wheels deploy rollback production --strategy=blue-green
Emergency Rollback
For critical situations:
# Skip all checks and hooks
wheels deploy rollback production --force --skip-hooks
# Direct symbolic link switch (last resort)
ssh deploy@prod.example.com "cd /var/www/app && ln -sfn releases/20240114093045 current"
Rollback Validation
After rollback, verify:
Application Health:
wheels deploy status production --health
Service Status:
ssh deploy@prod.example.com "systemctl status cfml-app"
Error Logs:
wheels deploy logs production --tail=100 --filter=error
Preventing Rollback Issues
Keep Sufficient Releases:
Configure keepReleases appropriately
Don't set too low (minimum 3-5)
Test Rollback Procedures:
Practice in staging environment
Document manual procedures
Automate where possible
Database Considerations:
Design reversible migrations
Backup before deployment
Test rollback scenarios
Rollback Limitations
Shared files/directories not rolled back
User-uploaded content preserved
External service changes not reverted
Some database changes irreversible
Use Cases
Failed Deployment: Immediate recovery from deployment failure
View and manage deployment logs for troubleshooting and monitoring.
Synopsis
wheels deploy logs [options]
Description
The wheels deploy logs command provides access to deployment logs, allowing you to view, search, and export logs from past and current deployments. This is essential for troubleshooting deployment issues, auditing deployment history, and monitoring deployment progress.
Options
--deployment-id, -d - Specific deployment ID to view logs for
--environment, -e - Filter logs by environment (default: all)
--tail, -f - Follow log output in real-time
--lines, -n - Number of lines to display (default: 100)
--since - Show logs since timestamp (e.g., "2023-01-01", "1h", "30m")
--until - Show logs until timestamp
--grep, -g - Filter logs by pattern (regex supported)
--level - Filter by log level (debug, info, warn, error)
--format - Output format (text, json, csv) (default: text)