11.15.2010

Clojure Report Generation

The entire code from this blog post is here. I will chunk it up to explain:
https://gist.github.com/4fcd64dc020a9c749b76

Working with collections in Clojure makes programming such a natural experience. Especially when you want to report on your data. Many different data stores have libraries which make getting information into Clojure trivial. They often use a collection of maps where map keys are the "column" names and the values are the data. The following generate-report function should work on any collection of maps.



Above I am taking a set of query results (i.e. a collection of maps) and any number of report items. A small sample of one of the maps in the collections with which I'm dealing is here.

The first thing to do is grab the headers. These are just the firsts of each report item. Pretty simple. The process function takes the keys from each report-item and applies manipulation-function to the result of (get-in % ks). Process returns a new function that takes a query-result, so I can map it over every report-item to get columns as a seq of functions. Finally to get rows, I use apply juxt to call every function in columns on each query-result to get a vector of each of the results of those calls.

You can tell from the doc string what report-items are supposed to look like, so I will show you two of my reports. The first one uses the generate-report function to put its :headers and :rows into an html table.



The code above is all that is required to create this:

Granted the manipulation functions in each report-item are doing most of the display work, the generate-report function makes it easier to focus on what matters out of each map in the collection of query-results.

The reason I wanted to break out the generate-report function is that reports on the same data might not always look the same. The next example looks at a report that needs to export to a spreadsheet. For the HTML table, I wanted to shorten up some of the fields, by displaying "Lastname, First initial." The title tag for the td displays the entire name if the user needs to know. However, once exported, there are no title tags, so the user would have no idea if he was looking at John Smith's or Jessica Smith's score. In xls-report I use a function to display all of the parts of the operators and evaluators which together constitute a unique identifier (see employee-display).



This report demonstrates the use of the nested maps by getting out the vector of [:client :account-numer] and [:client :account-name]. These are passed to a get-in call which gets me the :client of each score, and then the :account-number and :account-name of that result. Now the *.xls that gets saved can be sorted by either account-number or account-name.

Definitely handy stuff. I'm really starting to love this whole idea that code is data and vice versa.

11.04.2010

clojure map-keys

Someone in #clojure was asking about how to case-insensitively check the keys of a map. The best answer anyone present at the time came up with was pre-processing the map by naming, lowering, and keywordifying the keywords. We then found out the asker was looking for keys in nested maps, so I wrote a function for that.

After writing the function I realized I could extract the lowering function and let it take a function to apply to all keys of m and all keys in m's vals.

Here is the map-keys function with the lower-key function extracted. I run it on some goofy data at the end. Try it in a REPL.