In my last entry, I walked us through the basic security setup for the application. We modified the Home event so that it broadcasted an event. This event checked to see if we were logged in, and if not, set a result back to our Home event that would force us to a Logon event. However, I hard coded this so that it always thought we needed to logon. Today we will start building the user system so that you can really logon. You may ask - don't we need the registration system first? Yes - but I find the logon to be simpler so I typically start with that. At the end of this process (in tomorrow's entry) I'll place some temporary data in the database so we can test.

Let's start by designing our Users table. The following table lists out the fields and types:

username String (required/50 char max/primary key)
password String (required/50 char max)
name String (required/50 char max)

These columns and settings should be pretty obvious. A real user table would probably have more information than just the name, but again, we want to keep it simple. I'm going to create this table in a SQL Server database, but you should be able to create it in any type of database. Once you have set up your table (and you can use the install.sql file in the zip file attached to this entry), we then need to create a DSN. For this application, the dsn name will be PhotoGallery. If you remember, we added this setting in the last entry. Now you simply need to hook it up in the ColdFusion Administrator.

So far so good. Now we finally get to work on the Model. We've worked in the Controller already. We added the getAuthenticated method, but never tied it to our Model. We've worked with the View layer as well. When we added the new Logon event, we had to write a view file to render that event. Now we need to work on the User portion of the Model.

I'm not going to spend a lot of time talking about how I set up the User model, since that could be a series to itself. I will talk a bit about it at a high level so that hopefully you get what I'm doing, and you can dig a bit deeper later on. The way I design my CFCs now are such that every data type (user, product, etc) has 3 CFCs. The first CFC is the Bean. I tend to think of the Bean as a simple instance of the data. It has methods to set and get properties. It has a simple validation method. But that's it. No logic for inserting into the database or reading all users. It basically just handles one instance. The CFC is below:

<cfcomponent output="false" displayName="User Bean">

<cfset variables.instance = structNew() /> <cfset variables.instance.username = "" /> <cfset variables.instance.password = "" /> <cfset variables.instance.name = "" />

<cffunction name="setUsername" returnType="void" access="public" output="false"> <cfargument name="username" type="string" required="true"> <cfset variables.instance.username = arguments.username> </cffunction>

<cffunction name="getUsername" returnType="string" access="public" output="false"> <cfreturn variables.instance.username> </cffunction>

<cffunction name="setPassword" returnType="void" access="public" output="false"> <cfargument name="password" type="string" required="true"> <cfset variables.instance.password = arguments.password> </cffunction>

<cffunction name="getPassword" returnType="string" access="public" output="false"> <cfreturn variables.instance.password> </cffunction>

<cffunction name="setName" returnType="void" access="public" output="false"> <cfargument name="name" type="string" required="true"> <cfset variables.instance.name = arguments.name> </cffunction>

<cffunction name="getName" returnType="string" access="public" output="false"> <cfreturn variables.instance.name> </cffunction>

<cffunction name="validate" returnType="array" access="public" output="false"> <cfset var errors = arrayNew(1)>

<cfif not len(trim(getUsername())) or not isValid("email", getUsername())> <cfset arrayAppend(errors,"Username cannot be blank and must be a valid email address.")> </cfif>

<cfif not len(trim(getPassword()))> <cfset arrayAppend(errors,"Password cannot be blank.")> </cfif>

<cfif not len(trim(getName()))> <cfset arrayAppend(errors,"Name cannot be blank.")> </cfif>

<cfreturn errors> </cffunction>

<cffunction name="getInstance" returnType="struct" access="public" output="false"> <cfreturn duplicate(variables.instance)> </cffunction>

</cfcomponent>

As I said, I don't want to spend a lot of time on the particulars of the CFC, but basically the core of the CFC are methods to set and read the three properties we discussed above.

The next CFC I create is the DAO, or Data Access Object. The purpose of this CFC is to handle persistence. It will create, read, update, and delete users. (Also known as CRUD methods.) This CFC is below, and again, as it is mostly just simple SQL, I'm not going to spend a lot of time on it.

<cfcomponent output="false" displayName="User DAO">

<cfset variables.dsn = ""> <cfset variables.LOCK = "photogallery_user">

<cffunction name="init" access="public" returnType="UserDAO" output="false"> <cfargument name="dsn" type="string" required="true">

<cfset variables.dsn = arguments.dsn>

<cfreturn this> </cffunction>

<cffunction name="create" access="public" returnType="userBean" output="false"> <cfargument name="bean" type="userBean" required="true"> <cfset var insRec = ""> <cfset var checkUsername = "">

<cflock name="#variables.LOCK#" type="exclusive" timeout="30">

<cfquery name="checkUsername" datasource="#variables.dsn#"> select username from users where username = <cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.bean.getUsername()#" maxlength="50"> </cfquery>

