Calling controller from controller

153 views
Skip to first unread message

Alex Purice

unread,
Apr 4, 2012, 7:46:15 PM4/4/12
to framework-one
Hello folk,

I'm kind of throwing a white flag... :( After 10+ years of Fusebox, a
project in CFWheels, I decided to use FW/1 for my new project.
Everything went just perfect until I got to a situation when I need to
call a controller from a controller. The reason I need it is simple -
project is very big (200+ DB tables now and expected to be 300+ DB
tables soon). I'm want to distribute programming part of the project
between several programmers for CRUD ("sub"-form) programming of each
DB table. And in final stages I want to "join" big amount of those
"sub"-forms into a few bigger forms. Another reason of such approach
is that some "sub"-forms are identical on different bigger forms
So,
I can call sequence of services (to retrieve data for each specific
"sub"-form and to save respective parts of data from each "sub"-form
to the database),
I can call sequence of views (to make big forms or of a number of
smaller "sub"-forms),
but I can't call sequence of controller methods to perform
initialization of data input for each "sub"-form or validation of data
input for respective "sub"-forms.

I miss my Fusebox's DO verb. Please advice on how to approach the
problem, I'm really puzzled.

Sean Corfield

unread,
Apr 4, 2012, 8:17:13 PM4/4/12
to framew...@googlegroups.com
On Wed, Apr 4, 2012 at 4:46 PM, Alex Purice <alexei...@gmail.com> wrote:
> Everything went just perfect until I got to a situation when I need to
> call a controller from a controller.

Short answer: don't do that.

But if you have:

function foo(rc) {
...
}

then you *could* do:

function bar(rc) {
...
foo(rc);
...
}

I strongly discourage doing so, however.

Controllers should never call other controllers. If there is common
code, factor it into your service layer and call it from both
controllers instead. It sounds to me like you're just trying to make
your controller methods too "fat". They should be very simple. The
"work" should all be in your Model, in the service layer for
coordination of objects and in the business objects themselves.

> I miss my Fusebox's DO verb.

You want "do" service methods instead of controller methods. In a
large project, I'd also avoid service() and manage service calls
directly (use ColdSpring or DI/1 to manage object life cycles and auto
wiring).

To avoid a lot of boring CRUD work, you might want to look at ORM
(assuming you're on Railo or ACF9/10). I personally don't like ORMs***
but it will save you a boatload of tedious coding.

Hope that helps?

*** Like Ted Neward and Jeff Atwood, I view ORMs as the "Vietnam of
computer science":

http://www.codinghorror.com/blog/2006/06/object-relational-mapping-is-the-vietnam-of-computer-science.html

and:

http://www.codinghorror.com/blog/2004/07/why-objects-suck.html
--
Sean A Corfield -- (904) 302-SEAN
An Architect's View -- http://corfield.org/
World Singles, LLC. -- http://worldsingles.com/

"Perfection is the enemy of the good."
-- Gustave Flaubert, French realist novelist (1821-1880)

Judith Barnett

unread,
Apr 4, 2012, 9:27:00 PM4/4/12
to framew...@googlegroups.com
In a bit of an off topic thread hijack, just out of curiosity and
because I am always on a search for best methods, Sean how do you
handle data? Is there a magic Sean blog post somewhere that presents
your views on it?

Sean Corfield

unread,
Apr 4, 2012, 10:16:54 PM4/4/12
to framew...@googlegroups.com
Not sure what you mean by "how do you handle data?"... Could you be a
bit more specific?

Sean

Judith Barnett

unread,
Apr 4, 2012, 11:37:44 PM4/4/12
to framew...@googlegroups.com
As opposed to using ORM, what methods to you use to manipulate data?

> --
> FW/1 on RIAForge: http://fw1.riaforge.org/
>
> FW/1 on github: http://github.com/seancorfield/fw1
>
> FW/1 on Google Groups: http://groups.google.com/group/framework-one

Sean Corfield

unread,
Apr 5, 2012, 2:35:17 AM4/5/12
to framew...@googlegroups.com
On Wed, Apr 4, 2012 at 8:37 PM, Judith Barnett <jmba...@gmail.com> wrote:
> As opposed to using ORM, what methods to you use to manipulate data?

Ah, I'm with you now!

There are two things that are "bad" about ORM:
1) they traffic in objects (when structs or queries are more efficient
/ more effective)
2) they manage relationships between objects (so they tend to do some
very inefficient database fetches)

At World Singles, we've created a thin wrapper around JDBC that
traffics in structs (and arrays of structs). Our back end is
increasingly developed in Clojure now, which has maps (structs),
vectors (arrays) and sequences (also effectively arrays), so using the
Clojure JDBC library makes sense for us.

We also have a very thin generic "bean" CFC that acts as an Iterating
Business Object. This allows us to wrap an object-like API over a
result set, even if it is a join across multiple tables. We can
provide a specific CFC, to override the generic bean, so we have
calculated methods as well as getter / setter methods.

This gives us control over retrieving related objects so we can
optimize that (with custom SQL and joins) but also gives us the
flexibility of objects without the overhead. The combination of a few
thin layers means we can interact at different levels of abstraction
depending on the efficiency we need (from Clojure code to CFML structs
to CFML objects).

