Grails rejectValue - multiple checks causing ob.errors null - grails

My domain object booking has multiple attributes that are allowed to be null, because they will be set later after the object has been saved to the db.
Part of myService.action():
booking.properties = params
if (booking.contactFirstname?.length() <= 1) { booking.errors.rejectValue("contactFirstname", "empty") }
if (booking.contactLastname?.length() <= 1) { booking.errors.rejectValue("contactLastname", "empty") }
if (booking.contactPhone?.length() <= 1) { booking.errors.rejectValue("contactPhone", "empty") }
if (booking.contactMobile?.length() <= 1) { booking.errors.rejectValue("contactMobile", "empty") }
if (booking.contactEmail?.length() <= 1) { booking.errors.rejectValue("contactEmail", "empty") }
if (booking.hasErrors() || ! booking.validate()) {
return [success: false, model: booking]
} else {
booking.save(failOnError: true)
return [success: true, model: booking]
}
My controller does:
def result = myService.action(params)
if (result.success) {
flash.success = message(code: "msg.successfullySaved")
redirect(action: "registerEventConfirmation", id: result.model.uid, params: [lang: params.lang], mapping: "paginated")
} else {
flash.error = message(code: "msg.errorSavingCheckFields")
render(view: "registerEventStep3", params: [lang: params.lang], model: [booking: result.model])
I'm using
hasErrors(bean: booking,field:'contactFirstname', 'has-error')}
to mark error fields.
If I now submit the form without any values in textfields, all fields are red, booking.errors has >0 errors.
If I submit the form after with a firstname, booking.errors is NULL and no other field is marked.
Is this a Bug? I'm with Grails 2.3.6
additional information
I visit the form, submit it empty completely
I see all form fields in red, object.errors has >0 errors (VALID)
I enter a value in the first field, firstname and submit
I see none of the form fields in red, object.errors =0 errors (INVALID)
I re-submit the form with none changes
I see all empty form fields in red, object.errors has >0 errors (VALID)

