Skip to content

ValidateableDataBuilder#satisfyConstrained and unique vs. nullable vs. blank #111

@zyro23

Description

@zyro23

given a domain

class Foo {
	String name
	static constraints = {
		name blank: false, unique: true
	}
}

and a spec

@Build(Foo)
class FooSpec extends Specification implements DomainUnitTest<Foo>, BuildDataTest {
	def "test blank 'x' vs. unique constraint"() {
		when:
		Foo.build()
		Foo.build()
		Foo.build()
		currentSession.flush()

		then:
		def e = thrown(ValidationException)
		e.errors.getFieldError("name").rejectedValue == "name"
	}
}

fails with

Condition not satisfied:

e.errors.getFieldError("name").rejectedValue == "name"
| |      |                     |             |
| |      |                     x             false
| |      |                                   4 differences (0% similarity)
| |      |                                   (x---)
| |      |                                   (name)
| |      Field error in object 'myapp.Foo' on field 'name': rejected value [x]; codes [myapp.Foo.name.unique.error.myapp.Foo.name,myapp.Foo.name.unique.error.name,myapp.Foo.name.unique.error.java.lang.String,myapp.Foo.name.unique.error,foo.name.unique.error.myapp.Foo.name,foo.name.unique.error.name,foo.name.unique.error.java.lang.String,foo.name.unique.error,myapp.Foo.name.unique.myapp.Foo.name,myapp.Foo.name.unique.name,myapp.Foo.name.unique.java.lang.String,myapp.Foo.name.unique,foo.name.unique.myapp.Foo.name,foo.name.unique.name,foo.name.unique.java.lang.String,foo.name.unique,unique.myapp.Foo.name,unique.name,unique.java.lang.String,unique]; arguments [name,class myapp.Foo,x]; default message [null]
| org.grails.datastore.mapping.validation.ValidationErrors: 1 errors
| Field error in object 'myapp.Foo' on field 'name': rejected value [x]; codes [myapp.Foo.name.unique.error.myapp.Foo.name,myapp.Foo.name.unique.error.name,myapp.Foo.name.unique.error.java.lang.String,myapp.Foo.name.unique.error,foo.name.unique.error.myapp.Foo.name,foo.name.unique.error.name,foo.name.unique.error.java.lang.String,foo.name.unique.error,myapp.Foo.name.unique.myapp.Foo.name,myapp.Foo.name.unique.name,myapp.Foo.name.unique.java.lang.String,myapp.Foo.name.unique,foo.name.unique.myapp.Foo.name,foo.name.unique.name,foo.name.unique.java.lang.String,foo.name.unique,unique.myapp.Foo.name,unique.name,unique.java.lang.String,unique]; arguments [name,class myapp.Foo,x]; default message [null]
grails.validation.ValidationException: Validation error occurred during call to save():
- Field error in object 'myapp.Foo' on field 'name': rejected value [x]; codes [myapp.Foo.name.unique.error.myapp.Foo.name,myapp.Foo.name.unique.error.name,myapp.Foo.name.unique.error.java.lang.String,myapp.Foo.name.unique.error,foo.name.unique.error.myapp.Foo.name,foo.name.unique.error.name,foo.name.unique.error.java.lang.String,foo.name.unique.error,myapp.Foo.name.unique.myapp.Foo.name,myapp.Foo.name.unique.name,myapp.Foo.name.unique.java.lang.String,myapp.Foo.name.unique,foo.name.unique.myapp.Foo.name,foo.name.unique.name,foo.name.unique.java.lang.String,foo.name.unique,unique.myapp.Foo.name,unique.name,unique.java.lang.String,unique]; arguments [name,class myapp.Foo,x]; default message [null]

Expected :name

Actual   :x

log:

2018-05-29 08:16:11.773 DEBUG --- [           main] g.b.builders.ValidateableDataBuilder     : myapp.Foo.nullable constraint, field before adjustment: null
2018-05-29 08:16:11.775 DEBUG --- [           main] g.b.builders.ValidateableDataBuilder     : myapp.Foo.name field after adjustment for nullable: name
2018-05-29 08:16:11.982 DEBUG --- [           main] g.b.builders.ValidateableDataBuilder     : myapp.Foo.nullable constraint, field before adjustment: null
2018-05-29 08:16:11.982 DEBUG --- [           main] g.b.builders.ValidateableDataBuilder     : myapp.Foo.name field after adjustment for nullable: name
2018-05-29 08:16:11.992 DEBUG --- [           main] g.b.builders.ValidateableDataBuilder     : myapp.Foo.blank constraint, field before adjustment: name
2018-05-29 08:16:11.992 DEBUG --- [           main] g.b.builders.ValidateableDataBuilder     : myapp.Foo.name field after adjustment for blank: x
2018-05-29 08:16:11.993 DEBUG --- [           main] g.b.builders.ValidateableDataBuilder     : myapp.Foo.nullable constraint, field before adjustment: null
2018-05-29 08:16:11.993 DEBUG --- [           main] g.b.builders.ValidateableDataBuilder     : myapp.Foo.name field after adjustment for nullable: name
2018-05-29 08:16:11.995 DEBUG --- [           main] g.b.builders.ValidateableDataBuilder     : myapp.Foo.blank constraint, field before adjustment: name
2018-05-29 08:16:11.995 DEBUG --- [           main] g.b.builders.ValidateableDataBuilder     : myapp.Foo.name field after adjustment for blank: x
2018-05-29 08:16:11.995 DEBUG --- [           main] g.b.builders.ValidateableDataBuilder     : myapp.Foo.unique constraint, field before adjustment: x
2018-05-29 08:16:11.995  WARN --- [           main] g.b.builders.ValidateableDataBuilder     : Unable to find property generator handler for constraint unique!
  • first Foo.build()
    • name: "name" - unique - constraints satisfied
  • second Foo.build()
    • name: "x" - unique - constraints satisfied
  • third Foo.build()
    • name: "x" - not unique - constraints not satisfied

so when the unique constraint is not satisfied, the BlankConstraintHandler gets applied, that was unexpected.

as we have to take care of proper unique value generation anyway this is not blocking. just that the second build() passed and then the third fails with a value of x instead with the unique propertyName name is confusing.

referencing sample app in a minute.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions