Wheels-The Fast & Fun
CFML Framework!

Build apps quickly with an organized, Ruby on Rails-inspired
structure. Get up and running in no time!

Latest From the Wheels Dev Blog

CFWheels 1.4.1 maintenance release

Today sees a small maintenance release for the 1.4.x series Download 1.4.1 today to fix the following:

Bug Fixes

  • Skip callbacks when running calculation methods  [Adam Chapman, Per Djurner]
  • Fixed rewrite rules so base URL is rewritten correctly on Apache  [Jeremy Keczan, Per Djurner]
  • Removed incorrect path info information set by Apache [David Belanger, Per Djurner]
  • Fixed routing bug when running from a sub folder on Adobe ColdFusion 10 - [Brant Nielsen, Per Djurner]
  • Made sure error emails never depend on application variables being set - [Per Djurner]

Miscellaneous

  • Removed tests folder - [Per Djurner]
  • Updates to framework utility pages - Update logo, Fix links on congrats page to point to new documentation site - [Chris Peters]
Also don't forget to check the upgrade notes

May 30, 2015 by Tom King

Released: ColdFusion on Wheels Version 1.1.3

Today, we're releasing another maintenance release, ColdFusion on Wheels version 1.1.3. This release includes several bug fixes. Upgrade now to increase the stability of your Wheels applications. To upgrade from version 1.1.x, replace the wheels folder in your application to the new one included in the zip file. If you're upgrading from a version earlier than 1.1, there are instructions and notes in the documentation. Here are the updates included in version 1.1.3, listed in the CHANGELOG:
  • Allows for relative url linking to be turned off in autoLink()
  • Allow for default argument on sendMail() for from, to, and subject
  • You can now have bracket markers for all validation arguments
  • Columns marked as NOT NULL should allow for blank strings
  • Fixed issue with $create() supplying incorrect keys to $query()
  • The original transaction mode would not be respected during during callbacks
  • "none" transaction modes would never close
  • Incorrect $cache argument
  • Route formats prevented fullstops from being used in params
  • Controller in params should be upper camel case
  • application scope would not initialize in sub
  • validatesUniquenessOf() doesn't read soft-deletes
  • paginationLinks(): routes with page number marker variable would produce the wrong links
We'd like to extend a heart-felt thank you to everyone for continuing to support this project, submit bugs, and help resolve issues with the framework.

March 24, 2011 by Chris Peters

Wheels 3.0: Setting Up Your Development Environment

Accompanying Video

This blog post has an accompanying video posted to YouTube.


Introduction

The forthcoming release of the Wheels 3.0 framework is creating waves in the development community, promising transformative enhancements and simplified workflows. In this blog post, we will focus on establishing the foundational environment crucial for executing Wheels projects efficiently. We'll guide you through project creation using both the current and the upcoming 3.0 versions of the framework and will explore the differences in their directory structures. Let's dive right in!

Installing CommandBox

The cornerstone of our setup is CommandBox, a versatile tool that modernizes the CFML developer's workflow. CommandBox serves as a command line shell, a package manager, and a seamless interface to start a CFML engine in any given directory, without the need to juggle complex application server installations. CommandBox is accessible for Mac, Windows, and Linux OS, with installation methods varying accordingly. For Mac users like me, Homebrew is the chosen method for installation, while Windows users can turn to Chocolaty or a native installer, and Linux enthusiasts can rely on built-in package managers.

CommandBox and Wheels CLI Setup

Once CommandBox is installed, jump into the CommandBox shell via the `BOX` command in the terminal window. Note that the first launch involves downloading several packages which could take some time. Following the setup, install the Wheels CLI commands into CommandBox using `INSTALL WHEELS-CLI`. Although I've already completed this step earlier, executing this ensures you are equipped with the right tools to proceed.

Creating Projects with Wheels

With CommandBox ready, embark on creating two projects using the `WHEELS NEW` command. This command initiates a wizard that will actively guide you through the process of setting up a new Wheels project.

1. Creating a Current Version Project

  1. Naming the Project: First, supply a name for the project, for instance, "CURRENT", which creates a directory embodying the project files.
  2. Template Selection: Choose the template corresponding to the current version and proceed with the default options.
  3. Project Initialization: Conclude the process with a "Yes" to affirm configuration and initiate project setup. The CLI will create the project directory and integrate necessary files. Launch the server with `START` and witness the "Congratulations" page, assuring correct installation.