Nando

unread,
Apr 5, 2012, 8:55:59 AM4/5/12
to framew...@googlegroups.com
Sean,

What I find helpful about CF's ORM implementation is that for simple CRUD operations, I don't have to write and maintain the SQL. With the approach you are using, is is necessary to forgo that advantage? Do you still need to write all the insert and update statements when you store data in a MySql database via Clojure, or can you simply pass a struct in and it's persisted?




--
Nando M. Breiter
The CarbonZero Project
CP 234
6934 Bioggio
Switzerland

+41 91 606 6372

na...@carbonzero.ch
www.carbonzero.ch

Sean Corfield

unread,
Apr 5, 2012, 4:38:45 PM4/5/12
to framew...@googlegroups.com
On Thu, Apr 5, 2012 at 5:55 AM, Nando <d.n...@gmail.com> wrote:
> What I find helpful about CF's ORM implementation is that for simple CRUD
> operations, I don't have to write and maintain the SQL. With the approach
> you are using, is is necessary to forgo that advantage? Do you still need to
> write all the insert and update statements when you store data in a MySql
> database via Clojure, or can you simply pass a struct in and it's persisted?

The latter. It's handled in the Clojure JDBC library but it's easy
enough to write a generic CFML version that generates an INSERT or
UPDATE based on the keys in the struct you pass in. There's some
subtleties around PK handling but if you have a couple of simple
conventions, it falls into place easily (we use ID in all tables and
it's auto-generated by the DB; so a struct with an ID key is an UPDATE
and a struct without an ID key is an INSERT). You also need to
remember dirty data vs loaded data but that's easy with two structs:
getFoo() checks dirty.foo then loaded.foo; setFoo() writes to
dirty.foo; save() uses the dirty struct for INSERT/UPDATE.

Alex Purice

unread,
Apr 5, 2012, 8:27:17 PM4/5/12
to framework-one
Thank you so much, Sean, for the reply! :) Yeah, it does help.
Unfortunately you just puzzled me even deeper. HAHA!

Nah... I'm kidding. I'm fine. Your advice helped a lot, even I >was<
totally disagree with yesterday evening after I read your post. :D
Just a thing I'm not really sure about is - seems like if I use ORM
(which I spent a load of time on today for investigations and
considerations of if I want to apply it or not really) I don't want to
use my FW/1 service as ORM component. IE, if I have Products.cfc in FW/
1, I also want to have ORM's Products.cfc on a separate path and talk
to ORM's Products.cfc from within FW/1's Products.cfc. Is it a true
statement?

Re ORM
After CFWheels I'm really suspicious when it comes to acronym "ORM". I
like the idea of using partial ColdFusion's built-in ORM, when I can
put CUD to ORM and do the R on myself.


On Apr 4, 5:17 pm, Sean Corfield <seancorfi...@gmail.com> wrote:
> http://www.codinghorror.com/blog/2006/06/object-relational-mapping-is...
>
> and:
>
> http://www.codinghorror.com/blog/2004/07/why-objects-suck.html
> --
> Sean A Corfield -- (904) 302-SEAN
> An Architect's View --http://corfield.org/
> World Singles, LLC. --http://worldsingles.com/

Sean Corfield

unread,
Apr 6, 2012, 1:21:58 AM4/6/12
to framew...@googlegroups.com
On Thu, Apr 5, 2012 at 5:27 PM, Alex Purice <alexei...@gmail.com> wrote:
> Just a thing I'm not really sure about is - seems like if I use ORM
> (which I spent a load of time on today for investigations and
> considerations of if I want to apply it or not really) I don't want to
> use my FW/1 service as ORM component.

ORM is for Domain Objects, not services. I generally have my Domain
Objects under /model/beans (and my service CFCs under /model/services
because I manage my service CFCs using a DI framework).

> After CFWheels I'm really suspicious when it comes to acronym "ORM".

Oh? I've never used CFWheels (I'm not a fan of full-stack frameworks,
in case folks hadn't guessed). Did you run into problems?


--
Sean A Corfield -- (904) 302-SEAN

An Architect's View -- http://corfield.org/

World Singles, LLC. -- http://worldsingles.com/

Nando

unread,
Apr 6, 2012, 6:08:12 AM4/6/12
to framew...@googlegroups.com
That sounds very interesting. What about sql type? Is that handled automagically by the Clojure JDBC library? 

The only way I can think to deal with sql type if the approach you describe were implemented in CF would be some sort of hungarian notation, so that the code generator would be able to determine the cfsqltype parameter for the cfqueryparam tags from the struct key (for example, dirty.date_Birthdate or dirty.str_Name). And I remember from years ago that you said you hate hungarian notation. So I have my doubts that's the approach you're taking. :-)




--
Nando Breiter

Aria Media
via Rompada 40
6987 Caslano
Switzerland


Nando

unread,
Apr 6, 2012, 6:17:08 AM4/6/12
to framew...@googlegroups.com
Alex,

One thing that might help the transition to using services in FW/1 is to get something working in the controller first, and then refactor to a service to improve the architecture, and allow it to be reusable by other processes, as a second step. I find it difficult sometimes to be concerned about architecture and code detail at the same time. My thinking is split between the big picture and the nitty gritty, and I don't do that very well. So I've found refactoring to be helpful there.

