GORM Labs
Dependency :
compile ":gorm-labs:0.8.5"
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'))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()}"
}}static transactional = true
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.
class Foo { int bar }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".
Foo.query("bar.someBarProperty = ? and (baz = 0 or baz = ?)",
['bar', 1], [max:10])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" propertyOrder 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 = truePaginated 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:- Possibly inject Gorm into artefacts?
- find-or-create—Something like:
MyDomain.findByBar(bar, [create:true, flush:true, properties:[newb:42]])
- "save or exception", "save and flush" method
- countByQuery
- use c3p0 by config
- default value reflected in schema
- auto-save if transient object assigned
- auto-set relationships
- acts_as_tree
- "as Map" on domain classes
- assertSaveFailsOn domain, fieldName
- Automatic quoting of table names
- Auto-inject "dateCreated" and "lastUpdated"
- Automatic excludes on "bindData" based on "excludeProprties".
- Has Many Through
- assertSave in GroovyTestCase and GrailsUnitTestCase
- always check for transients save
- executeQuery returns lists of lists, not lists of arrays
- deleteBy
- Filters a la:
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.