2. Creating a Wheels 3.0 Version Project

  1. Naming the Project: Again, deploy `WHEELS NEW` to name your project.
  2. Template Selection: This time, select the "Bleeding Edge" template for a Wheels 3.0 implementation. Confirm defaults and proceed to build your project.
  3. Project Initialization: Use the `START` command to usher in a new CFML application server, culminating in a fresh "Congratulations" page highlighting the new branding and 3.0 framework base.

Exploring Directory Structures

A crucial step is to delve into the structural differences between the two projects:

  1. Current Version: This project exhibits a larger assortment of directories and files at its root, which poses an increased potential attack surface.
  2. Wheels 3.0: The new framework refines this exposure significantly, only housing four directories at the root. Key directories like config, controllers, and views reside under the `app` directory, while static resources like images and stylesheets find home under the `public` directory. The `vendor` directory now accommodates core framework files, enhancing security by mapping only the public folder to the webroot of the application server.

Conclusion

Setting up an environment for Wheels 3.0 marks the beginning of an exciting journey in CFML development. From installing CommandBox to exploring framework nuances, we have geared up for advanced Wheels projects.

March 15, 2025 by Peter Amiri

CFWheels Joins Open Source Collective

We are happy to announce that CFWheels has joined Open Source Collective. According to their website, Open Collective enables all kinds of groups to raise, manage, and spend money transparently. We’re also in good company in the collective. Other projects hosted by the Open Source Collective include Lucee, WebPack, PHP Foundation, Vue, LinuxServer, ESLint, Bower, Svelte, and the list literally goes on and on.

So what does this mean for CFWheels. Well, it allows us to finally be able to accept donations from our community. Many of you have offered your donations to us in the past but we really had no good way to do accept them legally. Plus we felt strongly that as an open source project we needed to adopt an open and transparent accounting practices. As a member of the collective, you’ll be able to donate and see every dollar we raise and what it is spent on. Creating a sustainable ecosystem is important for the long term viability of the CFWheels project. So how do you donate, visit Open Source Collective directly or any of our GitHub projects and look for the Sponsor this project link in the right side bar.

We’ve already received our first monthly donation and we are truly grateful. These funds will allow us to offer bounties for small issues or bugs, commission larger works, and pay for marketing, logo, or branding services.

April 25, 2022 by Peter Amiri

Building search forms with tableless models in CFWheels

This blog article was originally posted on Chris' personal blog and is republished here with his permission.

In this post, I hope to persuade you that you will rarely ever need the Tag-based form helpers (textFieldTagselectTag, etc.) in your CFWheels apps ever again.

“How?” you ask.

The answer: through the use of a wonderful feature that we affectionately call tableless models.

How you’re probably used to coding search forms in CFWheels

So let’s code up an index form using the Tag-based helpers that you’re probably accustomed to using in this situation. The view’s job is to display a list of invoice records along with a form for narrowing by start date and end date:

<cfoutput>

#startFormTag(route="invoices", method="get")#
  #textFieldTag(name="startDate", value=params.startDate)#
  #textFieldTag(name="endDate", value=params.endDate)#
  #submitTag(value="Filter Invoices")#
#endFormTag()#

<table>
  <thead>
    <tr>
      <th>Invoice</th>
      <th>Date</th>
      <th>Amount</th>
    </tr>
  </thead>
  <tbody>
    <cfloop query="invoices">
      <tr>
        <td>#h(id)#</td>
        <td>#DateFormat(createdAt)#</td>
        <td>#DollarFormat(amount)#</td>
      </tr>
    </cfloop>
  </tbody>
</table>

</cfoutput>

This is pretty common, and I wouldn’t go as far to say that it’s wrong.

Let’s code what we need in the controller to wire everything up.

component extends="Controller" {
  function index() {
    param name="params.startDate" default="";
    param name="params.endDate" default="";
    
    local.where = [];
    
    if (IsDate(params.startDate)) {
      ArrayAppend(local.where, "createdAt >= '#params.startDate#'");
    }
    
    if (IsDate(params.endDate)) {
      local.nextDay = DateAdd("d", 1, params.endDate);
      local.nextDay = DateFormat(local.nextDay, "m/d/yyyy");
      ArrayAppend(local.where, "createdAt < '#local.nextDay#'");
    }
    
    invoices = model("invoice").findAll(where=ArrayToList(local.where, " AND "));
  }
}

