How to add validation to limit number of rows being created

173 views Asked by At

I have a Grails domain class. I would like to add a validation such that when the application is running in TEST environment, the domain class can't have more than 100 records.

I can't figure out the best way to add this type of validation. I am on Grails 2.5.3.

This is what I have so far.

class MyDomain {

String test

static constraints = {
   test blank: false, nullable: false
   id blank: false, validator: {value, command -> 
      if (Environment.current == Environment.TEST) {
          //do validation for not allowing more than 100 records
      }
   }
}

How can I add this validation?

2

There are 2 answers

3
Shashank Agrawal On BEST ANSWER

Solution for a single domain

What @Joshua answered is perfectly fine but there are few other ways. One of them is:

class MyDomain {

    String test

    void beforeInsert() {
        if (Environment.current == Environment.TEST) {
            MyDomain.withNewSession {
                if (MyDomain.count() == 100) {
                    throw new Exception("Not allowing more than 100 records")
                }
            }
        }
    }

    static constraints = {
        test blank: false
    }
}

Also, please note two things:

  1. The blank: false on the id field of no use since it is not a string because blank constraint is applicable on a String
  2. The nullable: false is of no use since the default value of nullable constraint is false

Generic solution for across domain TL;DR

If you want this behavior across multiple domains, copying the same code is not recommended as your code won't be DRY. For that, you can register a custom event listener:

First define a Java annotation in src/groovy:

import java.lang.annotation.Documented
import java.lang.annotation.ElementType
import java.lang.annotation.Retention
import java.lang.annotation.RetentionPolicy
import java.lang.annotation.Target

@Documented
@Target([ElementType.TYPE, ElementType.FIELD])
@Retention(RetentionPolicy.RUNTIME)
public @interface LimitRowsFoo {

    int value() default 100
}

Now define another Groovy class:

import grails.util.Environment
import org.grails.datastore.mapping.engine.event.PreInsertEvent
import org.grails.datastore.mapping.engine.event.AbstractPersistenceEvent
import org.grails.datastore.mapping.engine.event.AbstractPersistenceEventListener

class PreInsertEventListener extends AbstractPersistenceEventListener {

    PreUpdateEventListener(final Datastore datastore) {
        super(datastore)
    }

    @Override
    protected void onPersistenceEvent(AbstractPersistenceEvent event) {
        // Instance of domain which is being created
        Object domainInstance = event.entityObject

        if (Environment.current == Environment.TEST) {
            if (domainInstance.class.isAnnotationPresent(LimitRowsFoo.class)) {
                // Just using any domain reference here
                MyDomain.withNewTransaction {
                    int maxRows = domainInstance.class.getAnnotation(LimitRowsFoo.class).value()
                    if (domainInstance.class.count() == maxRows) {
                        event.cancel()
                    }
                }
            }
        }
    }

    @Override
    boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
        // Only allow PreInsert event to be listened
        eventType.name == PreInsertEvent.class.name
    }
}

Now register this in Bootstrap.groovy:

application.mainContext.eventTriggeringInterceptor.datastores.each { k, datastore ->
        applicationContext.addApplicationListener new PreInsertEventListener(datastore)
    }

Now, in your domain, all you have to do is like these:

@LimitRowsFoo
class MyDomain {

    String test

    static constraints = {
        test blank: false
    }
}

@LimitRowsFoo(value = 200)   // to limit records to 200
class MyAnotherDomain {

    String name
}
0
Joshua Moore On

Wouldn't something like this do the trick for you?

class MyDomain {

String test

static constraints = {
   test blank: false, nullable: false
   id blank: false, validator: {value, command -> 
      if (Environment.current == Environment.TEST) {
          //do validation for not allowing more than 100 records
          if (MyDomain.count() > 100) return ['too.many.records']
      }
   }
}