<cfif checkUsername.recordCount> <cfthrow message="Username already exists."> <cfelse> <cfquery name="insRec" datasource="#variables.dsn#"> insert into users(username, password, name) values( <cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.bean.getUsername()#" maxlength="50">, <cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.bean.getPassword()#" maxlength="50">, <cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.bean.getName()#" maxlength="50"> ) </cfquery>

<cfreturn bean> </cfif> </cflock> </cffunction>

<cffunction name="delete" access="public" returnType="void" output="false"> <cfargument name="id" type="numeric" required="true">

<cfquery datasource="#dsn#"> delete from users where id = <cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.id#"> </cfquery>

</cffunction>

<cffunction name="read" access="public" returnType="userBean" output="false"> <cfargument name="username" type="string" required="true"> <cfset var bean = createObject("component","userBean")> <cfset var getit = "">

<cfquery name="getit" datasource="#dsn#"> select username, password, name from users where username = <cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.username#" maxlength="50"> </cfquery>

<cfif getit.recordCount> <cfloop index="col" list="#getit.columnlist#"> <cfinvoke component="#bean#" method="set#col#"> <cfinvokeargument name="#col#" value="#getit[col][1]#"> </cfinvoke> </cfloop> </cfif>

<cfreturn bean> </cffunction>

<cffunction name="update" returnType="userBean" access="public" output="false"> <cfargument name="bean" type="userBean" required="true">

<cfquery datasource="#dsn#"> update users set username = <cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.bean.getUsername()#" maxlength="50">, password = <cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.bean.getPassword()#" maxlength="50">, name = <cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.bean.getName()#" maxlength="50"> where id = <cfqueryparam cfsqltype="cf_sql_int" value="#arguments.bean.getID()#"> </cfquery>

<cfreturn arguments.bean> </cffunction>

</cfcomponent>

There are only a few "interesting" things going on here, and they don't have anything to do with Model-Glue. First off - notice that I use a lock when creating users. This will handle checking to see that a duplicate user isn't accidentally created. (I don't do the same check on Update, but my View will prevent it. That's something I need to return to later.) Next - notice my read method. I like how I use cfinvoke and the query columns to dynamically set the bean values. This is handy for larger beans, but a bit of overkill here. Again - this isn't Model-Glue stuff, just the way I do things.

Our final CFC is the Gateway. Now the previous two CFCs (the bean and the DAO) are things that I've seen other, smarter, folks do. I've learned this setup from Joe Reinhart (creator of Model-Glue) and Sean Corfield. I credit them both because I'm not sure which of them I saw use code like this. I'm not saying they do the exact thing I've done here, but I want to be sure folks don't think I "invented" this stuff out of the blue. As it stands, I'm still learning how best to set up my CFCs, as I bet most of my readers are. You will see that I don't use this format for all my open source applications, only my most recent ones. The Gateway CFC typically will do one thing for me - return a query of all users. I also use the Gateway for "misc" type methods. So for example, if my stats page needs to tell me the last 10 users logged on, I'll use the Gateway CFC to build a method for that. If my application has a search page, I'll build a search method in the Gateway CFC. In our application, the authenticate method will reside in the Gateway. Here is our code:

<cfcomponent>

<cfset variables.dsn = "">

<cffunction name="init" access="public" returnType="userGateway" output="false"> <cfargument name="dsn" type="string" required="true">

<cfset variables.dsn = arguments.dsn>

<cfreturn this> </cffunction>

<cffunction name="authenticate" access="public" returnType="boolean" output="false"> <cfargument name="username" type="string" required="true"> <cfargument name="password" type="string" required="true"> <cfset var q = "">

<cfquery name="q" datasource="#variables.dsn#"> select username from users where username = <cfqueryparam cfsqltype="cf_sql_varchar" maxlength="50" value="#arguments.username#"> and password = <cfqueryparam cfsqltype="cf_sql_varchar" maxlength="50" value="#arguments.password#"> </cfquery>

<cfreturn q.recordCount is 1> </cffunction>

<cffunction name="getUsers" access="public" returnType="query" output="false"> <cfset var q = "">

<cfquery name="q" datasource="#variables.dsn#"> select username, password, name from users order by name asc </cfquery>

<cfreturn q> </cffunction>

</cfcomponent>

Again, nothing terribly surprising here so I won't spend a lot of time on it. The important thing to remember is - nothing I talked about above has anything to do with Model-Glue. What do I mean by that? Model-Glue handles the communication between the Controller, the View, and the Model. But the Model can be anything. You do not need to set your CFCs that way I have. The awesome thing about Model-Glue is - you could create one CFC, for example, to handle all user interaction, hook it up to the Controller, and then change the Model later on. At most you will need to modify your Controller and how it talks to the Model, but your View need not change at all.

So we just spent a lot of time on this model, and now we need to actually hook it up to Model-Glue so we can actually logon. Since this entry is pretty darn long already though I hope you don't mind if I make you wait to the next entry. (I actually began the process of modifying the Controller, so you will see that in the zip, but I'll not be talking about it till tomorrow.)

Summary

What did we do here?

  • This entry focused on the Model, specifically, the code to handle User data.
  • We started by building the database table in the back end.
  • We then created three CFCs to manage our Model.

I've included an updated zip attached to this entry. Since there was zero change to the front end, I didn't update the online example.

Download attached file.