But wait! We can’t have a startDate that occurs after the endDate. We better add a check for that in the controller:

component extends="Controller" {
  function index() {
    param name="params.startDate" default="";
    param name="params.endDate" default="";
    
    local.where = [];
    
    // Let's make sure the start date and end date jive.
    if (IsDate(params.startDate) && IsDate(params.endDate) && params.startDate > params.endDate) {
      flashInsert(error="The start date must be on or before the end date.");
    }
    
    if (IsDate(params.startDate)) {
      ArrayAppend(local.where, "createdAt >= '#params.startDate#'");
    }
    
    if (IsDate(params.endDate)) {
      local.nextDay = DateAdd("d", 1, params.endDate);
      local.nextDay = DateFormat(local.nextDay, "m/d/yyyy");
      ArrayAppend(local.where, "createdAt < '#local.nextDay#'");
    }
    
    invoices = model("invoice").findAll(where=ArrayToList(local.where, " AND "));
  }
}

That index action is getting pretty beefy at this point. And now we’re starting to validate our data in the controller, which can quickly turn into a tangled mess after we’ve added another field or two to the form.

Cleaning up the search form with tableless models

As it turns out, models in CFWheels come with a bunch of really helpful methods for validating data. And even though we’re not using this form to save data to a database, we can still use the model validations to validate our data. Hooray!

All that we need to do is create a CFC in our models folder that represents this particular form. The initializer will contain a call to table(false), which tells CFWheels to not try to connect it to a database.

In addition to table(false), we can call all of the model validation initializers that we need to validate the data.

Lastly, we need to create a method that validates data passed into the model and runs the query if all is well.

Here is the finished product in models/InvoiceSearchForm.cfc:

component extends="Model" {
  function config() {
    // Make it tableless
    table(false);
    
    // Validations
    validatesFormatOf(properties="startDate,endDate", type="date", allowBlank=true);
    validate("startDateBeforeEndDateValidation");
  }
  
  boolean function run() {
    // Run validations and abort if failed.
    if (!this.valid()) {
      this.results = QueryNew("");
      return false;
    }
    
    // Continue with query if validation passed.
    local.where = [];
    
    if (IsDate(this.startDate)) {
      ArrayAppend(local.where, "createdAt >= '#this.startDate#'");
    }
    
    if (IsDate(this.endDate)) {
      local.nextDay = DateAdd("d", 1, this.endDate);
      local.nextDay = DateFormat(local.nextDay, "m/d/yyyy");
      ArrayAppend(local.where, "createdAt < '#local.nextDay#'");
    }
    
    this.results = model("invoice").findAll(where=ArrayToList(local.where, " AND "));
    return true;
  }
  
  private function startDateBeforeEndDateValidation() {
    if (IsDate(this.startDate) && IsDate(this.endDate) && this.startDate > this.endDate) {
      this.addError("startDate", "Start Date must be on or before End Date");
    }
  }
}

Notice that startDate and endDate become properties on the model in the this scope. This allows us to validate those properties and refer to them in an object-oriented manner.

When the run method is called, there will be a results property set on the object containing the search query.

Next, we rewire the controller to this much simpler form:

component extends="Controller" {
  function index() {
    // Note that moving this into an object named `search` will change the
    // `params` struct slightly.
    param name="params.search.startDate" default="";
    param name="params.search.endDate" default="";
    
    // We pass the `params.search` struct in as properties on the search form
    // object.
    search = model("invoiceSearchForm").new(argumentCollection=params.search);
    
    // This runs the search and adds an error message if validation fails.
    if (!search.run()) {
      flashInsert(error="There was an error with your search filters");
    }
  }
}

Much cleaner, huh? Excluding whitespace and comments, this reduces the contents of the index action from 16 lines of actual code to 5.

This methodology also improves the view because we can now use textField instead of textFieldTag, and we can display validation errors near the affected form fields:

<cfoutput>

