I’m a relative novice using ColdFusion’s ORM features having done just one “real” project so far that took advantage of it. I’m working on an application that needs to be able to set the datasource for each request based on the URL that the customer is using to access the site. For example, if a customer visits http://customera.demoapp.com I need the application to use the DSN named “dsn-customera”, http://customerb.demoapp.com gets “dsn-customerb” and so on.
Normally this wouldn’t be an issue–you’d do some sort of logic in the onRequestStart() method in Application.cfc and set a request or session scoped variable to the name of the DSN and go from there. However, when using ColdFusion’s ORM functions, this approach doesn’t work since “this.datasource” is configure in the pseudo-constructor at the top of Application.cfc like so:
<cfset this.name = "myApp" /> <cfset this.datasource = "dsn-myapp" />
The first way that I thought of solving this was to set this.datasource=”demo” in the pseudo-constructor and then overwrite the value in the onRequestStart() method like so:
1 2 3 4 5 6 7 8 9 10 11 12
<cfset this.name = "myApp" /> <cfset this.datasource = "dsn-myapp" /> <cffunction name="onRequestStart" output="false" > <cfset local.calculatedDSN = "" /> <!--- Logic to determine correct DSN name for current customer here ---> <cfset this.datasource = local.calculatedDSN /> </cffunction>
This did in fact override the value in this.datasource as evidenced by dumping the “this” scope at the start and end of the onRequestStart() method. However, CF’s ORM system always used the value set in the pseudo-constructor even though the this.datasource value was changed successfully. After scratching my head for a while, I posted a message to the cf-orm-dev Google group with what I was trying to do and the results I was seeing.
Turns out, I was on the right track, I just had my logic in the wrong place. After exchanging a few messages with Brian Kotek, Sumit Verma, and Andrew Scott, I learned that CF’s ORM system is configured before any of the event handlers specified in Application.cfc are fired.
In the end, the answer to what I was needing to do was to put a call to a custom, private function in the pseudo-constructor to set up the customer-specific DSNs and then CF’s ORM system would be configured dynamically on each each request for the correct customer database. The only other gotcha is that you have to have a unique application name set up for each customer in order for things to be cached correctly. Attempting to set this.datasource per request while using a single application name resulted in all requests using the datasource specified by the logic on the first request to the application.
The code ended up looking something similar to this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
<cfcomponent output="false"> <!--- Application settings ---> <cfset setupCustomerValues() /> <!--- Normal Application.cfc event handler methods ---> <cffunction name="setupCustomerValues" output="false" access="private" returntype="void" hint=""> <cfset local.customerName = "" /> <cfset local.customerDSN = "" /> <!--- Logic to determine customer-specific values here ---> <cfset this.name = "myapp-#local.customerName#" /> <cfset this.datasource = local.customerDSN /> <!--- more settings declarations ---> </cffunction>
This is obviously demo/proof-of-concept/blog-brevity type code so there are a couple of things to note:
- This method creates a separate ColdFusion application in memory for every client. If you have a large number of objects that are created and cached at application startup and a large number of customers hitting the same server, you could/will run into memory issues. In my case, this was less of a problem than having to deploy a completely separate code base for each customer using the application.
- The demo code doesn’t take into account any kind of caching of the results of the customer-specific logic. Obviously in a production application, you wouldn’t want to execute that logic on every request, but cache the result after the first execution for that customer (in my example, the first request to a particular subdomain)