Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Install CFWheels and get a local development server running
By far the quickest way to get started with CFWheels is via CommandBox. 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 CFWheels plugins and templates. More on that later.
One module that we have created is a module that extends CommandBox itself with commands and features specific to the CFWheels framework. The CFWheels CLI module for CommandBox is modeled after the Ruby on Rails CLI module and gives similar capabilities to the CFWheels developer.
The first step is to get CommandBox 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 Chocolatey on Windows, Homebrew 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.
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.
Okay, now that we have CommandBox installed, let's add the CFWheels CLI module.
install cfwheels-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 CFWheels application or scaffold out sections of your application. We'll see some of these commands in action momentarily.
Now that we have CommandBox installed and extended it with the CFWheels CLI module, let's start our first CFWheels app from the command line. We'll look at the simplest method for creating a CFWheels 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 CFWheels 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 (cfwheels-base-template) from ForgeBox.io, then downloaded the framework core files (cfwheels) from ForgeBox.io and placed it in the 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 CFWheels project. You're likely to use the wizard when starting a new CFWheels 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 CFWheels 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 CFWheels 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.
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
and will create a server.json
file in your webroot. We can add various options to server.json
to customize our server. Here's an example:
In this example, I've set the server name to myApp
, meaning I can now start the server from any directory by simply calling start myApp
. I've also specified a specific port, 60000
, but you can specify any port you want, or just remove that to start on a random port each time. Lastly, I've enabled URL rewriting and pointed the URL rewrite configuration file to urlrewrite.xml
, which is included starting from CFWheels 2.x. (If you've used the wheels new
command to create your app, this will already be done for you).
You can also specify hosts other than localhost: there's a useful CommandBox module to do that (Host updater) 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.
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
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.
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
CommandBox> start cfengine="adobe@>9.0 <=11"
Or via server.json
CFIDE / Lucee administrators
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 has all the basic Lucee extensions that you are going to need installed, 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. CFWheels 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
)
In this tutorial, we'll be writing a simple application to make sure we have CFWheels installed properly and that everything is working as it should.
Let's make sure we're all on the same page. I'm going to assume that you've followed the Getting Started 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.
Okay, so you have CFWheels installed and can see the CFWheels "Congratulations!" page as shown below. That wasn't that hard now, was it?
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 CFWheels magic along the way.
Let's create a controller from scratch to illustrate how easy it is to set up a controller and plug it into the CFWheels framework.
First, create a file called Say.cfc
in the app/controllers
directory and add the
code below to the file.
Congratulations, you just created your first CFWheels 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 CFWheels application.
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 CommandBox.
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,
CFWheels assumes that we want the default action. Out of the box, the default
action in CFWheels is set to index
. So in our example, CFWheels tried to find
the index
action within the say
controller, and it threw an error because it
couldn't find its view page.
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:
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.
By default, when an action is called, CFWheels 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, CFWheels 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
CFWheels 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:
Save your hello.cfm
file, and let's call our say/hello
action once again.
You have your first working CFWheels page if your browser looks like Figure 3
below.
You have just created your first functional CFWheels 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.
We will add some simple dynamic content to our hello
action and add a second
action to the application. We'll then use some CFWheels code to tie the 2
actions together. Let's get get to it!
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:
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.
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.
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.
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.
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:
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.
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:
The linkTo() function is a built-in CFWheels 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. CFWheels will
always create a valid link for you as long as you configure it correctly when you make infrastructure changes to your application.
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 CFWheels created a link for us and added an appropriate URL for
the say/goodbye
action to the link.
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
If you now call the say/goodbye
action in your browser, your browser should
look like Figure 7 below.
You now know enough to be dangerous with CFWheels. 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.
Learn the goals of CFWheels as well as web development frameworks in general. Then learn more about some key concepts in CFWheels.
This chapter will introduce you to frameworks in general and later specifically to CFWheels. We'll help you decide if you even need a framework at all and what common problems a framework tries to solve. If we're able to convince you that using a framework is the right thing for you, then we'll present our goals with creating CFWheels and show you some key CFWheels concepts.
So let's get started.
Short answer, no. If you don't mind doing the same thing over and over again and are getting paid by the hour to do so, then by all means keep doing that.
Slightly longer answer, no. If you're working on a highly customized project that does not fall within what 9 out of 10 web sites/applications normally do then you likely need a high percentage of custom code, and a framework will not help much.
However, if you're like most of us and have noticed that for every new project you start on--or even every new feature you add to an existing project--you waste a lot of time re-creating the wheel, then you should read on because CFWheels may just be the solution for you!
CFWheels will make starting a new project or building a new feature quick and painless. You can get straight to solving business problems on day one! To understand how this is achieved, we figured that a little background info on frameworks in general may help you out.
All good frameworks rise from the need to solve real problems in real world situations. CFWheels is based heavily on the Rails framework for Ruby and also gets inspiration from Django and, though to a lesser extent, other frameworks in the ColdFusion space (like Fusebox, for example). Over the years the contributors to these frameworks have identified problems and tedious tasks in their own development processes, built a solution for it, and abstracted (made it more generic so it suits any project) the solution into the framework in question. Piggy-backing on what all these great programmers have already created and adding a few nice solutions of our own, CFWheels stands on solid ground.
OK, so that was the high level overview of what frameworks are meant to do. But let's get a little more specific.
Most web development frameworks set out to address some or all of these common concerns:
Map incoming requests to the code that handles them.
Separate your business logic from your presentation code.
Let you work at a higher level of abstraction, thus making you work faster.
Give you a good code organization structure to follow.
Encourage clean and pragmatic design.
Simplify saving data to a storage layer.
Like all other good frameworks, CFWheels does all this. But there are some subtle differences, and certain things are more important in CFWheels than in other frameworks and vice versa. Let's have a look at the specific goals with CFWheels so you can see how it relates to the overall goals of frameworks in general.
As we've said before, CFWheels is heavily based on Ruby on Rails, but it's not a direct port, and there are some things that have been changed to better fit the CFML language. Here's a brief overview of the goals we're striving for with CFWheels (most of these will be covered in greater detail in later chapters):
We strive for simplicity on a lot of different levels in CFWheels. We'll gladly trade code beauty in the framework's internal code for simplicity for the developers who will use it. This goal to keep things simple is evident in a lot of different areas in CFWheels. Here are some of the most notable ones:
The concept of object oriented programming is very simple and data-centric in CFWheels, rather than 100% "pure" at all times.
By default, you'll always get a query result set back when dealing with multiple records in CFWheels, simply because that is the way we're all used to outputting data.
CFWheels encourages best practices, but it will never give you an error if you go against any of them.
With CFWheels, you won't program yourself into a corner. If worse comes to worse, you can always drop right out of the framework and go back to old school code for a while if necessary.
Good old CFML code is used for everything, so there is no need to mess with XML for example.
What this means is that you don't have to be a fantastic programmer to use the framework (although it doesn't hurt). It's enough if you're an average programmer. After using CFWheels for a while, you'll probably find that you've become a better programmer though!
If you've ever downloaded a piece of open source software, then you know that most projects lack documentation. CFWheels hopes to change that. We're hoping that by putting together complete, up-to-date documentation that this framework will appeal, and be usable, by everyone. Even someone who has little ColdFusion programming background, let alone experience with frameworks.
Besides what is already mentioned above, there are some key concepts in CFWheels that makes sense to familiarize yourself with early on. If you don't feel that these concepts are to your liking, feel free to look for a different framework or stick to using no framework at all. Too often programmers choose a framework and spend weeks trying to bend it to do what they want to do rather than follow the framework conventions.
Speaking of conventions, this brings us to the first key concept:
Instead of having to set up tons of configuration variables, CFWheels will just assume you want to do things a certain way by using default settings. In fact, you can start programming a Wheels application without setting any configuration variables at all!
If you find yourself constantly fighting the conventions, then that is a hint that you're not yet ready for CFWheels or CFWheels is not ready for you.
Beautiful (for lack of a better word) code is code that you can scan through and immediately see what it's meant to do. It's code that is never repeated anywhere else. And, most of all, it's code that you'll enjoy writing and will enjoy coming back to 6 months from now.
Sometimes the CFWheels structure itself encourages beautiful code (separating business logic from request handling, for example). Sometimes it's just something that comes naturally after reading documentation, viewing other CFWheels applications, and talking to other CFWheels developers.
If you've investigated frameworks in the past, then you've probably heard this terminology before. Model-View-Controller, or MVC, is a way to structure your code so that it is broken down into three easy-to-manage pieces:
Model: Just another name for the representation of data, usually a database table.
View: What the user or their browser sees and interacts with (a web page in most cases).
Controller: The behind-the-scenes guy that's coordinating everything.
"Uh, yeah. So what's this got to do with anything?" you may ask. MVC is how CFWheels structures your code for you. As you start working with CFWheels applications, you'll see that most of the code you write (database queries, forms, and data manipulation) are very nicely separated into one of these three categories.
The benefits of MVC are limitless, but one of the major ones is that you almost always know right where to go when something needs to change.
If you've added a column to the vehicles table in your database and need to give the user the ability to edit that field, all you need to change is your View. That's where the form is presented to the user for editing.
If you find yourself constantly getting a list of all the red cars in your inventory, you can add a new method to your model called getRedCars()
that does all the work for you. Then when you want that list, just add a call to that method in your controller and you've got 'em!
The Object Relational Mapping, or ORM, in CFWheels is perhaps the one thing that could potentially speed up your development the most. An ORM handles mapping objects in memory to how they are stored in the database. It can replace a lot of your query writing with simple methods such as user.save()
, blogPost.comments(order="date")
, and so on. We'll talk a lot more about the ORM in CFWheels in the chapters about models.
So there you have it, a completely fair and unbiased introduction to CFWheels. ;)
If you've been developing ColdFusion applications for a while, then we know this all seems hard to believe. But trust us; it works. And if you're new to ColdFusion or even web development in general, then you probably aren't aware of most of the pains that Wheels was meant to alleviate!
That's okay. You're welcome in the CFWheels camp just the same.
Using CFWheels 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.
CFWheels was designed to be as lightweight as possible, so this keeps your options fairly open for developing AJAX features into your application.
While there are several flavors of JavaScript libraries out there with AJAX support, we will be using the jQuery framework in this tutorial. Let's assume that you are fairly familiar with the basics of jQuery and know how to set it up.
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.
In this example, we'll wire up some simple JavaScript code that calls a CFWheels action asynchronously. All of this will be done with basic jQuery code and built-in CFWheels 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.
The longhand way would look like:
The shorthand method would look like:
You can decide which method you prefer. Both sets of code above are equivalent.
Then, let's create a link to a controller's action in a view file, like so:
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.
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 javaScriptIncludeTag() ):
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.)
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, CFWheels controllers only generate HTML responses, but there is an easy way to generate JSON instead using CFWheels's provides() and renderWith() functions:
In this controller's config()
method, we use the provides() 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 renderWith() 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 provides() and renderWith(), reference the chapter on Responding with Multiple Formats.
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 p
with the appropriate data.
That is it! Hopefully now you have a clearer picture on how to create AJAX-based features for your web applications.
A quick tutorial that demonstrates how quickly you can get database connectivity up and running with CFWheels.
CFWheels'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.
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 CFWheels ORM.
Download source code
You can download all the source code for this sample application from https://github.com/dhgassoc/Cfwheels-Beginner-Tutorial-Hello-Database
By default, CFWheels will connect to a data source wheels.fw
. To change this default behavior, open the file at app/config/settings.cfm
. In a fresh install of CFWheels, you'll see the follwing code:
These lines provide CFWheels 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.
CFWheels 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. CFWheels 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
:
Note a couple things about this users
table:
The table name is plural.
The table has an auto-incrementing primary key named id
.
These are database conventions used by CFWheels. 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.
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.
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 CFWheels routing, this requires a plural resource, which we'll name users
.
Because a users
resource is more specific than the "generic" routes provided by CFWheels, 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 is referenced in your code to tell CFWheels where to point forms and links.
Method is the HTTP verb that CFWheels listens for to match up the request.
URL Path is the URL that CFWheels listens for to match up the request.
Don't forget to reload
You will need to reload your application after adding new routes!
First, let's create a simple form for adding a new user to the users
table. To do this, we will use CFWheels's form helper functions. CFWheels includes a whole range of functions that simplifies all of the tasks that you need to display forms and communicate errors to the user.
Now create a new file in app/views/users
called new.cfm
. This will contain the view code for our simple form.
Next, add these lines of code to the new file:
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!
To generate the form tag's action
attribute, the startFormTag() function takes parameters similar to the linkTo()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 linkTo().
To end the form, we use the endFormTag() function. Easy enough.
The textField() and passwordField() helpers are similar. As you probably guessed, they create <input>
elements with type="text"
and type="password"
, respectively. And the submitTag() function creates an <input type="submit" />
element.
One thing you'll notice is the textField() and passwordField() 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.
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
.
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 model() 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 new() method.
Notice how we have prefixed the model() function with application.wo
. This is because, in CFWheels, all the global functions and functions for your controller reside in the application.wo
structure. So, whenever you need to call a global function or a function for your controller, you have to prefix them with application.wo
. You will see its use throughout the documentation wherever required.
CFWheels 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 CFWheels 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.
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:
So far we have a fairly well-formed, accessible form, without writing a bunch of repetitive markup.
Next, we'll code the create
action in the controller to handle the form submission and save the new user to the database.
A basic way of doing this is using the model object's create() method:
Because we used the objectName
argument in the fields of our form, we can access the user data as a struct in the params
struct.
There are more things that we can do in the create
action to handle validation, but let's keep it simple in this tutorial.
Notice that our create
action above redirects the user to the users
index route using the redirectTo() 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.
First, let's get the data that the listing needs. Create an action named index
in the users
controller like so:
This call to the model's findAll() 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
.
In the view at app/views/users/index.cfm
, it's as simple as looping through the query and outputting the data
When to use EncodeForHtml
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 CFWheels helpers like linkTo
, buttonTo
, startFormTag
, textField
, etc. However, you need to escape data that is displayed directly onto the page without a CFWheels helper.
We'll now show another cool aspect of form helpers by creating a screen for editing users.
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:
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:
Pretty cool, huh?
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.
Now we'll create the update
action. This will be similar to the create
action, except it will be updating the user object:
To update the user
, simply call its update() method with the user
struct passed from the form via params
. It's that simple.
After the update, we'll add a success message using the Flash and send the end user back to the edit form in case they want to make more changes.
Notice in our listing above that we have a delete
action. Here's what it would look like:
We simply load the user using the model's findByKey() method and then call the object's delete() method. That's all there is to it.
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 CFWheels framework. There's plenty more.
Be sure to read on to some of the related chapters listed below to learn more about working with CFWheels's ORM.
Column Name | Data Type | Extra |
---|---|---|
Name | Method | URL Path | Description |
---|---|---|---|
id
int
auto increment
username
varchar(100)
varchar(255)
passwd
varchar(15)
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
What you need to know and have installed before you start programming in CFWheels.
We can identify 3 different types of requirements that you should be aware of:
Project Requirements. Is CFWheels a good fit for your project?
Developer Requirements. Do you have the knowledge and mindset to program effectively in CFWheels?
System Requirements. Is your server ready for CFWheels?
Before you start learning CFWheels 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 CFWheels 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 CFWheels--simple CRUD (create, read, update, delete) website applications.
A simple ten page website won't do much data manipulation, so you don't need CFWheels 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, CFWheels 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 CFWheels 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...
Yes, there are actually some things you should familiarize yourself with before starting to use CFWheels. 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.
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; Lucee offers an excellent open source alternative. Using CommandBox is a great and simple way to get a local development environment of your choice up and running quickly.
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, 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 CFWheels structures your code for you. As you start working with CFWheels applications, you'll see that most of the code you write is very nicely separated into one of these 3 categories.
CFWheels requires that you use one of these CFML engines:
Adobe ColdFusion 2018 / 2021 / 2023
Lucee 5.2.1.9+ / 6
Your ColdFusion or Lucee engine can be installed on Windows, Mac, UNIX, or Linux—they all work just fine.
You also need a web server. CFWheels 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 CommandBox.
Don't worry though. CFWheels 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 URL Rewriting chapter.
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
CFWheels maybe incompatible with newer MySQL JDBC drivers. It is recommended you downgrade the driver to version 5.1.x for full ORM functionality.
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 CFWheels 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 here.
We also recommend using the InnoDB engine if you want Transactions to work.
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 CFWheels, and a suitable project to start with.
Instructions for installing CFWheels on your system.
Installing CFWheels 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 CFWheels...
You have 2 choices when downloading CFWheels. You can either use the latest official release of CFWheels, or you can take a walk on the wild side and go with the latest committed source code in our Git repository.
The latest official releases can always be found in the Releases section of GitHub, and the Git repository is available at our GitHub repo.
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...
Getting an empty website running with CFWheels 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.
In case you're not sure, here are the instructions for setting up an empty CFWheels 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 Requirements for a list of tested systems).
Create a new folder under your web root (usually C:\Inetpub\wwwroot
) named wheels_site
and unzip the CFWheels .zip
file into the root of it.
Create a new website using IIS called CFWheels Site
with localhost
as the host header name and C:\Inetpub\wwwroot\mysite
as the path to your home directory.
If you want to run a CFWheels-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 URL Rewriting chapter.)
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.
If you don't want to be bothered by opening up a CFWheels 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 CFWheels will use that when you haven't set the dataSourceName
setting using the Set() function.
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 page saying "Welcome to CFWheels!"
That's it. You're done. This is where the fun begins!
Instructions for upgrading CFWheels applications
Generally speaking, upgrading CFWheels 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.
Adobe Coldfusion 2016 and below are no longer compatible with CFWheels going forward. Consequently, these versions have been removed from the Wheels Internal Test Suites.
Prefix global and controller functions with application.wo
. For example, change the model("")
function in your controllers to application.wo.model("")
.
After installing CFWheels 3.x, you'll have to run box install
to intall testbox and wirebox in your application as they are not shipped with CFWheels 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
.
Replace the wheels
folder with the new one from the 3.0.0 download.
Move the wheels
folder inside the vendor
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.
Replace the wheels
folder with the new one from the 2.3.0 download.
Replace the wheels
folder with the new one from the 2.2.0 download.
Replace the wheels
folder with the new one from the 2.1.0 download.
Rename any instances of findLast()
to findLastOne()
Create /events/onabort.cfm
to support the onAbort
method
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.
CFWheels 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)
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)
By default, this is limited to GET
requests for security reasons.
It is strongly recommended that you enable CFWheels 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 POST
ing data to the actions that require post
, patch
, and delete
verbs.
If you have previously been using the dbmigrate plugin, you can now use the inbuilt version within the CFWheels 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.
Replace the wheels
folder with the new one from the 1.4 download.
Replace URL rewriting rule files – i.e, .htaccess
, web.config
, IsapiRewrite.ini
In addition, if you're upgrading from an earlier version of CFWheels, we recommend reviewing the instructions from earlier reference guides below.
If you are upgrading from CFWheels 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 CFWheels, 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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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
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).
CFWheels 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 CFWheels 3.x, will replace RocketUnit as the default testing framework.
Starting with CFWheels 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 CFWheels 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 CFWheels 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 .
Tutorials, demonstrations, and presentations about the ColdFusion on Wheels framework.
Create a basic CRUD interface in CFWheels 2.x https://youtu.be/K5HLItTru1g
Create a basic JSON API in CFWheels 2.x https://youtu.be/qZr5JzO0vo4
Routing in CFWheels 2.x - Part 1 https://youtu.be/BnPGApAvMVQ
Routing in CFWheels 2.x - Part 2 https://youtu.be/0CiGxJyJEIQ
Introduction to Unit Testing in CFWheels 2.x https://youtu.be/XgMuzzmBQ98
Unit Testing Controllers in CFWheels 2.x https://youtu.be/cygj9WDqHjY
Please note that all the webcasts below were created with CFWheels 1.x in mind, and are listed here as they might still be useful to those starting out.
Episode 1: "C" Is for "Create" - Basic CRUD Learn about basic create operations when building standard CRUD functionality in CFWheels
Episode 2: "R"; Is for "Read" - Basic CRUD Learn about basic read operations when building standard CRUD functionality in CFWheels
Episode 3: "U" Is for "Update" - Basic CRUD Chris Peters demonstrates updating data in a simple CRUD CFWheels application
Episode 4: "D" Is for Delete - Basic CRUD Learn how simple it is to delete records in a basic CRUD application using CFWheels
Episode 1: Setting up ColdFusion on Wheels Chris Peters starts the webcast series by demonstrating how to set up ColdFusion on Wheels
Episode 2: Form Helpers Chris Peters demonstrates how to bind a Wheels model object to a form through the use of form helpers
Episode 3: Object Validation and Showing Errors
Chris Peters adds data validation to the user registration form
Episode 4: Redirects and the Flash 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
Episode 5: Object Validation Chris Peters teaches you about more validation options and how you can add them to the registration form quickly and easily
Episode 6: Styling Forms Chris Peters stylizes form markup globally using a Wheels feature called global helpers
Episode 7: Authentication with Filters Learn how to set up simple user authentication on a website by using a Wheels feature called filters
Episode 8: Reading and Displaying a Single Record Learn the mechanics of reading a single record from the database and displaying its data in the view
Episode 9: Adding a Route for User Profiles Creating custom URL patterns is a breeze in ColdFusion on Wheels
Episode 10: Displaying Sets of Records Learn how to fetch multiple records from your model with findAll() and then display them to the user using ColdFusion on Wheels
Episode 11: Custom View Helpers Learn how to factor out logic in your view templates into custom helper functions in ColdFusion on Wheels
Episode 12: Joining Models with Associations Chris Peters demonstrates joining data together with model associations using ColdFusion on Wheels
Episode 13: Pagination All it takes to offer pagination is two extra arguments to findAll() and a call to a view helper called paginationLinks()
Episode 14: Responding with Multiple Formats Learn how to use the provides() and renderWith() functions to automatically serialize data into XML, JSON, and more
Hello World Peter Amiri walks you through setting up a "Hello World" application using the ColdFusion on Wheels framework
CFUnited 2010: Simplifying Database Code with the ColdFusion on Wheels ORM Chris Peters gives a high level overview of the ORM included with ColdFusion on Wheels
ColdRoute Plugin Chris Peters from Liquifusion demonstrates the ColdRoute plugin for CFWheels
Wirebox Plugin for CFWheels Doug Boude demonstrates using his new Wirebox plugin for CFWheels
DBMigrate Create Operations Chris Peters from Liquifusion demonstrates creating tables and records in the DBMigrate plugin for ColdFusion on Wheels
CF Meetup, March 10 2011 Online ColdFusion Meetup (coldfusionmeetup.com) session for March 10 2011, "What's New in CFWheels 1.1", with Chris Peters:
CFWheels Textmate Bundle Demo A quick demo of the CFWheels Textmate bundle by Russ Johnson
The command line tools extends the functionality of CommandBox with some commands specifically designed for CFWheels development.
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 CFWheels plugins and templates. More on that later.
One module that we have created is a module that extends CommandBox itself with commands and features specific to the CFWheels framework. The CFWheels CLI module for CommandBox is modeled after the Ruby on Rails CLI module and gives similar capabilities to the CFWheels developer.
The first step is to get CommandBox 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 Chocolatey on Windows, Homebrew 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.
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.
Okay, now that we have CommandBox installed, let's add the CFWheels CLI module.
install cfwheels-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 CFWheels application or scaffold out sections of your application. We'll see some of these commands in action momentarily.
These tools allow you to adopt a more modern workflow and allow you to create and manipulate many CFWheels objects from the command line. By making these tools available in the command line, not only will you be able to speed up your development but you can also utilize these commands in Continuous Integration (CI) and Continuous Deployment (CD) work flows.
These are the top level commands in the wheels
namespace.
wheels info
This command is the most basic of the commands and other than printing some pretty ASCII art it also displays the Current Working Directory, the CommandBox Module Root which can be handy when trying to diagnose version discrepancies, and lastly the CFWheels version currently installed. The version is determined from a variety of sources. First and foremost, if there is a box.json
file in the vendor/wheels/
directory the version is extracted from that box.json
. Alternatively, if there is no box.json
file in the wheels/
directory, we look in vendor/wheels/events/onapplicationstart.cfm
and extract a version number from that file. That is the version number that is displayed on the default congratulations screen by the way. If both of these fail to get us a version number we can use, we ask you to let us know what version of wheels you are using and give you the option of generating a box.json
file. This is handy for bringing old legacy installations under CLI control.
wheels init
This will attempt to bootstrap an EXISTING wheels app to work with the CLI.
We'll assume the database/datasource exists and the other config options like reloadpassword is all set up. If there's no box.json, create it, and ask for the version number if we can't determine it. If there's no server.json, create it, and ask for cfengine preferences. We'll ignore other templating objects for now, as this will probably be in place too.
wheels reload
This command will reload your CFWheels application. In order for this command to work, your local server needs to be started and running. This command basically issues a request to the running CFWheels application to reload as if you were doing it from your browsers address bar. You will be prompted for your reload password that will be passed to the reload endpoint.
wheels test
This command will call the Test Runner in a running server and return the results. The command can be run from within the directory of the running server or you can specify the server name to run against. Additionally you can specify the test suite to run, possible choices include the running application's test suite (app), the core framework test suite (core), or a particular plugin's test suite by passing in the plugin name.
wheels scaffold
This command will completely scaffold a new object. Typically you would run this command to stub out all the CRUD related files and then follow it up with a series of wheels g property
commands to add the individual fields to the object. This command will:
Create a model file
A Default CRUD Controller complete with create/edit/update/delete code
View files for all those actions
Associated test stubs
DB migration file
This command can be run without the server running except the database migration portion because that requires a running database. So if your server is already up and running you can run this command completely including the database migration portion. Afterwards make sure to run wheels reload
to reload your application since we just made model changes. If the server isn't running, you can run this command and stub out all the files, then start your server with server start
and finally migrate the database with wheels db latest
.
wheels destroy
This command will destroy a given object. This is highly destructive, given the name, so proceed with caution. If you created an object using the wheels scaffold [objectName]
command, this command is the inverse of that command and will remove all elements created by that command.
This command will destroy the following elements:
the models definition file
the controllers definition file
the view sub folder and all it's contents
the model test file
the controller test file
the views test file
resource route configuration
Parameter | Required | Default | Description |
---|
Parameter | Required | Default | Description |
---|
Parameter | Required | Default | Description |
---|
Parameter | Required | Default | Description |
---|
mode | false | development | possible values development, testing, maintenance, production |
type | false | app | Either Core, App, or the name of the plugin |
servername | false | Servername to run the tests against |
reload | false | false | Force Reload |
debug | false | false | Output passing tests as well as failing ones |
format | false | json | Force a specific return format for debug |
adapter | false | Attempt to override what dbadapter wheels uses |
Name | true | Name of the object to scaffold out |
Name | true | Name of the object to destroy |
These are the commands in the wheels generate
namespace. These commands are called by some of the top level commands but you can use them directly to speed up your development process.
wheels generate
The wheels generate
command is what CommandBox calls a namespace and contains many sub commands beneath it. It's main purpose is to isolate those sub commands so there is no name collisions. It also has an alias of wheels g
which allows you to shorten the commands you have to type. However, we have opted to show all the commands with their full names in the list below.
wheels generate app-wizard
Creates a new CFWheels application using our wizard to gather all the necessary information. This is the recommended route to start a new application.
This command will ask for:
An Application Name (a new directory will be created with this name)
Template to use
A reload Password
A datasource name
What local cfengine you want to run
If using Lucee, do you want to setup a local h2 dev database
Do you want to initialize the app as a ForgeBox module
All these three commands are equivalent and will call the same wizard. The wizard in turn gathers all the required data and passes it all off to the wheels generate app
command to do all the heavy lifting.
Let's take a look at the wizard pages after issuing the wheels new
command:
You can accept the name offered or change it to whatever name you like. We try to clean up the name and take out special characters and spaces if we need to.
You can select a template to use for your app.
The datasource is something you'll have to take care of unless you opt to use the H2 Embedded database in a Lucee server. Here you can define the datasource name if you would like to use something different than the application name.
In this step you can choose what CF Engine and version to launch. Lucee has an embedded SQL compliant database server called H2. So if you use one of the Lucee options you can specify to use the H2 database as well. Notice the last item allows you to specify the module slug or URI to a CF Engine not on the list.
On this step you are asked if you'd like to use the H2 Database, in which case we can set everything up for you, or if you would prefer to use another database engine and will take care of setting up the database yourself.
On this last step, you are asked if you want us to include a box.json file so you can eventually submit this to ForgeBox.io for sharing with the world.
This is the confirmation screen that shows all the choices you've made and gives you one last chance to bail out. If you choose to continue, the choices you've made will be sent over to the wheels g app
command to do the actual app creation.
If you opted to continue you'll see a bunch of things scroll across your screen as the various items are downloaded and configured. Eventually you will see this status screen letting you know that everything was installed properly.
wheels generate app
Create a blank CFWheels app from one of our app templates or a template using a valid Endpoint ID which can come from ForgeBox, HTTP/S, git, github, etc.
By default an app named MyCFWheelsApp will be created in a sub directory called MyCFWheelsApp.
The most basic call...
This can be shortened to...
Here are the basic templates that are available for you that come from ForgeBox
CFWheels Base Template - Stable (default)
CFWheels Base Template - Bleeding Edge
CFWheels Template - HelloWorld
CFWheels Template - HelloDynamic
CFWheels Template - HelloPages
CFWheels Example App
CFWheels - TodoMVC - HTMX - Demo App
The template parameter can also be any valid Endpoint ID, which includes a Git repo or HTTP URL pointing to a package. So you can use this to publish your own templates to use to start new projects.
wheels generate route
Adds a default resources Route to the routes table. All the normal CRUD routes are automatically added.
wheels generate controller
I generate a controller in the app/controllers/
directory. You can either pass in a list of actions to stub out or the standard CRUD methods will be generated.
Create a user controller with full CRUD methods
Create a user object with just "index" and "customaction" methods
wheels generate model
This command generates a model in the app/models/
folder and creates the associated DB Table using migrations.
Create "users" table and "User.cfc" in models:
Create just "User.cfc" in models:
wheels generate property
This command generates a dbmigration file to add a property to an object and scaffold into _form.cfm and show.cfm if they exist (i.e, wheels generate property table columnName columnType).
Create the a string/textField property called firstname on the User model:
Create a boolean/Checkbox property called isActive on the User model with a default of 0:
Create a boolean/Checkbox property called hasActivated on the User model with a default of 1 (i.e, true):
Create a datetime/datetimepicker property called lastloggedin on the User model:
All columnType options: biginteger,binary,boolean,date,datetime,decimal,float,integer,string,limit,text,time,timestamp,uuid
wheels generate view
This command generates a view file in the app/views/
directory when specifying the object name and the action. If a directory for the object does not exist a subdirectory will be created in the app/views/
directory and the action NAME.cfm file place into it.
Create a default file called show.cfm without a template
Create a default file called show.cfm using the default CRUD template
The "crud/show" parameter of this command is referring to an existing template to be used to generate the "user/show" view. The prefix "crud" points to a templates directory in the app that holds a "crud" directory in which templates like "show" are placed. In this way the "crud/show" parameter fetches the show.txt template in the "templates/crud" directory and uses that for generating the "user/show" view.
wheels generate test
This command generates a test stub in /tests/Testbox/specs/TYPE/NAME.cfc
.
You can set what you want to use as your reload password or accept the default. Please make sure to change this before you go into production. Ideally this should be kept out of your source repository by using something like the (CFWheels DotEnvSettings Plugin)[].
Parameter | Required | Default | Description |
---|
Parameter | Required | Default | Description |
---|
Parameter | Required | Default | Description |
---|
Parameter | Required | Default | Description |
---|
Parameter | Required | Default | Description |
---|
Parameter | Required | Default | Description |
---|
Parameter | Required | Default | Description |
---|
name | false | MyCFWheelsApp | The name of the app you want to create |
template | false | base template | The name of the app template to use |
directory | false | mycfwheelsapp/ | The directory to create the app in |
reloadPassword | false | ChangeMe | The reload password to set for the app |
datasourceName | false | MyCFWheelsApp | The datasource name to set for the app |
cfmlEngine | false | Lucee | The CFML engine to use for the app |
setupH2 | false | false | Setup the H2 database for development |
init | false | false | "init" the directory as a package if it isn't already |
force | false | false | Force installation into an none empty directory |
objectname | true | The name of the resource to add to the routes table |
name | true | Name of the controller to create without the .cfc |
actionList | false | optional list of actions, comma delimited |
directory | false | if for some reason you don't have your controllers in controllers/ |
name | true | Name of the model to create without the .cfc: assumes singular |
db | false | Boolean attribute specifying if the database table should be generated as well |
name | true | Table Name |
columnName | false | Name of Column |
columnType | false | Type of Column on of biginteger, binary, boolean, date, datetime, decimal, float,integer, string, limit, text, time, timestamp, uuid |
default | false | Default Value for column |
null | false | Whether to allow null values |
limit | false | character or integer size limit for column |
precision | false | precision value for decimal columns, i.e. number of digits the column can hold |
scale | false | scale value for decimal columns, i.e. number of digits that can be placed to the right of the decimal point (must be less than or equal to precision) |
objectname | true | View path folder, i.e user |
name | true | Name of the file to create, i.e, edit |
template | false | template (used in Scaffolding) - options crud/_form,crud/edit,crud/index,crud/new,crud/show |
type | true | Type of test to generate. Options are model, controller, and view |
objectname | true | View path folder or name of object, i.e user |
name | true | Name of the action/view |
These are the commands in the wheels dbmigrate
namespace. They allow you to manipulate the database structure and script the changes. This makes is easy to share your changes with coworkers, check them into source control, or apply them automatically when you deploy your code to production.
wheels dbmigrate
This is another namespace with sub commands within it. It also has an alias of wheels db
which allows you to shorten the command you need to type.
wheels dbmigrate info
This command doesn't take any inputs but simply tries to communicate with the running server and gather information about your migrations and displays them in a table.
From the information presented in the two tables you can see how many migration files are in your application and of those how many have already been applied and available to be applied. You are also presented with the datasource name and database type information.
wheels dbmigrate latest
This command will migrate the database to the latest version. This command will apply each migration file from the current state all the way to the latest one at a time. If a SQL error is encountered in one of the files, the command will stop at that point and report the error.
wheels dbmigrate reset
This command will migrate the database to version 0 effectively resetting the database to nothing.
wheels dbmigrate up
This command will process the next migration file from the current state. If the database is already at the latest version this command will have no effect.
wheels dbmigrate down
This command will reverse the current migration file and take the database one step backwards. If the database is already at version 0 then this command will have no effect.
wheels dbmigrate exec
This command will run a particular migration file and take the database to that version. The four directional migration commands above latest
, reset
, up
, and down
each in turn call this command to process their intended action.
wheels dbmigrate create table
This command will generate a new migration file for creating a table in the database. Keep in mind you will still have to run the migration file but this will add it to the migration history.
wheels dbmigrate create column
This command will generate a new migration file for adding a new column to an existing table.
wheels dbmigrate create blank
wheels dbmigrate remove table
This command generates a migration file to remove a table. The migration file is will still need to be run individual but this command gets the migration generated and into the history.
Parameter | Required | Default | Description |
---|---|---|---|
Parameter | Required | Default | Description |
---|---|---|---|
Parameter | Required | Default | Description |
---|---|---|---|
Parameter | Required | Default | Description |
---|---|---|---|
version
true
The version to migrate the database to
name
true
The name of the database table to create
force
false
false
Force the creation of the table
id
false
true
Auto create ID column as autoincrement ID
primaryKey
false
ID
Overrides the default primary key column name
name
true
The name of the database table to modify
columnType
true
The column type to add
columnName
false
The column name to add
default
false
The default value to set for the column
null
false
true
Should the column allow nulls
limit
false
The character limit of the column
precision
false
The precision of the numeric column
scale
false
The scale of the numeric column
name
true
The name of the database table to remove
With a convention-over-configuration framework like CFWheels, it's important to know these conventions. This is your guide.
There is a specific set of standards that CFWheels follows when you run it in its default state. This is to save you time. With conventions in place, you can get started coding without worrying about configuring every little detail.
But it is important for you to know these conventions, especially if you're running an operating system and/or DBMS configuration that's picky about things like case sensitivity.
CFWheels uses a very flexible routing system to match your application's URLs to controllers, views, and parameters.
Within this routing system is a default route that handles many scenarios that you'll run across as a developer. The default route is mapped using the pattern [controller]/[action]/[key]
.
Consider this example URL:http://localhost/users/edit/12
http://localhost/users/edit/12
This maps to the Users
controller, edit
action, and a key of 12
. For all intents and purposes, this will load a view for editing a user with a primary key value in the database of 12
.
This URL pattern works up the chain and will also handle the following example URLs:
Note that the above conventions are for GET
requests and only apply when you have a wildcard()
call in app/config/routes.cfm
(which is the default). See Routing for instructions on overriding this behavior and how to deal with PUT
, POST
etc.
Controllers, actions, and views are closely linked together by default. And how you name them will influence the URLs that CFWheels will generate.
First, a controller is a CFC file placed in the controllers
folder. It should be named in PascalCase
. For example, a site map controller would be stored at app/controllers/SiteMap.cfc
.
Multi-word controllers will be delimited by hyphens in their calling URLs. For example, a URL of /site-map
will reference the SiteMap
controller.
See Routing for instructions on overriding this behavior.
Methods within the controllers, known as actions, should be named in camelCase
.
Like with controllers, any time a capital letter is used in camelCase
, a hyphen will be used as a word delimiter in the corresponding URL. For example, a URL of /site-map/search-engines
will reference the searchEngines
action in the SiteMap
controller.
See Routing for instructions on overriding this behavior.
By default, view files are named after the action names and are stored in folders that correspond to controller names. Both the folder names and view file names should be all lowercase, and there is no word delimiter.
In our /site-map/search-engines
URL example, the corresponding view file would be stored at app/views/sitemap/searchengines.cfm
.
For information on overriding this behavior, refer to documentation for the renderView() function and read the Pages chapter.
A special type of view file called a layout defines markup that should surround the views loaded by the application. The default layout is stored at app/views/layout.cfm
and is automatically used by all views in the application.
Controller-level layouts can also be set automatically by creating a file called layout.cfm
and storing it in the given controller's view folder. For example, to create a layout for the users
controller, the file would be stored at app/views/users/layout.cfm
.
When a controller-level layout is present, it overrides the default layout stored in the root app/views
folder.
For information on overriding the layout file to be loaded by an action, see the chapter on Layouts and documentation for the renderView function.
By default, the names of CFWheels models, model properties, database tables, and database fields all relate to each other. CFWheels even sets a sensible default for the CFML data source used for database interactions.
By default, the datasource is set to wheels.fw
in the app/config/settings.cfm
file. You can change the value in the set(dataSourceName="wheels.fw")
function to whatever you want the name of teh datasource to be.
Refer to the Configuration and Defaults chapter for instructions on overriding data source information.
CFWheels adopts a Rails-style naming conventions for database tables and model files. Think of a database table as a collection of model objects; therefore, it is named with a plural name. Think of a model object as a representation of a single record from the database table; therefore, it is named with a singular word.
For example, a user
model represents a record from the users
database table. CFWheels also recognizes plural patterns like binary/binaries
, mouse/mice
, child/children
, etc.
Like controller files, models are also CFCs and are named in PascalCase
. They are stored in the app/models
folder. So the user model would be stored at app/models/User.cfc
.
For instructions on overriding database naming conventions, refer to documentation for the table() function and the chapter on Object Relational Mapping.
In your database, both table names and column names should be lowercase. The customersegments
table could have fields called title
, regionid
, and incomelevel
, for example.
Because of CFML's case insensitive nature, we recommend that you refer to model names and corresponding properties in camelCase
. This makes for easier readability in your application code.
In the customersegments
example above, you could refer to the properties in your CFML as title
, regionId
, and incomeLevel
to stick to CFML's Java-style roots. (Built-in CFML functions are often written in camelCase
and PascalCase
, after all.)
For information on overriding column and property names, refer to documentation for the property() function and the Object Relational Mapping chapter.
There are many default values and settings that you can tweak in CFWheels when you need to. Some of them are conventions and others are just configurations available for you to change. You can even change argument defaults for built-in CFWheels functions to keep your code DRYer.
For more details on what you can configure, read the Configuration and Defaults chapter.
URL | Controller | Action | Key |
---|---|---|---|
users
edit
12
users
new
users
index
An overview of CFWheels configuration and how is it used in your applications. Learn how to override a CFWheels convention to make it your own.
We all love the "Convention over Configuration" motto of CFWheels, but what about those two cases that pop into everyone's head? What if I want to develop in my own way? Or, What about an existing application that I need to port into CFWheels? Gladly, that's what configuration and defaults are there for. Let's take a look at exactly how this is performed.
You will find configuration files in the app/config
folder of your CFWheels application. In general, most of your settings will go in app/config/settings.cfm
.
You can also set values based on what environment you have set. For example, you can have different values for your settings depending on whether you're in development
mode or production
mode. See the chapter on Switching Environments for more details.
To change a CFWheels application default, you generally use the set() function. With it, you can perform all sorts of tweaks to the framework's default behaviors.
Use the get() function to access the value of a CFWheels application setting. Just pass it the name of the setting.
In CFML's standard Application.cfc
, you can normally set values for your application's properties in the this
scope. CFWheels still provides these options to you in the file at app/config/app.cfm
.
Here is an example of what can go in app/config/app.cfm
:
There are several types of configurations that you can perform in CFWheels to override all those default behaviors. In CFWheels, you can find all these configuration options:
Let's take a closer look at each of these options.
Not only are the environments useful for separating your production settings from your "under development" settings, but they are also opportunities for you to override settings that will only take effect in a specified environment.
The setting for the current environment can be found in app/config/environment.cfm
and should look something like this:
Full Listing of Environment Settings
Sometimes it is useful for our applications to "force" URL rewriting. By default, CFWheels will try to determinate what type of URL rewriting to perform and set it up for you. But you can force in or out this setting by using the example below:
The code above will tell CFWheels to skip its automatic detection of the URL Rewriting capabilities and just set it as "Off"
.
You can also set it to "Partial" if you believe that your web server is capable of rewriting the URL as folders after index.cfm
.
For more information, read the chapter about URL Rewriting.
Probably the most important configuration of them all. What is an application without a database to store all of its precious data?
The data source configuration is what tells CFWheels which database to use for all of its models. (This can be overridden on a per-model basis, but that will be covered later.) To set this up in CFWheels, it's just as easy as the previous example:
OK, here it's where the fun begins! CFWheels includes a lot of functions to make your life as a CFML developer easier. A lot of those functions have sensible default argument values to minimize the amount of code that you need to write. And yes, you guessed it, CFWheels lets you override those default argument values application-wide.
Let's look at a little of example:
That little line of code will make all calls to the findAll() method in CFWheels return a maximum number of 20 record per page (if pagination is enabled for that findAll() call). How great is that? You don't need to set the perPage
value for every single call to findAll() if you have a different requirement than the CFWheels default of 10 records.
You'll generally want to configure how CFWheels handles errors and debugging information based on your environment. For example, you probably won't want to expose CFML errors in your production environment, whereas you would want to see those errors in your development environment.
For example, let's say that we want to enable debugging information in our "development" environment temporarily:
Full Listing of Debugging and Error Settings
For more information, refer to the chapter about Switching Environments.
CFWheels does a pretty good job at caching the framework and its output to speed up your application. But if personalization is key in your application, finer control over caching settings will become more important.
Let's say your application generates dynamic routes and you need it to check the routes on each request. This task will be as simple as this line of code:
Full Listing of Caching Settings
For more information, refer to the chapter on Caching.
The CFWheels ORM provides many sensible conventions and defaults, but sometimes your data structure requires different column naming or behavior than what CFWheels expects out of the box. Use these settings to change those naming conventions or behaviors across your entire application.
For example, if we wanted to prefix all of the database table names in our application with blog_
but didn't want to include that at the beginning of model names, we would do this:
Now your post
model will map to the blog_posts
table, comment
model will map to the blog_comments
table, etc.
Full Listing of ORM Settings
There are several settings that make plugin development more convenient. We recommend only changing these settings in development
mode so there aren't any deployment issues in production
, testing
, and maintenance
modes. (At that point, your plugin should be properly packaged in a zip file.)
If you want to keep what's stored in a plugin's zip file from overwriting changes that you made in its expanded folder, set this in app/config/development/settings.cfm
:
See the chapter on Installing and Using Plugins for more information.
Configure how CFWheels handles linking to assets through view helpers like imageTag(), styleSheetLinkTag(), and javaScriptIncludeTag().
See the chapter about Date, Media, and Text Helpers for more information.
Full Listing of Asset Settings
CFWheels includes a powerful routing system. Parts of it are configurable with the following settings.
See the chapters about Using Routes and Obfuscating URLs for more information about how this all works together.
Full Listing of Miscellaneous Settings
CFWheels has a multitude of view helpers for building links, forms, form elements, and more. Use these settings to configure global defaults for their behavior.
CFWheels includes built-in Cross-Site Request Forgery (CSRF) protection for form posts. Part of the CSRF protection involves storing an authenticity token in the session (default) or within an encrypted cookie. Most of the settings below are for when you've chosen to store the authenticity token within a cookie instead of the server's session store.
CFWheels includes built-in Cross-Origin Resource Sharing (CORS) which allows you to configure which cross-origin requests and methods are allowed. By default, this feature is turned off which will deny cross origin requests at the browser level.
In this first version, the user can enable this feature, which will allow requests from all origins and all methods.
Name | Type | Default | Description |
---|---|---|---|
Name | Type | Default | Description |
---|---|---|---|
Name | Type | Default | Description |
---|---|---|---|
Name | Type | Default | Description |
---|---|---|---|
Name | Type | Default | Description |
---|---|---|---|
Name | Type | Default | Description |
---|---|---|---|
Name | Type | Default | Description |
---|---|---|---|
Name | Type | Default | Description |
---|---|---|---|
Name | Type | Default | Description |
---|---|---|---|
Name | Type | Default |
---|---|---|
Name | Type | Default | Description |
---|---|---|---|
Setting | Type | Default | Description |
---|---|---|---|
environment
string
development
Environment to load. Set this value in app/config/environment.cfm. Valid values are development, testing, maintenance, and production.
reloadPassword
string
[empty string]
Password to require when reloading the CFWheels application from the URL. Leave empty to require no password.
redirectAfterReload
boolean
Enabled in maintenance and production
Whether or not to redirect away from the current URL when it includes a reload request. This hinders accidentally exposing your application's reload URL and password in web analytics software, screenshots of the browser, etc.
ipExceptions
string
[empty string]
IP addresses that CFWheels will ignore when the environment is set to maintenance. That way administrators can test the site while in maintenance mode, while the rest of users will see the message loaded in events/onmaintenance.cfm.
allowEnvironmentSwitchViaUrl
boolean
true
Set to false to disable switching of environment configurations via URL. You can still reload the application, but switching environments themselves will be disabled.
errorEmailServer
string
[empty string]
Server to use to send out error emails. When left blank, this defaults to settings in the ColdFusion Administrator (if set).
errorEmailAddress
string
[empty string]
Comma-delimited list of email address to send error notifications to. Only applies if sendEmailOnError is set to true.
errorEmailSubject
string
Error
Subject of email that gets sent to administrators on errors. Only applies if sendEmailOnError is set to true.
excludeFromErrorEmail
string
[empty string]
List of variables (or entire scopes) to exclude from the scope dumps included in error emails. Use this to keep sensitive information from being sent in plain text over email.
sendEmailOnError
boolean
Enabled in production environments that have a TLD like .com, .org, etc.
When set to true, CFWheels will send an email to administrators whenever CFWheels throws an error.
showDebugInformation
boolean
Enabled in development mode.
When set to true, CFWheels will show debugging information in the footers of your pages.
showErrorInformation
boolean
Enabled in development, maintenance, and testing mode.
When set to false, CFWheels will run and display code stored at events/onerror.cfm instead of revealing CFML errors.
cacheActions
boolean
Enabled in maintenance, testing, and production
When set to true, CFWheels will cache output generated by actions when specified (in a caches() call, for example).
cacheControllerConfig
boolean
Enabled in development, maintenance, testing, and production
When set to false, any changes you make to the config() function in the controller file will be picked up immediately.
cacheCullInterval
numeric
5
Number of minutes between each culling action. The reason the cache is not culled during each request is to keep performance as high as possible.
cacheCullPercentage
numeric
10
If you set this value to 10, then at most, 10% of expired items will be deleted from the cache.
cacheDatabaseSchema
boolean
Enabled in development, maintenance, testing, and production
When set to false, you can add a field to the database, and CFWheels will pick that up right away.
cacheFileChecking
boolean
Enabled in development, maintenance, testing, and production
When set to true, CFWheels will cache whether or not controller, helper, and layout files exist
cacheImages
boolean
Enabled in development, maintenance, testing, and production
When set to true, CFWheels will cache general image information used in imageTag() like width and height.
cacheModelConfig
boolean
Enabled in development, maintenance, testing, and production
When set to false, any changes you make to the config() function in the model file will be picked up immediately.
cachePages
boolean
Enabled in maintenance, testing, and production
When set to true, CFWheels will cache output generated by a view page when specified (in a renderView() call, for example).
cachePartials
boolean
Enabled in maintenance, testing, and production
When set to true, CFWheels will cache output generated by partials when specified (in a includePartial() call for example).
cacheQueries
boolean
Enabled in maintenance, testing, and production
When set to true, CFWheels will cache SQL queries when specified (in a findAll() call, for example).
clearQueryCacheOnReload
boolean
true
Set to true to clear any queries that CFWheels has cached on application reload.
cacheRoutes
boolean
Enabled in development, maintenance, testing, and production
When set to true, CFWheels will cache routes across all page views.
defaultCacheTime
numeric
60
Number of minutes an item should be cached when it has not been specifically set through one of the functions that perform the caching in CFWheels (i.e., caches(), findAll(), includePartial(), etc.)
maximumItemsToCache
numeric
5000
Maximum number of items to store in CFWheels's cache at one time. When the cache is full, items will be deleted automatically at a regular interval based on the other cache settings.
afterFindCallbackLegacySupport
boolean
true
When this is set to false and you're implementing an afterFind() callback, you need to write the same logic for both the this scope (for objects) and arguments scope (for queries). Setting this to false makes both ways use the arguments scope so you don't need to duplicate logic. Note that the default is true for backwards compatibility.
automaticValidations
boolean
true
Set to false to stop CFWheels from automatically running object validations based on column settings in your database.
setUpdatedAtOnCreate
boolean
true
Set to false to stop CFWheels from populating the updatedAt timestamp with the createdAt timestamp's value.
softDeleteProperty
string
deletedAt
Name of database column that CFWheels will look for in order to enforce soft deletes.
tableNamePrefix
string
[empty string]
String to prefix all database tables with so you don't need to define your model objects including it. Useful in environments that have table naming conventions like starting all table names with tbl
timeStampOnCreateProperty
string
createdAt
Name of database column that CFWheels will look for in order to automatically store a "created at" time stamp when records are created.
timeStampOnUpdateProperty
string
updatedAt
Name of the database column that CFWheels will look for in order to automatically store an "updated at" time stamp when records are updated.
transactionMode
string
commit
Use commit, rollback, or none to set default transaction handling for creates, updates and deletes.
useExpandedColumnAliases
boolean
false
When set to true, CFWheels will always prepend children objects' names to columns included via findAll()'s include argument, even if there are no naming conflicts. For example, model("post").findAll(include="comment") in a fictitious blog application would yield these column names: id, title, authorId, body, createdAt, commentID, commentName, commentBody, commentCreatedAt, commentDeletedAt. When this setting is set to false, the returned column list would look like this: id, title, authorId, body, createdAt, commentID, name, commentBody, commentCreatedAt, deletedAt.
modelRequireConfig
boolean
false
Set to true to have CFWheels throw an error when it can't find a config() method for a model. If you prefer to always use config() methods, this setting could save you some confusion when it appears that your configuration code isn't running due to misspelling "config" for example.
deletePluginDirectories
boolean
true
When set to true, CFWheels will remove subdirectories within the plugins folder that do not contain corresponding plugin zip files. Set to false to add convenience to the process for developing your own plugins.
loadIncompatiblePlugins
boolean
true
Set to false to stop CFWheels from loading plugins whose supported versions do not match your current version of CFWheels.
overwritePlugins
boolean
true
When set to true, CFWheels will overwrite plugin files based on their source zip files on application reload. Setting this to false makes plugin development easier because you don't need to keep rezipping your plugin files every time you make a change.
showIncompatiblePlugins
boolean
false
When set to true, an incompatibility warning will be displayed for plugins that do not specify the current CFWheels version.
assetQueryString
boolean
false in development mode, true in the other modes
Set to true to append a unique query string based on a time stamp to JavaScript, CSS, and image files included with the media view helpers. This helps force local browser caches to refresh when there is an update to your assets. This query string is updated when reloading your CFWheels application. You can also hard code it by passing in a string.
assetPaths
struct
false
Pass false or a struct with up to 2 different keys to autopopulate the domains of your assets: http (required) and https. For example: {http="asset0.domain1.com,asset2.domain1.com,asset3.domain1.com", https="secure.domain1.com"}
loadDefaultRoutes
boolean
true
Set to false to disable CFWheels's default route patterns for controller/action/key, etc.
obfuscateUrls
boolean
false
Set to true to obfuscate primary keys in URLs.
booleanAttributes
any
allowfullscreen, async, autofocus, autoplay, checked, compact, controls, declare, default, defaultchecked, defaultmuted, defaultselected, defer, disabled, draggable, enabled, formnovalidate, hidden, indeterminate, inert, ismap, itemscope, loop, multiple, muted, nohref, noresize, noshade, novalidate, nowrap, open, pauseonexit, readonly, required, reversed, scoped, seamless, selected, sortable, spellcheck, translate, truespeed, typemustmatch, visible
A list of HTML attributes that should be allowed to be set as boolean values when added to HTML tags (e.g. disabled
instead of disabled="disabled"
). You can also pass in true
(all attributes will be boolean) or false
(no boolean attributes allowed, like in XHTML).
csrfStore
string
session
Which storage strategy to use for storing the CSRF authenticity token. Valid values are session
or cookie
.
Choosing session
requires no additional configuration.
Choosing cookie
for this requires additional configuration listed below.
csrfCookieEncryptionAlgorithm
string
AES
Encryption algorithm to use for encrypting the authenticity token cookie contents. This setting is ignored if you're using session
storage. See your CF engine's documentation for the Encrypt()
function for more information.
csrfCookieEncryptionSecretKey
string
Secret key used to encrypt the authenticity token cookie contents. This value must be configured to a string compatible with the csrfCookieEncryptionAlgorithm
setting if you're using cookie
storage. This value is ignored if you're using session
storage. See your CF engine's documentation for the Encrypt()
function for more information.
csrfCookieEncryptionEncoding
string
Base64
Encoding to use to write the encrypted value to the cookie. This value is ignored if you're using session
storage. See your CF engine's documentation for the Encrypt()
function for more information.
csrfCookieName
string
_wheels_authenticity
The name of the cookie to be set to store CSRF token data. This value is ignored if you're using session
storage.
csrfCookieDomain
string
Domain to set the cookie on. See your CF engine's documentation for cfcookie
for more information.
csrfCookieEncodeValue
boolean
Whether or not to have CF encode the cookie. See your CF engine's documentation for cfcookie
for more information.
csrfCookieHttpOnly
boolean
true
Whether or not the have CF set the cookie as HTTPOnly
. See your CF engine's documentation for cfcookie
for more information.
csrfCookiePath
string
/
Path to set the cookie on. See your CF engine's documentation for cfcookie
for more information.
csrfCookiePreserveCase
boolean
Whether or not to preserve the case of the cookie's name. See your CF engine's documentation for cfcookie
for more information.
csrfCookieSecure
boolean
Whether or not to only allow the cookie to be delivered over the HTTPS protocol. See your CF engine's documentation for cfcookie
for more information.
allowCorsRequests
boolean
false
disableEngineCheck
boolean
false
Set to true
if you don't want CFWheels to block you from using older CFML engines (such as ColdFusion 9, Railo etc).
enableMigratorComponent
boolean
true
Set to false
to completely disable the migrator component which will prevent any Database migrations
enablePluginsComponent
boolean
true
Set to false
to completely disable the plugins component which will prevent any plugin loading, and not load the entire plugins system
enablePublicComponent
boolean
true
Set to false
to completely disable the public component which will disable the GUI even in development mode
autoMigrateDatabase
Boolean
false
Automatically runs available migration on applicationstart.
migratorTableName
String
migratorversions
The name of the table that stores the versions migrated.
createMigratorTable
Boolean
true
Create the migratorversions database table.
writeMigratorSQLFiles
Boolean
false
Writes the executed SQL to a .sql file in the app/migrator/sql directory.
migratorObjectCase
String
lower
Specifies the case of created database object. Options are 'lower', 'upper' and 'none' (which uses the given value unmodified)
allowMigrationDown
Boolean
false (true in development mode)
Prevents 'down' migrations (rollbacks)
Finding your way around a Wheels application.
After downloading and unzipping Wheels, here's the directory structure that you will see:
app/ build/ db/ docker/ docs/ guides/ public/ tests/ vendor/ .env CFConfig.json box.json compose.yml server.json
Your configuration settings will be done in the app/config directory.
Your application code will end up in four of the folders, namely app/controllers, app/events, app/models, and app/views.
Static media files should be placed in the public/files, public/images, public/javascripts and public/stylesheets folders.
Place anything that need to be executed outside of the framework in the public/miscellaneous folder. The framework does not get involved when executing .cfm
files in this folder. (The empty Application.cfc
takes care of that.) Also, no URL rewriting will be performed in this folder, so it's a good fit for placing CFCs that need to be accessed remotely via <cfajaxproxy>
and Flash AMF binding, for example.
Place Wheels plugins in the app/plugins folder.
And the last directory? That's the framework itself. It exists in the vendor/wheels directory. Please go in there and have a look around. If you find anything you can improve or new features that you want to add, let us know!
Let's go through all of the files and directories now starting with the ones you'll spend most of your time in: the code directories.
This is where you create your controllers. You'll see two files in here already: Controller.cfc
and Wheels.cfc
. You can place functions inside Controller.cfc
to have that function shared between all the controllers you create. (This works because all your controllers will extend Controller
.) Wheels.cfc
is an internal file used by Wheels.
This is where you create your model files (or classes if you prefer that term). Each model file you create should map to one table in the database.
The setup in this directory is similar to the one for controllers, to share methods you can place them in the existing Model.cfc
file.
This is where you prepare the views for your users. As you work on your website, you will create one view directory for each controller.
If you want code executed when ColdFusion triggers an event, you can place it here (rather than directly in Application.cfc
).
Make all your configuration changes here. You can set the environment, routes, and other settings here. You can also override settings by making changes in the individual settings files that you see in the subdirectories.
Any files that you intend to deliver to the user using the sendFile()
function should be placed here. Even if you don't use that function to deliver files, this folder can still serve as file storage if you like.
For application-wide globally accessible functions
This is a good place to put your images. It's not required to have them here, but all Wheels functions that involve images will, by convention, assume they are stored here.
This is a good place to put your JavaScript files.
This is a good place to put your CSS files.
This is where unit tests for your application should go
Use this folder for code that you need to run completely outside of the framework. (There is an empty Application.cfc
file in here, which will prevent Wheels from taking part in the execution.)
This is most useful if you're using Flash to connect directly to a CFC via AMF binding or if you're using <cfajaxproxy>
in your views to bind directly to a CFC as well.
Place any plugins you have downloaded and want installed here.
Database Migration CFC files and generated SQL files (This directory will only visible once you start using the migrator)
This is the framework itself. When a new version of Wheels is released it is often enough to just drop it in here (unless there has been changes to the general folder structure).
This file is used by Apache, and you specifically need it for URL rewriting to work properly. If you're not using Apache, then you can safely delete it. No longer included by default in 2.x
If you use Tomcat and Tuckey, or CommandBox URL Rewriting, you'll need this file. Otherwise, you can safely delete it.
URL rewriting for version 7 of IIS. If you're not using IIS, then you can safely delete it. No longer included by default in 2.x
These are needed for the framework to run. No changes should be done to these files.
You can add more directories if you want to, of course. Just remember to include a blank Application.cfc
in those directories. Otherwise, Wheels will try to get itself involved with those requests as well.
\
Environments that match your development stages.
CFWheels allows you to set up different environments that match stages in your development cycle. That way you can configure different values that match what services to call and how your app behaves based on where you are in your development.
The Development environment is the most convenient one to use as you start building your application because it does not cache any data. Therefore, if you make any changes to your controllers and actions, for example, it will immediately be picked up by CFWheels.
Other environment modes cache this information in order to speed up your application as much as possible. Making changes to the database in these most modes will cause CFWheels to throw an error. (Although that can be avoided with a reload
call. More on that later.)
The fastest environment mode in terms of page load time is the Production mode. This is what you should set your application to run in before you launch your website.
By default, all new applications will start in the Development environment which is middle-of-the-road in terms of convenience versus speed.
Besides the 2 environments mentioned above, there are 2 more. Let's go through them all one by one so you can see the differences between them and choose the most appropriate one given your current stage of development.
Development
Shows friendly CFWheels specific errors as well as regular ColdFusion errors on screen.
Does not email you when an error is encountered.
Caches controller and model initialization (the config()
methods).
Caches the database schema.
Caches routes.
Caches image information.
Production
Caches everything that the Development mode caches.
Activates all developer controlled caching (actions, pages, queries and partials).
Shows your custom error page when an error is encountered.
Shows your custom 404 page when a controller or action is not found.
Sends an email to you when an error is encountered.
Testing
Same caching settings as the Production mode but using the error handling of the Development mode. (Good for testing an application at its true speed while still getting errors reported on screen.)
Maintenance
Shows your custom maintenance page unless the requesting IP address or user agent is in the exception list (set by calling set(ipExceptions="127.0.0.1")
in app/config/settings.cfm
or passed along in the URL as except=127.0.0.1
, or as except=myuseragentstring
to match against the user agent instead. Please note that if passing an exception on the URL using the except
parameter, you must also provide the password
parameter if a reload password has been define. This eliminates the possibility of a rouge actor breaking out of maintenance mode by simply adding an except
to the URL.
This environment mode comes in handy when you want to briefly take your website offline to upload changes or modify databases on production servers.
You change the current environment by modifying the app/config/environment.cfm
file. After you've modified it, you need to either restart the ColdFusion service or issue a reload
request. (See below for more info.)
The reload Request
Issuing a reload request is the easiest way to go from one environment to another. It's done by passing in reload as a parameter in the URL, like this:
This tells CFWheels to reload the entire framework (it will also run your code in the app/events/onapplicationstart.cfm
file), thus picking up any changes made in the app/config/environment.cfm
file.
Lazy Reloading
There's also a shortcut for lazy developers who don't want to change this file at all. To use it, just issue the reload request like this instead:
This will make CFWheels skip your app/config/environment.cfm
file and just use the URL value instead (testing
, in this case).
Password-Protected Reloads
For added protection, you can set the reloadPassword
variable in app/config/settings.cfm
. When set, a reload request will only be honored when the correct password is also supplied, like this:
Don't forget your reload password in production
You really don't want random users hitting ?reload=development on a production server, as it could potentially expose data about your application and error messages. Always set a reload password!
If you're deploying to a container based environment, or one that you know you'll never want to switch out of production mode, you can disable URL based environment switching completely via:
set(allowEnvironmentSwitchViaUrl = false);
This is just an additional check to ensure that your production mode acts in the way you expect! Application reloading is still allowed, but the configuration can not be altered.\
With CFWheels, writing automated tests for your application is part of the development lifecycle itself, and running the tests is as simple as clicking a link.
At some point, your code is going to break. Upgrades, feature enhancements, and bug fixes are all part of the development lifecycle. Quite often with deadlines, you don't have the time to test the functionality of your entire application with every change you make.
The problem is that today's fix could be tomorrow's bug. What if there were an automated way of checking if that change you're making is going to break something? That's where writing tests for your application can be invaluable.
In the past, writing test against your application meant downloading, configuring and learning a completely separate framework. This often caused more headaches than it was worth and was the reason why most developers didn't bother writing tests. With CFWheels, we've included a small and very simple testing framework based on RocketUnit to help address just this issue.
Like everything else in CFWheels, the testing framework is very simple, yet powerful. You don't need to remember a hundred different functions because CFWheels' testing framework contains only a handful.
In order to run tests against your application, all tests must reside in the tests
directory off the root of your CFWheels application, or within a subdirectory thereof.
When you run the tests for your application, CFWheels recursively scans your application's tests
directory for valid tests. Whilst you have freedom to organize your subdirectories, tests and supporting files any way you see fit, we would recommend using the directory structure below as a guide:
What are these directories for?
The "functions" directory might contain test packages that cover model methods, global or view helper functions.
The "requests" directory might contain test packages that cover controller actions and the output that they generate (views).
Any components that will contain tests must extend the wheels.Test
component:
If the testing framework sees that a component does not extend wheels.Test
, that component will be skipped. This lets you create and store any mock components that you might want to use with your tests and keep everything together.
Any test methods must begin their name with "test":
If a method does not begin with test
, it is ignored and skipped. This lets you create as many helper methods for your testing components as you want.
Do not var
-scope any variables used in your tests. In order for the testing framework to access the variables within the tests that you're writing, all variables need to be within the component's variables
scope. The easy way to do this is to just not var
variables within your tests, and your CFML engine will automatically assign these variables into the variables
scope of the component for you. You'll see this in the examples below.
When writing a group of tests, it's common for there to be some duplicate code, global configuration, and/or cleanup needs that need to be run before or after each test. In order to keep things DRY (Don't Repeat Yourself), the testing framework offers 2 special methods that you can optionally use to handle such configuration.
setup()
: Used to initialize or override any variables or execute any code that needs to be run before each test.
teardown()
: Used to clean up any variables or execute any code that needs to be ran after each test.
Example:
assert()
: This is the main method that you will be using when developing tests. To use, all you have to do is provide a quoted expression. The power of this is that ANY 'truthy' expression can be used.
An example test that checks that two values equal each other:
An example test that checks that the first value is less then the second value:
You get the idea since you've used these kinds of expressions a thousand times. If you think of the assert()
command as another way of using evaluate()
, it will all make sense. Remember that you can use any expression that evaluates to a boolean value, so if you can write assertions against structures, arrays, objects, you name it, you can test it!
An example test that checks that a key exists in a structure:
raised()
: Used when you want to test that an exception will be thrown. raised()
will raise and catch the exception and return to you the exception type (think cfcatch.type
). Just like assert()
, raised()
takes a quoted expression as its argument.
An example of raising the Wheels.TableNotFound
error when you specify an invalid model name:
debug()
: Will display its output after the test result so you can examine an expression more closely.
expression
(string) - a quoted expression to display
display
(boolean) - whether or not to display the output
TIP
Overloaded arguments will be passed to the internal cfdump
attributeCollection
The first part of your application that you are going to want to test against are your models because this is where all the business logic of your application lives. Suppose that we have the following model:
As you can see from the code above, our model has a beforeSave
callback that runs whenever we save a user object. Let's get started writing some tests against this model to make sure that our callback works properly.
First, create a test component called /tests/models/TestUserModel.cfc
, and in the setup
function, create an instance of the model that we can use in each test that we write. We will also create a structure containing some default properties for the model.
As you can see, we invoke our model by using the model()
method just like you would normally do in your controllers.
The first thing we do is add a simple test to make sure that our custom model validation works.
Now that we have tests to make sure that our model validations work, it's time to make sure that the callback works as expected when a valid model is created.
The next part of our application that we need to test is our controller. Below is what a typical controller for our user model would contain for creating and displaying a list of users:
Notice the return
in the create
action in the redirectTo()
method? The reason for this is quite simple, under the covers, when you call redirectTo()
, CFWheels is using cflocation
. As we all know, there is no way to intercept or stop a cflocation
from happening. This can cause quite a number of problems when testing out a controller because you would never be able to get back any information about the redirection.
To work around this, the CFWheels test framework will "delay" the execution of a redirect until after the controller has finished processing. This allows CFWheels to gather and present some information to you about what redirection will occur.
The drawback to this technique is that the controller will continue processing and as such we need to explicitly exit out of the controller action on our own, thus the reason why we use return
.
Let's create a test package called /tests/controllers/TestUsersController.cfc
to test that the create
action works as expected:
Notice that a lot more goes into testing a controller than a model. The first step is setting up the params
that will need to be passed to the controller. We then pass the 'params' to the processRequest()
function which returns a structure containing a bunch of useful information.
We use this information to make sure that the controller redirected the visitor to the index
action once the action was completed.
Note: processRequest()
is only for use within the test framework.
Below are some examples of how a controller can be tested:
If you want to test a variable that's being set on a controller you can make use of the this
scope. This way it's available from outside the controller, which makes it testable.
If you think that's too "ugly", you can instead make a public function on the controller that returns the value and then call that from your tests.
You may at some point want to test a partial (usually called via includePartial()
) outside of a request. You'll notice that if you just try and call includePartial()
from within the test suite, it won't work. Thankfully there's a fairly easy technique you can use by calling a "fake" or "dummy" controller.
Next we will look at testing the view layer. Below is the code for new.cfm
, which is the view file for the controller's new
action:
Testing the view layer is very similar to testing controllers, we will setup a params structure to pass to the processRequest()
function which will return (among other things) the generated view output.
Once we have this output, we can then search through it to make sure that whatever we wanted the view to display is presented to our visitor. In the test below, we are simply checking for the heading.
Next up is testing global helper functions. Below is a simple function that removes spaces from a string.
Testing these helpers is fairly straightforward. All we need to do is compare the function's return value against a value that we expect, using the assert()
function.
Testing your view helpers are very similar to testing application helpers except we need to explicitly include the helpers in the setup()
function so our view functions are available to the test framework.
Below is a simple function that returns a string wrapped in h1
tags.
And in our view test package:
Testing plugins requires slightly different approaches depending on the mixin
attribute defined in the plugin's main component.
Below is a simple plugin called timeAgo
that extends CFWheels' timeAgoInWords
view helper by appending "ago" to the function's return value. Take note of the mixin="controller"
argument as this will play a part in how we test the plugin.
In order to test our plugin, we'll need to do a little setup. Our plugin's tests will reside in a directory within our plugin package named tests
. We'll also need a directory to keep test assets, in this case a dummy controller that we will need to instantiate in out test's setup()
function.
The /plugins/timeago/tests/assets/controllers/Dummy.cfc
controller contains the bare minimum for a controller.
Firstly, in our /plugins/timeago/tests/TestTimeAgo.cfc
we'll need to copy the application scope so that we can change some of CFWheels' internal paths. Fear not, we'll reinstate any changes after the tests have finished executing using the teardown
function. so that if you're running your tests on your local development machine, your application will continue to function as expected after you're done testing.
Once the setup is done, we simply execute the plugin functions and assert that the return values are what we expect.
If your plugin is uses mixin="model"
, you will need to create and instantiate a dummy model component.
Down in the debug area of your CFWheels application (that grey area at the bottom of the page), you will notice a some links for running tests for the following areas:
Run Tests Runs all tests that you have created for your application. You should run these tests before deploying your application.
View Tests Shows a list of all your test packages with links to run individual packages or single tests.
Framework Tests If you have cloned the CFWheels repository, you will also see a link to run the core framework unit tests.
Plugin Tests If a plugin has a /tests
directory, you will also see a link to run the plugin's tests.
The test URL will look something like this:
/index.cfm?controller=wheels&action=wheels&view=tests&type=app
Running an individual package:
/index.cfm?controller=wheels&action=wheels&view=tests&type=app&package=controllers
Running a single test:
/index.cfm?controller=wheels&action=wheels&view=tests&type=app&package=controllers&test=testCaseOne
These URLs are useful should you want an external system to run your tests.
Test Results Format
CFWheels can return your test results in either HTML, JUnit or JSON formats, simply by using the format
url parameter. Eg: format=junit
Once you're comfortable with the concepts thus far, there are a few more functions available which can be used to modify the behavior of your test suite.
beforeAll()
- Runs once before the test suite has run. Here is where you might populate a test database or set suite-specific application variables*.
afterAll()
- Runs once after the test suite has finished.
packageSetup()
- Used in a test package, similar to setup()
but only runs once before a package's first test case. Here is where you might set variables that are common to all the tests in a CFC.
packageTeardown()
- Used in a test package, similar to teardown()
but only runs once after a package's last test case. Here is where you might cleanup files, database rows created by test cases or revert application variables in a CFC.
A typical test request lifecycle will look something like this:
Test.cfc -beforeAll()
Foo.cfc - packageSetup()
Foo.cfc - setup()
Foo.cfc - testCaseOne()
Foo.cfc - teardown()
Foo.cfc - setup()
Foo.cfc - testCaseTwo()
Foo.cfc - teardown()
Foo.cfc - packageTeardown()
Bar.cfc - packageSetup()
Bar.cfc - setup()
Bar.cfc - testCaseThree()
Bar.cfc - teardown()
Bar.cfc - packageTeardown()
Test.cfc - afterAll()
In order to use beforeAll()
and afterAll()
, you'll need to make a few small changes to your test suite. Firstly, create a Test.cfc
in the root of your /tests/
directory. This is where you'll define your beforeAll()
and afterAll()
functions and it should look something like this:
For your test packages to inherit these functions, you'll need to change the extends
attribute in your
test packages to extends="tests.Test"
. This enables the CFWheels test framework to run your functions.
Since we've implemented the new Test.cfc
component, we now have global setup()
and teardown()
functions will run respectively before and after every test case. If we want to prevent these from running in a particular package, we simply override the global functions like this:
If we want to run the global function AND some package-specific setup & teardown code, here's how achieve that:
Whilst best practice recommends that tests should be kept as simple and readable as possible, sometimes moving commonly used code into test suite helpers can greatly improve the simplicity of your tests.
Some examples may include, serializing complex values for use in assert()
or grouping multiple assertions together. Whatever your requirements, there are a number of ways to use test helpers.
Put your helper functions in your /tests/Test.cfc
.
These will be available to any package that extends this component. Be mindful of
functions you put in here, as it's easy to create naming collisions.
If you've arranged your tests into subdirectories, you can create a helpers.cfm
file in any given
directory and simply include it in the package.
Put package-specific helper functions in the same package as the tests that use it.
These will only be available to the tests in that package. To ensure that these test helpers
are not run as tests, use a function name that doesn't start with "test_". Eg: $simplify()
Overloading application vars.. CFWheels will revert the application scope after all tests have completed.
Caveat: The test suite request must complete without uncaught exceptions. If an uncaught exception occurs, the application scope may stay 'dirty', so it's recommended to reload the application by adding reload=true
param to your url whilst developing your test packages.
The CFWheels core uses this test framework for its unit test suite and contains a wealth of useful examples. They can all be found in the tests
folder of the CFWheels git repo.
Here are the rules of the road for contributing code to the project. Let's follow this process so that we don't duplicate effort and so the code base is easy to maintain over the long haul.
The official Git repository for Wheels is located at our GitHub repository.
Anyone may fork the cfwheels
repository, make changes, and submit a pull request.
To make sure that we don't have too many chefs in the kitchen, we will be limiting direct commit access to a core team of developers.
At this time, the core team consists of these developers:
This does not restrict you from being able to contribute. See "Process for Implementing Code Changes" below for instructions on volunteering. With enough dedication, you can earn a seat on the core team as well!
Here's the process that we'd like for you to follow. This process is in place mainly to encourage everyone to communicate openly. This gives us the opportunity to have a great peer-review process, which will result in quality. Who doesn't like quality?
Open an issue in the issue tracker, outlining the changes or additions that you would like to make.
A member of the core team will review your submission and leave feedback if necessary.
Once the core team member is satisfied with the scope of the issue, they will indicate so in a comment to the issue. This is your green light to start working. Get to coding, grasshopper!
Need help or running across any issues while coding? Start a Discussion.
When you have implemented your enhancement or change, use your Git repository to create a pull request with your changes.
You should annotate your commits with the issue number as #55
if the code issue is 55
A core team member will review it and post any necessary feedback in the issue tracker.
Once everything is resolved, a core team member will merge your commit into the Git repository.
If needed, open an issue to have the additions and changes in your revision documented in the CHANGELOG. You may claim the issue if you'd like to do this, but it's entirely your choice.
To easily develop and test CFWheels locally on multiple CFML engines using Docker, check out the Docker Instructions
All framework code should use the guidelines at https://github.com/cfwheels/cfwheels/wiki/Code-Style-Guide. This will make things more readable and will keep everyone on the same page. If you're working on code and notice any violations of the official style, feel free to correct it!
Additionally, we recommend that any applications written using the CFWheels framework follow the same style. This is optional, of course, but still strongly recommended.
All code for CFWheels should be written for use with both Adobe ColdFusion 2018 upwards, and Lucee 5 upwards.
To stay true to our ColdFusion and Java roots, all names must be camelCase. In some cases, such as internal CFML functions, the first letter should be capitalized as well. Refer to these examples.
Since moving to CFWheels 2.x, the old loc
scope has now been deprecated and you should use the function local
scope.
Code Example
All CFC methods should be made public. If a method is meant for internal use only and shouldn't be included in the API, then prefix it with a dollar sign $. An example would be $query().
One of a developer’s biggest time sinks is coming up with and writing accurate and engaging documentation. Even if it’s an internal project with.. well, just you… it’s still incredibly valuable to have: coming back to projects a few months or even years later can often lead to a bit of head scratching and screams of “why?”, especially with your own code.
Whilst we’re not promising that CFWheels will write all your documentation for you, we have put some tools in place to hopefully make the process a little less painful. With a small amount of adjustment in how you document your core functions, documentation doesn’t necessarily have to be such a time consuming process.
In the CFWheels core, we've adopted javadoc-style syntax (with a few twists of our own) to automatically create our main documentation, and there's nothing stopping you adding to this core set of documentation for your own app. It just needs a bit of markup.
The first thing to notice is the new ‘[Docs]’ link in the debug section in the footer: Following that link leads you to the main internal documentation.
The three-column layout is designed to allow for quick filtering by section or function name. On the right are the main CFWheels core categories, such as Controller and Model functions. These categories are further divided into subcategories, such as Flash and Pagination functions. Clicking on a link in the right column will filter the list in the left and middle columns to show all matching functions, including child functions of that category.
Filtering by function name is made simple by a “filter-as-you-type” search field in the left column, making it very quick to find the right function.
The middle column contains the main function definition, including tags, parameters and code samples.
Each function in the core is now appropriately marked up with javaDoc style comments. This, combined with getMetaData()
allows us to parse the markup into something useful.
Example Core Function
The [section]
and [category]
tags categorise the function as appropriate, and the @html
part describes the function’s parameter. The additional parameter data, such as whether it’s required, type and any defaults are automatically parsed too. This results in a display like:
Any function which is available to Controller.cfc
or Model.cfc
is automatically included; if there’s no javaDoc comment, then they’ll appear in uncategorized
. But of course, there’s nothing stopping you creating your own [section]
and [category]
tags, which will then automatically appear on the left hand column for filtering: you’re not restricted to what we’ve used in the core.
As an example, if you wanted to document all your filters, you might want to have a [section: Application]
tag, with [category: filters]
. This way, your application documentation grows as you create it.
Something as simple as a sublime text snippet for a new function which includes the basic javaDoc skeleton can get you in the habit pretty quickly!
We also introspect plugins for the same markup. We encourage plugin authors to adjust their plugin code to include [section: Plugins]
at the very least.
You can export the docs via JSON just by changing the URL string:
i.e ?controller=wheels&action=wheels&view=docs&type=core&format=json
You could also include the functions used to create the docs and create your own version (perhaps useful for a CMS or other application where you have internal documentation for authenticated users). Whilst this isn’t officially supported (the main functions may be subject to change!) it is technically possible. The best example is the thing itself – see the main output if you’re interested. Please note that the user interface isn’t available in production mode (for obvious reasons we hope!), so if you wanted to expose this data to an end user, you would probably need to “roll your own” with this approach.
Note that the CFC introspection doesn’t automatically happen on every request, so you will need to ?reload=true
to see changes to your code. Additionally, Adobe ColdFusion is more aggressive in caching CFC metadata, so depending on your settings, you may not see changes until a server restart.
Whilst the core has additional code samples which can be loaded from text files, there’s no support for your application functions to take advantage of this yet.
Whilst this API/function explorer is a great first step, you’ll notice your controller and model specific functions aren’t included (only those shared amongst controllers, or in the /app/global/functions.cfm
file. This is because we’re only looking at the main Model.cfc
and Controller.cfc
and what it can access.
In CFWheels 2.1, we’ll look at adding a full Controller and Model metadata explorer using the same techniques, and map functions like show()
to their respective routes too.
Code Element | Examples | Description |
---|---|---|
CFC Names
MyCfc.cfc, BlogEntry.cfc
CapitalizedCamelCase
Variable Names
myVariable, storyId
camelCase
UDF Names
myFunction()
camelCase
Built-in CF Variables
result.recordCount, cfhttp.fileContent
camelCase
Built-in CF Functions
IsNumeric(), Trim()
CapitalizedCamelCase
Scoped Variables
application.myVariable, session.userId
lowercase.camelCase
CGI Variables
cgi.remote_addr, cgi.server_name
cgi.lowercase_underscored_name
Showing content to the user.
A big part of a controller's task is to respond to the user. In Wheels you can respond to the user in three different ways:
Displaying content
Redirecting to another URL
Sending a file
You can only respond once per request. If you do not explicitly call any of the response functions ( renderView(), sendFile() etc) then Wheels will assume that you want to show the view for the current controller and action and do it for you.
This chapter covers the first method listed above—displaying content. The chapters about Redirecting Users and Sending Files cover the other two response methods.
This is the most common way of responding to the user. It's done with the renderView() function, but most often you probably won't call it yourself and instead let Wheels do it for you.
Sometimes you will want to call it though and specify to show a view page for a controller/action other than the current one. One common technique for handling a form submission, for example, is to show the view page for the controller/action that contains the form (as opposed to the one that just handles the form submission and redirects the user afterwards). When doing this, it's very important to keep in mind that renderView() will not run the code for the controller's action—all it does is process the view page for it.
You can also call renderView() explicitly if you wish to cache the response or use a different layout than the default one.
If the controller
and action
arguments do not give you enough flexibility,
you can use the template
argument that is available for renderView().
Refer to the Pages chapter for more details about rendering content. More specifically, that chapter describes where to place those files and what goes in them.
This is done with the renderPartial() function. It's most often used with AJAX requests that are meant to update only parts of a page.
Sometimes you don't need to return anything at all to the browser. Perhaps you've made an AJAX request that does not require a response or executed a scheduled task that no end user sees the results of. In these cases you can use the renderNothing() function to tell Wheels to just render an empty page to the browser.
This is done with the renderText() function. It just returns the text you specify. In reality it is rarely used but could be useful as a response to AJAX requests sometimes.
Normally when you call any of the rendering functions, the result is stored inside an internal Wheels variable. This value is then outputted to the browser at the end of the request.
Sometimes you may want to do some additional processing on the rendering result
before outputting it though. This is where the returnAs
argument comes in
handy. It's available on both renderView() and renderPartial().
Setting returnAs
to string
will return the result to you instead of placing
it in the internal Wheels variable.
Two of the functions listed above are capable of caching the content for you;
renderView() and renderPartial(). Just pass in cache=true
(to use
the default cache time set in app/config/settings.cfm
) or cache=x
where x
is
the number of minutes you want to cache the content for. Keep in mind that this
caching respects the global setting set for it in your configuration files so
normally no pages will be cached when in Design or Development mode.
We cover caching in greater detail in the Caching chapter.
The renderView() function accepts an argument named layout
. Using this
you can wrap your content with common header/footer style code. This is such an
important concept though so we'll cover all the details of it in the chapter
called Using Layouts.
How Wheels handles an incoming request
Wheels is quite simple when it comes to figuring out how incoming requests map to code in your application. Let's look at a URL for an e-commerce website and dissect it a little.
But before we do that, a quick introduction to URLs in Wheels is in order.
URLs in Wheels generally look something like this:
It's also possible that the URLs will look like this in your application:
This happens when your web server does not support the cgi.path_info
variable.
Regardless of your specific setup, Wheels will try to figure out how to handle
the URLs. If Wheels fails to do this properly (i.e. you know that your web
server supports cgi.path_info
, but Wheels insists on creating the URLs with
the query string format), you can override it by setting URLRewriting
in
app/config/settings.cfm
to either On, Partial
, or Off
. The line of code should
look something like this:
"Partial URL Rewriting" is what we call that first URL up there with the
/index.cfm/
format.
In the example URLs used above, shop
is the name of the controller to call,
and products
is the name of the action to call on that controller.
Unless you're familiar with the Model-View-Controller pattern, you're probably wondering what controllers and actions are.
Put very simply, a controller takes an incoming request and, based on the parameters in the URL, decides what (if any) data to get from the model (which in most cases means your database), and decides which view (which in most cases means a CFML file producing HTML output) to display to the user.
An action is an entire process of executing code in the controller, including a view file and rendering the result to the browser. As you will see in the example below, an action usually maps directly to one specific function with the same name in the controller file.
Mapping an incoming URL to code is only one side of the equation. You will also need a way to create these URLs. This is done through a variety of different functions like linkTo() (for creating links), startFormTag() (for creating forms), and redirectTo() (for redirecting users), to name a few.
Internally, all of these functions use the same code to create the URL, namely the URLFor() function. The URLFor() function accepts a controller and an action argument, which are what you will use most of the time. It has a lot of other arguments and does some neat stuff (like defaulting to the current controller when you don't specifically pass one in). So check out the documentation for the URLFor() function for all the details.
By the way, by using URL rewriting in Apache or IIS, you can completely get rid
of the index.cfm
part of the URL so that
http://localhost/index.cfm/shop/products
becomes
http://localhost/shop/products
. You can read more about this in the
URL Rewriting chapter.
For the remainder of this chapter, we'll type out the URLs in this shorter and prettier way.
Let's look a little closer at what happens when Wheels receives this example incoming request.
First, it will create an instance of the shop
controller
(app/controllers/Shop.cfc
) and call the function inside it named products
.
Let's show how the code for the products
function could look to make it more
clear what goes on:
The only thing this does is specify the view page to render using the renderView() function.
The renderView() function is available to you because the shop
controller extends the main Wheels Controller
component. Don't forget to include that extends
attribute in your cfcomponent
call as you build your controllers!
So, how does renderView() work? Well, it accepts the arguments controller
and action
(among others, such as route
), and, based on these, it will try to include a view file. In our case, the view file is stored at app/views/shop/products.cfm
.
You can read the chapter about Rendering Content for more information about the renderView() function.
It's important to note that the renderView() function does not cause any controller actions or functions to be executed. It just specifies what view files to get content from. Keep this in mind going forward because it's a common assumption that it does. (Especially when you want to include the view page for another action, it's easy to jump to the incorrect conclusion that the code for that action would also get executed.)
Because Wheels favors convention over configuration, we can remove a lot of the code in the example above, and it will still work because Wheels will just guess what your intention is. Let's have a quick look at exactly what code can be removed and why.
The first thing Wheels assumes is that if you call renderView() without arguments, you want to include the view page for the current controller and action.
Therefore, the code above can be changed to:
… and it will still work just fine.
Does Wheels assume anything else? Sure it does. You can actually remove the entire renderView() call because Wheels will assume that you always want to call a view page when the processing in the controller is done. Wheels will call it for you behind the scenes.
That leaves you with this code:
That looks rather silly, a products
function with no code whatsoever. What do
you think will happen if you just remove that entire function, leaving you with
this code?
…If you guessed that Wheels will just assume you don't need any code for the
products
action and just want the view rendered directly, then you are correct.
In fact, if you have a completely blank controller like the one above, you can delete it from the file system altogether!
This is quite useful when you're just adding simple pages to a website and you
don't need the controller and model to be involved at all. For example, you can
create a file named about.cfm
in the app/views/home
folder and access it at
http://localhost/home/about
without having to create a specific controller
and/or action for it, assuming you're still using wildcard routing.
This also highlights the fact that Wheels is a very easy framework to get started in because you can basically program just as you normally would by creating simple pages like this and then gradually "Wheelsifying" your code as you learn the framework.
params
StructBesides making sure the correct code is executed, Wheels also does something
else to simplify request handling for you. It combines the url
and form
scopes into one. This is something that most CFML frameworks do as well. In
Wheels, it is done in the params
struct.
The params
struct is available to you in the controller and view files but
not in the model files. (It's considered a best practice to not mix your request
handling with your business logic.) Besides the form
and url
scope
variables, the params
struct also contains the current controller and action
name for easy reference.
If the same variable exists in both the url
and form
scopes, the value in
the form scope will take precedence.
To make this concept easier to grasp, imagine a login form on your website that
submits to http://localhost/account/login?sendTo=dashboard
with the variables
username
and password
present in the form. Your params
struct would look
like this:
Now instead of accessing the variables as url.sendTo
, form.username
, etc.,
you can just use the params
struct for all of them instead.
This concept becomes even more useful once we start getting into creating forms specifically meant for accessing object properties. But let's save the details of all that for the Form Helpers and Showing Errors chapter.
If you're constructing a JSON API, you're inevitably going to come across how to deal with "incoming" json packets to your routing endpoints. Instead of having to test each request with whether it's valid json etc, CFWheels will automatically map the JSON body in a request to the params struct, as long as it has application/json
as it's mime type.
So in the same way that the url
and form
scopes are merged, so a valid json
body would be. The exception to the rule is when a javascript array is the root element, where it's then added to params._json
to follow Rails conventions. (For obvious reasons, we can't merge an array into a struct!)
NOTE
The mapping of a json array to params._json was introduced in CFWheels 2.1
For more advanced URL-to-code mappings, you are encourage to use a concept called routing. It allows for you to fully customize every URL in your application, including which HTTP verb can be used. You can read more about this in the chapter called Routing.
Use CFWheels to send files to your users securely and with better control of the user experience.
Sending files?! Is that really a necessary feature of CFWheels? Can't I just place the file on my web server and link to it? You are correct, there is absolutely no need to use CFWheels to send files. Your web server will do a fine job of sending out files to your users.
The convention in CFWheels is to place all files you want users to be able to download in the public/files
folder.
Assuming you've placed a file named wheels_tutorial_20081028_J657D6HX.pdf
in that folder, here is a quick example of how you can deliver that file to the user. Let's start with creating a link to the action that will handle the sending of the file first.
Here's the sendTutorial
action:
That's one ugly file name though, eh? Let's present it to the user in a nicer way by suggesting a different name to the browser:
Much better! :)
Here's an example:
Example
You can even send files which are stored in ram://
- this is particularly useful when you're dynamically creating files (such as PDF reports) which don't need to be written to the file system.
However, there is a security flaw here. Can you figure out what it is?
You may have guessed that the files folder is placed in your web root, so anyone can download files from it by typing http://www.domain.com/files/wheels_tutorial_20081028_J657D6HX.pdf
in their
browser. Although users would need to guess the file names to be able to access the files, we would still need something more robust as far as security goes.
There are two solutions to this.
The easiest one is to just lock down access to the folder using your web server. CFWheels won't be affected by it since it gets the file from the file system.
This assumes you've moved the folder two levels up in your file system and into a folder named "tutorials".
Use redirection to keep your application user friendly.
When a user submits a form, you do not want to show any content on the page that handles the form submission! Why? Because if you do, and the user hits refresh in their browser, the form handling code could be triggered again, possibly causing duplicate entries in your database, multiple emails being sent, etc.
Let's look at the three ways you can redirect in CFWheels.
The referring URL is retrieved from the cgi.http_referer
value. If this value is blank or comes from a different domain than the current one, CFWheels will redirect the visitor to the root of your website instead.
Sometimes it's useful to be able to send the visitor back to the same URL they came from but with extra parameters added to it. You can do this by using the params
argument. Note that CFWheels will append to the URL and not replace it in this case.
addToken
and statusCode
ArgumentsYou can also set the type of redirect to something other than the default 302
redirect, by passing in statusCode=3xx
. For example, 301
indicates a permanent redirect.
Name | Value |
---|---|
However, if you want a little more control over the way the user's browser handles the download or be able to secure access to your files then you might find the function useful.
Now let's send the file to the user in the sendTutorial
controller action. Just like the rendering functions in CFWheels, the function should be placed as the very last thing you do in your action code.
In this case, that's the only thing we are doing, but perhaps you want to track how many times the file is being downloaded, for example. In that case, the tracking code needs to be placed before the function.
Also, remember that the function replaces any rendering. You cannot send a file and render a page. (This will be quite obvious once you try this code because you'll see that the browser will give you a dialog box, and you won't actually leave the page that you're viewing at the time.)
By default, the function will try and force a download dialog box to be shown to the user. The purpose of this is to make it easy for the user
to save the file to their computer. If you want the file to be shown inside the browser instead (when possible as decided by the browser in question), you can set the disposition
argument to inline
.
You can also specify what HTTP content type to use when delivering the file by using the type
argument. Please refer to the API for the
function for complete details.
Perhaps the main reason to use the function is that it gives you an easy way to secure access to your files. Maybe the tutorial file used in the above example is something the user paid for, and you only want for that user to be able to download it (and no one else). To accomplish this, you can just add some code to authenticate the user right before the call in your action.
If that is not an option, the other option is simply to move the files folder out of the web root, thus making it inaccessible. If you move the folder, you'll need to accommodate for this in your code by changing your calls to specify the path as well, like this:
A final note of warning: Be careful to not allow just any parameters from the URL to get passed through to the because then a user would be able to download any file from your server by playing around with the URL. Be wary of how you're using the params
struct in this context!
To avoid the above problem, it is recommended to always redirect the user after submitting a form. In CFWheels this is done with the function. It is basically a wrapper around the cflocation
tag in CFML.
Being that is a CFWheels function, it can accept the route, controller, action
, and key
arguments so that you can easily redirect
to other actions in your application.
You can redirect the user to another action in your application simply by passing in the controller, action
, and key
arguments. You can also pass in any other arguments that are accepted by the function, like host, params, etc. (The function is what CFWheels uses internally to produce the URL to redirect to.)
If you have configured any routes in app/config/routes.cfm
, you can use them when redirecting as well. Just pass in the route's name to the route argument together with any additional arguments needed for the route in question. You can read more about routing in the chapter.
It's very common that all you want to do when a user submits a form is send them back to where they came from. (Think of a user posting a comment on a blog post and then being redirected back to view the post with their new comment visible as well.) For this, we have the back
argument. Simply pass in back=true
to , and the user will be redirected back to the page they came from.
If you want to specify exactly where to send the visitor when the referring domain is blank/foreign, you can pass in the normal arguments like route, controller, action
, etc. These will be used only when CFWheels can't redirect to the referrer because it's invalid.
The function uses <cflocation>
under the hood; if you need to pass client variable information automatically in the URL for client management purposes, simply set addToken=true
.
params.controller
account
params.action
login
params.sendTo
dashboard
params.username
joe
params.password
1234
Use CFWheels to simplify the task of setting up automated emails in your application.
Sending emails in CFWheels is done from your controller files with the sendEmail() function. It's basically a wrapper around cfmail
, but it has some smart functionality and a more CFWheels-like approach in general.
Getting this to work in CFWheels can be broken down in 3 steps. We'll walk you through them.
We recommend using CFWheels ability to set global defaults for sendEmail() so that you don't have to specify more arguments than necessary in your controller files. Because it's likely that you will use the same mail server across your application, this makes it worthwhile to set a global default for it.
This setting should be done in the app/config/settings.cfm
file and can look something like this:
By specifying these values here, these arguments can be omitted from all sendEmail() function calls, thus providing cleaner, less cluttered code.
But you are not limited to setting only these 3 variables. In fact, you can set a global default for any optional argument to sendEmail() and since it accepts the same arguments that cfmail
does. That's quite a few.
Alternatively, most modern CFML engines allow setting SMTP information directly within the application configuration. So you can actually add this in /app/config/app.cfm
: here's an example configuration:
An email template is required for sendEmail() to work and forms the basis for the mail message content. Think of an email template as the content of your email.
Templates may be stored anywhere within the /app/views/
folder, but we recommend a structured, logical approach. If different controllers utilize sendEmail()
and each require a unique template, place each email template within the app/views/controllername
folder structure.
Consider this example scenario:
Multiple templates may be stored within this directory should there be a need.
The content of the template is simple: simply output the content and any expected variables.
Here's an example for myemailtemplate.cfm
, which will contain HTML content.
As we've said before, sendEmail() accepts all attribute of CFML's cfmail
tag as arguments. But it also accepts any variables that you need to pass to the email template itself.
Consider the following example:
Here we are sending an email by including the myemailtemplate
template and passing values for recipientName
and startDate
to it.
Note that the template
argument should be the path to the view's folder name and template file name without the extension. If the template is in the current controller, then you don't need to specify a folder path to the template file. In that case, just be sure to store the template file in the folder with the rest of the views for that controller.
The logic for which template file to include follows the same logic as the template
argument to renderView().
Did you notice that we did not have to specify the type
attribute of cfmail
? CFWheels is smart enough to figure out that you want to send as HTML since you have tags in the email body. (You can override this behavior if necessary though by passing in the type
argument.)
The intelligence doesn't end there though. You can have CFWheels send a multipart email by passing in a list of templates to the templates
argument (notice the plural), and CFWheels will automatically figure out which one is text and which one is HTML.
Like the template
argument, the logic for which file to include follows the same logic as the template
argument to renderView().
You can attach files to your emails as well by using the file
argument (or files
argument if you want multiple attachments). Simply pass in the name of a file that exists in the public/files
folder (or a subfolder of it) of your application.
Alternatively you can pass in mail parameters directly if you require more control (such as sending a dynamically generated PDF which isn't written to disk):
Much like the layouts outlined in the Layouts chapter, you can also create layouts for your emails.
A layout should be used just as the name implies: for layout and stylistic aspects of the email body. Based on the example given above, let's assume that the same email content needs to be sent twice.
Message is sent to a new member with a stylized header and footer.
A copy of message is sent to an admin at your company with a generic header and footer.
Best practice is that variables (such as recipientName
and startDate
, in the example above) be placed as outputs in the template file.
In this case, the two calls to sendEmail() would be nearly identical, with the exception of the layout
argument.
CFWheels also lets you set up layouts for the HTML and plain text parts in a multipart email.
If we set up generic email layouts at app/views/plainemaillayout.cfm
and app/views/htmlemaillayout.cfm
, we would call sendEmail() like so:
For both the templates
and layouts
arguments (again, notice the plurals), we provide a list of view files to use. CFWheels will figure out which of the templates and layouts are the HTML versions and separate out the MIME parts for you automatically.
Now you're all set to send emails the CFWheels way. Just don't be a spammer, please!
Controller: | Email Template: |
---|---|
Membership
/app/views/membership/myemailtemplate.cfm
CFWheels controllers provide some powerful mechanisms for responding to requests for content in XML, JSON, and other formats. You can build an API with ease using these functions.
If you've ever needed to create an XML or JSON API for your Wheels application, you may have needed to go down the path of creating a separate controller or separate actions for the new format. This introduces the need to duplicate model calls or even break them out into a super long list of before filters. With this, your controllers can get pretty hairy pretty fast.
Using a few CFWheels functions, you can easily respond to requests for HTML, XML, JSON, and PDF formats without adding unnecessary bloat to your controllers.
With CFWheels Provides functionality in place, you can request different formats using the following methods:
URL Variable
URL Extension
Request Header
Which formats you can request is determined by what you configure in the controller. See the section below on Responding to Different Formats in the Controller for more details.
CFWheels will accept a URL variable called format
. If you wanted to request the
XML version of an action, for example, your URL call would look something like
this:
The same would go for JSON:
Perhaps a cleaner way is to request the format as a "file" extension. Here are the XML and JSON examples, respectively:
This works similarly to the URL variable approach mentioned above in that
there will now be a key in the params
struct set to the format
requested.
With the XML example, there will be a variable at params.format
with a value
of xml
.
If you are calling the CFWheels application as a web service, you can also request
a given format via the HTTP Accept
header.
If you are consuming the service with another CFWheels application, your
<cfhttp>
call would look something like this:
In this example, we are sending an Accept
header with the value for the xml
format.
Here is a list of values that you can grab from mimeTypes() with CFWheels out of the box.
html
xml
json
csv
pdf
xls
You can use addFormat() to set more types to the appropriate MIME type for reference. For example, we could set a Microsoft Word MIME type in
app/config/settings.cfm
like so:
The fastest way to get going with creating your new API and formats is to call
provides() from within your controller's config()
method.
Take a look at this example:
By calling the provides() function in config()
, you are instructing the CFWheels controller to be ready to provide content in a number of formats.
Possible choices to add to the list are html
(which runs by default), xml
,
json
, csv
, pdf
, and xls
.
This is coupled with a call to renderwith() in the following actions. In the example above, we are setting a query result of products and passing it to renderwith(). By passing our data to this function, CFWheels gives us the ability to respond to requests for different formats, and it even gives us the option to just let CFWheels handle the generation of certain formats automatically.
You can also use the onlyProvides() call in an individual controller action to define which formats the action will respond with. This can be used to define behavior in individual actions or to override the controller's config()
.
When CFWheels handles this response, it will set the appropriate MIME type in the
Content-Type
HTTP header as well.
Responding to requests for the HTML version is the same as you're already used to with Rendering Content. renderwith() will accept the same arguments as renderView(), and you create just a view template in the views
folder like normal.
If the requested format is xml
or json
, the renderwith() function will automatically transform the data that you provide it. If you're fine with what the function produces, then you're done!
Best Practices for Providing JSON
Unfortunately there have been a lot of JSON related issues in CFML over the years. To avoid as many of these problems as possible we thought we'd outline some best practices for you to follow.
First of all, always return data as an array of structs. This is done by using the returnAs
argument (on findAll() for example), like this:
The reason for doing it this way is that it will preserve the case for the struct / JSON keys.
Secondly, make use of CFWheels ability to return the JSON values in a specified type. This is done in the renderWith()function, like this:
With that in place you can be sure that firstName
will always be treated as a string (i.e. wrap in double quotes) and booksForSale
as an integer (i.e. no decimal places) when producing the JSON output. Without this, your CFML engine might guess what the data type is, and it wouldn't always be correct unfortunately.
If you need to provide content for another type than xml
or json
, or if you
need to customize what your CFWheels application generates, you have that option.
In your controller's corresponding folder in app/views
, all you need to do is
implement a view file like so:
If you need to implement your own XML- or JSON-based output, the presence of your new custom view file will override the automatic generation that CFWheels normally performs.
Example: PDF Generation
If you need to provide a PDF version of the product catalog, the view file at
app/views/products/index.pdf.cfm
may look something like this:
HTML
Type | Example |
---|---|
html
app/views/products/index.cfm
xml
app/views/products/index.xml.cfm
json
app/views/products/index.json.cfm
csv
app/views/products/index.csv.cfm
app/views/products/index.pdf.cfm
xls
app/views/products/index.xls.cfm
Using the Flash to pass data from one request to the next.
The Flash is actually a very simple concept. And no, it has nothing to do with Adobe's Flash Player.
The Flash is just a struct in the session
or cookie
scope with some added
functionality. It is cleared at the end of the next page that the user views.
This means that it's a good fit for storing messages or variables temporarily
from one request to the next.
By the way, the name "Flash" comes from Ruby on Rails, like so many other cool things in Wheels.
The code below is commonly used in Wheels applications to store a message about an error in the Flash and then redirect to another URL, which then displays the message in its view page.
The following example shows how code dealing with the Flash can look in an action that handles a form submission.
Here's an example of how we then display the Flash message we just set in the
view page for the edit
action. Please note that this is done on the next
request since we performed a redirect after setting the Flash.
As you can see above, you use the flashInsert() function with a named argument when you want to store data in the Flash and the flash() function when you want to display the data in a view.
The key chosen above is success
, but it could have been anything that we wanted. Just like with a normal struct, the naming of the keys is your job.
As an example, you may choose to use one key for storing messages after the user made an error, called error
, and another one for storing messages after a successful user operation, called success
.
The more you work with Wheels and the Flash, the more that you're going to find that you keep repeating that flashInsert()/ redirectTo() combo all the time. Wheels has a solution for that within the redirectTo() function itself:
That piece of code does exactly the same thing as the example shown previously
in this chapter. The Wheels redirectTo() function sees the success
argument coming in and knows that it's not part of its own declared arguments.
Therefore, you (the developer) must intend for it to be stored in the Flash,
so Wheels goes ahead and calls flashInsert(success="The user was updated successfully.")
for you behind the scenes.
So what if you want to redirect to the edit
action and set a key in the Flash
named action
as well? Simply prepend the key with flash
to tell Wheels to
avoid the argument naming collision.
CFScript
We don't recommend naming the keys in your Flash action
, but these naming
collisions can potentially happen when you want to redirect to a route that
takes custom arguments, so remember this workaround.
Besides flash() and flashInsert() that are used to read from/insert to the Flash, there are a few other functions worth mentioning.
flashCount() is used to count how many key/value pairs there are in the Flash.
flashClear() and flashDelete() do exactly the same as their
counterparts in the struct world, StructClear
and StructDelete
—they clear
the entire Flash and delete a specific key/value from it, respectively.
flashKeyExists() is used to check if a specific key exists. So it would
make sense to make use of that function in the code listed above to avoid
outputting an empty <p>
tag on requests where the Flash is empty.
( flash() will return an empty string when the specified key does not
exist.)
Check out the Controller > Flash Functions section in the API listing of all the functions that deal with the Flash.
Throw the flashIsEmpty() function into the mix, and you might find yourself writing code across your Wheels projects that looks something like this:
All of that above code can be replaced with a single call to the flashMessages() function:
Whenever any value is inserted into the Flash, flashMessages() will
display it similarly to the complex example above, with class
attributes set
similarly (errorMessage
for the error
key and successMessage
for the
success
key).
You can also use flashMessages()'s key/keys
argument to limit its reach
to a list of given keys. Let's say that we only want our layout to show messages
for the alert
key but not for the error
or success
keys (or any other for
that matter). We would write our call like so:
Just keep in mind that this approach isn't as flexible, so if you need to customize the markup of the messages beyond flashMessages()'s capabilities, you should revert back to using flashIsEmpty(), flash(), and other related functions manually.
Earlier, we mentioned that the data for the Flash is stored in either the
cookie
or the session
scope. You can find out where Wheels stores the Flash
data in your application by outputting get("flashStorage")
. If you have
session management enabled in your application, Wheels will default to storing
the Flash in the session
scope. Otherwise, it will store it in a cookie on the
user's computer.
You can override this setting in the same way that you override other Wheels settings by running the set() function like this:
Note: Before you set Wheels to use the session
scope, you need to make sure
that session management is enabled. To enable it, all you need to do is add
this.SessionManagement = true
to the app/config/app.cfm
file.
So what storage option should you choose? Well, to be honest, it doesn't really matter that much, so we recommend you just go with the default setting. If you're a control freak and always want to use the optimal setting, here are some considerations.
Although the Flash data is deserialized before stored in a cookie (making it possible to store complex values), you need to remember that a cookie is not the best place to store data that requires a lot of space.
If you run multiple ColdFusion servers in a clustered environment and use session-based Flash storage, users might experience a loss of their Flash variables as their request gets passed to other servers.
Using cookies is, generally speaking, less secure than using the session
scope. Users could open their cookie file up and manually change its value.
Sessions are stored on the server, out of users' reach.
From CFWheels 2.1, you can now change the default flash behavior to append to an existing key, rather than directly replacing it. To turn on this behavior, add set(flashAppend=true)
to you /app/config/settings.cfm
file.
An example of where this might be useful:
Which, when output via flashMessages()
would render:
With set(flashAppend=true)
, you can also directly pass in an array of strings like so:
In 2.2
upwards:
There are certain circumstances where you might not be using the flash: for example, if you have wheels setup as a stateless API (perhaps in a cluster behind a load balancer) where you're using tokens vs sessions. In this circumstance, you've probably turned off session management, which only leaves cookie
as a viable route. But if set to cookie
, you always get Set-Cookie
being returned, which is unnecessary.
In wheels 2.2 you can now set(flashStorage = "none")
which will prevent the creation of the cookie, and also not try and write to a session.
Stop repeating yourself with the use of before and after filters.
If you find the need to run a piece of code before or after several controller actions, then you can use filters to accomplish this without needing to explicitly call the code inside each action in question.
This is similar to using the onRequestStart
/ onRequestEnd
functions in CFML's Application.cfc
file, with the difference being that filters tie in better with your CFWheels controller setup.
One common thing you might find yourself doing is authenticating users before allowing them to see your content. Let's use this scenario to show how to use filters properly.
You might start out with something like this:
Sure, that works. But you're already starting to repeat yourself in the code. What if the logic of your application grows bigger? It could end up looking like this:
Ouch! You're now setting yourself up for a maintenance nightmare when you need to update that IP range, the messages given to the user, etc. One day, you are bound to miss updating it in one of the places.
As the smart coder that you are, you re-factor this to another function so your code ends up like this:
Much better! But CFWheels can take this process of avoiding repetition one step further. By placing a filters() call in the config()
function of the controller, you can tell CFWheels what function to run before any desired action(s).
Besides the advantage of not having to call restrictAccess()
twice, you have also gained two other things:
The developer coding secretStuff()
and evenMoreSecretStuff()
can now focus on the main tasks of those two actions without having to worry about repetitive logic like authentication.
The config()
function is now starting to act like an overview for the entire controller.
All of these advantages will become much more obvious as your applications grow in size and complexity. This was just a simple example to put filters into context.
So far, we've only been dealing with one controller. Unless you're building a very simple website, you'll end up with a lot more.
The question then becomes, "Where do I place the restrictAccess()
function so I can call it from any one of my controllers?" The answer is that because all controllers extend Controller.cfc
, you should probably put it there. The config()
function itself with the call to filters() should remain inside your individual controllers though.
If you actually want to set the same filters to be run for all controllers, you can go ahead and move it to the Controller.cfc
file's config()
function as well. Keep in mind that if you want to run the config()
function in the individual controller and in Controller.cfc
, you will need to call super.config()
from the config()
function of your individual controller.
You specify if you want to run the filter function before or after the controller action with the type
argument to the filters() function. It defaults to running it before the action.
The previous example with authentication showed a "before filter" in action. The other type of filter you can run is an "after filter." As you can tell from the name, an after filter executes code after the action has been completed.
This can be used to make some last minute modifications to the HTML before it is sent to the browser (think translation, compression, etc.), for example.
If you want to get a copy of the content that will be rendered to the browser from an after filter, you can use the response() function. To set your changes to the response afterward, use the setResponse() function.
As an example, let's say that you want to translate the content to Gibberish before sending it to your visitor. You can do something like this:
By default, filters apply to all actions in a controller. If that's not what you want, you can tell CFWheels to only run the filter on the actions you specify with the only
argument. Or you can tell it to run the filter on all actions except the ones you specify with the except
argument.
Here are some examples showing how to setup filtering in your controllers. Remember, these calls go inside the config()
function of your controller file.
Sometimes it's useful to be able to pass through arguments to the filters. For one, it can help you reduce the amount of functions you need to write. Here's the easy way to pass through an argument:
Now the byIP
argument will be available in the authorize
function.
To help you avoid any clashing of argument names, CFWheels also supports passing in the arguments in a struct as well:
Because your controller's config()
function only runs once per application start, the passing of arguments can also be written as expressions to be evaluated at runtime. This is helpful if you need for the value to be dynamic from request to request.
For example, this code would only evaluate the value for request.region
on the very first request, and CFWheels will store that particular value in memory for all subsequent requests:
To avoid this hard-coding of values from request to request, you can instead pass an expression. (The double pound signs are necessary to escape dynamic values within the string. We only want to store a string representation of the expression to be evaluated.)
Now instead of evaluating request.region
inside the config()
function, it will be done on each individual request.
You probably don't want anyone to be able to run your filters directly (by modifying a URL on your website for example). To make sure that isn't possible we recommend that you always make them private. As you can see in all examples on this page we make sure that we always have access="private"
in the function declaration for the filter.
If you need to access your filters on a lower level, you can do so by using the filterChain() and setFilterChain()functions. Typically, you'll want to call filterChain() to return an array of all the filters set on the current controller, make your desired changes, and save it back using the setFilterChain() function.
Verify the existence and type of variables before allowing for a given controller action to run.
Verification, through the verifies() function, is just a special type of filter that runs before actions. With verifications defined in your controller, you can eliminate the need for wrapping your entire actions in <cfif>
blocks checking for the existence and types of variables. You also can limit your actions' scopes to specific request types like post, get
, and AJAX requests.
Let's say that you want to make sure that all requests coming to a page that handles form submissions are post
requests. While you can do this with a filter and the isPost() function, it is more convenient and DRY to do it with the verifies() function.
All that you need to do is add this line to your controller's config()
function:
The code above will ensure that all requests coming to the create
and update
actions are from form submissions. All other requests will be aborted.
There are also boolean arguments for get
and ajax
request types.
You can also specify different behavior for when the verification fails in a special handler
function registered with the handler argument.
In this example, we register the incorrectRequestType()
function as the handler:
Note that you have to either do a redirectTo() call or abort the request completely after you've done what you wanted to do inside your handler function. If you don't do anything at all and just let the function exit on its own, Wheels will redirect the user back to the page they came from. In other words, you cannot render any content from inside this function but you can let another function handle that part by redirecting to it.
A very convenient and common use of verifies() is when you want to make sure that a variables exists and is of a certain type; otherwise, you would like for your controller to redirect the user to a different page.
Step back in time for a moment and remember how you used to code websites before Wheels. (Yes I know those were dark days, but stay with me.)
On your edit.cfm
page, what you probably did was write some code at the top of that looked like this:
With this snippet of code, you could ensure that any request to the edit.cfm
had to have the userId
in the form scope and that userId
had to be of type guid
. If these conditions weren't met, the request was redirected to the index.cfm
page. This was a very tedious but necessary task.
Now let's see how using the verifies() function within Wheels improves this:
With that one line of code, Wheels will perform the following checks when a request is made to the controller's edit
action:
Make sure that the request is a post
request.
Make sure that the userId
variable exists in the params
struct.
Make sure that the params.userId
is of typeguid
.
If any of those checks fail, redirect the request to the index
action and place an error key in the Flash containing the message "Invalid user ID."
All that functionality in only one line of code!
What if you wanted do this for two or more variables? The params
and paramsTypes
each accept a list so you can include as many variables to check against as you want.
The only thing you need to make sure of is that the number of variables in the params
list matches the number types to check against in the paramsTypes
list. This also goes for the session/sessionTypes
and cookie/cookieTypes
arguments, which check for existence and type in the session
and cookie
scopes, respectively.
verifies() exists solely to validate controller and environment level variables and is not a substitute for Object Validationin your model.
A basic example of this is to validate params passed through to your controller from routes. Suppose we have the following route in our application:
In this example, we will want to verify that the userId
integer and address
struct are both present in the params
struct and also that userId
is of a certain type:
However, verifies() should not be used to make sure that values within the address struct themselves are valid (such as making sure that address.zipCode
is correct). Because the address
struct will be passed in to the model, the validation will be performed there.
Use the standard CFML application events through the framework.
Because the Application.cfc
file is in the public
folder of your Wheels site and it has the necessary framework initialization like onApplicationStart
, onRequestStart
, It uses Wirebox to initiailize the vendor/wheels/Global.cfc
, which contains the initialization for controller and global functions like model(), you may wonder what the best way is to use CFML's onApplicationStart, onRequestStart, etc. functions.
While it's perfectly possible to add your code directly to the public/Application.cfc
or vendor/wheels/Global.cfc
file, we certainly don't recommend it. If you add code in there, you both increase the risk of accidentally modifying how the framework functions, and you also make it a lot harder to upgrade to future versions of Wheels.
The general recommendation is to never touch any files in the wheels folder. OK, with that little warning out of the way, how does one go about using the CFML events?
The answer is to use the app/events
folder. There is a file in there for every single event that CFML triggers. So if you want some code executed on application start for example, just place your code in onapplicationstart.cfm
, and Wheels will run it when your application starts.
If you look closely in the events
folder, you will also notice that there are some custom files in there that do not match up with standard CFML events. The onmaintenance.cfm
file is one example. Let's have a closer look at these.
On Maintenance
The onmaintenance.cfm
file is included when Wheels is set to maintenance mode. After the file is included, cfabort
is called by Wheels so no other code runs in this mode.
On Error
You can place a generic error message in the onerror.cfm
file to be displayed to the users whenever your site throws an error.
If you need to access the error information here (for logging purposes, for example) it is available at arguments.exception
.
On Missing Template
The onmissingtemplate.cfm
file works in a similar way as the error file above, but it gets called when a controller or action in your application could not be found.
Note: If you want to make sure that all browsers show your custom 404 page you need to make it larger than 512 bytes in size. Google Chrome, for example, will display a friendly help page of its own when the 404 page is less than 512 bytes.
Sometimes it's useful to add functions right in the Application.cfc
file to make them available to all templates. To achieve the same thing in Wheels, you can place your functions in /app/global/functions.cfm
.
Again, because there is no Application.cfc
file for you to work with in Wheels, you have to find a suitable place to set application settings such as SessionManagement
, SessionTimeout, ScriptProtect, SetClientCookies
, and so on. These are usually set in the constructor area of an Application.cfc
file. We recommend that you set them in the app/config/app.cfm
file instead.
The routing system in CFWheels encourages a conventional RESTful and resourceful style of request handling.
The CFWheels routing system inspects a request's HTTP verb and URL and decides which controller and action to run.
Consider the following request:
The routing system may match the request to a route like this, which tells CFWheels to load the show
action on the Products
controller:
To configure routes, open the file at app/config/routes.cfm
.
The CFWheels router begins with a call to mapper(), various methods chained from that, and lastly ends with a call to end()
.
In many cases, if you need to know where to go in the code to work with existing functionality in an application, the routes.cfm
file can be a handy map, telling you which controller and action to start looking in.
The various route mapping methods that we'll introduce in this chapter basically set up a list of routes, matching URL paths to a controllers and actions within your application.
The terminology goes like this:
A route name is set up for reference in your CFML code for building links, forms, and such. To build URLs, you'll use this name along with helpers like linkTo(), startFormTag(), urlFor(), and so on.
A HTTP request method must be defined: GET
, POST
, PATCH
, or DELETE
.
You typically want to require POST
, PATCH
, or DELETE
when a given action changes the state of your application's data:
Creating record(s): POST
Updating record(s): PATCH
Deleting record(s): DELETE
You can permit listing and showing records behind a normal HTTP GET
request method.
A pattern is a URL path, sometimes with parameters in [squareBrackets]
. Parameter values get sent to the controller in the params
struct.
You'll see patterns like these in routes:
In this example, key
and slug
are parameters that must be present in the URL for the first route to match, and they are required when linking to the route. In the controller, these parameters will be available at params.key
and params.slug
, respectively.
When a request is made to CFWheels, the router will look for the first route that matches the requested URL. As an example, this means that if key
is present in the URL but not slug
, then it's the second route above that will match.
Please note that .
is treated as a special characters in patterns and should generally not be used (one exception being when you are responding with multiple formats). If your parameters may have .
in their value, please use the long form URL format: /?controller=[controller_name]&action=[action_name]&[parameter_name]=[parameter_value]
In the debugging footer, you'll see a Routes link:
[info, View Routes, Docs, Tests, Migrator, Plugins]
Clicking that will load a filterable list of routes drawn in the app/config/routes.cfm
file, including name, method, pattern, controller, and action.
If you don't see debugging information at the bottom of the page, see the docs for the showDebugInformation
setting in the Configuration and Defaults chapter.
Many parts of your application will likely be CRUD-based (create, read, update, delete) for specific types of records (users, products, categories). Resources allow you to define a conventional routing structure for this common use case.
Resources are important
You'll want to pay close attention to how resource-based routing works because this is considered an important convention in CFWheels applications.
If we have a products
table and want to have a section of our application for managing the products, we can set up the routes using the resources() method like this in app/config/routes.cfm
:
This will set up the following routes, pointing to specific actions within the products
controller:
Because the router uses a combination of HTTP verb and path, we only need 4 different URL paths to connect to 7 different actions on the controller.
What's with the PUT
?
There has been some confusion in the web community on whether requests to update data should happen along with a PUT
or PATCH
HTTP verb. It has been settled mostly that PATCH
is the way to go for most situations. CFWheels resources set up both PUT
and PATCH
to address this confusion, but you should probably prefer linking up PATCH
when you are able.
Standard resources using the resources() method assume that there is a primary key associated with the resource. (Notice the [key]
placeholder in the paths listed above in the Strongly Encouraged: Resource Routing section.)
CFWheels also provides a singular resource for routing that will not expose a primary key through the URL.
This is handy especially when you're manipulating records related directly to the user's session (e.g., a profile or a cart can be managed by the user without exposing the primary key of the underlying database records).
Calling resource() (notice that there's no "s" on the end) then exposes the following routes:
Note that even though the resource path is singular, the name of the controller is plural by convention.
Also, this example is slightly contrived because it doesn't make much sense to create a "new" cart as a user typically just has one and only one cart tied to their session.
As you've seen, defining a resource creates several routes for you automatically, and it is great for setting up groupings of routes for managing resources within your application.
But sometimes you just need to define a single one-off route pattern. For this case, you have a method for each HTTP verb: get(), post(), patch(), put(), and delete().
As a refresher, these are the intended purpose for each HTTP verb:
Security Warning
We strongly recommend that you not allow any GET
requests to modify resources in your database (i.e., creating, updating, or deleting records). Always require POST
, PUT
, PATCH
, or DELETE
verbs for those sorts of routing endpoints.
Consider a few examples:
Rather than creating a whole resource for simple one-off actions or pages, you can create individual endpoints for them.
Notice that you can use the to="controller##action"
or use separate controller
/action
arguments. The to
argument allows you to delineate controller
and action
within a single string using a #
separator (which must be escaped as a double ##
because of CFML's special usage of that symbol within string syntax).
In fact, you could mock a users
resource using these methods like so (though obviously there is little practical reason for doing so):
If you need to limit the actions that are exposed by resources() and resource(), you can also pass in only
or except
arguments:
While web standards advocate for usage of these specific HTTP verbs for requests, web browsers don't do a particularly good job of supporting verbs other than GET
or POST
.
To get around this, the CFWheels router recognizes the specialized verbs from browsers (PUT
, PATCH
, and DELETE
) in this way:
Via a POST
request with a
POST
variable named _method
specifying the specific HTTP verb (e.g., _method=delete
)
See the chapter on Linking Pages for strategies for working with this constraint.
Note that using CFWheels to write a REST API doesn't typically have this constraint. You should confidently require API clients to use the specific verbs like PATCH
and DELETE
.
The CFWheels router allows for namespaces: the ability to add a route to a "subfolder" in the URL as well as within the controllers
folder of your application.
Let's say that we want to have an "admin" section of the application that is separate from other "public" sections. We'd want for all of the "admin" controllers to be within an admin subfolder both in the URL and our application.
That's what the namespace() method is for:
In this example, we have an admin section that will allow the user to manage products. The URL would expose the products section at /admin/products
, and the controller would be stored at app/controllers/admin/Products.cfc
.
Let's say that you want to group a set of controllers together in a subfolder (aka package) in your application but don't want to affect a URL. You can do so using the package
mapper method:
With this setup, end users will see /articles
and /profile
in the URL, but the controllers will be located at app/controllers/public/Articles.cfc
and controllers/public/Profiles.cfc
, respectively.
You'll often find yourself implementing a UI where you need to manipulate data scoped to a parent record. Creating nested resources allows you to reflect this nesting relationship in the URL.
Let's consider an example where we want to enable CRUD for a customer
and its children appointment
records.
In this situation, we'd perhaps want for our URL to look like this for editing a specific customer's appointment:
To code up this nested resource, we'd write this code in app/config/routes.cfm
:
That will create the following routes:
Notice that the routes for the appointments
resource contain a parameter named customerKey
. The parent resource's ID will always be represented by its name appended with Key
. The child will retain the standard key
ID.
You can nest resources and routes as deep as you want, though we recommend considering making the nesting shallower after you get to a few levels deep.
Here's an example of how nesting can be used with different route mapping methods:
CFWheels 1.x had a default routing pattern: [controller]/[action]/[key]
. The convention for URLs was as follows:
With this convention, the URL above told CFWheels to invoke the show
action in the news
controller. It also passed a parameter called key
to the action, with a value of 5
.
If you're upgrading from 1.x or still prefer this style of routing for your CFWheels 2+ application, you can use the wildcard() method to enable it part of it:
CFWheels 2 will only generate routes for [controller]/[action]
, however, because resources and the other routing methods are more appropriate for working with records identified by primary keys.
Here is a sample of the patterns that wildcard
generates:
The wildcard
method by default will only generate routes for the GET
request method. If you would like to enable other request methods on the wildcard, you can pass in the method
or methods
argument:
Security Warning
Specifying a method
argument to wildcard
with anything other than get
gives you the potential to accidentally expose a route that could change data in your application with a GET
request. This opens your application to Cross Site Request Forgery (CSRF) vulnerabilities.
wildcard
is provided for convenience. Once you're comfortable with routing concepts in CFWheels, we strongly recommend that you use resources (resources
, resource
) and the other verb-based helpers (get
, post
, patch
, put
, and delete
) listed above instead.
CFWheels gives precedence to the first listed custom route in your app/config/routes.cfm
file.
Consider this example to demonstrate when this can create unexpected issues:
In this case, when the user visits /users/promoted
, this will load the show
action of the users
controller because that was the first pattern that was matched by the CFWheels router.
To fix this, you need the more specific route listed first, leaving the dynamic routing to pick up the less specific pattern:
Sometimes you need a catch-all route in CFWheels to support highly dynamic websites (like a content management system, for example), where all requests that are not matched by an existing route get passed to a controller/action that can deal with it.
Let's say you want to have both /welcome-to-the-site
and /terms-of-use
handled by the same controller and action. Here's what you can do to achieve this.
First, add a new route to app/config/routes.cfm
that catches all pages like this:
Now when you access /welcome-to-the-site
, this route will be triggered and the show
action will be called on the pages
controller with params.title
set to welcome-to-the-site
.
The problem with this is that this will break any of your normal controllers though, so you'll need to add them specifically before this route. (Remember the order of precedence explained above.)
You'll end up with a app/config/routes.cfm
file looking something like this:
products
and sessions
are your normal controllers. By adding them to the top of the routes file, CFWheels looks for them first. But your catch-all route is more specific than the site root (/
), so your catch-all should be listed before the call to root().
The constraints feature can be added either at an argument level directly into a resources()
or other individual route call, or can be added as a chained function in it's own right. Constraints allow you to add regex to a route matching pattern, so you could for instance, have /users/1/
and /users/bob/
go to different controller actions.
Constraints can also be used as a wrapping function:
In this example, the key
argument being made of digits only will apply to all the nested resources
Wildcard segments allow for wildcards to be used at any point in the URL pattern.
In the above example, anything/you/want
you gets set to the params.username
including the /
's. The second example would require /search/
to be on the end of the URL
If you have a nested resource where you want to enforce the presence of the parent resource, but only on creation of that resource, then shallow routes can give you a bit of a short cut. An example might be Blog Articles, which have Comments. If we're thinking in terms of our models, let's say that Articles Have Many Comments.
Without shallow routes, this block would create RESTful actions for all the nested resources, for example /articles/[articleKey]/comments/[key]
With Shallow resources, we can automatically put the index
, create
and new
RESTful actions with the ArticleKey
in the URL, but then separate out edit
, show
, update
and delete
actions into their own, and simpler URL path; When we edit or update a comment, we're doing it on that object as it's own entity, and the relationship to the parent article already exists.
So in this case, we get index
, new
and create
with the /articles/[articleKey]/
part in the URL, but to show
, edit
, update
or delete
a comment, we can just fall back to /comments/
A member()
block is used within a nested resource to create routes which act 'on an object'; A member route will require an ID, because it acts on a member. photos/1/preview
is an example of a member route, because it acts on (and displays) a single object.
A collection route doesn't require an id because it acts on a collection of objects. photos/search
is an example of a collection route, because it acts on (and displays) a collection of objects.
As of CFWheels 2.1, you can now use a redirect
argument on GET
, POST
, PUT
, PATCH
, and DELETE
requests. This will execute before reaching any controllers, and perform a 302
redirect immediately after the route is matched.
CFScript
This is useful for the occasional redirect, and saves you having to create a dedicated controller filter or action just to perform a simple task. For large amounts of redirects, you may want to look into adding them at a higher level - e.g in an Apache VirtualHost configuration, as that will be more performant.
Note
Introduced in CFWheels 2.1
By default, CFWheels will add .[format]
routes when using resources()
. You may wish to disable this behavior to trim down the number of generated routes for clarity and performance reasons (or you just don't use this feature!).
You can either disable this via mapFormat = false
on a per resource basis, or more widely, on a mapper basis:
\
Name | HTTP Verb | Path | Controller & Action | Description |
---|---|---|---|---|
Name | HTTP Verb | Path | Controller & Action | Description |
---|---|---|---|---|
HTTP Verb | Meaning |
---|---|
HTTP Verb | Path | Controller & Action | Description | |
---|---|---|---|---|
products
GET
/products
products.index
Display a list of all products
product
GET
/products/[key]
products.show
Display a specific product
newProduct
GET
/products/new
products.new
Display a form for creating a new product
products
POST
/products
products.create
Create a new product record
editProduct
GET
/products/[key]/edit
products.edit
Display a form for editing an existing product
product
PATCH/PUT
/products/[key]
products.update
Update an existing product record
product
DELETE
/products/[key]
products.delete
Delete an existing product record
cart
GET
/cart
carts.show
Display the cart
newCart
GET
/cart/new
carts.new
Display a form for creating a new cart
cart
POST
/cart
carts.create
Create a new cart record
editCart
GET
/cart/edit
carts.edit
Display a form for editing the cart
cart
PATCH/PUT
/cart
carts.update
Update the cart record
cart
DELETE
/cart
carts.delete
Delete the cart
GET
Display a list or record
POST
Create a record
PATCH/PUT
Update a record or set of records
DELETE
Delete a record
newCustomerAppointment
GET
/customers/[customerKey]/appointments/new
appointments.new
Display a form for creating a new appointment for a specific customer
customerAppointment
GET
/customers/[customerKey]/appointments/[key]
appointments.show
Display an existing appointment for a specific customer
editCustomerAppointment
GET
/customers/[customerKey]/appointments/[key]/edit
appointments.edit
Display a form for editing an existing appointment for a specific customer
customerAppointment
PATCH/PUT
/customers/[customerKey]/appointments/[key]
appointments.update
Update an existing appointment record for a specific customer
customerAppointment
DELETE
/customers/[customerKey]/appointments/[key]
appointments.delete
Delete an existing appointment record for an specific customer
customerAppointments
GET
/customers/[customerKey]/appointments
appointments.index
List appointments for a specific customer
customerAppointments
POST
/customers/[customerKey]/appointments
appointments.create
Create an appointment record for a specific customer
newCustomer
GET
/customers/new
customers.new
Display a form for creating a customer
customer
GET
/customers/[key]
customers.show
Display an existing customer
editCustomer
GET
/customers/[key]/edit
customers.edit
Display a form for editing an existing customer
customer
PATCH/PUT
/customers/[key]
customers.update
Update an existing customer
customer
DELETE
/customers/[key]
customers.delete
Delete an existing customer
customers
GET
/customers
customers.index
Display a list of all customers
customers
POST
/customers
customers.create
Create a customer
Making URLs prettier using URL rewriting.
URL rewriting is a completely optional feature of Wheels, and all it does is get rid of the index.cfm
part of the URL.
For example, with no URL rewriting, a URL in your application could look like this:
After turning on URL rewriting, it would look like this:
Combine this with the routing functionality of Wheels, and you get the capability of creating some really human-friendly (easier to remember, say over the phone, etc.) and search engine friendly (easier to crawl, higher PageRank, etc.) URLs.
Once you have added the rewrite rules (usually in either .htaccess
, web.config
or urlrewrite.xml
), Wheels will try and determine if your web server is capable of rewriting URLs and turn it on for you automatically. Depending on what web server you have and what folder you run Wheels from, you may need to tweak things a little though. Follow these instructions below for details on how to set up your web server and customize the rewrite rules when necessary.
Head's Up!
Since 2.x, engine specific URL rewrite files are not included in the default distribution. Don't worry - we've got you covered though!
For webserver specific instructions look at the following pages:
If you need to make changes to get URL rewriting to work, it's important to remember to always restart the web server and the ColdFusion server to make sure the changes are picked up by Wheels.
If you don't have access to restart services on your server, you can issue a reload=true
request. It's often enough.
URL rewriting instructions for Apache
First in your web.xml add the following servlet mapping above the default servlet mapping:
For this you can use the global web.xml that for instance can be found at: /opt/lucee/tomcat/conf/web.xml. Or, if you are on shared hosting, use a local web.xml that for instance can be found at: yourwebroot/WEB-INF/web.xml.
After that use the following .htaccess
file and Apache will pick up and use them automatically on server start-up.
Most of the time this will work. There are some exceptions though...
If you have installed Apache yourself you may need to turn on the rewrite module and/or change the security settings before URL rewriting will work:
Check that the Apache rewrite_module
has been loaded by ensuring there are no pound signs before the line that says LoadModule rewrite_module modules/mod_rewrite.so
in the httpd.conf
file.
Make sure that Apache has permission to load the rewrite rules from the .htaccess
file. This is done by setting AllowOverride
to All
under the Directory section corresponding to the website you plan on using Wheels on (still inside the httpd.conf
file).
If you have an older version of Apache and you're trying to run your Wheels site in a sub folder of an existing site you may need to hard code the name of this folder in your rewrite rules.
Change the last line of the .htaccess
file to the following: RewriteRule ^(.*)$ /sub_folder_name_goes_here/rewrite.cfm/$1 [L]
. Don't forget to change sub_folder_name_goes_here
to the actual folder name first of course.
Note that it's often considered better practice to include this URL rewriting configuration at the <virtualhost>
block level, but get it working with a .htaccess
file first.
URL rewriting instructions for IIS
Similar to Apache, IIS 7 will pick up the rewrite rules from a file located in the Wheels installation. In the case of IIS 7, the rules are picked up by adding the following web.config
file.
This requires that the URL Rewrite Module is installed. It's an IIS extension from Microsoft that you can download for free.
Missing Lucee Assets?
If you had an issue with missing Lucee CSS files, try changing{SCRIPT_NAME}
to{PATH_INFO}
in the code above, as this reportedly can resolve the issue.
Deprecated
Please note that IIS6 was official End of Life as of 2015. These notes are included for historical purposes only.
Unfortunately, there is no built-in URL rewriting mechanism in IIS 6, so getting Wheels working with pretty URLs is a little more complicated than with Apache and IIS 7 (which often comes with the official "URL Rewrite Module" installed by default). Here's what you need to do:
Download Ionic's ISAPI Rewrite Filter. NOTE: the version must be v1.2.16 or later.
Unzip the file, get the IsapiRewrite4.dll
file from the lib folder and put it in the root of your website. (It needs to be in the same folder as the IsapiRewrite4.ini
file.)
To enable the rewrite filter in IIS 6, click on Properties for your website, then go to the ISAPI Filters tab and click the Add... button.
Type in anything you want as the Filter Name and point the Executable to the IsapiRewrite4.dll
file.
Uncomment the rewrite rules in the IsapiRewrite4.ini
file.
NOTE: Make sure you have "Verify that file exists" disabled for your site.
Right click your website and select Properties.
Click Home Directory tab.
Click the Configuration button.
Under the Wildcard application mapping section, double-click path for the jrun_iis6_wildcard.dll
.
Uncheck Verify that file exists.
Click OK until all property screens are closed.
URL Rewriting for Nginx web server.
Example Nginx configuration
URL rewriting instructions for Tomcat
Tomcat 8 can be configured using RewriteValve
. See http://tonyjunkes.com/blog/a-brief-look-at-the-rewrite-valve-in-tomcat-8/ for examples.
UrlRewriteFilter (commonly referred to as Tuckey) is a Java web filter for compliant web application servers such as Tomcat, Jetty, Resin and JBoss. Unfortunately UrlRewriteFilter depends on XML with its extremely strict syntax.
First follow the (install instructions on the UrlRewriteFilter website).
Append the servlet-mapping markup to the end of the <filter mapping>
element in your WEB-INF/web.xml
Add the pretty urls rule markup to the <urlrewrite>
element to your WEB-INF/urlrewrite.xml
configuration.
Restart the web application server.
Servlet-Mapping markup
Example markup with UrlRewriteFilter and Wheels pretty URLs for WEB-INF/web.xml
.
Pretty URLs Rule markup
A complete barebones WEB-INF/urlrewrite.xml
configuration example with pretty URLs.
Hide your primary key values from nosy users.
The Wheels convention of using an auto-incrementing integer value as the primary key in your database tables will lead to a lot of URLs on your website exposing this value. Using the built-in URL obfuscation functionality in Wheels, you can hide this value from nosy users.
When URL obfuscation is turned off (which is the default setting in Wheels), this is how a lot of your URLs will end up looking:
Here, 99
is the primary key value of a record in your users table.
After enabling URL obfuscation, this is how those URLs will look instead:
The value 99
has now been obfuscated by Wheels to b7ab9a50
. This makes it harder for nosy users to substitute the value to see how many records are in your users table, to name just one example.
To turn on URL obfuscation, all you have to do is call set(obfuscateURLs=true)
in the app/config/settings.cfm
file.
No, this is not meant to add a high level of security to your application. It just obfuscates the values, making casual observation harder. It does not encrypt values, so keep that in mind when using this approach.
For instance, unless you specify it in your app/config/routes.cfm
file, you can still directly access numeric keys in the URL, e.g. /users/view/99
; However, there is a small work around you can implement to prevent this at least, using the routes constraints
argument.
This uses Regex to ensure the params.key
argument is an alpha numeric key and not just purely numeric: otherwise the route won't match.
Once you do that, Wheels will handle everything else. Obviously, the main things Wheels does is obfuscate the primary key value when using the function and deobfuscate it on the receiving end. Wheels will also obfuscate all other params sent in to as well as any value in a form sent using a get request.
In some circumstances, you will need to obfuscate and deobfuscate values yourself if you link to pages without using the function, for example. In these cases, you can use the obfuscateParam()
and deObfuscateParam()
functions to do the job for you.
How to speed up your website by caching content.
If your website doesn't get a whole lot of traffic, then you can probably skip this chapter completely. Just remember that it's here, waiting for your triumphant return.
On the other hand, you're probably hoping for massive amounts of traffic to reach your website very soon, an imminent sell-out to Google, and a good life drinking Margaritas in the Caribbean. Knowing a little bit about the CFWheels caching concepts will prepare you for this. (Except for the Margaritas... You're on your own with those ;).)
Consider a typical page on a CFWheels website. It will likely call a controller action, get some data from your database using a finder method, and render the view for the user using some of the handy built-in functions.
All this takes time and resources on your web and database servers. Let's assume this page is accessed frequently, rarely changes, and looks the same for all users. Then you have a perfect candidate for caching.
CFWheels will configure all caching parameters behind the scenes for you based on what environment mode you are currently in. If you leave everything to CFWheels, caching will be optimized for the different environment modes (and set to have reasonable settings for cache size and culling). This means that all caching is off when working in Development mode but on when in Production mode, for example.
Here are the global caching parameters you can set, their default values, and a description of what they do. Because these are not meant to be set differently based on the environment mode, you would usually set these in the app/config/settings.cfm
file:
The total amount of items the cache can hold. When the cache is full, items will be deleted automatically at a regular interval based on the other settings below.
Note that the cache is stored in ColdFusion's application
scope, so take this into consideration when deciding the number of items to store.
This is the percentage of items that are culled from the cache when maximumItemsToCache
has been reached.
For example, if you set this value to 10, then at most, 10% of expired items will be deleted from the cache.
If you set this to 100, then all expired items will be deleted. Setting it to 100 is perfectly fine for small caches but can be problematic if the cache is very large.
This is the number of minutes between each culling action. The reason the cache is not culled during each request is to keep performance as high as possible.
The measurement unit to use during caching. The default is minutes ("n
"), but you can set it to seconds ("s
") for example if you want more precise control over when a cached item should expire. For the rest of this chapter, we'll assume you've left it at the default setting.
The number of minutes an item should be cached when it has not been specifically set through one of the functions that perform the caching in CFWheels (i.e., caches()
, findAll()
, includePartial()
, etc).
When set to true
, CFWheels caches identical queries during a request. See the section titled Automatic Caching of Identical Queries below for more information about this setting.
When set to true
, CFWheels will clear out all cached queries when doing a reload=true
request. This is usually a good idea, but if you want to avoid hitting the database too hard on application reloads, you can set this to false
to keep queries cached and ease the load on the database.
If you set this to false
, all plugins will reload on each request (as opposed to during reload=true
requests only). Setting this is only recommended if you are developing a plugin yourself and need to see the impact of your changes as you develop it on a per-request basis.
The following cache variables are usually set per environment mode:
The settings shown above are what's in use in the Development mode. As you can see, when running in that mode, very little is cached. This makes it possible to do extensive changes without having to issue a reload=true
request or restart the ColdFusion server, for example. The downside is that it makes for slightly slower development because the pages will not load as fast in this environment.
The variables themselves are fairly self-explanatory. When cacheDatabaseSchema
is set to false
, you can add a field to the database, and CFWheels will pick that up right away. When cacheModelConfig
is false, any changes you make to the config()
function in the model file will be picked up. And so on.
No more Design mode
Note that "Design" mode has been removed in CFWheels 2.x: please use development mode instead.
Please refer to the Configuration and Defaults chapter for a complete listing of all the variables you can set and their default values.
In CFWheels, you can cache data in 4 different ways:
Action caching is the most effective of these methods because it caches the entire resulting HTML for the user.
Page caching is similar to action caching, but it will only cache the view page itself and in reality, this caching method is rarely used.
Partial caching is used when you only want to cache specific parts of pages. One reason for this could be that the page is personalized for a specific user, and you can only cache the sections that are not personalized.
Query caching is the least effective of the 4 caching options because it only caches result sets that you get back from the database. But if you have a busy database and you're not too concerned with leaving pages/partials uncached, this could be a good option for you.
This code specifies that you want to cache the browseByUser
and browseByTitle
actions for 30 minutes:
As you can see, the caches()
function call goes inside the config()
function at the top of your controller file and accepts a list of action names and the number of minutes the actions should be cached for.
So what happens when users request the browseByUser
page?
When CFWheels receives the first request for this page, it will handle everything as it normally would, with the exception that it also adds a copy of the resulting HTML to the cache before ending the processing.
CFWheels creates an internal key for use in the cache as well. This key is created from the controller, action, key
, and params
variables. This means, for example, that paginated pages are all stored individually in the cache (because the URL variable for the page to display would be different on each request).
When the second user requests the same page, CFWheels will serve the HTML directly from the cache.
All subsequent requests now get the cached page until it expires.
But there are 2 exceptions to this (which you can make good use of in your code to have the cache re-created at the right times). If the request is a post request (normally coming from a form submission) or if the Flash (you can read everything about the Flash in the Using the Flash chapter) is not empty, then the cache won't be used. Instead, a new fresh page will be created.
One way to use this feature is to submit your forms to the same page to have it re-created or redirect to the cached page with a message in the Flash.
Here is some code that shows this technique with using the Flash to expire the cache. (Imagine that the showArticle
page is cached and a user is adding a new comment to it.)
Note that by default, any filters set for the action are being run as normal. This means that if you do authentication in the filter (which is a common technique for sites with content where you have to login first to see it), you can still cache those pages safely using the caches() function.
However, to achieve the fastest possible cache, you can override this default and tell CFWheels to cache the HTML and serve that exactly as it is to all subsequent requests without running any filters. To do this, set the static
argument on caches() to true
. This will cache your content using the cfcache
tag behind the scenes. This means that the CFWheels framework won't even get involved with the subsequent requests until they expire from the cache again (please note that application events like onSessionStart
, onRequestStart
, etc. will run though).
This code specifies that you want to cache the view page for the browseByUser
action for 1 hour:
The difference between action caching and page caching is that page caching will run the action and then only cache the view page itself. Action caching, as explained above, does not run the action code at all (but it does run filters and verifications).
When your site contains personalized information (maybe some text specifying who you are logged in as, a list of items in your shopping cart, etc.), then action caching is not an option, and you need to cache at a lower level. This is where being able to cache only specific parts of pages comes in handy.
In CFWheels, this is done by using the cache
argument in a call to includePartial() or renderPartial(). You can pass in cache=true
or cache=x
where x
is the number of minutes you want to cache the partial for.
If you just pass in true
, the default cache expiration time will be used.
So, for example, if you have an e-commerce site that lists products with a shopping cart in the sidebar, then you'd create a partial for the list of products and cache only that.
Example code:
Behind the scenes CFWheels creates the cache key based on the name of the partial plus all of the arguments passed in to it. This means that if you pass in a userId
variable for example, they will be cached separately.
You can also use this to your advantage when it comes to expiring cached content. You could, for example, update an updatedAt
property on a user object and pass that in to includePartial
. As soon as it changes, CFWheels will recreate the cached content (since the cache key is now different).
You can cache result sets returned by your queries too. As a ColdFusion developer, this probably won't be new to you because you've always been able to use the cachedwithin
attribute in the <cfquery>
tag. The query caching in CFWheels is very similar to this.
You can use query caching on all finder methods, and it looks like this:
So there you have it: 4 easy ways to speed up your website!
When working with objects in CFWheels, you'll likely find yourself using all of the convenient association functions CFWheels makes available to you.
For example, if you have set up a belongsTo
association from article
to author
, then you will likely write a lot of article.author().name()
calls. In this case, CFWheels is smart enough to only call the database once per request if the queries are identical. So don't worry about adding performance hits when making multiple calls like that in your code.
You can turn off this functionality either by using the reload
argument to findAll() (or any of the dynamic methods that end up calling findAll() behind the scenes) or globally by adding set(cacheQueriesDuringRequest=false)
to your configuration files.
The caching of these queries are stored on a per-model basis and CFWheels will conveniently clear the cache for a model whenever anything changes in the database for a model (e.g. you call something that creates, updates or deletes records).
With the new routing system in CFWheels 2.x, there are lots of nice features which allow for better code organization. One of these is the ability to nest controllers into folders using the namespace()
method in our mapper()
call.
For example, we may have a whole "Admin" section, where for each endpoint, we need to check some permissions, and possibly load some default data. Let's say we have a Users
controller which provides standard CRUD operations.
This will automatically look for the Users.cfc
controller in app/controllers/admin/
.
By default, all your controllers extend="Controller"
, but with a nested controller, we need to change this, as the main Controller.cfc
isn't at the same folder level.
We've added a new mapping in 3.x, called app
; This mapping will correspond to the app
folder, so in our Users.cfc
we now have two options - extend the core Controller.cfc
via the app mapping, or perhaps extend another component (possibly Admin.cfc
) which extends the core Controller instead.
In the above example, we're using the app
mapping to "go to" the app
folder, and then look for a folder called controllers
, and within that, our main Controller.cfc
.
Our super.config()
call will then run the config()
function in our base Controller.
We could of course have the following too (just for completeness sake):
And then add the app.controllers.Controller
mapping to Admin.cfc
, and the extends="Admin"
in the Users.cfc
.
Of course, we can extend this concept (ha!) to Models too. However, this is either limited to tableless models, or models where you implicitly specify the table()
call. As Wheels will look for the tablename dependent on the model file location, it'll get confused if in a sub-directory.
It also potentially makes your model()
calls more complex, as you need to specify the model name in dot notation:
Configure CFWheels to properly handle CORS requests
CFWheels can often act as the "backend" in a modern web application, serving data to multiple types of frontend clients. Typically this would be in the form of (but not limited to) JSON served as an API, with something like VueJS or React on the front end, possibly served under a different domain.
When we separate our systems in such a manner, we need to consider CORS (Cross Origin Resource Sharing) and how to properly serve requests which modern browsers will allow.
If you just need to satisfy your CORS requirement quickly, you can do so from CFWheels 2.0 onwards with a simple configuration switch in your /app/config/settings.cfm
file: set(allowCorsRequests=true);
.
By default, this will enable the following CORS headers:
This will satisfy most requirements to get going quickly, but is more of a blanket "catch all" configuration which doesn't really restrict anything, or provide much information to the API consumer about your available resources.
From CFWheels 2.1
The options below were introduced in CFWheels 2.1
From CFWheels 2.1, we can be more specific. We still need to specify set(allowCorsRequests=true);
in our /app/config/settings.cfm
to turn on the main CORS functionality, but we can now provide some additional configuration options to fine tune our responses.
The Access Control Allow Origin header tells the browser whether the domain they are connecting from can access the requested resource.
By default, this header is set to a wildcard allowing connection from any domain. But it might be your VueJS app lives at app.domain.com
and we only want to allow access from that domain to our API.
You can also take advantage of the environment specific configurations, such as only allowing access to localhost:8080
in /app/config/development/settings.cfm
for example.
CFWheels 2.2 allows for subdomain wildcard matching for CORS permitted origins:
The Access Control Allow Methods tells the browser what HTTP Methods (Verbs) are allowed to be performed against the requested resource.
By default these are set to be all possible Methods, GET, POST, PATCH, PUT, DELETE, OPTIONS
. If our API only allows specific methods, we can specify them: note that this is application-wide and not dependent on route.
cfscript
Whilst setting Access Control Allow Methods site-wide is fine, it doesn't actually fulfill the CORS requirement properly - the value returned by this header should indicate what methods are available at that url. For instance, /cats
might only allow GET,POST
requests, and /cats/1/
might only allow GET,PUT,PATCH,DELETE
requests.
Thankfully, we can pull this information in from the routing system automatically! Note, set(accessControlAllowMethodsByRoute=true)
will override set(accessControlAllowMethods())
If you're sending credentials such as a cookie from your front end application, you may need to turn this header on.
If you need to specify a specific list of allowed headers, you can simply pass them into this configuration setting
The CORS spec specifies that you are only allowed either a * wildcard, or a specific URL , i.e - it doesn't in itself allow for wildcard subdomains. However in this scenario CFWheels will attempt to match the wildcard and return the full matched domain.
Where to place your view files and what to put in them.
We've talked previously about how the controller is responsible for deciding which view files to render to the user. Read the Rendering Content chapter if you need to refresh your memory about that topic.
In this chapter, we'll explain exactly where to place these files and what to put in them.
In the simplest case, your controller action (typically a function inside your controller CFC file) will have a view file associated with it. As explained in the Rendering Content chapter, this file will be included automatically at the end of the controller action code. So if you're running the show
action in the blog
controller, for example, Wheels will include the app/views/blog/show.cfm
file.
Some rules can be spotted here:
All view files live in the app/views
folder.
Each controller gets a subfolder named after it in the app/views
folder.
The view file to include is just a regular .cfm
file named after the action.
For creating standard pages, your work process will likely consist of the following steps:
Create the controller action (a function in the controller CFC file).
Create the corresponding view file for it (a .cfm
file in the controller's view folder).
There can be some exceptions to this process though, so let's go through some possible scenarios.
Not all controller actions need a corresponding view file. Consider the case where you process a form submission. To make sure it's not possible for the user to refresh the page and cause multiple submissions, you may choose to perform the form processing and then send the user directly to another page using the redirectTo() function.
Sometimes you want the controller action to render the view file for a different action than the one currently executing. This is especially common when your application processes a form and the user makes an input error. In this case, you'll probably choose to have your application display the same form again for correction.
In this case, you can use the renderView() function and specify a different action in the action
argument (which will include the view page for that action but not run the controller code for it).
Sometimes it's useful to have a view file that can be called from several controller actions. For these cases, you'll typically call renderView() with the template
argument.
When using the template
argument, there are specific rules that Wheels will follow in order to locate the file you want to include:
If the template argument starts with the /
character, Wheels will start searching from the app/views
folder. Example: renderView(template="/common/page")
will include the app/views/common/page.cfm
file.
If it contains the /
character elsewhere in the string, the search will start from the controller's view folder. Example: renderView(template="misc/page")
will include the app/views/blog/misc/page.cfm
file if we're currently in the blog
controller.
In all other cases (i.e. when the template argument does not contain the /
character at all), Wheels will just assume the file is in the controller's view folder and try to include it. Example: renderView(template="something")
will include the app/views/blog/something.cfm
file if we're currently in the blog
controller.
Also note that both renderView(template="thepage")
and renderView(template="thepage.cfm")
work fine. But most of the time, Wheels developers will tend to leave out the .cfm
part.
This is the output of your application: what the users will see in their browsers. Most often this will consist of HTML, but it can also be JavaScript, CSS, XML, etc. You are of course free to use any CFML tags and functions that you want to in the file as well. (This is a CFML application, right?)
In addition to this normal code that you'll see in most ColdFusion applications—whether they are made for a framework or not—Wheels also gives you some nice constructs to help keep your code clean. The most important ones of these are Layouts , Partials, and Helpers.
When writing your view code, you will have access to the variables you have set up in the controller file. The idea is that the variables you want to access in the view should be set unscoped (or in the variables
scope if you prefer to set it explicitly) in the controller so that they are available to the view template.
In addition to the variables you have set yourself, you can also access the params
struct. This contains anything passed in through the URL or with a form. If you want to follow MVC rules more closely though, we recommend only accessing the params
struct in the controller and then setting new variables for the information you need access to in the view.
The most important thing to remember when creating your view is to be careful not to put too much code in there. Avoid code dealing with the incoming request (this can be moved to the controller) and code containing business logic (consider moving this to a model). If you have view-related code but too much of it, it may be beneficial to break it out into a helper or a partial.
A view's job is also to clean up and format the values provided by the controller before being displayed. This is especially important when content from a data source is not HTML-encoded.
For example, if the view is to display the title
column from a query object called posts
, it should encode HTML special characters:
Please note that you do not need to do this when passing in data to CFWheels view helpers. The view helpers themselves will handle calling EncodeForUrl
, EncodeForHtml
and EncodeForHtmlAttribute
internally as needed.
By "view helpers" we mean everything listed as such in the API reference, so be aware that global helpers, such as capitalize, humanize etc, do not encode the content you pass in. When in doubt, simply test by passing in a string and check the HTML source of the output to see whether CFWheels encoded it or not.
To control encoding in general you have three global settings at your disposal (they all default to true
):
encodeURLs
: When true
, calls EncodeForUrl
to encode parameter name and values in URLFor.
encodeHtmlTags
: When true
, calls EncodeForHtml
to encode tag content in linkTo, textAreaTag etc.
encodeHtmlAttributes
: When true
, calls EncodeForHtmlAttribute
to encode attribute values in linkTo, textAreaTag etc.
All individual functions also have their own encode
argument (can be set to true
/ false
or attributes
) that overrides the global setting. Setting it to attributes
will only encode HTML attribute values but leave tag content as is. Note that the attributes
option is not available on functions that don't produce any tag content (such as imageTag for example), in those cases it's enough to pass in either true
or false
.
Simplify your views by breaking them down into partial page templates.
Partials in Wheels act as a wrapper around the good old <cfinclude>
tag. By calling includePartial() or renderPartial(), you can include other view files in a page, just like <cfinclude>
would. But at the same time, partials make use of common Wheels features like layouts, caching, model objects, and so on.
These functions also add a few cool things to your development arsenal like the ability to pass in a query or array of objects and have the partial file called on each iteration (to name one).
Websites often display the same thing on multiple pages. It could be an advertisement area that should be displayed in an entire section of a website or a shopping cart that is displayed while browsing products in a shop. You get the idea. To avoid duplicating code, you can place it in a file (the "partial" in Wheels terms) and include that file using includePartial() on the pages that need it.
Even when there is no risk of code duplication, it can still make sense to use a partial. Breaking up a large page into smaller, more manageable chunks will help you focus on each part individually.
If you've used <cfinclude>
a lot in the past (and who hasn't?!), you probably already knew all of this though, right?
To make it clear that a file is a partial and not a full page, we start the filename with an underscore character. You can place the partial file anywhere in the app/views
folder. When locating partials, Wheels will use the same rules as it does for the template
argument to renderView(). This means that if you save the partial in the current controller's view folder, you reference it simply by its name.
For example, if you wanted to have a partial for a comment in your blog
controller, you would save the file at app/views/blog/_comment.cfm
and reference it (in includePartial() and renderPartial()) with just "comment
" as the first argument.
Sometimes it's useful to share partials between controllers though. Perhaps you have a banner ad that should be displayed across several controllers. One common approach then is to save them in a dedicated folder for this at the root of the app/views
folder. To reference partials in this folder, in this case named shared, you would then pass in "/shared/banner"
to includePartial() instead.
Now that we know why we should use partials and where to store them, let's make a call to includePartial() from a view page to have Wheels display a partial's output.
That code will look for a file named _banner.cfm
in the current controller's view folder and include it.
Let's say we're in the blog
controller. Then the file that will be included is app/views/blog/_banner.cfm
.
As you can see, you don't need to specify the .cfm
part or the underscore when referencing a partial.
You can pass in data by adding named arguments on the includePartial() call. Because we use the partial
argument to determine what file to include, you can't pass in a variable named partial though. The same goes for the other arguments as well, like layout
, spacer
, and cache
.
Here is an example of passing in a title to a partial for a form:
Now you can reference the title variable as arguments.title
inside the _loginregisterform.cfm
file.
If you prefer, you can still access the view variables that are set outside of the partial. The advantage with specifically passing them in instead is that they are then scoped in the arguments
struct (which means less chance of strange bugs occurring due to variable conflicts). It also makes for more readable and maintainable code. (You can see the intent of the partial better when you see what is passed in to it).
Special arguments
The query
, object
and objects
arguments are special arguments and should not be used for other purposes than what's documented in the sections further down on this page.
There is an even more elegant way of passing data to a partial though. When you start using a partial on several pages on a site spread across multiple controllers, it can get quite cumbersome to remember to first load the data in an appropriate function in the controller, setup a before filter for it, pass that data in to the partial, and so on.
Wheels can automate this process for you. The convention is that a partial will always check if a function exists on the controller with the same name as the partial itself (and that it's set to private
and will return a struct). If so, the partial will call the function and add the output returned to its arguments
struct.
This way, the partial can be called from anywhere and acts more like a "black box." All communication with the model is kept in the controller as it should be for example.
If you don't want to load the data from a function with the same name as the partial (perhaps due to it clashing with another function name), you can specify the function to load data from with the dataFunction
argument to includePartial() and renderPartial().
Just like a regular page, Wheels partials also understand the concept of layouts. To use this feature, simply pass in the name of the layout file you want to wrap the partial in with the layout
argument, like this:
This will wrap the partial with the code found in app/views/boxes/_blue.cfm
. Just like with other layouts, you use includeContent() to represent the partial's content.
That said, your _blue.cfm
file could end up looking something like this:
One difference from page layouts is that the layout file for partials has to start with the underscore character.
It's also worth noting that it's perfectly acceptable to include partials inside layout files as well. This opens up the possibility to nest layouts in complex ways.
Caching a partial is done the same way as caching a page. Pass in the number of minutes you want to cache the partial for to the cache
argument.
Here's an example where we cache a partial for 15 minutes:
Because it's quite common to use partials in conjunction with objects and queries, Wheels has built-in support for this too. Have a look at the code below, which passes in an object to a partial:
That code will figure out that the cust
variable contains a customer
model object. It will then try to include a partial named _customer.cfm
and pass in the object's properties as arguments to the partial. There will also be an object
variable available in the arguments
struct if you prefer to reference the object directly.
Try that code and <cfdump>
the arguments
struct inside the partial file, and you'll see what's going on. Pretty cool stuff, huh?
Similar to passing in an object, you can also pass in a query result set to includePartial(). Here's how that looks:
In this case, Wheels will iterate through the query and call the _customer.cfm
partial on each iteration. Similar to the example with the object above, Wheels will pass in the objects' properties (in this case represented by records in the query) to the partial.
In addition to that, you will also see that a counter variable is available. It's named current
and is available when passing in queries or arrays of objects to a partial.
The way partials handle objects and queries makes it possible to use the exact same code inside the partial regardless of whether we're dealing with an object or query record at the time.
If you need to display some HTML in between each iteration (maybe each iteration should be a list item for example), then you can make use of the spacer
argument. Anything passed in to that will be inserted between iterations. Here's an example:
There is a feature of CFML that is very handy: the ability to output a query with the group attribute. Here's an example of how this can be done with a query that contains artists and albums (with the artist potentially being duplicated since they can have more than one album):
We have ported this great functionality into calling partials with queries as well. Here's how you can achieve it:
When inside the partial file, you'll have an additional subquery made available to you named group
, which contains the albums for the current artist in the loop.
As we've hinted previously in this chapter, it's also possible to pass in an array of objects to a partial. It works very similar to passing in a query in that the partial is called on each iteration.
So far we've only talked about includePartial(), which is what you use from within your views to include other files. There is another similar function as well: renderPartial(). This one is used from your controller files when you want to render a partial instead of a full page. At first glance, this might not make much sense to do. There is one common usage of this though—AJAX requests.
Let's say that you want to submit comments on your blog using AJAX. For example, the user will see all comments, enter their comment, submit it, and the comment will show up below the existing ones without a new page being loaded.
In this case, it's useful to use a partial to display each comment (using includePartial() as outlined above) and use the same partial when rendering the result of the AJAX request.
Here's what your controller action that receives the AJAX form submission would look like:
Please note that there currently is no support for creating the AJAX form directly with Wheels. This can easily be implemented using a JavaScript library such as jQuery or Prototype though.
CFWheels does some magic to help you link to other pages within your app. Read on to learn why you'll rarely use an <a> tag ever again.
When installing CFWheels, if you open the file at app/config/routes.cfm
, you'll see something like this:
For example, if we had a widgets
controller with a new
action, we could link to it like this:
That would generally produce this HTML markup:
Let's work with a set of sample routes to practice creating links:
With this in place, we can load the webroot of our application and click the "Routes" link in the debugging footer to get a list of our routes. You'll see information presented similarly to this:
(As you become more experienced, you'll be able look at routes.cfm
and understand what the names and parameters are. Of course, this Routes functionality is a great tool too.)
If we want to link to the routes named newWidget
and widgets
, it's fairly simple:
As you can see, you create links by calling a method with the route name passed into the route
argument. That will generate these links:
The widget
route requires an extra step because it has that [key]
parameter in its pattern. You can pass that parameter into linkTo
as a named argument:
That will produce this markup:
If you have a route with multiple parameters, you must pass all of the placeholders as arguments:
Resources are the encouraged routing pattern in CFWheels, and you will likely find yourself using this type of route most often.
Once you setup a resource in app/config/routes.cfm
, the key is to inspect the routes generated and get a feel for the names and parameters that are expected.
Consider this sample posts
resource:
If we wanted to link to the various pages within that resource, we may write something like this on the index:
The above code would generate markup like this:
Namespaces will generally add the namespace name to the beginning of the route.
Consider this namespace:
To link to the roles
resource, you would prefix it with the namespace name:
However, new
and edit
routes add the action name to the beginning of the route name:
You have the ability to nest a resource within a resource like so:
To link to the pages
resource, you add the parent resource's singular name first (e.g., the parent website
is added, making the route name websitePage
):
CFWheels 2.0 introduced security improvements for actions that change data in your applications (i.e., creating, updating, and deleting database records). CFWheels protects these actions by requiring that they happen along with a form POST
in the browser.
A common UI pattern is to have a link to delete a record, usually in an admin area. Unfortunately, links can only trigger GET
requests, so we need to work around this.
To link to a delete request's required DELETE
method, we need to code the link up as a simple form with submit button:
Then it is up to you to style the form and submit button to look like a link or button using CSS (using whatever class
es that you prefer in your markup, of course).
By the way, this will work with any request method that you please: post
, patch
, and put
as well as delete
.
Which would generate this HTML (or something like it):
You can also use your CFML engine's built-in string interpolation to embed other HTML into the link text in a fairly readable manner:
Security Notice
If you decide to opt out of encoding, be careful. Any dynamic data passed in to un-encoded values should be escaped manually using your CFML engine's EncodeForHtml()
function.
The same goes for any other argument that you pass, including but not limited to id, rel, onclick
, etc.
CFWheels will still correctly build the link markup:
An <a>
tag is easy enough, isn't it? Why would we need to use a function to do this mundane task? It turns out that there are some advantages. Here's the deal.
CFWheels gives you a good amount of structure for your applications. With this, instead of thinking of URLs in the "old way," we think in terms of what route we're sending the user to.
What's more, CFWheels is smart enough to build URLs for you. And it'll do this for you based on your situation with URL rewriting. Are you using URL rewriting in your app? Great. CFWheels will build your URLs accordingly. Not fortunate enough to have URL rewriting capabilities in your development or production environments? That's fine too because CFWheels will handle that automatically. Are you using CFWheels in a subfolder on your site, thus eliminating your ability to use URL rewriting? CFWheels handles that for you too.
Oh, and another reason is that it's just plain cool too. ;)
CFWheels's built-in function does all of the heavy lifting involved with linking the different pages of your application together. You'll generally be using within your view code.
As you'll soon realize, the function accepts a whole bunch of arguments. We won't go over all of them here, so don't forget to have a look at the for the complete details.
The call to allows a simple linking structure where we can use the helper to link to a combination of controller and action.
If you're developing a non-trivial CFWheels application, you'll quickly grow out of the wildcard-based routing. You'll likely need to link to URLs containing primary keys, URL-friendly slugged titles, and nested subfolders. Now would be a good time to take a deep dive into the chapter and learn the concepts.
When you're using to create links to routes, you need to pay attention to 2 pieces of information: the route name and any parameters that the route requires.
Name | Method | Pattern | Controller | Action |
---|
We would see these linkable routes generated related to the posts. (See the chapter on for information about posting forms to the rest of the routes.)
Name | Method | Pattern | Controller | Action |
---|
The chapter lists your options for generating URLs that are available in your application. Following is an explanation of how to link to the various types of routes available.
The helper generates a form with submit button. As you can see from the example, you can style the submit button itself by prepending any arguments with input
(e.g., inputClass
).
If you need even more control, you can code up your own with whatever markup that you like. Just be sure to pass method="delete"
to the call to startFormTag
.
If we were to use all of the parameters for , our code may look something like this:
If you'd like to use an image as a link to another page, pass the output of to the text
argument of and use the encode
argument to instruct linkTo
to only encode attributes
:
Like many of the other CFWheels view helpers, any additional arguments that you pass to will be added to the generated <a>
tag as attributes.
For example, if you'd like to add a class
attribute value of button to your link, here's what the call to would look like:
CFWheels will handle linking to pages without URL rewriting for you automatically. Let's pretend that you still have CFWheels installed in your site root, but you do not have URL rewriting on. How you write your call will not change:
The same would be true if you had CFWheels installed in a subfolder, thus perhaps eliminating your ability to use (depending on what web server you have). The same code above may generate this HTML if you had CFWheels installed in a subfolder called foo
:
If you see the pattern, this gives your application a good deal of portability. For example, you could later enable URL rewriting or move your application to a different subfolder. As long as you're using to build your links, you won't need to change anything extra to your code in order to accommodate this change.
Lastly, if you later install a that needs to modify link markup, that plugin's hook is the helper.
newWidget | GET | /widgets/new | widgets | new |
widget | GET | /widgets/[key] | widgets | show |
widgets | GET | /widgets | widgets | index |
posts | GET | /posts | posts | index |
newPost | GET | /posts/new | posts | new |
editPost | GET | /posts/[key]/edit | posts | edit |
post | GET | /posts/[key] | posts | show |