#startFormTag(route="invoices", method="get")#
  #textField(objectName="search", property="startDate")#
  #errorMessageOn(objectName="search", property="startDate")#
  
  #textField(objectName="search", property="endDate")#
  #errorMessageOn(objectName="search", property="endDate")#
  
  #submitTag(value="Filter Invoices")#
#endFormTag()#

<table>
  <thead>
    <tr>
      <th>Invoice</th>
      <th>Date</th>
      <th>Amount</th>
    </tr>
  </thead>
  <tbody>
    <cfloop query="search.results">
      <tr>
        <td>#h(id)#</td>
        <td>#DateFormat(createdAt)#</td>
        <td>#DollarFormat(amount)#</td>
      </tr>
    </cfloop>
  </tbody>
</table>

</cfoutput>

As a bonus, our InvoiceSearchForm model allows us to do things like set the labels/error message labels on the form fields using property, and allows us to do most of what models allow us to do: namely validations and callbacks.

component extends="Model" {
  function config() {
    table(false);
    
    // Set property labels for form fields and related error messages.
    property(name="startDate", label="Start");
    property(name="endDate", label="End");
    
    //...
  }

  //...
}

I find this to be a nice pattern because it ties the form to the model in a fairly clean, object-oriented way: the model represents the form, so it makes sense for it to define how labels on the form should appear.

Other uses for tableless models

Here are some other ideas where tableless models are a Good Idea™:

Authentication forms
The model takes care of all authentication logic.

Password change/reset forms
Move interface-based concepts like validatesConfirmationOf out of the table-based user model and into a tableless model.

Database transactions involving multiple models
Nested properties have their limits and logic related to them can really pollute your table-based models. Handle all of the logic in a model that’s intimately involved with the form.

Reports
Have you ever found yourself in a situation where you needed to run a query involving multiple database tables, but it was unclear which model to write the query in? Tableless models are a perfect way to avoid making a random decision.

NoSQL and API integration
Are you saving your data somewhere other than a relational database? You can still model the business logic using CFWheels models.

Props

Most of this inspiration came from a similar concept known as Form Objects in Ruby on Rails. (There is a great Railscast about Form Objectshere too.)

Also, major kudos to Tony Petruzzi for adding this awesome feature into CFWheels, which made its way into the v1.3 release.

November 12, 2023 by Chris Peters

10 years of CFWheels / Welcome Adam / CFWheels 2.x

A bit of history: It's slightly hard to put an exact date on it, but this year (probably) celebrates 10 years of CFWheels! Obviously, in the internet age, 10 years is an awfully long time. The first mention I can find if from Pete Freitag's "Get Wheelin" blog post celebrating CFWheels 0.1 in November 2005. Rob Cameron, the original author moved over to Rails full time a few years later: you can catch up with him at http://ridingtheclutch.com/. Over the years there have been a lot of contributors - whilst our GitHub repo hasn't quite got that (very) early history, since Jul 23, 2006, we've had:
  • 2825 commits (Per Djurner has the dubious claim to fame of the first commit, and at time of writing, the most recent too :))
  • 22 Branches
  • 43 Releases
  • 76 forks
  • 453 issues
Whilst there was a "bit of a break" around 2012/13, Wheels has been going from strength to strength. Contributors have changed and moved on, and so have core team members. Our thanks go out to all of them! Welcome Adam! We're very pleased that Adam Chapman (@chapmandu) has agreed to join the CFWheels core team! He’s been a long-time supporter of wheels, we're very glad to have him on board. We expect great things AC.... great things. :) You can find Adam's blog here. CFWheels 2.x: Lots of chat at the moment about the next major release of CFWheels - please do get involved on the Google group if you've got ideas. At the moment, amongst lots of micro improvements, we're looking at:
  • integrating the ColdRoute plugin into the core:  ColdRoute allows you to define RESTful resources through new expressive routing helpers and controller conventions. It also allows you to organize controllers and views into subfolders via "namespaces" or "modules."
  • improving wheels as a true RESTful service provider: you can already return JSON, XML and lots of other good stuff, but we're looking to improve things like setting custom headers, and really controlling your APIs response
  • improving the plugin architecture, and generally looking a more "modular" way of doing things.
  • dropping CF8/9 support; dropping Railo (as you should all be on Lucee now!!)
  • better Commandbox support: we'll be looking at CLI type stuff to make getting going with wheels even quicker.
