binding form is one part of Clojure that I just didn't get at first. The description in the API docs is clear, but reading it with my background was like telling a blind man that something is red. Since I had never had a feature like this available in a language, I had no experience with how one might actually use it.If you look at some of my earlier blog posts about unit testing you will see that at that time I was still in the dark about
binding. I was struggling to find a way to mock services and was creating functions with def calls, a big Clojure no-no.I have since changed my ways and now use
binding as it should be used when testing to create environments with alternative implementations of functions.This post is not about that. It is about how
binding saved me some yak shaving one day last week.I started a new web project last week, and was excited to build something new on top of my sandbar library. The project needed to have a secure area where administrators could edit content on the site. This means that I will need to have a way to edit users, passwords, roles etc. The development module of sandbar has some code that creates a user interface for doing just this. It also has some code for creating the tables that I will need to back it in MySQL. If all goes well, I should be able to save myself many hours of work.
During the previous week, I completed getting a stable version of the library into Clojars. I added sandbar to my dependencies and ran
lein deps. The first step was to create the tables that I needed in MySQL. Here is the function that does that:
(defn create-tables
([] (create-tables nil))
([drop]
(let [db (get-connection)
drop-fn (partial database/db-drop-table db)
insert-fn (partial database/db-insert db)
create-table-fn (partial database/db-do-commands db)]
(do
(if drop
(do (drop-fn :user_role)
(drop-fn :role)
(drop-fn :app_user)))
(create-user-table create-table-fn)
(create-role-table create-table-fn)
(create-role-user-table create-table-fn)
(insert-role-records insert-fn)
(insert-user-records insert-fn)
(assoc-users-with-roles insert-fn)))))
There is a lot of stuff here but the part that curbed my enthusiasm was (get-connection). I had hard-coded the database configuration. That's why this code is in the development module, I thought. I started to think about all the ways I could get around this problem. Copy the code into my project and make the changes, fix the problem in the library and re-deploy, etc.But then I remembered
binding. get-connection is a function that returns a map with a database configuration. All I need to do is use binding to create a new binding for the var get-connection to a function that returns the correct configuration.So I fired up the REPL and...
(binding [get-connection
(fn [] {:connection
{:classname "com.mysql.jdbc.Driver"
:subprotocol "mysql"
:subname "//localhost/the_db"
:user "the_user"
:password "123456789"}})]
(create-tables))
Nice job Clojure!This would have been easy for me to fix because I am in control of both projects. But what if you wanted to use this function right now in your project? I haven't yet fixed the problem.
binding provides the workaround.binding could definitely be used to win a 'most unreadable code ever' contest. It is a powerful feature and can be abused. Part of the difficulty in learning to use binding properly is knowing when not to use it. But I'm glad it's there. In my opinion, it is one more reason to love (or at least like) Clojure.

No comments:
Post a Comment