<!-- wp:quote -->
<blockquote class="wp-block-quote"><!-- wp:paragraph -->
<p>This blog article was originally posted on Chris' personal blog and is republished here with his permission.</p>
<!-- /wp:paragraph --></blockquote>
<!-- /wp:quote -->
<!-- wp:paragraph -->
<p>In this post, I hope to persuade you that you will rarely ever need the <code>Tag</code>-based form helpers (<code>textFieldTag</code>, <code>selectTag</code>, etc.) in your CFWheels apps ever again.</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>“How?” you ask.</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>The answer: through the use of a wonderful feature that we affectionately call <a href="http://docs.cfwheels.org/docs/object-relational-mapping#models-without-database-tables" target="_blank" rel="noreferrer noopener">tableless models</a>.</p>
<!-- /wp:paragraph -->
<!-- wp:heading -->
<h2 class="wp-block-heading">How you’re probably used to coding search forms in CFWheels</h2>
<!-- /wp:heading -->
<!-- wp:paragraph -->
<p>So let’s code up an index form using the <code>Tag</code>-based helpers that you’re probably accustomed to using in this situation. The view’s job is to display a list of <code>invoice</code> records along with a form for narrowing by start date and end date:</p>
<!-- /wp:paragraph -->
<!-- wp:code -->
<pre class="wp-block-code"><code><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></code></pre>
<!-- /wp:code -->
<!-- wp:paragraph -->
<p>This is pretty common, and I wouldn’t go as far to say that it’s wrong.</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>Let’s code what we need in the controller to wire everything up.</p>
<!-- /wp:paragraph -->
<!-- wp:code -->
<pre class="wp-block-code"><code>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 "));
}
}</code></pre>
<!-- /wp:code -->
<!-- wp:paragraph -->
<p>But wait! We can’t have a <code>startDate</code> that occurs after the <code>endDate</code>. We better add a check for that in the controller:</p>
<!-- /wp:paragraph -->
<!-- wp:code -->
<pre class="wp-block-code"><code>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 "));
}
}</code></pre>
<!-- /wp:code -->
<!-- wp:paragraph -->
<p>That <code>index</code> 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.</p>
<!-- /wp:paragraph -->
<!-- wp:heading -->
<h2 class="wp-block-heading">Cleaning up the search form with tableless models</h2>
<!-- /wp:heading -->
<!-- wp:paragraph -->
<p>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!</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>All that we need to do is create a CFC in our <code>models</code> folder that represents this particular form. The initializer will contain a call to <code>table(false)</code>, which tells CFWheels to not try to connect it to a database.</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>In addition to <code>table(false)</code>, we can call all of the model validation initializers that we need to validate the data.</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>Lastly, we need to create a method that validates data passed into the model and runs the query if all is well.</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>Here is the finished product in <code>models/InvoiceSearchForm.cfc</code>:</p>
<!-- /wp:paragraph -->
<!-- wp:code -->
<pre class="wp-block-code"><code>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");
}
}
}</code></pre>
<!-- /wp:code -->
<!-- wp:paragraph -->
<p>Notice that <code>startDate</code> and <code>endDate</code> become properties on the model in the <code>this</code> scope. This allows us to validate those properties and refer to them in an object-oriented manner.</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>When the <code>run</code> method is called, there will be a <code>results</code> property set on the object containing the search query.</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>Next, we rewire the controller to this much simpler form:</p>
<!-- /wp:paragraph -->
<!-- wp:code -->
<pre class="wp-block-code"><code>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");
}
}
}</code></pre>
<!-- /wp:code -->
<!-- wp:paragraph -->
<p>Much cleaner, huh? Excluding whitespace and comments, this reduces the contents of the <code>index</code> action from 16 lines of actual code to 5.</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>This methodology also improves the view because we can now use <code>textField</code> instead of <code>textFieldTag</code>, and we can display validation errors near the affected form fields:</p>
<!-- /wp:paragraph -->
<!-- wp:code -->
<pre class="wp-block-code"><code><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></code></pre>
<!-- /wp:code -->
<!-- wp:paragraph -->
<p>As a bonus, our <code>InvoiceSearchForm</code> model allows us to do things like set the labels/error message labels on the form fields using <code>property</code>, and allows us to do most of what models allow us to do: namely validations and callbacks.</p>
<!-- /wp:paragraph -->
<!-- wp:code -->
<pre class="wp-block-code"><code>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");
//...
}
//...
}</code></pre>
<!-- /wp:code -->
<!-- wp:paragraph -->
<p>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.</p>
<!-- /wp:paragraph -->
<!-- wp:heading -->
<h2 class="wp-block-heading">Other uses for tableless models</h2>
<!-- /wp:heading -->
<!-- wp:paragraph -->
<p>Here are some other ideas where tableless models are a Good Idea™:</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p><strong>Authentication forms</strong><br>The model takes care of all authentication logic.</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p><strong>Password change/reset forms</strong><br>Move interface-based concepts like <code>validatesConfirmationOf</code> out of the table-based <code>user</code> model and into a tableless model.</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p><strong>Database transactions involving multiple models</strong><br>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.</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p><strong>Reports</strong><br>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.</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p><strong>NoSQL and API integration</strong><br>Are you saving your data somewhere other than a relational database? You can still model the business logic using CFWheels models.</p>
<!-- /wp:paragraph -->
<!-- wp:heading -->
<h2 class="wp-block-heading">Props</h2>
<!-- /wp:heading -->
<!-- wp:paragraph -->
<p>Most of this inspiration came from a similar concept known as <a target="_blank" rel="noreferrer noopener" href="https://robots.thoughtbot.com/activemodel-form-objects">Form Objects</a> in Ruby on Rails. (There is a great <a target="_blank" rel="noreferrer noopener" href="http://railscasts.com/episodes/416-form-objects">Railscast about Form Objects</a>here too.)</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>Also, major kudos to Tony Petruzzi for adding this awesome feature into CFWheels, which made its way into the v1.3 release.</p>
<!-- /wp:paragraph -->