Sean Corfield

unread,
Apr 6, 2012, 9:37:59 PM4/6/12
to framew...@googlegroups.com
On Fri, Apr 6, 2012 at 3:08 AM, Nando <d.n...@gmail.com> wrote:
> That sounds very interesting. What about sql type? Is that handled
> automagically by the Clojure JDBC library?

Sort of. The underlying JDBC driver actually verifies / coerces the
types to some degree but you can't pass a string ("123") where an
integer is expected. Luckily Railo actually keeps numbers as numbers
and dates as dates (instead of converting back and forth to strings
like ACF does) so interop between Railo and Clojure is seamless and
when you pass data down to the JDBC layer, it's already the right
type. It does mean that you need to be a bit more careful about types
in your own code, e.g., calling val() on form & URL variables that are
supposed to be numeric and parseDateTime() or similar for dates, but
that's just good practice anyway - and should make your code run
faster.

> The only way I can think to deal with sql type if the approach you describe
> were implemented in CF would be some sort of hungarian notation

Ugh! Horrible idea. Having worked with the Clojure wrapper around
JDBC, I actually find CFML's cfqueryparam stuff to be horribly bulky
and redundant - the driver knows what types the columns are so a
simple parameterized SQL statement should not need any additional type
information. Here's a typical SQL query:

variables.orm.createIterator(
name = "user",
sql = "SELECT * FROM user WHERE siteId = ? AND username LIKE ?",
params = [ siteId, pattern ]
);

That calls orm.execute( sql, params ) - which drops down thru Clojure
to a PreparedStatement in Java - and returns a sequence of maps (think
array of structs in CFML). The name parameter specifies what bean type
to use (i.e,. which CFC to instantiate) and that is initialized with
the sequence and marked as an iterator. Standard methods hasMore() and
getNext() implement the iterator pattern. The getters return
data[currentRow][colName]. Setter update the dirty struct. Calling
save() performs an update based on the dirty struct.

There is no code generation here, BTW.

Nando

unread,
Apr 9, 2012, 12:37:15 PM4/9/12
to framew...@googlegroups.com
Thanks very much for this comprehensive reply, Sean. It's inspired me to look deeper into cfmljure and clojure.

Sean Corfield

unread,
Apr 9, 2012, 1:23:55 PM4/9/12
to framew...@googlegroups.com
On Mon, Apr 9, 2012 at 9:37 AM, Nando <d.n...@gmail.com> wrote:
> Thanks very much for this comprehensive reply, Sean. It's inspired me to
> look deeper into cfmljure and clojure.

The latest cfmljure CFC that we use at World Singles is up-to-date in
github but the documentation may be a bit behind.

Note also that interop between ColdFusion and Clojure is nowhere near
as seamless as between Railo and Clojure due to how arrays of numbers
are handled (in ColdFusion they are mostly arrays of strings that
ColdFusion auto converts on demand, which does not work when you pass
it to Clojure / Java; in Railo, they are arrays of numbers - with the
consequent performance improvement as well).