Got an idea? Get on the Google Group and let us know!

March 23, 2016 by Tom King

Download ColdFusion on Wheels 0.9.3

Wheels matures a little more with version 0.9.3. Download it today. For those of you upgrading from Wheels 0.9.2, the most that you'll probably need to do is delete the wheels folder from your install and replace it with the new wheels folder. Voila! What's new in this release?

Bug Fixes

  • Fixes to make Wheels run better on Railo 3.1 and the upcoming Adobe ColdFusion 9.
  • Fix to make it possible to use routes in forms.
  • Fix so that functions added to the events/functions.cfm file are globally available in the application and not just from ColdFusion events.
  • Fixes to the Oracle database adapter.
  • Fixes to make the ORM work better when mapping properties to columns with different names.
  • …and more minor fixes.

Feature Enhancements

  • Partials now allow you to pass a query, which will cause the partial to be run for every record in the query.
  • Improvement to the simpleFormat() function.
  • Improvements for display of pagination links.
  • For security reasons, Wheels will now abort all requests that don't go through index.cfm or rewrite.cfm in the root.

New Functionality

  • Added 3 new object callback types: afterNew(), afterFind(), and afterInitialization().
  • Support for the PostgreSQL DBMS.
  • Support for setting default values on objects based on the database settings (through the new(), create(), and save() methods).
  • New dependent setting for associations, which decides which join type to use in queries and whether or not to delete associated objects when the parent is deleted.
Watch for updates to the Documentation over the next few days.

December 10, 2009 by Chris Peters

The cf.Objective() Finale

I will be speaking at cf.Objective() 2011 in a little less than an hour. If you're around, come by to get a free Wheels t-shirt! Awesome scotch glass logo design by the team at Hipervinculo. P.S. To keep things consistent with the CFUnited post from earlier this year, I'm attaching another blurry photo. All photos of t-shirts must look like Big Foot sightings.

May 14, 2011 by Chris Peters

Upcoming (and Previous) Wheels Presentations

Thanks to the ColdFusion Community for supporting Wheels. Hat-tips to upcoming ColdFusion Conferences cf.Objective() and CFUnited for accepting and expressing interest in Wheels sessions. Individual thanks to Sean Corfield for asking people to submit Wheels topics to CFUnited and Charlie Arehart for hosting Wheels topics on the Online ColdFusion Meetup.

Upcoming

Previous

Screencasts (each under 10 minutes)

Screencast page Postcast feed

Call for Speakers and Audiences

If you are interesting in presenting or have someone present for your event, please contact Wheels via the Google Group. The group will be glad to help.

December 11, 2010 by Mike Henke

ColdFusion on Wheels 1.1 Beta 1 Is Now Available

After tons of work on some exciting new features, the core team is happy to release the first beta of ColdFusion on Wheels 1.1 today. Now we are turning to the community to help us find bugs and put the new features through their paces. We need your help! Here is a taste of some of the new features:
  • Nested properties allow you to save changes to an object and all of its children in a single call to save(), create(), or update().
  • All database calls are wrapped in transactions by default.
  • Wheels now supports SQLite and H2 databases.
  • Automatic validations give Wheels the ability to introspect your database and automatically validate max lengths, presence of, and numericality of your data.
  • You can now respond with multiple output formats in a single controller action.
  • Automatic dependency handling for your models' associated data is now available.
  • Nested layouts allow you to extend layouts and keep your view code DRY.
  • A testing framework for your applications and plugins. (Documentation and details coming soon.)
  • cfwheels.org now allows you to browse reference guides and API documentation for both 1.0.x and 1.1.x versions of Wheels (and beyond).
  • Many other major and minor improvements. See the changelog for more details.
The core team will be fixing bugs and improving documentation over the next few weeks. We appreciate any feedback that you can give us in the Google Group or the issue tracker. We have started a chapter in the docs on upgrading from 1.0.x to 1.1.x and will be adding to the instructions as we run across more gotchas. Now let's get going on continuing to improve the best ColdFusion framework out there!

December 10, 2010 by Chris Peters

Welcome to Our Community

Welcome to Our Community - a place where like-minded people connect, share ideas,
and grow together in a positive and supportive environment.

Explore community
Wheels.dev Community

Top Contributors