You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: source/documentation/4.0/cfml-and-clojure.markdown
+71-40Lines changed: 71 additions & 40 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,7 +1,7 @@
1
1
---
2
2
layout: page
3
3
title: "Using Clojure with CFML"
4
-
date: 2016-05-27 09:40
4
+
date: 2016-09-14 15:00
5
5
comments: false
6
6
sharing: false
7
7
footer: true
@@ -126,14 +126,14 @@ it also generates a test skeleton which you can run like this:
126
126
Because the test skeleton doesn't have any valid tests -- just one deliberate failure -- you should see:
127
127
128
128
lein test myapp.core-test
129
-
129
+
130
130
lein test :only myapp.core-test/a-test
131
-
131
+
132
132
FAIL in (a-test) (core_test.clj:7)
133
133
FIXME, I fail.
134
134
expected: (= 0 1)
135
135
actual: (not (= 0 1))
136
-
136
+
137
137
Ran 1 tests containing 1 assertions.
138
138
1 failures, 0 errors.
139
139
Tests failed.
@@ -154,20 +154,20 @@ You should go through the [Getting Started](https://github.com/boot-clj/boot#get
154
154
This specifies an external dependency (on my `boot-new` task library), then the name of the task (`new`) then the arguments to that task (the `t`ype
155
155
of the template and `n`ame of project to create).
156
156
157
-
If you want to get going quickly and you've already installed **Leiningen** and gone through the project creation and testing steps above, you can just create a `build.boot` file in
157
+
If you want to get going quickly and you've already installed **Leiningen** and gone through the project creation and testing steps above, you can just create a `build.boot` file in
158
158
that (**Leiningen**-created) project and run code and tests like this:
159
159
160
160
;; build.boot
161
161
(set-env! :resource-paths #{"src"}
162
162
:dependencies '[[org.clojure/clojure "1.8.0"]])
163
-
163
+
164
164
(require 'myapp.core)
165
165
(deftask run []
166
166
(myapp.core/-main))
167
167
168
-
Like `project.clj` under **Leiningen**, we specify the project dependencies. We also specify where our source code is -- there's no default -- using `:resource-paths`.
169
-
Next we load the main namespace of our new application and define a task called `run` that calls the main function in that application. Whilst **Leiningen** has a
170
-
built-in task called `run`, you still have to declare the main namespace (or specify it as a command line argument). **Boot** takes the approach that this is
168
+
Like `project.clj` under **Leiningen**, we specify the project dependencies. We also specify where our source code is -- there's no default -- using `:resource-paths`.
169
+
Next we load the main namespace of our new application and define a task called `run` that calls the main function in that application. Whilst **Leiningen** has a
170
+
built-in task called `run`, you still have to declare the main namespace (or specify it as a command line argument). **Boot** takes the approach that this is
171
171
"just code" so you can define whatever tasks you want, to run whatever Clojure code you want.
172
172
173
173
Accordingly, **Boot** does not have a built-in task to run tests, but there are readily available libraries to provide this functionality. We'll update `build.boot` as follows:
@@ -177,9 +177,9 @@ Accordingly, **Boot** does not have a built-in task to run tests, but there are
177
177
:source-paths #{"test"}
178
178
:dependencies '[[org.clojure/clojure "1.8.0"]
179
179
[adzerk/boot-test "1.0.7"]])
180
-
180
+
181
181
(require '[adzerk.boot-test :refer [test]])
182
-
182
+
183
183
(require 'myapp.core)
184
184
(deftask run []
185
185
(myapp.core/-main))
@@ -226,7 +226,7 @@ files and folders that would see in your `myapp` Clojure test project above:
226
226
.gitignore LICENSE README.md doc resources target
227
227
boot.properties build.boot project.clj src test
228
228
229
-
The files shown in the first line are for FW/1. You might later add a `controllers` folder and a `model` folder if you
229
+
The files shown in the first line are for FW/1. You might later add a `controllers` folder and a `model` folder if you
230
230
write any of those pieces in CFML.
231
231
232
232
The files shown in the second line are generated by **Leiningen** and you can pretty much ignore them. You'll see that by
@@ -257,9 +257,9 @@ in `hello/controllers/main.clj`.
257
257
If you run `lein test` in the `6helloclojure` folder, you'll see:
258
258
259
259
lein test hello.controllers.main-test
260
-
260
+
261
261
lein test hello.services.greeter-test
262
-
262
+
263
263
Ran 2 tests containing 3 assertions.
264
264
0 failures, 0 errors.
265
265
@@ -365,7 +365,7 @@ tests. The arrow syntax means "take this thing and pass it through these functio
365
365
366
366
(-> {} default :greeting)
367
367
368
-
takes an empty struct and passes it as the first argument to the `default` function (our handler method being tested) and then
368
+
takes an empty struct and passes it as the first argument to the `default` function (our handler method being tested) and then
369
369
pass the result to the `:greeting` function -- remember that `(:greeting my-struct)` is like `my_struct.greeting` in CFML. So
370
370
this tests that if the `rc` is empty and you run `main.default` you get a new key called `:greeting` whose value is `"Hello anonymous!"`.
371
371
@@ -515,7 +515,7 @@ or `test` folder, except that `-` in a namespace identifier matches `_` in the f
515
515
files but it isn't very common to see that, so don't worry about it.
516
516
517
517
In the `6helloclojure` example, you'll see `hello.controllers.main` for `src/hello/controllers/main.clj` and `hello.controllers.main-test` for
518
-
`test/hello/controllers/main_test.clj`. If you follow this basic convention, you won't go wrong. If your `ns` declaration doesn't match the
518
+
`test/hello/controllers/main_test.clj`. If you follow this basic convention, you won't go wrong. If your `ns` declaration doesn't match the
519
519
filesystem path, you can get strange errors when you attempt to access it. Think of it much like the dotted-path used to access CFCs in CFML.
520
520
521
521
The second important part of `ns` is the list of namespaces your code `:require`s. There are two basic forms here:
@@ -587,7 +587,7 @@ The third entry is Clojure's core JDBC library.
587
587
The fourth entry is the Clojure language and core functions.
588
588
589
589
Now run `lein repl` and we can try this out. As the REPL starts up, it will download the new
590
-
libraries and then you'll get the prompt. Let's create test database and write and read some
590
+
libraries and then you'll get the prompt. Let's create test database and write and read some
If all is true, return all tasks, else just the incomplete ones."
@@ -800,7 +800,7 @@ We'll add a dummy controller so we can test this:
800
800
If we hit the app in our browser with `?reload=true` in the URL, you should see:
801
801
802
802
List All | Add New Task
803
-
803
+
804
804
You have nothing to do!
805
805
806
806
If you click `Add New Task` and fill in the form and click `Add!`, you'll end up back on this page with no tasks listed.
@@ -926,13 +926,13 @@ Now run this with `?reload=true` in the URL and it should work just as it did be
926
926
How do you know it's using the Clojure version? Because `framework.ioclj` adds the Clojure namespaces it finds after any CFCs it finds, overwriting any beans
927
927
with the same alias. If you want to convince yourself, remove `controllers/main.cfc` from your `taskmanager` folder in the webroot, and reload the application again.
928
928
929
-
So why would we write our controllers in Clojure instead of CFML?
929
+
So why would we write our controllers in Clojure instead of CFML?
930
930
931
-
* As simple functions (that take `rc` as input and produce an updated `rc` as output), they're easy to write unit tests for.
931
+
* As simple functions (that take `rc` as input and produce an updated `rc` as output), they're easy to write unit tests for.
932
932
* You don't need to convert `rc` data structures back and forth between CFML and Clojure since that's taken care of automatically in FW/1 (technically in `cljcontroller.cfc`).
933
933
* You can work in the REPL building and testing your entire application's functionality (and then work on the views with a browser).
934
934
* We get the full power of Clojure's data abstractions, concurrency, and immutability working for us.
935
-
* With your services and controllers in Clojure, you're one step away from building all-Clojure web applications using [FW/1 for Clojure](https://github.com/framework-one/fw1-clj).
935
+
* With your services and controllers in Clojure, you're one step away from building all-Clojure web applications using [FW/1 for Clojure](https://github.com/framework-one/fw1-clj).
936
936
937
937
### RC Value Conversion
938
938
@@ -947,6 +947,37 @@ functions:
947
947
*`->double` -- Accepts any value, string or numeric, and tries to convert it to a `Double` value. You can provide a second argument which specifies the default to return if conversion fails (otherwise it will return `0.0`).
948
948
*`->boolean` -- Accepts any value, boolean, string or numeric, and tries to convert it to a `Boolean` value. You can provide a second argument which specifies the default to return if conversion fails (otherwise it will return `false`). This treats the strings `"true"` and `"yes"` as `true` (not case sensitive), as well as treating non-zero numeric values as `true` (and zero as `false`), just like CFML.
949
949
950
+
### Augmenting RC With Clojure Expressions
951
+
952
+
Normally `rc` is automatically converted back and forth between a CFML "struct" and a Clojure hash map so that inside Clojure controllers, the keys are lowercase keywords (as expected in Clojure). This automatic conversion handles "regular" data types just fine but there may be times when you want to pass more complex data into Clojure controllers, such as a `Component` providing the application's configuration or state. The `cljcontroller.cfc` file provides an extension point for this -- `callClojure()` -- to allow you to add Clojure expressions to the request context (after conversion), before calling the actual controller (and then removing them again before returning to CFML). Write a CFC that extends `framework.cljcontroller` and overrides `callClojure()`. Make sure to call `super.callClojure()`. Then tell DI/1 that `cljcontroller` is your CFC instead:
953
+
954
+
```
955
+
// myapp/cljcontroller.cfc:
956
+
component extends=framework.cljcontroller {
957
+
958
+
// add/remove application.clojure_component to the request context
959
+
// this is a Clojure Component (Stuart Sierra's "reloaded" workflow)
960
+
// and is not convertible to/from CFML -- note that rcClj is a Clojure
961
+
// hash map at this point so you must use Clojure functions to manipulate it
962
+
function callClojure( string qualifiedName, any rcClj ) {
963
+
var core = variables.cfmljure.clojure.core;
964
+
var appComponent = core.keyword( "application-component" );
0 commit comments