In the last entry, I spent a lot of time of talking about the User model behind the application. While this was important, it actually didn't have a lot to do with Model-Glue. In fact, our application didn't actually use it. Now it's time to correct that. Let's start by examining what will happen when a user logs on. First, notice the action field from our logon form:
<form action="#viewstate.getValue("myself")#logonattempt" method="post">
I talked about this in my second entry. The viewstate.getValue("myself") acts like a shorthand way of saying "root". All I needed to do was append the name of the event to run. If you tried to logon in the past few days, you would have seen Model-Glue throw an error stating that the event didn't exist. So let's add this to our ModelGlue.xml file.
<event-handler name="LogonAttempt">
<broadcasts>
<message name="authenticate" />
</broadcasts>
<results>
<result name="loggedIn" do="Home" />
<result name="notLoggedIn" do="Logon" />
</results>
</event-handler>
So what am I doing here? First I broadcast an authenticate method. This call will either return loggedIn or notLoggedIn as an event result. Notice then I check for those event results and fire the Home event or rerun the Logon event. Make sense? Now I need to add a listener for the event. In the same config file, go up to myController and add this new listener:
<message-listener message="authenticate" function="authenticate" />
Alright, we are halfway there. Now we need to add the method to the controller. Here is the code I used:
<cffunction name="authenticate" access="public" returnType="void" output="false">
<cfargument name="event" type="ModelGlue.Core.Event" required="true">
<cfset var username = arguments.event.getValue("username")>
<cfset var password = arguments.event.getValue("password")>
<cfif variables.userGateway.authenticate(username,password)>
<cfset session.loggedIn = true>
<cfset session.userBean = variables.userDAO.read(username)>
<cfset arguments.event.addResult("loggedin")>
<cfelse>
<cfset arguments.event.setValue("badlogin", 1)>
<cfset arguments.event.addResult("notloggedin")>
</cfif>
</cffunction>
The first thing you want to notice is how I get the username and password. Notice I didn't address the form scope. Instead, since Model-Glue merges all input data for me into the event object, I simply use the event.getValue() function. The cool thing about this function is that it works just fine even if the data doesn't exist. It also works if my form switches from POST to GET method. Basically, it just works. I next talk to the gateway CFC I created in the last entry. If the authentication passes, I set a marker in the session variable (the same one I checked in the getAuthenticated method). I also use the DAO CFC to create an instance of the User bean and store that in my session. This will give me access to all the user's values. Where did I get the userGateway and userDAO from? I modified the init() method to load them on startup:
<cffunction name="Init" access="Public" returnType="Controller" output="false" hint="I build a new SampleController">
<cfargument name="ModelGlue" required="true" type="ModelGlue.ModelGlue" />
<cfargument name="InstanceName" required="true" type="string" />
<cfset super.Init(arguments.ModelGlue) />
<!--- Controllers are in the application scope: Put any application startup code here. --->
<cfset variables.userGateway = createObject("component", "model.userGateway").init(getModelGlue().getConfigSetting("dsn"))>
<cfset variables.userDAO = createObject("component", "model.userDAO").init(getModelGlue().getConfigSetting("dsn"))>
<cfreturn this />
</cffunction>
The new lines here are the ones creating variables.userGateway and variables.userDAO. In general these are just simple createObject() calls. But notice how I get the DSN setting:
getModelGlue().getConfigSetting("dsn")
Where did this value came from? The ModelGlue.xml file has a config setting on top. Normally you use this to define how ModelGlue will work with your application. You can use it to turn off debugging, define caching, etc. You can also add your own values. I've added this to the config block:
<!-- Photo Gallery Settings -->
<setting name="dsn" value="PhotoGallery" />
Once I've done that, my Controller can use it as described above. By the way, I mentioned this code in an earlier entry's summary, but it was never actually in the entry. Sorry for the confusion.
So everything is ready. If you try to logon, you won't get an error. But of course, since the database is empty, you won't ever be able to logon. I've added a username/password combo for "admin" that will let you logon. You can test this here.
Summary
- As mentioned above, the last entry focused on the user model, and this entry focused on the Model-Glue hookup to that model.
- I first added an event to respond to the logon attempt.
- This event called an authenticate method in the controller file.
- If you successfully logon, I create a session variable. This same session variable is picked up by the getAuthenticated method which the Home method, and our later methods, will use.
- I added a DSN setting to the config file. The controller uses this when creating my model CFC instances.
- All in all, we have now hooked up the view, controller, and model, and hey, isn't that the point! The rest is just details!
By the way, this time I really did include the SQL file in the zip. Our folks ok so far? Am I going too slow, too fast? Skipping over stuff? Let me know!
Archived Comments
oi mate,
just curious... do you have planned out how many parts there will be?
Nah, I'm totally playing this by ear. My "vague" idea for the next few parts are:
Register support
Gallery support
Image support
(At this point you cna upload photos into galleries, so it's mostly done)
Password feature for your galleries so you can share them publicly.
Wrap up and stuff I'd change.
Cool an da' gang!
Just noticed, you link to: http://pg.camdenfamily.com/ rather than http://pg1.camdenfamily.com/
Fixed. Thanks. Normally I wouldn't bother much with small issues (well, a broken url isnt small), but I want these to be as perfect as possible. I'll be packaging them up into one zip with PDFs + code all together.
Tha pacing is great. They are just the right length to be a small thing I can work on each day without disrupting my daily workload.
Trying to get the application to run and I am encountering the following:
Could not find the ColdFusion Component model.userGateway.
Without going line by line in code, where is the best location to troubleshoot these types of errors?
Tim, is your code running from web root? Is model directly under web root? Do you have a / mapping? (If so, remove it.)
FYI, for those waiting, part 5 is about half way done. Going to publish it tomorrow morning.
Directory structure: \Inetpub\wwwroot\PhotoGallery and then I have \Inetpub\wwwroot\ModelGlue and then mapped accordingly. So you are saying that I have to move the ModelGlue directory under my PhotoGallery folder? Also is this necessary for the applications to work; what I mean is do the ModelGlue framework files have to be located under each project (MyProject/ModelGlue, NewProject/ModelGlue) because Model-Glue doesn't support the mapping?
I'd keep ModelGlue where it is, but PhotoGallery needs to be moved to web root. If yu have other stuff there, you should use a virtual server so it is "alone" at root.
How come you didn't use a IoC and use a config bean for the dsn instead of place in the configuration settings in ModelGlue.xml? It's essentially the same thing and could be swapped out, just wondering why you chose this way instead. Wouldn't it be better to encapsulate that kind of information and you could generate different connections settings on the fly without ever have to touch your main configs?
BTW, this tutorial is helping me tremendously. Thanks, so much!
theinternot - I'm just trying to keep things simple. I'm planning a post on the end to talka bout stuff I'd do different.
Just to follow up on Tim Forcelle's issue - I had the same thing - I had the ModelGlue directory in the correct location (sample applications worked fine_ - but had my application in a virtual directory so I need to add that:
createObject("component", "myDirectory.model.userGateway")
Hope that helps someone else - this is a great tutorial.
I see you have "variables.userDAO".
1. Is variables.userDAO an object?
2. Wou8ld it be too zealous to call it "variables.objUserDAO"?
1) yes.
2) If you want. When it comes to variable naming, I have two rules.
First, make it obvious what is it. Ie, don't use "x", but "numberOfHits". (I only use x for loop iterators.)
Second - be consistent.
The snippet below appears to be out of date with the new model glue specification. Where does the application startup code belong now?
<cffunction name="Init" access="Public" returnType="Controller" output="false" hint="I build a new SampleController">
<cfargument name="ModelGlue" required="true" type="ModelGlue.ModelGlue" />
<cfargument name="InstanceName" required="true" type="string" />
<cfset super.Init(arguments.ModelGlue) />
<!--- Controllers are in the application scope: Put any application startup code here. --->
<cfset variables.userGateway = createObject("component", "model.userGateway").init(getModelGlue().getConfigSetting("dsn"))>
<cfset variables.userDAO = createObject("component", "model.userDAO").init(getModelGlue().getConfigSetting("dsn"))>
<cfreturn this />
</cffunction>
You _can_ still use Init() with controllers. But it is probably best to use ColdSpring to inject the values you want into the controller.
First, what a fabulous overview!
Thanks...I've been messing with this code for several hours and I've got it to work, but I'm not sure how!
What process invokes the Init() method of the controller?
I hardcoded the DSN value :( because I can't figure out how to encapsulate the DSN value as a session wide variable. I found some articles that try to explain it how to specify a bean in ColdSpring and then somehow pass the parameter as an argument but I haven't got it to work yet.
Sill plugging away ...will not stop until the DSN is done correctly.
Joe
Model-GLue will automatically create and run the init methods.
Thanks for all your help.
The init method you outline works fine with a hardcoded dsn or with a session variable, but if I use this
getModelGlue().getConfigSetting("dsn")
I get the error:
Model-Glue: Config setting "dsn" is not defined
I've been trying to resolve this for about 6 hours, I think it's time for a break.
Got it...
Add this to ColdSpring.xml
<bean id="DataConfiguration" class="ModelGlue.Bean.CommonBeans.SimpleConfig">
<property name="config">
<!-- In Coldspring, a "map" represents a struct -->
<map>
<entry key="DSN">
<value>Logon</value>
</entry>
<entry key="username">
<value>Username</value>
</entry>
<entry key="password">
<value>Password</value>
</entry>
</map>
</property>
</bean>
Then just change the init parameter to
getModelGlue().getBean("DataConfiguration").getConfigSetting("Dsn")
<cfset variables.SecurityUserGateway = createObject("component", "Logon.model.data.Gateway.SecurityuserGateway").init(getModelGlue().getBean("DataConfiguration").getConfigSetting("Dsn"))>
<cfset variables.SecurityUserDAO = createObject("component", "Logon.model.data.dao.SecurityUserDao").init(getModelGlue().getBean("DataConfiguration").getConfigSetting("Dsn"))>