Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Groovy/Grails: How are constraints implemented?

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?

like image 257
Harry Avatar asked Jul 01 '13 14:07

Harry


People also ask

What are Grails constraints?

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..

What is Grails domain class?

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.


2 Answers

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.

Edit:

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

like image 103
tim_yates Avatar answered Oct 02 '22 08:10

tim_yates


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()
like image 36
Sergey Orshanskiy Avatar answered Oct 02 '22 08:10

Sergey Orshanskiy