Search code examples
mongodbgrailsconstraintsgrails-orm

Constraints on an embedded subclass - Grails, GORM, Mongo


I've been pulling my hair out with this issue for a couple of days.

I have an embedded subclass with several specified constraints. My issue is that these constraints are never enforced, I'm using grails 2.3.11 and the mongodb plugin 3.0.2.

Here is my setup (Simplified slightly).

Media class

class Media{
ObjectId id;
String name;
Film film;

static mapWith = "mongo"
static embedded = ["film"]
}

Film Class

class Film{
ObjectId id;
String name;

static mapWith = "mongo"
static belongsTo = [media : Media]
static mapping = {
    lazy:false
}
static constraints = {
name(nullable:false) //works as expected. Save fails if name is set to null
}
}

ActionFilm Class

class ActionFilm extends Film{
int score;
String director;

//These constraints are never enforeced. No matter what value I set the fields to the save is always successful
static constraints = {
score(min:50) 
director(nullable:true)
}
}

Is this an issue with Mongo and Gorm? Is it possible to have contraints in bth a parent and subclass?

Example code when saving

public boolean saveMedia(){
ActionFilm film = new ActionFilm()
film.setName("TRON");
film.setScore(2)
film.setDirector("Ted")

Media media = new Media()
media.setName("myMedia")
media.setFilm(film)
media.save(flush:true, failOnError:false)  //Saves successfully when it shouldn't as the score is below the minimum constrains

}

Edit I've played aroubd some more and the issue only persits when I'm saving the Media object with ActionFilm as an embedded object. If I save the ActionFilm object the validation is applied.

 ActionFilm film = new ActionFilm()
    film.setName("TRON");
    film.setScore(2)
    film.setDirector("Ted")
    film.save(flush:true, failOnError:false)  //Doesn't save as the diameter is wrong. Expected behaviour.

So the constraints are applied as expected when I save the ActionFilm object but aren't applied if its an embedded object.


Solution

  • I've solved my issue in case anyone else comes across this. It may not be the optimal solution but I haven't found an alternative.

    I've added a custom validator to the Media class that calls validate() on the embedded Film class and adds any errors that arise to the Media objects errors

    class Media{
    ObjectId id;
    String name;
    Film film;
    
    static mapWith = "mongo"
    static embedded = ["film"]
    
        static constraints = {
                film(validator : {Film film, def obj, def errors ->
                    boolean valid = film.validate()
                    if(!valid){
                        film.errors.allErrors.each {FieldError error ->
                            final String field = "film"
                            final String code = "$error.code"
                            errors.rejectValue(field,code,error.arguments,error.defaultMessage )
                        }
                    }
                    return valid
                }
                )
            }