GORM Labs

  • Tags : utility, gorm
  • Latest : 0.8.5
  • Last Updated: 08 May 2010
  • Grails version : 1.1 > *
  • Authors : null
2 votes
Dependency :
compile ":gorm-labs:0.8.5"

Documentation

Summary

Installation

grails install-plugin gorm-labs

Description

GORM Labs

Info

This plugin is a playground of potential new GORM functionality. It provides a wide series of enhancements to the way that GORM works. Although the APIs provided here are tested, they may be subject to change if the API is found to be lacking or awkward. If such changes do occur, it will be first with logging messages (at DEBUG) notifying people how to change their code to adapt.

Functionality Provided

assertSaved

In a GrailsUnitTestCase, you can now run "assertSaved" and the object will attempt to be saved, with failures giving you a particularly useful error message.

assertSaved(new Feature(featureId:1, name:'test', type:featureType, status:'EDIT'))

If there is a request to add this to some other class (perhaps a parent of GrailsUnitTestCase?), let me know.

Opt-In Transactional Actions

A controller can now specify actions that should be transactional by setting the "transactional" property:

class FooController {

static transactional = ['index']

def index = { assert new Foo().save() if(params.explode) rollback() render "${Foo.count()}" }

}

All actions can become transactional by specifying:

static transactional = true

That "transactional" property can also be per-instance and may be artificial ("boolean getTransactional()" or "Collection getTransactional()"), should you feel like having that kind of customization.

As demonstrated in the example, any transactional action can signal a rollback by calling "rollback()" — when the action completes, the transaction will be rolled back.

Aggregate Static Properties

  • Domain classes now have static properties of the form ".minPropName", ".maxPropName", ".avgPropName", ".sumPropName", ".countPropName", ".countDistinctPropName" that query the database using those aggregate functions.
Example:
class Foo { int bar }
Now has the following properties:
Foo.minBar
Foo.maxBar
Foo.avgBar
Foo.sumBar
Foo.countBar
Foo.countDistinctBar

.connection, .sql, .session, .flush()

Domain classes now have a static "connection" property which provides direct access to the underlying database connection, a "sql" property which provides a groovy.sql.Sql, and a "session" property that gives the current Hibernate session. There is also a "flush()" method that flushes the session.

query(...)

  • There is now an abbreviated version of "executeQuery" called "query". It assumes you're querying based on properties hanging off of the object without any joins: just specify the HQL "where" clause. The rest of the arguments behave exactly like "executeQuery".
Example:
Foo.query("bar.someBarProperty = ? and (baz = 0 or baz = ?)", 
                   ['bar', 1], [max:10])
...is the same as…
Foo.executeQuery("""
    from $Foo.name
    where bar.someBarProperty = ? and 
               (baz = 0 or baz = ?)
  """, ['bar', 1], [max:10])

Errors

  • The "toString" method of the "errors" domain class property, along with object and field errors, has been modified to provide a human-readable message. This is done through the same i18n look-up that the Grails view logic does.
  • The "errors" domain class property also will look up field errors as a property look-up:
foo.errors.bar // Retrieves errors that occurred on "bar" property

Order Criteria Expressions

  • Criteria can now be ordered by child objects, assuming you can navigate to them directly (has-one/belongs-to relationships only). So if there's a domain class "Foo" that has a "bar" object with a "baz" property, you can fetch all the Foo objects sorted by their bar.baz by going:
Foo.withCriteria {
    order("bar.baz")
  }

Session Object Dehydration

  • Objects dumped into the session can eat up a lot of space and lead to annoying problems (like LazyInitializationExceptions). This capability will save and dehydrate objects after the request ends and will rehydrate them when a new request comes in. While this reduces session size and solves the LazyInitializationException issue, it also means that bogus modifications to domain objects will be lost (the save will fail, and the retrieve happens).
  • Currently, only top-level session domain objects, or domain objects stashed in maps, lists, or some combination thereof (maps of lists of lists of maps of maps of lists...) will be dehydrated.
  • Objects that have not been saved (i.e. for whom the "id" property is false) will not be dehydrated. This means objects you're building won't be dehydrated as long as they are not saved.
  • To enable Session Object Dehydration, set the following in Config.groovy:
gormLabs.dehydrateSession = true

Paginated HasMany Properties

For every "hasMany" property, there is now an associated method that takes pagination properties ("offset" and "max") and produces the appropriate page. There is also a "countBars" instance property that will provide the total size of the "bars" collection. This is all done with a separate database query if the collection has not been initialized previously, so you can avoid loading all the elements of the collection.

It's important to realize that the default "hasMany" mapping is as a Set, in which case the concept of pagination doesn't really apply. The method will attempt to cope with a Set, but you aren't guarantied to get consistent results between pagination calls.

class DomainWithChildren {
  static hasMany = [children:ChildOfDomain]
  List children
}

class ChildOfDomain { int value }

def fixture = new DomainWithChildren() (1..10).each { fixture.addToChildren(new ChildOfDomain(value:it)) } assertEquals("Count is off", 10, fixture.countChildren) def result = fixture.children(max:2, offset:2)*.value assertEquals("Size is off", 2, result.size()) assertEquals("First value is of", 3, result[0]) assertEquals("Second value is of", 4, result[1])

gormlabs.Gorm class

There is a class called "gormlabs.Gorm": any static method that can be called on a domain class can be called on "gormlabs.Gorm". The application must have at least one domain class in order for Gorm to work, however.

On the Docket

Here are the things I'd like to add to GORM Labs:

Important Implementation Detail

Since 0.6.5, GORM Labs immediately invokes a bogus method ( thisIsATotallyBogusMethodPlacedHereJustToTriggerGORMHydration() ) in order to circumvent the lazy initialization of GORM methods via the methodMissing handler. That magic was a hold-over from a time when meta-object mangling was much slower, and experiments on 1.1.1 show that the start-up time penalty is pretty minimal. That lazy initialization behavior lead to much more complicated (and expensive) extensibility, as well as to a number of bugs, such as http://jira.codehaus.org/browse/GRAILS-4580. It also made metaObject querying basically lie to you if you asked before or after hydration. Basically, life is better without it.

Notes for Aspiring Contributors

  • As of this point, all of the logic to mangle the domain classes is dumped straight into GormLabsGrailsPlugin.groovy -- this is sub-optimal, and I'd like to create some conventional way of attaching new functionality to domain classes.

License

To the extent possible under law, Robert Fischer has waived all copyright and related or neighboring rights to GORM Labs. This work is published from the United States.