The TL;DR guide to getting cfmljure working with Tomcat (I haven't
tested it with other containers) is:
* Use Leiningen to create / manage your Clojure project (it's a simple
build tool)
* If your Clojure project folder is /path/to/folder, add
/path/to/folder and /path/to/folder/src to common.loader in
{tomcat}/conf/catalina.properties
* After running lein deps (to download / install libraries for your
Clojure project - including Clojure itself), copy
/path/to/folder/lib/*.jar to {web root}/WEB-INF/lib/ - although watch
out for JAR conflicts (e.g., DB drivers, javax.mail...)
* Restart Tomcat to pick up those libraries - only needed when you
update dependencies

In your CFML code, create a new cfmljure CFC and call install(
"list,of,namespaces", somestruct ) to install those namespaces from
Clojure as keys in the struct - I generally install into the this
scope of a CFC which I reference as variables.clj (injected
everywhere). For simple use, you can install( "list,of,namespaces",
variables ) to add the Clojure namespaces to your variables scope.

e.g.,

var clj = new cfmljure();
clj.install( "clojure.core", variables );
var x = variables.clojure.core.map( variables.clojure.core._inc(), [
1, 2, 3, 4 ] );

The ._{name}() notation returns a reference to {name} so it's how you
get clojure.core/inc to pass as the first argument to
clojure.core/map. You could also do:

var x = variables.clojure.core.map( variables.clojure.core._( "inc" ),
[ 1, 2, 3, 4 ] );

._( "{name}" ) is the same as ._{name}()

HTH

Nando

unread,
Apr 9, 2012, 4:58:34 PM4/9/12
to framew...@googlegroups.com

On Apr 9, 2012, at 19:23, Sean Corfield <seanco...@gmail.com> wrote:

> On Mon, Apr 9, 2012 at 9:37 AM, Nando <d.n...@gmail.com> wrote:
>> Thanks very much for this comprehensive reply, Sean. It's inspired me to
>> look deeper into cfmljure and clojure.
>
> The latest cfmljure CFC that we use at World Singles is up-to-date in
> github but the documentation may be a bit behind.

Thanks again Sean. Good to know. I assume it's fine to use Clojure 1.3 with cfmljure? I saw a note on Github about reverting to 1.2 to get everything working again, but have the impression that's out of date.

>
> Note also that interop between ColdFusion and Clojure is nowhere near
> as seamless as between Railo and Clojure due to how arrays of numbers
> are handled (in ColdFusion they are mostly arrays of strings that
> ColdFusion auto converts on demand, which does not work when you pass
> it to Clojure / Java; in Railo, they are arrays of numbers - with the
> consequent performance improvement as well).

Planning on using Railo from your previous comments. For the rest, I have some catching up to do. Where is the best place to ask questions if I get stuck?

Sean Corfield

unread,
Apr 9, 2012, 6:14:19 PM4/9/12
to framew...@googlegroups.com
On Mon, Apr 9, 2012 at 1:58 PM, Nando <d.n...@gmail.com> wrote:
> Thanks again Sean. Good to know. I assume it's fine to use Clojure 1.3 with cfmljure?

1.3.0 is what we use at World Singles.

> I saw a note on Github about reverting to 1.2 to get everything working again, but have the impression that's out of date.

It's due to the examples requiring the old contrib library. I need to fix that!

> Planning on using Railo from your previous comments. For the rest, I have some catching up to do. Where is the best place to ask questions if I get stuck?

https://groups.google.com/forum/?fromgroups#!forum/cfmljure (looks
like you tried back in October but ran into the ACF issues as well as
noting the 1.2 compatibility issue).

One thing I'm planning to do is create an "express" bundle of Jetty +
Railo + cfmljure + Clojure libs so folks can just download, expand and
play. Time, just need time!

Alex Purice

unread,
Apr 9, 2012, 7:40:46 PM4/9/12
to framework-one
UGGHHH...

Update! :)



> ORM is for Domain Objects, not services. I generally have my Domain
> Objects under /model/beans (and my service CFCs under /model/services
> because I manage my service CFCs using a DI framework).

Thank you for this tip. It helped a lot. But eventually opened Pandora
box of ORM and now I don't really know if it was a smart decision to
use ORM. I'm under a kind of impression that the ORM problem somehow
related to FW/1, so I decided to post it here, maybe you will be able
confirm that it is FW/1 related or reject it. I will show a code
sample, it is pretty much real code, I just removed some "kind of"
unrelated stuff.

services.cfc
<cfcomponent>
<cffunction name="create" returntype="void">
<cfargument name="tableName" required="true" type="string">
<cfargument name="dataContainer" required="false" default=""
type="any">
<cfset local.ormObject = entityNew(arguments.tableName)>
<cfset local.ormObject = setORMAttributes(local.ormObject,
arguments)>
<cfset entitySave(local.ormObject)>
<cfset ormFlush()>
</cffunction>

<cffunction name="update" returntype="void">
<cfargument name="tableName" required="true" type="string">
<cfargument name="primaryKey" required="true" type="numeric">
<cfargument name="dataContainer" required="false" default=""
type="any">
<cfset local.ormObject = entityLoadByPK(arguments.tableName,
arguments.primaryKey)>
<cfset local.ormObject = setORMAttributes(local.ormObject,
arguments)>
<cfset entitySave(local.ormObject)>
<cfset ormFlush()>
</cffunction>

...

</cfcomponent>


OrderRequest.cfc
<cfcomponent extends="services"></cfcomponent>


Calling
<cfset variables.fw.service("common:OrderRequest.create", "",
{tableName='Orders'})>
works like a charm, form field names match table names, form field
names have tableName. prefix, so I just "automatically" save form data
to the database.

By when I call
<cfset variables.fw.service("common:OrderRequest.update", "",
{tableName='Orders', primaryKey=41})
I get
Message: a different object with the same identifier value was already
associated with the session: [Orders#41]

And seems like there is no way to fix it, I tried all combinations of
<cfset ormEvictEntity(arguments.tableName)>
<cfset ormCloseSession()>
and so on.

Nothing worked, session persists, which shouldn't be really happen,
and I have no idea why it is happening. My guess (may be totally
wrong) that ORM object is cached within application scope where FW/1
service resides which make life of an ORM session indefinite. It is
correct or not really...?

Another fear here is that if my guess is correct, session persists and
doesn't clear, than multiple sessions build up ummm... somewhere until
server will run out of memory.

I've read a bunch of articles on this topic, like this one
http://www.compoundtheory.com/?action=displayPost&ID=419
but nothing really helped.

> Oh? I've never used CFWheels (I'm not a fan of full-stack frameworks,
> in case folks hadn't guessed). Did you run into problems?
Yeah. I'm kind of start to hate ORM... :/
Here you go
http://groups.google.com/group/cfwheels/browse_thread/thread/1e60ff71ab381dd3/03343cf43605dca7
Basically, you can't create more than one Many-To-One relationship
between 2 tables. ORM just doesn't that situation, as result, the only
workaround it to loop queries, which produces up to 1000 queries per
click.

Thanks,
Alex

Sean Corfield

unread,
Apr 9, 2012, 11:14:44 PM4/9/12
to framew...@googlegroups.com
On Mon, Apr 9, 2012 at 4:40 PM, Alex Purice <alexei...@gmail.com> wrote:
> I'm under a kind of impression that the ORM problem somehow
> related to FW/1

Not based on what code you've shown.

>        <cfset local.ormObject = entityNew(arguments.tableName)>
>        <cfset local.ormObject = setORMAttributes(local.ormObject,
> arguments)>
>        <cfset entitySave(local.ormObject)>
>        <cfset ormFlush()>

Couple of things:
* have you disabled flush at end of session for ORM?
* instead of ormFlush(), wrap operations in cftransaction:

<cftransaction>
entityNew...
...
entitySave...
</cftransaction>

<cftransaction>
entityLoadByPK...
...
entitySave...
</cftransaction>

> My guess (may be totally
> wrong) that ORM object is cached within application scope where FW/1

Totally wrong :)

> Another fear here is that if my guess is correct, session persists and
> doesn't clear, than multiple sessions build up ummm... somewhere until
> server will run out of memory.

Also totally wrong :)

The ORM "session" is actually per-request by default and (essentially)
per-transaction when you take over Hibernate object lifecycle
management yourself.

> http://groups.google.com/group/cfwheels/browse_thread/thread/1e60ff71ab381dd3/03343cf43605dca7

A limitation of cfWheels, not of Hibernate (fortunately).

Nando

unread,
Apr 10, 2012, 6:40:49 AM4/10/12
to framew...@googlegroups.com
Alex,

CF ORM works well enough for me (in FW/1) when I'm simply dealing with one entity, a single Employee with relatively simple relationships for instance. I prefer it over writing out and maintaining all the complex SQL / cfqueryparam statements. Where it breaks down for me, to give another practical example, is when the application might need to persist 10's of thousands of WorkPeriods to the database in one process, (the data arising from WorkPeriodTemplates that are defined in the application). Then the tradeoff of lessened efficiency and increased server resources for convenience breaks down, and I've reverted to using SQL for these inserts to help lessen the load on the server. I've found for me that practically ORM is only well suited to one-at-a-time CRUD operations. I still write queries to fetch and display lists of data rather than creating arrays of objects and iterating over them.

Where ORM might also break down is in applications that receive a lot of traffic. The extra server resources required might cost significantly more than the time/money saved by using ORM over time. 

Here's how I have ORM set up in Application.cfc:

this.ormsettings = {
cfclocation="./model",
dbcreate="update",
eventhandling="true",
eventhandler="root.model.EventHandler",
logsql="true",
dialect = "MySQLwithInnoDB",
flushatrequestend = false,
autoManageSession=false
};

I have "flushatrequestend = false" so that I control when an entity is persisted, since I use objects as data containers to populate forms and validate values.

and here's how I persist an employee:

<cffunction name="persist" returntype="void" access="public" output="false">
<cfargument name="employee" type="any" required="true" />
<cfset var trans = ORMGetSession().beginTransaction() />
<cftry>
<cfset entitySave(arguments.employee) />
<cfset trans.commit() />
<cfcatch type="any">
<cfset trans.rollback() />
<cfrethrow />
</cfcatch>
</cftry>
</cffunction>

The trans.commit() statement flushes the orm session, and causes the employee to be persisted to the database. See Mark Mandel's post here:



Sean Corfield

unread,
Apr 10, 2012, 11:56:23 AM4/10/12
to framew...@googlegroups.com
On Tue, Apr 10, 2012 at 3:40 AM, Nando <d.n...@gmail.com> wrote:
> cfqueryparam statements. Where it breaks down for me, to give another
> practical example, is when the application might need to persist 10's of
> thousands of WorkPeriods to the database in one process, (the data arising

I'd argue that your architecture is flawed there. A single request
should not try to save tens of thousands of anything - unless you're
talking about a background process job? (for which CFML isn't really
suitable since it's inherently a request/response-based system). I'd
also note that Java developers are able to use Hibernate like this -
but of course the overhead of Java objects compared to CFCs is much
less (and it _is_ reasonable to write background process jobs in Java
anyway).

FWIW, one of the nice things about dropping down into Clojure is that
it's easy to dispatch work in the background:

(future (doseq [period period-collection] (save-row :workperiod period)))

"future" says execute this code in another thread and just continue on.

But I agree that a single SQL insert with multiple rows will be more
efficient than "10's of thousands" of individual inserts. Clojure's
JDBC library has a shorthand for that as long as you're willing to
convert your records into arrays of values and provide an array of
column names up front:

(future (jdbc/insert-values :workperiod [:id :name :value ...] [ [1
"first" 42 ] [ 2 "second" 13 ] ... ]))

And even that's easy to do - assuming all your work period records
have all the same keys:

(future
(let [cols (keys (first period-collection))]
(jdbc/insert-values :workperiod cols (map (apply juxt cols)
period-collection))))

"keys" returns the keys of the first record in the array and the keys
are keywords (:foo) so they can be used as functions (taking a record,
returning the matching key's value). "juxt" takes a number of
functions and returns a new function that produces an array by
applying the sequence of functions to its argument - (juxt :id :name
:value) is a function that takes a record and returns an array
containing the ID, the name and the value elements of that record.
"apply" is how you convert a collection to an argument list: (apply
juxt [:id :name :value]) is equivalent to (juxt :id :name :value) so
you can use computed argument lists (like argumentCollection= in
CFML).

If the records in period-collection don't all have the same full set
of keys, then (reduce (fn [ks m] (union ks (set (keys m)))) #{}
period-collection) will give you a full set of the unique keys across
all of those records. Or (apply union (map (comp set keys)
period-collection)) if you prefer :)

Anyways, that's probably more Clojure than anyone wanted in a thread
that started out asking about controllers calling controllers - it was
mostly for Nando's benefit since he's been asking about Clojure.

> and here's how I persist an employee:

I'd probably start the transaction, load/create the entity, populate
and validate it, save it - if valid, and then end the transaction
since to me that's the real extent of the transaction, not just the
save operation. I think dropping down to the underlying Hibernate
session machinery like that is really ugly code - and unnecessary if
you wrap <cftransaction> around code at the appropriate level.

Alex Purice

unread,
Apr 10, 2012, 12:36:39 PM4/10/12
to framework-one
> <cftransaction>
> entityNew...
> ...
> entitySave...
> </cftransaction>
>
> <cftransaction>
> entityLoadByPK...
> ...
> entitySave...
> </cftransaction>

Just did it and it didn't help.



> A limitation of cfWheels, not of Hibernate (fortunately).

Yeah, I know. But because of it, generally nice project just stuck in
the middle of nowhere.

Alex Purice

unread,
Apr 10, 2012, 12:44:42 PM4/10/12
to framework-one
Hello Nando,

> <cfset var trans = ORMGetSession().beginTransaction() />
> ...
> <cfset trans.commit() />

This one did work either. I probably just have to give it up and to go
with regular SQL.

Nando

unread,
Apr 10, 2012, 12:46:28 PM4/10/12
to framew...@googlegroups.com
It's a background process that I'm concerned about. Sean, just to let you know, you're getting "a bit" ahead of me regarding Clojure, but I do appreciate you taking the time to point out possibilities like this. 

Regarding the transaction aspect, I adopted that from advice that Mark Mandel gave here: 

Nando

unread,
Apr 10, 2012, 12:48:15 PM4/10/12
to framew...@googlegroups.com
Alex,

The errors you get using ORM can be somewhat cryptic, so it can be a challenge to get it working. Up to you if you want to tackle it.

Alex Purice

unread,
Apr 10, 2012, 12:51:35 PM4/10/12
to framework-one
Damn typos!

This one did NOT work either.

Alex Purice

unread,
Apr 10, 2012, 2:01:25 PM4/10/12
to framework-one

> The errors you get using ORM can be somewhat cryptic, so it can be a
> challenge to get it working. Up to you if you want to tackle it.


I feel as Paisley Houndstooth (google it). It was really stupid... :(

You may not change arguments scope within a method. Construction
<cfset arguments.blah = 12345> will throw an error. so, if you have
something coming into a method as argument which you want to change
later, you have to copy it to a local/whatever variable. While doing
it, you don't really copy the variable, you just create a pointer to
it (not actually a pointer, but doesn't matter), so in order to create
a true copy of an argument, I just wrap it with duplicate, so <cfset
local.blah = arguments.blah> becomes <cfset local.blah =
duplicate(arguments.blah)>
In my code sample you can see following line
<cfset local.ormObject = setORMAttributes(local.ormObject,
arguments)>
which calls a method which calls of ORM setters, here we go
<cffunction name="setORMAttributes">
<cfargument name="ormObject" required="true">
<cfargument name="callerArguments" required="true" type="struct">

<cfset local.ormObject = duplicate(arguments.ormObject)>
<cfif isStruct(arguments.callerArguments.dataContainer)>
<cfloop collection="#arguments.callerArguments.dataContainer#"
item="local.thisKey">
<cfset local.thisValue =
arguments.callerArguments.dataContainer[local.thisKey]>
<cfif isSimpleValue(local.thisValue)>
<cfinvoke component="#local.ormObject#"
method="set#listLast(local.thisKey, '.')#">
<cfinvokeargument name="#listLast(local.thisKey, '.')#"
value="#duplicate(local.thisValue)#">
</cfinvoke>
</cfif>
</cfloop>
<cfelseif listLen(arguments.callerArguments.dataContainer) GT 1>


---------------------------------------------------------

So, I followed my habit
<cfset local.ormObject = duplicate(arguments.ormObject)>
created a duplicate copy of the ORM object, flushed its "parent" copy,
keeping the duplicate.

Thank you guys for advising me, so I finally went down there and found
it.

Cheers,
Alex

Sean Corfield

unread,
Apr 10, 2012, 4:52:43 PM4/10/12
to framew...@googlegroups.com
On Tue, Apr 10, 2012 at 11:01 AM, Alex Purice <alexei...@gmail.com> wrote:
> You may not change arguments scope within a method. Construction
> <cfset arguments.blah = 12345> will throw an error.

No it won't. I've seen lots of code that updates arguments scope variables.

>                <cfset local.ormObject = duplicate(arguments.ormObject)>

Calling duplicate() on an object is a pretty dangerous thing to do,
IMO. I preferred the CFMX7 behavior where duplicating a CFC threw an
exception. I was sorry they changed it in CF8. I blogged about it:

http://corfield.org/entry/duplicate_is_bad_for_your_objects_health

(it lost formatting when I imported it into MangoBlog so it's a little
hard to read)

Glad you got it working!

Alex Purice

unread,
Apr 10, 2012, 8:12:39 PM4/10/12
to framework-one
> No it won't. I've seen lots of code that updates arguments scope variables.
Really??? Do you know when this behavior changed...? I've been doing
it for years. LOL



> Glad you got it working!
That part - yes. But I still can't get entire thing done.
I can't call a controller from another controller. But I can call a
service. Services are queued up after controllers, so any variables
set in a services aren't visible in controller (just because they
don't exists during controller execution).

Question... Where to put shared validation code? For example, I have
an user defined data type "EntityName", I use this field about 200
times across the database, I have a definitive set of rules for
validating this field. I want to have a single method for validating
this field and call it from different parts of the framework.
What is the best way to do it?

Sean Corfield

unread,
Apr 10, 2012, 10:07:52 PM4/10/12
to framew...@googlegroups.com
On Tue, Apr 10, 2012 at 5:12 PM, Alex Purice <alexei...@gmail.com> wrote:
>> No it won't. I've seen lots of code that updates arguments scope variables.
> Really??? Do you know when this behavior changed...? I've been doing
> it for years. LOL

As far as I am aware, you've always been able to assign to arguments.
In fact, before CFMX had a var (local) scope, that was the only thread
safe way to handle local variables: by assigning into arguments scope!

> I can't call a controller from another controller. But I can call a
> service. Services are queued up after controllers, so any variables
> set in a services aren't visible in controller (just because they
> don't exists during controller execution).

They would be visible in the endItem() controller method so use
startItem() / endItem() instead of just item().

Or manage services yourself (either explicitly or via a DI framework)
and call them directly in the item() method. The service queueing
machinery is something most people outgrow fairly quickly.

> Question... Where to put shared validation code?

In a service.

> I want to have a single method for validating
> this field and call it from different parts of the framework.

Yup, that's exactly what services are for.

Nando

unread,
Apr 11, 2012, 4:56:31 PM4/11/12
to framew...@googlegroups.com
Sean,

Just to follow up on your point regarding my use of the Hibernate session machinery (and set the record straight for Alex in case I've misled him), I've considered it carefully and well, what to say, you're right! Mark Mandel's post, combined with an ingrained expectation from the sql world that a transaction should be simply wrapped around an insert or update process and some trouble figuring out how commits should be triggered, led me somewhat astray. I've reworked one of my controllers in the app I'm building as follows, moving the transaction to the appropriate level as you suggested and removed the Hibernate session stuff from the service:
controller
transaction action='begin' {
if(structkeyexists(rc, 'id')) {
rc.maskItem = getMaskService().getMaskItem(rc.id);
}
if(ListContains(session.permission,"mask.approve")) {
rc.maskItem.setIsActive(2);
rc.maskItem.setIsLocked(1);
rc.maskItem.setIsVirgin(0);
// the transaction is commited in 2 steps so that the mask item has all parameters set before the sync is run 
try {
getMaskService().persistMaskItem(rc.maskItem);
transactionCommit();
} catch (Any exception) {
transactionRollback();
rethrow;
}
try {
getMaskService().syncWorkPeriodsToMask(rc.id);
transactionCommit();
} catch (Any exception) {
transactionRollback();
rethrow;
}
}
} // end transaction 
service
<cffunction name="persistMaskItem" returntype="void" access="public" output="false">
<cfargument name="maskItem" type="any" required="true" />
<cfscript>
entitySave(arguments.maskItem);
</cfscript>
</cffunction>

That's now much better. The transaction is at the appropriate level in this rather complex part of the application, and that turns out to be in the controller because of the multiple service calls that often occur in one request in this part of the app. Thanks again for spurring me on! 


I'd probably start the transaction, load/create the entity, populate
and validate it, save it - if valid, and then end the transaction
since to me that's the real extent of the transaction, not just the
save operation. I think dropping down to the underlying Hibernate
session machinery like that is really ugly code - and unnecessary if
you wrap <cftransaction> around code at the appropriate level.
--
Sean A Corfield -- (904) 302-SEAN
An Architect's View -- http://corfield.org/
World Singles, LLC. -- http://worldsingles.com/

"Perfection is the enemy of the good."
-- Gustave Flaubert, French realist novelist (1821-1880)

Sean Corfield

unread,
Apr 11, 2012, 6:40:46 PM4/11/12
to framew...@googlegroups.com
On Wed, Apr 11, 2012 at 1:56 PM, Nando <d.n...@gmail.com> wrote:
> That's now much better. The transaction is at the appropriate level in this
> rather complex part of the application, and that turns out to be in the
> controller because of the multiple service calls that often occur in one
> request in this part of the app. Thanks again for spurring me on!

Cool. That's much cleaner code. I'd probably do this tho':

var needToSync = false;
transaction {


if(structkeyexists(rc, 'id')) {
rc.maskItem = getMaskService().getMaskItem(rc.id);
}
if(ListContains(session.permission,"mask.approve")) {
rc.maskItem.setIsActive(2);
rc.maskItem.setIsLocked(1);
rc.maskItem.setIsVirgin(0);
// the transaction is commited in 2 steps so that the mask item has
all parameters set before the sync is run

getMaskService().persistMaskItem(rc.maskItem);
needToSync = true;
}
}
if ( needToSync ) {
transaction {
getMaskService().syncWorkPeriodsToMask(rc.id);
}
}

I don't think your second rollback would rollback the already
committed transaction that saved the maskItem, so I don't think it's
clear to nest it inside the same transaction?

I'd probably also forgo getMaskService().persistMaskItem(rc.maskItem)
and just use entitySave(rc.maskItem) - the service method doesn't do
anything useful so it seems like wasteful delegation to me. The same
may be true of getMaskService().getMaskItem(rc.id)? I tend to avoid
service methods that add no value - and try to keep services for
orchestrating operations across multiple domain objects.

I'd probably also add a convenience method to all my domain objects
(via a base CFC):

function save() {
entitySave(this);
}

so that in my controller, I can just say:

rc.maskItem.save();

That reduces the coupling between the controller and the service - and
empowers the business object.

If mask approval is active = 2 (a magic number - bad!), locked = 1,
virgin = 0, I'd probably put that in a method on the domain object
too:

function approve() {
this.setIsActive( 2 ); // use a symbolic name for this!!
this.setIsLocked( 0 );
this.setIsVirgin( 0 );
}

Then your controller becomes:

var needToSync = false;
transaction {


if(structkeyexists(rc, 'id')) {
rc.maskItem = getMaskService().getMaskItem(rc.id);

// or rc.maskItem = entityLoadByPK( "Mask", rc.id );
// or whatever the ORM syntax is
}
if(ListContains(session.permission,"mask.approve")) {
rc.maskItem.approve();
rc.maskItem.save();
needToSync = true;
}
}
if ( needToSync ) {
transaction {
getMaskService().syncWorkPeriodsToMask(rc.id);
}
}

Potentially no service dependency. No business logic for the
"approval". And a very clear transaction structure.

Alex Purice

unread,
Apr 11, 2012, 6:43:06 PM4/11/12
to framework-one
> Or manage services yourself (either explicitly or via a DI framework)
> and call them directly in the item() method.

You know, Sean... One of my co-workers is on a diet trying to lose
some weight. I brought some chocolate muffin bites one day and offered
him one or two, He looked at me with eyes full of sadness and said -
You are an evil person, Alex - taking a muffin bite.

You know, Sean, you are an evil person. You say "ORM", I spend time to
consider and implement it, then spend a couple of days to weed out
bugs, once I'm happy with the result, you say - "DI Framework, Alex...
DI Framework", and I get into a new circle! LOL

Thank you so very much! Your advice helps a LOT! I wish I could share
the same office/cubic with you.

Sean Corfield

unread,
Apr 11, 2012, 9:25:52 PM4/11/12
to framew...@googlegroups.com
On Wed, Apr 11, 2012 at 3:43 PM, Alex Purice <alexei...@gmail.com> wrote:
> You know, Sean, you are an evil person.

*grin*

> Thank you so very much! Your advice helps a LOT! I wish I could share
> the same office/cubic with you.

Remember: we're never done learning and the best way to eat an
elephant is one bite at a time :D

I try to live by the motto "learn something new every day" (and
working with Clojure that's pretty much guaranteed at the moment!).

Judith Barnett

unread,
Apr 11, 2012, 9:32:01 PM4/11/12
to framew...@googlegroups.com
That's my motto too. The curious thing is I usually learn my
something new from you.

Geoff Parkhurst

unread,
Apr 12, 2012, 9:46:27 AM4/12/12
to framew...@googlegroups.com
On 11 April 2012 21:56, Nando <d.n...@gmail.com> wrote:

> if(ListContains(session.permission,"mask.approve")) {

Just a quick point: Careful with ListContains! it returns sub-strings
of list elements:

It'll find "approved" in "blah,blah,disapproved,blah"

but listfind() won't.

Seth Johnson

unread,
Apr 12, 2012, 12:38:56 PM4/12/12
to framew...@googlegroups.com
I've been bit by that one Geoff, I also recommend using ListFindNoCase() just in case you enter some permission in camel case by habit.

Nando

unread,
Apr 12, 2012, 1:06:27 PM4/12/12
to framew...@googlegroups.com
Geoff,

Thanks for the reminder. Don't think it will affect anything because of how the permission values are defined, but will replace all ListContains anyway!

Alex Purice

unread,
May 22, 2012, 12:49:15 PM5/22/12
to framework-one
Now I see >why< you said to don't do it - because by doing it one
loses power of framework (switching back to plain OOP) and have to
replicate more or less of its functionality on ones own.

I'm still under impression though that there could be a framework
native way to fire controller from another controller.

On Apr 4, 5:17 pm, Sean Corfield <seancorfi...@gmail.com> wrote:

> Short answer: don't do that.
Reply all
Reply to author
Forward
0 new messages