Groovy/Grails/GORM Tricks and Recipes – 1

I now have my Digital Humanities project, Celtic Poets in North America (described in earlier blog posts), running on CloudBees (you can see it here).

I’ve created this system using the Groovy/Grails platform (which I’ll abbreviate as “G/G” henceforth), which has been an interesting experience, learning the language and framework as I go along. Although there are many great features to G/G, there is a lot to learn, it is idiosyncratic, and my project does not always fit the common model that G/G often assumes, even if its conventions may be shared by many other web-apps. Although I’ve got a couple of useful books about G/G (published by Apress), they don’t always address the unconventional requirements I have.

So, I’ve had to figure out how to go beyond the norm to make my system work, resorting to Google searches and experimentation. Perhaps I can spare other people some of my agony by sharing a few techniques I’ve figured out on my evolving journey.

(NOTE: I am using the Spring Source Groovy/Grails Tool Suite v. 3.3.0; Groovy 2.0.7; Grails 2.2.3. These tricks and recipes may not work on earlier versions.)

Change Data Easily at Bootstrap

I have a custom-curated dataset that users cannot (at this point) modify. In order to make editing this dataset easy for me, and so that it is human-readable and won’t become quickly obsolete, it is formatted as a JSON object in a text file.

I need the database to be loaded with the dataset from the JSON object when the web-app begins, but because I am constantly editing this material, it would be tedious to have to recompile and re-deploy the web-app every time I changed the dataset. (This kind of process would often be done by putting the file amongst others in the web-app and compiling a new war file.)

I got around this issue by storing the textfile in the Public folder of my Dropbox storage! Now, all I have to do is edit that file from my home computer and restart the web-app on CloudBees so that the new dataset is read in!

Another consideration is that the dataset contains UTF-8 characters — so I need to specify that the UTF-8 character encoding is used when the textfile is opened. Here’s an extract of my code from the BootStrap class:

def url = new URL("https://dl.dropboxusercontent.com/myFile.txt")
def slurper = new JsonSlurper()
def parsedData = slurper.parseText(url.getText("UTF-8"))

From there you can parse the JSON data as usual.

Displaying UTF-8 Characters

If your strings contain UTF-8 encoded characters, you may need to make special provisions for them to be displayed properly. Modify the following lines in your Config.groovy file:

grails.views.default.codec = "none"
grails.views.gsp.encoding = "UTF-8"
grails.converters.encoding = "UTF-8"

You may also need to invoke

.encodeAsHTML()

for your strings on GSP view pages — but this may not be necessary.

Static Web pages

G/G generally assumes that web-pages are generated dynamically by controllers representing data returned by SQL searches and other operations, but at the top level of my web-app are static pages: a Home page, an About page, etc. I don’t need or want the dynamic capabilities of G/G in these cases.

The solution (which I’ve stolen wholesale) is first to create a controller called “static” under which such pages will go. Create a static controller that has methods for all of your static pages, like this:

class StaticController {
 def index() { }
 def about() { }
 def error() { }
}

You’ll need to create GSP view pages for each of these methods in a folder called “static.”

Then, you’ll need to modify the UrlMappings method/file so that top-level URL addresses correspond to addresses within your static controller (these URL patterns must appear first in the file). Leave in the directive to handle other URL patterns normally. You might also want to specify that any errors invoke the error page assigned to your static controller.

class UrlMappings {
  static mappings = {
    // these direct root pages to the "static" controller
    "/"(controller:"static")
    "/$action"(controller:"static")

    "/$controller/$action/$id?"
    "500" (controller:"static", view:'error')
  }
}

Flexible, Dynamic Run-time Searching

The objects in my database have many different fields and I want to allow users at run-time to decide which of these fields will be used as criteria in their search. This means that there is a huge number of possible permutations that I can’t anticipate and using something like a case-switch structure would be a horrid solution.

Enter the dynamic Builder capability of Groovy (technically, they are helper classes), which, when combined with the power of GORM, yields a very simple and concise solution.

Let’s say that my object model has fields called field1, field2, etc. And let’s say that the controller collects user input and sets the values of variables as follows: variable applyF1 is true if the user specifies that field1 is to be used as a criterion for the search and f1String is the user-supplied data for the search; applyF2 and f2String, and so on. The code is as simple as this:

def c = MyObject.createCriteria()
def results = c.list {
  and {
    if (applyF1) eq("field1", f1String)
    if (applyF2) eq("field2", f2String)
    if (applyF3) eq("field3", f3String)
  } // and
  if (sortByF1)
    order("field1")
  else
    order("field2")
} // list

In this case, all of the user-supplied conditions must be true for an object match to qualify. Note that the results are either sorted in order of field1 or field2.

Showing Search Results on a GSP

For searches such as the above, you’ll get back a list of Objects whose fields are still accessible by name. So, when you pass them off to a view page, you can do something simple, like:

<g:each in="${results}" var="item">
  <p><b>Item:</b> ${item.field1}, ${item.field2} ...</p>
</g:each>

However, not all of my search needs were met by GORM. There are times when I’ve had to resort to using HQL, which returns me back an unmarked list of values (in other words, it is no longer possible to treat each item in the list as an Object whose fields can be accessed by name). For example, I might get back something that looks like:

[ ["Fred", 5], ["Ann", 14], ...]

One of the features of my system is the charting of summary information about data of various types. I form an HQL string that might look something like the following (if the user wanted a report about the first names of Poets):

SELECT name, COUNT(name) AS count FROM Poet GROUP BY name

I get back an “anonymous” list as above, but when I use HighCharts to create graphs, what it wants is an array of values for one axis. For example, in the case of the above HQL result, I would have to supply a JSON object in the JavaScript that looks like:

xAxis: {
  categories: [
   "5", "14", ...
  ]
},

There is actually a simple and elegant solution for producing this with GSP. Let’s say that you pass the array of values to your view as a variable called results. The necessary code on your GSP view page would look like this:

xAxis: {
  categories: [
   <g:join in="${results.collect { '"'+ it[1].toString() + '"' } }" delimiter=", " />
  ]
},

The results.collect method goes through each of the items in the array and creates a new array; each item in the new array is a string formed from item[1] of the old array turned into a string with double-quotes on either side. The <g:join> tag takes that new array and outputs each item separated by a comma. Pretty concise, eh?

Comments

Those are a few of my insights and tricks. Perhaps you’ve got improvements or comments about these or other recipes.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s