Thursday, May 29, 2008

Currying Closures

This threw my brain for a loop... Groovy code using a "Execute Around Method" pattern:

def tellFortunes(closure) {
Date date = new Date("11/15/2007")

//closure date, "Your day is filled with ceremony"
//closure date, "They're features, not bugs"
// You can curry to avoid sending date repeatedly

postFortune = closure.curry(date)

postFortune "Your day is filled with ceremony"
postFortune "They're features, not bugs"
}

tellFortunes() { date, fortune ->
println "Fortune for ${date} is '${fortune}'"
}

From "Programming Groovy" pps 92-93

Result is:
Fortune for Thu Nov 15 00:00:00 EST 2007 is 'Your day is filled with ceremony'
Fortune for Thu Nov 15 00:00:00 EST 2007 is 'They're features, not bugs'

  1. Basically tellFortunes() accepts date and fortune as parameters, defines a code block which prints the message with the date and fortune in it
  2. This calls postFortune(closure) accepting the code block (called a Closure in Groovy)
  3. the first use of postFortune binds the date to the inbound closure and holds a reference in postFortune
  4. the next two postFortunes pass the string (and the date by use of curry) to the code block thus printing the message each time

Wednesday, May 28, 2008

Grails Web Info - dated?

I have been working on a Grails project for the last few weeks. Form my previous posts, you can see that I am working with a legacy database with my primary table configured with a composite primary key.

I've been struggling with the insert and update aspects of writing to the database. Now, in all fairness I am not extremely familiar with Hibernate so I am relying on documentation and information from the web.

One challenge with the information on the web is that a lot of the posts are out of date. When I read anything earlier that the 2nd half of 2007, I tend to not take the suggestions. Many of those tell you to configure your hibernate.cfg.xml file. However, since version 1.0 of Grails many of those configuration options are available within the domain class.

One feature I just discovered from CK's Blog was the composite key setting "generator='assigned'" which is not in the Grails documentation.

Now to give this a spin...

Wednesday, May 14, 2008

Redirecting from index.gsp

To redirect to an initial page from the index.gsp

1. Create a TagLib file
  grails-app\taglib\MyAppTagLib.groovy

2. Define a method that routes to your first page
  class MyAppTagLib {
    def redirectMainPage = {
      response.sendRedirect("${request.contextPath}/myController/welcomePage/")
    }
  }


3. Change your index.gsp to contain only:
  <g:redirectMainPage/>

Note: One advantage to this approach is that you have a coding opportunity over simply mapping the "/" url.

For example, if you want to provide features or data based on a specific user's login id.

Tuesday, May 13, 2008

Legacy DB equals frustration

Been spending a lot of time trying to solve problems with Grails when you are using a legacy database. It could be a combination of factors but I am trying to do "simple" one to one mapping of a code table to a column value and I can not get any dynamic finders working!!!

So I tried a simple:
def results = MyMainDomain.findAll()
and I get:
No row with the given identifier exists: [MyLookupClass#someValueInLookupClass ]

When I change the variables back to Strings that message goes away.

Then I get results back with all rows (rowcount = to db row count) containing empty values!

Turns out: DO NOT define any columns in your composite primary key from other Domain Classes!!! I changed them to Strings and it worked.

Thursday, May 8, 2008

Composite Key in Domain Class - round 2

Still tinkering with the composite key issues.

I changed my previous approach a bit, I now define my own getId and setId methods to essentially serialize and deserialize the the key fields strung together

Created a delimiter
static final String delimiter = '~@'

Defined (overriding default) accessors:
def getIdAsMap() {
["fieldOne": fieldOne, "fieldTwo": fieldTwo]
}

def getId() {
fieldOne + delimiter + fieldTwo
}

def setId(String inString) {
def fieldList = new ArrayList()
def items = inString.split(delimiter)
items.each { fieldList.add (it) }
this.setFieldOne (fieldList[0])
this.setFieldTwo (fieldList[1])
getIdAsMap()
}

Its a little clunky but when you can pass around the map, use getIdAsMap(), in other cases the String is available with myclass.id


Inserting new rows requires a little tweak, by default with version: false set, GORM will try to perform a sql update on new records. To fix this, add insert:true to your save method

myClass.save(insert: true)

UI changes - on the fly?

I love the rapid feedback from Grails, but too much of a good thing...

I was changing some of the field presentation from a <input to <g:select and it was not showing up on the page.

So:
Refreshed the browser page - nope
Shut down the app, at the command prompt typed 'grails clean', restarted - negative

Finally I closed the browser and reopened it, and viola!

Tuesday, May 6, 2008

Composite Key in Domain Class

Using the default id column in the database works great. However, if you are facing a "legacy" database design and unable to alter the table definition, you have a bit of work to do.

Defining your domain class:
1. The class must implement Serializable
class MyClass implements Serializable {
...
static mapping = {
table "my_table"
version false
id composite: ["myFieldOne","myFieldTwo"]
...

2. The scaffolding will by default setup all communication via the id column. The id column in this case is a serialized value. However, there is an issue with Grails v1.0.2 that does not deserialize the value properly. So, for now you have to work around using the primary key:

3. Add new Method to domain class - define a map of the values
def getPK ( ) {
["myFieldOne":myFieldOne, "myFieldTwo":myFieldTwo]
}


4. List view - Add a link on the page to show details for the current record

<span class="actionButton">
<g:link action="show" params="${myClass.getPK()}">Detail </g:link>
</span>

NOTE: "id=" was changed to "params=" and you are passing in the map returned from myClass.getPK()

5. MyClassController.groovy - modify the retrieval code in the show method
def show = {
def myClass = MyClass.get( new MyClass(params) ) //here are your inbound params

if(!myClass) {
flash.message = "MyClass not found with ${params}"
redirect(action:list)
}
else { return [ myClass: myClass] }
}