Now that I fully understand the situation and since I was having trouble sleeping I thought I give you a very concise answer so that you can hopefully make full sense and use things properly.
Firstly I know creating a validation bean sounds like it will be a lot of work so let me teach you how to do it all relatively simply and why it is my preferred method.
It is my preferred method simply because when you do
class MyController {
def myAction(Mybean bean) {
// 1. the object allowed into this save action
// are all that is available objects withing MyBean.
// If it has user define but not telephone. Then
// if telephone is passed to myAction it will fail and not recognise
// field
// When declaring Date someField or User user then the params now
// received as bean this way is now actually properly bound
// to the data / domainType declared.
// Meaning user will now be actual user or someField actually Date
}
So now to explain how to best solve this issue. When creating beans simply copy over the actual domain class from your domain folder into src/groovy/same/package in grails 2 or src/main/groovy/same/package in grails 3
Change name / class or copy as from Booking to BookingBean so it has a different name.
Add #Validateable above actual BookingBean in grails 2 or add implements to main class like Class BookingBean implements Validateable { in grails 3
Now since it is copied all the objects are identical and at this point a save from the controller would be
class MyController {
def myAction(BookingBean bean) {
Booking booking = new Booking()
// this will save all properties
booking.properties = bean
booking.save()
}
}
But you have a special circumstance and you wanted to declare a transient field in the main domain class what I would do instead is
class BookingBean {
def id
String contactFirstname
String contactLastname
boolean secondSave=false
static constraints = {
id(nullable: true, bindable: true)
contactFirstname(nullable:true) //,validator:checkHasValue)
contactLastname(nullable:true) //,validator:checkHasValue)
secondSave(nullable:true,validator:checkHasValue))
}
//use the same validator since it is doing identical check
static checkHasValue={value,obj,errors->
// So if secondSave has a value but contactFirstName
// is null then complain about contactFirstName
// you can see how secondSave gets initialise below
//typical set this to true when you are about to save on 2nd attempt
//then when set run validate() which will hit this block below
// Check all the things you think should have a
// value and reject each field that don't
if (val) {
if ( !obj.contactFirstname) {
errors.rejectValue('contactFirstname',"invalid.contactFirstname")
}
if ( !obj.contactSecondname) {
errors.rejectValue('contactSecondname',"invalid.contactSecondname")
}
//and so on
}
}
So now in your controller:
class MyController {
def save1(BookingBean bean) {
Booking booking = new Booking()
// this will save all properties
booking.whatEver = bean.whatEver
booking.save()
// you can choose to validate or not here
// since at this point the secondSave has
// not been set therefore validation not called as yet in the bean
}
//you probably have id and it should bind with actual domain class
def save2(BookingBean bean) {
booking.secondSave=true
if (!bean.validate()) {
//this is your errors
//bean.errors.allErrors
return
}
//otherwise out of that loop since it hasn't returned
//manually set each object
booking.contactFirstname=bean.contactFirstName
booking.contactSecondname=bean.contactSecondname
booking.save()
}
}
e2a side note - above should answer
well don't validate it until you have created it. Only validate it after you created the object then added a value. Alternative create a function possibly in a validation bean that you run as part of your 2nd check. This Example bean is not validated until formatRequest is called as seen here

I don't grasp the specifics of your question, so I will give some general guidance since I have just dug into this.
Don't call hasErrors() before validate(). If you do, Grails won't hand you errors from domain constraints and you will only end up with the errors you set yourself using rejectValue().
Be careful with using rejectValue(). Try to set all your errors using domain constraints. If you have sophisticated constraints use the validator syntax and obj.getPersistentValue() might be your friend once in a while.
If you still have to use rejectValue(), understand that any later calls to validate() will start from scratch and erase your prior errors. I have written a workaround for this (to be placed in your domain object) although I can't assure you it is 100% ok:
def validateWithErrors(def fields = null) {
def existingErrors = this.errors
def ret = (fields ? this.validate(fields) : this.validate())
existingErrors?.allErrors?.each { error ->
this.errors.rejectValue(error.field, error.code)
}
return (existingErrors?.allErrors ? false : ret)
}

Related

What would be the best way to check whether all fields are valid?

I have fields in a Window, some with validators and all bound to properties.
The validation works as expected.
But -
I do not want to proceed when any field is invalid. What would be the best way to determine if any validation went wrong?
There are several ways of dealing with validation in Vaadin, all supported by Vaadin (no need for custom boolean afterValidationFlag).
One possible way (preffered by me) shown below:
public class CustomWindow extends Window {
DateField customBeanFirstPropertyName = new DateField("Caption1");
ComboBox customBeanSecondPropertyName = new ComboBox("Caption2");
TextArea customBeanThirdPropertyName = new TextArea("Caption3");
BeanFieldGroup<CustomBean> binder = new BeanFieldGroup<>(CustomBean.class);
public CustomWindow(CustomBean customBean) {
buildLayout();
binder.buildAndBindMemberFields(this);
binder.setItemDataSource(new BeanItem<>(customBean));
//add validators
customBeanFirstPropertyName.addValidator((Validator) value -> {
if (value == null) throw new Validator.InvalidValueException("nonnull required");
});
customBeanThirdPropertyName.addValidator(
new RegexpValidator(".{3,20}", "length between 3-20 required")
);
/*
or have basic validators on #Entity level with e.g. javax.validation.constraints.Size
example:
#Size(min = 3, max = 20)
#Column(name = "customBeanThirdPropertyName", unique = true)
private String customBeanThirdPropertyName;
*/
}
void commit(Button.ClickEvent event) { //method called by "save" button
try {
binder.commit(); //internally calls valid() method on each field, which could throw exception
CustomBean customBeanAfterValidation = binder.getItemDataSource().getBean(); //custom actions with validated bean from binder
this.close();
} catch (FieldGroup.CommitException e) {
Map<Field<?>, Validator.InvalidValueException> invalidFields = e.getInvalidFields(); //do sth with invalid fields
}
}
}
If you use a FieldGroup instance to bind your fields with the properties, which is the recommended way, you can write:
fieldGroup.isValid();
That checks on all field validations of the fields managed by the field group.
Maintain a flag. Before proceeding, check if the flag is set. In the validation code, set the flag if the validation fails.

populating own error-messages to the grails domain errors

I'd like to know, if (and how) I could append some own error-messages to the domain-object after (or before) a validation.
My intention is, I have to check the uploaded file in a form for some attributes (image size etc.) and if something is wrong, I would like to add an error-message which is displayed in the usual grails ".hasErrors" loop.
(And I think I need to have the possibility to express errors in some cross-domain check failure...)
Thanks in advance,
Susanne.
You can add custom validation errors as described in the errors docs as follows:
class SampleController {
def save() {
def sampleObject = new SampleObject(params)
sampleObject.validate()
if(imageSizeIsTooBig(sampleObject)) {
sampleObject.errors.rejectValue(
'uploadedFile',
'sampleObject.uploadedFile.sizeTooBig'
)
}
private def imageSizeIsTooBig(SampleObject sampleObject) {
// calculation on sampleObject, if size is too big
}
Perhaps, you could even handle your case with a custom validator, so you can call validate() one time and be sure, that all constraints are fulfilled.
Here is a real example with a custom domain error:
def signup(User user) {
try {
//Check for some condition
if (!params.password.equals(params.passwordRepeat)) {
//Reject the value if condition is not fulfilled
user.errors.rejectValue(
'password',
'user.password.notEquals',
'Default message'
)
//Throw an exception to break action and rollback if you are in a service
throw new ValidationException('Default message', user.errors)
}
//Continue with your logic and save if everything is ok
userService.signup(user)
} catch (ValidationException e) {
//Render erros in the view
respond user.errors, view:'/signup'
return
}
}

Grails isDirty() not working with associations

in my app, I'm updating an object Voucher which has 1:1 association to Patient entity. In my controller, I call "voucherInstance.properties = params" to bind the new values. But when I change the Patient in Voucher (not saving it yet), and then I call isDirty('patient'), which IMO should return true in this case, it actually returns false.
Also, the getPersistentValue('patient') returns the changed value, not the original one. Do I undestand these methods correctly?
Thanks,
Lojza
In my controller class:
def update() {
Voucher voucherInstance = voucherService.get(id)
voucherInstance.properties = params // patient is being sent from view by params.patient.id
voucherService.update(voucherInstance)
}
In my VoucherService class:
public Voucher update(Voucher voucher) {
if (voucher.isDirty('patient')) { // returns false
// do something
Patient oldPatient = voucher.getPersistentValue('patient') // returns the updated patient
}
voucher.save(flush: true)
}
The correct use here should be voucherInstance.patient.isDirty. The parameterized version of isDirty is meant for bean fields iirc.
I did some more googling and found one solution, though not a good one: http://stuff4j.blogspot.com/2011/05/i-encountered-few-times-strange.html
def update() {
Voucher voucherInstance = voucherService.get(id)
voucherInstance.patient = null
voucherInstance.properties = params // patient is being sent from view by params.patient.id
voucherService.update(voucherInstance)
}
This seems to work. But I have to explicitly set all associations to null before I can update them.

Save On My Domain Class Object Is Not Working.

I have a User class which has a List field namely pt. This field is not initialized when User register his account. But when user goes this controller action :
def updatePt() {
//performs some action
def user = User.get(springSecurityService.principal.id) //find the user
user.pt = []
//on certain conditions i put values into user.pt like this
user.pt << "E"
//at last I save it
user.save()
}
But using user/show action via scaffolding I found that pt field is not saved on users object. Where I'm making a mistake?
You have to provide a static mapping in the Users domain class so that Grails knows the field must be persisted:
class User {
static hasMany = [pt: String]
}
It's possible because of validation error. Try with
if (!user.save()) {
log.error('User not saved')
user.errors.each {
log.error('User error: $it')
}
}
PS or you can use println instead of log.error

Grails service not saving Domain Object When triggered by Message Queue

I have a grails application that has a service that creates reports. The report is defined as:
class Report {
Date createDate
String reportType
List contents
static constraints = {
}
}
The service generates a report and populates contents as a list that is returned by createCriteria.
My problem is that my service claims to be saving the Report, no errors turn up, logging says that its all there, but when I go to call show from the controller on that report, it says contents is null.
Another relevant bit, my Service is called by an ActiveMQ message queue. The message originating from my report controller.
Controller:
class ReportController {
def scaffold = Report
def show = {
def rep = Report.get(params.id)
log.info("Report is " + (rep? "not null" : "null")) //says report is not null
log.info("Report content is " + (rep.contents? "not null" : "null")) //always says report.contents is null.
redirect(action: rep.reportType, model: [results: rep.contents, resultsTotal: rep.contents.size()])
}
}
My service that creates the report:
class ReportService {
static transactional = false
static expose = ['jms']
static destination = "Report"
void onMessage(msg)
{
this."$msg.reportType"(msg)
}
void totalQuery(msg)
{
def results = Result.createCriteria().list {
//This returns exactly what i need.
}
Report.withTransaction() {
def rep = new Report(createDate: new Date(), reportType: "totalQuery", contents: results)
log.info("Validation results: ${rep.validate()}")
if( !rep.save(flush: true) ) {
rep.errors.each {
log.error(it)
}
}
}
}
Is there something obvious that I'm missing here? My thought is that since all my unit tests work, that the hibernate context is not being passed through the message queue. But that would generate Exceptions wouldn't it? I've been beating my head on this problem for days, so a point in the right direction would be great.
Thanks,
You can't define an arbitrary List like that, so it's getting ignored and treated as transient. You'd get the same behavior if you had a def name field, since in both cases Hibernate doesn't know the data type, so it has no idea how to map it to the database.
If you want to refer to a collection of Results, then you need a hasMany:
class Report {
Date createDate
String reportType
static hasMany = [contents: Result]
}
If you need the ordered list, then also add in a List field with the same name, and instead of creating a Set (the default), it will be a List:
class Report {
Date createDate
String reportType
List contents
static hasMany = [contents: Result]
}
Your unit tests work because you're not accessing a database or using Hibernate. I think it's best to always integration test domain classes so you at least use the in-memory database, and mock the domain classes when testing controllers, services, etc.

Resources