In the book, Getting Started with Grails - 2e, page 42 (the electronic page, and not the paper page), the following example is listed.
class Race {
static constraints = {
name(blank:false, maxSize:50)
// NOTE: This doesn’t do
// what you think it does
startDate(min: new Date())
// And this is what we're supposed
// to be using:
// startDate(validator: {return (it > new Date())})
}
String name
Date startDate
// ...
}
The reader is then advised to use the version of startDate
commented out above. The reason cited is: The static constraints
property would get evaluated only once (at server startup time), whereas our intent is to have it evaluated on each instantiation and subsequent validation of Race
.
My question is: Why then the non-closure style of the name
constraint works on each validation attempt but not the startDate
constraint? And, conversely, if the closure flavor is required for startDate
, then why is it not required for name
as well?
If I understand the above Groovy syntax correctly, it seems that each of the constraints listed in the static constraint
block is, syntactically, a call to a function that takes various validation attributes as a Map
. Now since the static block would get evaluated (once) at server startup time, both function calls too would happen (once) at the server startup time, and should lead to identical and consistent behavior in their non-closure form. Isn't it so?
Constraints provide Grails with a declarative DSL for defining validation rules, schema generation and CRUD generation meta data. For example, consider these constraints: class User { ... static constraints = { login size: 5.. 15, blank: false, unique: true password size: 5..
A domain class fulfills the M in the Model View Controller (MVC) pattern and represents a persistent entity that is mapped onto an underlying database table. In Grails a domain is a class that lives in the grails-app/domain directory.
If you go with:
startDate(min: new Date())
Then new Date()
will be evaluated at server start-time and will never change. So next week (assuming the server keeps running) it will validate the date against last week.
The second form:
startDate(validator: {return (it > new Date())})
Will be evaluated each time the constraint is checked, so it will always validate against today no matter how long the server has been running.
On the other hand, when name
is concerned, it is validated against static content, viz maxSize
being 50 which makes sense to have it as key value pair instead of using validator
closure, since the value 50
is not evaluated everytime a validation is done on name
as it is done for startDate
.
When the call name( maxSize:50 )
call is evaluated, it actually creates a MaxSizeConstraint
object for the field name
. This map of property->constraints
is then used by grails to check properties when the object is validated. As you can see in that class, maxSize
is a private property. Indeed, if you wanted maxSize
to change over time, then you'd need to use a custom validator as with Date
I've done some experiments, and here are some conclusions. This is all empirical research; please, correct me if I am wrong.
First of all, it is easy to see, when the call to constraints
gets evaluated. Just add some debug output:
static constraints = {
println "Entering constraints..."
name(blank:false, maxSize:50)
// etc.
println "Exiting constraints..."
}
You will see that it is indeed evaluated at application startup. For some reason that I don't understand, it is typically evaluated twice. Also notice that constraints
is marked as static, so it has nothing to do with particular instances of the class Race
.
Next, it is easy to find out that name
, startDate
, etc. are, indeed, function calls. Just try to specify constraints on a non-existing property:
static constraints = {
no_such_thing(nullable:true)
}
You will not be able to do that! The result:
Error Error executing script Shell: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'transactionManagerPostProcessor': Initialization of bean failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'transactionManager': Cannot resolve reference to bean 'sessionFactory' while setting bean property 'sessionFactory'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sessionFactory':
Invocation of init method failed; nested exception is org.codehaus.groovy.runtime.InvokerInvocationException: groovy.lang.MissingMethodException:
No signature of method: Race.no_such_thing() is applicable for argument types: (java.util.LinkedHashMap) values: [[nullable:true]]
Of course, you have never defined all those methods, i.e. name
, startDate
, etc., and you haven't inherited your domain class from anything else either. But since Grails recognizes it as a domain class, it uses the power of Groovy to inject the methods into the object, bypassing the restrictions of traditional object-oriented programming.
Now, it doesn't literally inject the methods into the object. You can easily check:
static constraints = {
println Race.metaClass.methods*.name.sort().unique()
// You can even construct an object if you really want to mess with the framework
println new Race().metaClass.methods*.name.sort().unique()
}
You will not see any methods called name
, startDate
, etc., and you can't println Race.name
inside the constraints { }
block either. What I think happens is that Groovy intercepts calls to nonexistent methods Race.name
, Race.startDate
, etc., and records this constraint information somewhere else for future use. If you want, try actually implementing methods called e.g. Race.name
; I think I was able to prevent the constraints from working by doing so, but I can't reproduce it.
Regarding the question of what gets evaluated when, I think we have some confusion here about Groovy closures. Take a look at
startDate(validator: {return (it > new Date())})
Here we have a closure: {return (it > new Date())}
. If Groovy were a pure interpreted language like Python, it would just store this literal code and reinterpret it on any and every call. Thus, you would also get the most current date. My guess is that Groovy mimics this behavior, even those the code is presumably compiled: it will wrap this code into a closure object, and will call this object every time validation is requested. This closure object will be stored somewhere; presumably, in the same place where all the other constraints are stored. The confusion arises because of the nature of closures: if you store 3
, it will always stay 3
; if you store a closure (a function), it can evaluate to different results on different occasions.
Just to reiterate, the code
{return (it > new Date())}
is not "executed" or "evaluated" on application startup; it is merely stored for future use. You can easily check it:
static constraints = {
startDate(validator: {println "Validating startDate..."})
}
Then run grails shell
and
groovy> (new Race()).validate()
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With