Fork me on GitHub

4 Jan 2010

Upgrading Grails 1.1 -> 1.2

We've just successfully upgraded our app from Grails 1.1 to 1.2 and pushed it to our QA environment. I thought I'd write up some of the issues we encountered in case anyone is bashing their heads against them.


Custom constraints that execute queries

Grails 1.2 now executes domain object validation on any unsaved objects when the Hibernate session flushes. One upshot of this is that if you have a custom constraint that executes a query you can end up with a StackOverflowError; the query causes the session to flush, causing the object to be validated, causing the query to run, causing the session to flush, etc.

The solution is to use one of the new domain class dynamic methods withNewSession to execute the query in a separate Hibernate session.

For example, we have a domain class where only a single instance can exist in a 'live' state. A simple unique constraint won't work as any number of instances can exist in other states. The final constraint looks something like this:

static constraints = {
    state validator: { value, self ->
        boolean valid = true
        if (value == State.LIVE) {
            Homepage.withNewSession {
                valid = Homepage.countByStateAndIdNotEqual(State.LIVE, self.id) == 0
            }
        }
        return valid
    }
}

Errors set outside constraints

The new validation on flush behaviour also caused us a more subtle and difficult to trace problem. One of our controllers checks that a date/time property on a domain class instance is in the future when the instance is saved. Doing this in a constraint isn't desirable as it makes it awkward to set up test data or re-save instances in other contexts - the rule really only applies to the domain class when the user creates or updates it on the one particular form.

The validation code in the controller looks something like this:

domainInstance.validate()
if (domainInstance.goLiveTime < new Date()) {
    domainInstance.rejectValue "goLiveTime", "min.notMet"
}
if (!domainInstance.hasErrors() && domainInstance.save()) {
    // redirect
} else {
    // re-render form with errors
}

When upgrading one of our Selenium tests started failing as, although the save was failing, the error message did not get rendered on the form. What was happening is that at the end of the controller action the entity was not saved as it had errors but Hibernate attempted to flush and triggered re-validation which wiped out the error the controller had set.

If save had been called and validation failed then the object would have been made read-only so Hibernate would not attempt to flush it. My solution was to do this explicitly in the else block before rendering the form using GrailsHibernateUtil.setObjectToReadOnly(domainInstance, sessionFactory). It's certainly not ideal that the controller should be aware of this kind of low-level detail so some refactoring is in order here, but it solved the problem.

Specifying fetch modes on associations in criteria queries

We have one particularly nasty query that retrieves a domain object instance and loads an entire hierarchy of associations using several fetchMode "property", FetchMode.JOIN in the criteria query. For example if Author has many books and each Book has many chapters the query previously looked like:

Author.withCriteria {
    // some criteria
    fetchMode "books", FetchMode.JOIN
    fetchMode "chapters", FetchMode.JOIN
}

This always seemed syntactically odd as it appears that chapters is a property of Author when it is in fact a property of each member of Author.books. In Grails 1.2 the syntax makes much more sense as you nest the fetchMode declarations just as you would other criteria. So the example above would become:

Author.withCriteria {
    // some criteria
    fetchMode "books", FetchMode.JOIN
    books {
        fetchMode "chapters", FetchMode.JOIN
    }
}

Date binding format

Date binding now uses a fixed format of yyyy-MM-dd HH:mm:ss.S rather than the short format of the request locale. We have a couple of forms where dates are entered as text rather than using a date picker or rich control and the users (and our tests) expect to be able to enter dates as dd/MM/yy. In order to keep this functionality working I had to create a class that implements PropertyEditorRegistrar and deploy it in grails-app/conf/spring/resources.groovy. The class registers a custom date editor using the required format.

Because the custom property editor registration happens on a per-request basis it would be simple to use the same technique to allow users to enter dates in the locale format they are used to (Americans with their crazy month-day-year format and everyone else with something sensible, for example).

Setting String values in g:set tags

One of the main features of Grails 1.2 is the enhanced GSP rendering performance. One way this was achieved as I understand it is that mutable streams are used rather than immutable String instances where possible. One minor change I had to make to support this was changing g:set tags in the format <g:set var="name">${value}</g:set> to <g:set var="name" value="${value}"/> Variable created in the first manner are now instances of StreamCharBuffer rather than java.lang.String which sometimes caused problems when they were used later.

Flash scope and null values

The flash scope object available to controllers is now an instance of ConcurrentHashMap which means none of its keys can map to null. There were a few places where we were setting flash.message to the result of a method call that might return null. I simply ensured the empty string was used instead.

Ultimately the upgrade took a lot less time and effort than when we went from Grails 1.0.3 to 1.1 and we thankfully didn't encounter any blocking issues. The impression I have is that the platform's really maturing nicely. Will be cool to start using some of the new features of 1.2 in the next few weeks.

9 comments:

Jason Blum said...

Thanks for the sharing - one step back: Does one upgrade merely by typing in the 'grails upgrade' command? That probably answers itself, but am new to Grails and am a little unclear on how the command knows where the Grails installation to upgrade is, and whether anything needs to be done to the app itself? Thanks!

johnrellis said...

Nice! @phenotypical download and configure your environment for 1.2, then go to you project dir and perform a "grails upgrade". If the project is less than 1.2, it will be upgraded!

Jason Blum said...

Ah gotcha - I guess I was thinking it might just upgrade and configure the environment on its own like a plugin, but then I guess that could be dangerous. Thanks!

Chris said...

Thanks for the information!

I posted about some issues we faced as well in case you are interested:

http://codingintexas.blogspot.com/2010/01/upgrading-from-grails-112-to-12-final.html

jasonbob said...

lebron 17
yeezy 350
a bathing ape
supreme clothing
golden goose outlet
golden goose
hermes handbags
curry shoes
curry 6 shoes
adidas yeezy

Unknown said...

additional reading view website have a peek at this web-site dolabuy look at more info visit this site

Unknown said...

conseils utiles répliques de sacs à main Ysl cliquez pour lire Dolabuy Celine mor e Bottega Veneta Dolabuy

thesough said...

replica bags from china go to the website m3j59t0u78 replica bags in dubai replica bags paypal replica hermes handbags q6o03c4a96 replica bags in china browse this site s9t57e7r14 replica louis vuitton replica bags philippines v7r82w4p96

Jack Dowson said...

This involves building a stage for the immediate exchanging of computerized resources using market creators that are programmed (AMMs) and liquidity pools. Contrasted with incorporated trades, clients can profit from quicker exchanging, diminished expenses, and greater secrecy. A DeFi marking improvement administrations organization gives a custom UI and client experience configuration, organization and wallet similarity, and significant level usefulness like breaking point orders, stop-misfortune security, and robotized exchanging bots>> defi wallet development solutions