Archive for the ‘Documentation’ Category


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.

Two New Repositories Published

We have published two new repositories named cfwheels/cfwheels-www and cfwheels/cfwheels-api which handle the CFWheels.org landing page site and the api.cfwheels.org API documentation site respectively.

The cfwheels-api repository is a good example of how you can structure your own projects and keep sensitive information and the core framework files out of your project’s source control repository. The project used a .gitignore file that keeps these files out of the repo. It also uses a box.json file to specify some dependencies to pull the required files back in.

Using dependencies in a box.json file is nothing new but what’s interesting is that cfwheels/cfwheels-api has three dependencies defined. One is the core framework itself and pulls in the latest framework folder. The second is the newly published CFWheels DotEnvSettings Plugin which allows keeping sensitive information out of the repository by using a locally defined .env file. The last dependency is probably the most interesting, cause that one pulls in the Semantic Version module from ForgeBox which is a module that wasn’t specifically written for CFWheels but can be pulled in and used none the less. This opens up the potential of using many more modules from ForgeBox instead of being confined to just CFWheels Plugins.

CFWheels Guides Moved to GitBook

We are glad to announce that the CFWheels Guides have been moved to GitBook.com. The good folks at GitBook are proud to support CFWheels and have granted us an Open Source Community account. We have migrated all the guides from our old provider to GitBook and will be making some more changes as we review all the links now that the domain has been switched. A few things you’ll notice right off the bat.

There is now a PDF download link to the right of the screen when viewing the guides. The link allows you to download the section you are on or the entire CFWheels Guides. Which by the way, is nearly 300 pages long. There is also a new search feature that will allow you to find topics easier. But the biggest change comes from the fact that by moving to GitBook we were able to move the guides to GitHub as well.

In fact the guides have been added as a sub directory in the cfwheels/cfweels GitHub repository. By moving the guides within the codebase, you can finally include both code changes and documentation changes in the same PR. This will make the guides more accessible to our contributors and make it easier to keep the codebase and guides in sync.

Please have a look through the new guides and let us know what you think. Oh, and if you find something that needs to be updated, you know the drill, clone, edit, and submit a PR.

CFWheels Fully Embraces ForgeBox Packages

As you may know, many years ago CFWheels embraced the distribution of Plugins via ForgeBox packages instead of maintaining our own directory. But the framework itself remains illusive. There was some work done in the last few months to put up packages for the framework but those packages were being maintained by hand which made them a show stopper for a long term solution.

Well, thanks to a new CI workflow based on GitHub Actions we now have the building and publishing of the packages fully automated. Giving credit where credit is due, the new workflow borrows heavily from the ColdBox workflow. It used GitHub Actions, Ant, and CommandBox to automate the process.

So what does all this mean for you, let’s cut to the chase. This means you can now install a fresh copy of the framework using the following command:

box install cfwheels-base-template

This will pull down a copy of the latest stable release of the template files and then pull down a copy of the latest stable release of the framework via package dependencies. In fact the CI workflow mentioned about publishes two packages cfwheels which is the core framework directory and cfwheels-base-template which is all the other files you need to scaffold the framework.

We’ve even backfilled all the prior released versions of the framework all the way back to version 1.0.0. So you can install a particular version of the framework using the following command:

box install [email protected]

In addition you can install the bleeding edge which includes all the work in process towards the next major release using:

box install cfwheels-base-template@be

And if you ever just need to get a copy of the latest framework files simply use the following command:

box install cfwheels

All this means that upgrading to a newer version of the framework should be much easier going forward. Frankly you should just need to modify the version of the dependency in the box.json file and issue a box update command. But we’ll document that more fully when we make our next release.

For now please feel free to play with all this package goodness and let us know if we fumbled anything.

Internal Documentation in CFWheels 2.0-beta

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.

Browse the Core API

The first thing to notice is the new ‘[View Docs]’ link in the debug section in the footer:

Following that link leads you to the internal documentation. This is a dynamically created set of documention created via javaDoc commenting in the main CFWheels Core.

The three column layout is designed to allow for quick filtering by section or function name. On the left are the main CFWheels core categories, such as Controller and Model functions, which are then broken down into sub categories, such as Flash and Pagination functions etc. Clicking on a link in the first column will filter the list in the second  and third columns with all the functions which match (including child functions of that category).

Filtering by function name is made simple by a “Filter as you type” search field in the second column, so getting to the right function should be very quick.

The third column contains the main function definition, including tags, parameters and code samples.

How is it generated?

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.

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:

Documenting your own functions

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!

Plugins too!

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. We do appreciate it will take a while for the plugin eco-system to catch up with this though. More on plugins later.

Exports & Re-use

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.

Updating

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.

Code samples

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.

Roadmap

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 /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.

 

Moving towards 2.x Beta

It’s been a while in the making (probably 9 months or more) but we’re finally rolling towards a 2.0 beta release, with just a few tickets in the queue left before we can set it free on the world.

In the meantime, you may have noticed a few changes in the CFWheels ecosystem, which we’ve needed to do to support CFWheels 2.x properly.

HTTPS baby!

Firstly, all CFWheels domains are now running over https://, which in this day and age, should be a given. No excuses for not doing this now.

guides.cfwheels.org

“docs.cfwheels.org” has been renamed guides.cfwheels.org and is where the main extended documentation and tutorials will live. This system is based on readme.io, and has been great in quickly creating some nice looking docs. We will continue to add to what’s there over the beta launch too.

However, it’s not perfect: the API reference on there is simply not designed to take something of the scope of the CFWheels API. The page can crash mobile browsers and is a little…unwieldy at best. Adding to the extremely odd SEO of the reference section (i.e, it’s not indexed at all), it’s time to move it off.

api.cfwheels.org (alpha)

So as docs.cfwheels.org has been deprecated, we’re going to split out the API reference to a new home, api.cfwheels.org. There’s lots of good reasons for this: With CFWheels 2.x, not only has the entire core (well, almost the entire core) been moved to cfscript, we’ve also commented every single public API function with javadoc style comments.

This means all the core functions you know and love now have been categorized and commented in the source code, so when pull requests come in, we can keep the documentation up to date at the same time.

The 2.x core has a new built in javadoc parser, so now we can just extract a JSON file where we can power api.cfwheels.org. This also has a huge advantage – we can offer offline/built in documentation browsers from within the debug section of your app. It will even read your own functions – so your local documentation reference will be increasingly valuable. More on this in detail soon.

More to come!

Over the next few weeks, we’ll try and get some blog posts out detailing some of the new 2.x beta features in detail. If there’s something in particular you’d like to know about, please ping a comment or find us in slack/mailing list/gitHub.