grails 2.3.x: Strange behavior with autogenerated controller - grails

A fresh grails 2.3.x project. Im using autogenerated controller and views for a domain class. One (not null) property i want to set directly in controller, so i removed the input field from the _form.gsp.
Domain class:
class Demo {
String demo
String status
static constraints = {
demo nullable: false
status nullable: false
}
In form i only fill the demo field.
Autogenerated controller results in:
...
#Transactional
def save(Demo demoInstance) {
if (demoInstance == null) {
notFound()
return
}
// set the status property to "test"
demoInstance.status = "test"
println "1 STATUS: ${demoInstance.status}"
if (demoInstance.hasErrors()) {
println "2 STATUS: ${demoInstance.status}"
respond demoInstance.errors, view: 'create'
return
}
println "3 STATUS: ${demoInstance.status}"
...
Fill the form and leave the status property blank results in
println 1 -> status = 'test'
println 2 -> status = 'test'
Redirect to create page with a message: "Property [status] of class [Demo] cannot be null"
When i change the controller as in grails 2.2.x:
...
#Transactional
def save() {
def demoInstance = new Demo(params)
if (demoInstance == null) {
notFound()
return
}
// set the status property to "test"
demoInstance.status = "test"
println "1 STATUS: ${demoInstance.status}"
if (demoInstance.hasErrors()) {
println "2 STATUS: ${demoInstance.status}"
respond demoInstance.errors, view: 'create'
return
}
println "3 STATUS: ${demoInstance.status}"
...
This results in the expected output:
println 1 -> status = 'test'
println 3 -> status = 'test'
And the input is stored in Database.
Can anyone explain this behavior? Thanks.

I had the same issue. Do not know the exact reason, but what works is:
bindData(demoInstance, [status: 'test'])
Grails 2.3 has a new DataBinding. you can use the old Spring style if you set
grails.databinding.useSpringBinder = true
in Config.groovy
Using this SpringBinder, this issue does not occur.

I don't know why you're seeing the behavior you're seeing with the 2.2.x code, but the 2.3.x behavior looks correct to me regardless of which data binder you use. Data binding occurs before the controller action is called and since you left a property unset, the instance has a validation error. You then set a valid value but don't call validate() or save(), so there's no reason to expect that hasErrors() would change from false to true.
If you add
demoInstance.validate()
after
demoInstance.status = "test"
then I would expect to see the output you're seeing in your 2.2.x version (although again, without a validate or save call I can't imagine why it would work in 2.2 either).

Related

Grails rejectValue - multiple checks causing ob.errors null

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)
}

grails 2.4.4 issue inverting filters schema

In a new project based on grails 2.4.4 I am using filter schema with invert option. Few controllers and some actions from another controllers are excluded in filters by inverting the rule.
Filter will not be applied to Login, ForgotPassword ans ServerError Controllers and saveUser, verifyRegistration actions from different user controller. This filter schema doesn't work as expected.
When I am calling login api inside login controller, filter is getting executed and throws exception.
package com.project.filters
import grails.converters.JSON
class MyProjectAuthorizationFilters {
def userService
def grailsApplication
def filters = {
checkXAuthToken(controller:'login|forgotPassword|serverError', action:'saveUser|verifyRegistration', invert: true) {
before = {
try{
String tokenValue = request.getHeader('X-Auth-Token')
if(tokenValue == null && tokenValue == ""){
throw new MyCustomException(401, "Please provide X-Auth-Token in Header")
}
userService.getUserByAuthToken(tokenValue)
}catch (MyCustomException error) {
error.stackTrace = ""
response.setStatus(error.status)
render error as JSON
return false
}
}
}
}
}
I know we can also use controllerExclude, actionExclude, but did not know why this is breaking?
EDIT
I even tried using controllerExclude and actionExclude but it doesn't work as expected. Is this a weired behaviour or I am doing something wrong. Posting whole filter class code.
Thanks.

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: NullPointerException while save()

I am new to Grails, I tried to save domain object by calling save() in my *service.groovy as shown below
render " ${user.username}"
render " ${user.email}"
render " ${user.password}"
def savedUser = user.save(flush: true)
if(savedUser!=null) {
return savedUser
} else {
return user
}
The render shows all elements have the values which have been passed from controller.
but an NullPointerexception is thrown in save().
the actual error got is
ERROR errors.GrailsExceptionResolver - NullPointerException occurred when processing request: [POST]
Validation error might have occurred, but I checked all validation error in controller by using command class.
How can avoid the exception here?
That's pretty nonstandard code. Rather than checking for a null return value, it's more common (and sensible/useful) to check if there were validation errors. E.g.
user.save(flush: true)
if (user.hasErrors()) {
// do something with the invalid "user" instance
}
else {
// do something with the valid "user" instance
}
For your scenario, you can ignore the return value and just work with the original instance:
user.save(flush: true)
return user
If you're working with the standard templates (or something similar) then this should work fine since there's logic there to check if there are attached errors and display them.

How to make my audit trail plugin work

I am in an need to use audit trail in my grails application i have tried all methods but audit log is empty is there any way to rectify it.I need to actually record operations such as insert,delete and update.
Below is what I followed:-
package audit
class Person {
static auditable = true
String firstName
static constraints = {
firstName(nullable:true,size:0..60)
}
def onSave = {
println "new person inserted"
}
def onUpdate = {
println "person was updated"
}
def onDelete = {
println "person was deleted"
}
def onChange = { oldMap,newMap ->
println "Person was changed ..."
oldMap.each{ key, oldVal ->
if(oldVal != newMap[key]) {
println " * $key changed from $oldVal to " + newMap[key]
}
}
}
}
Other listservs that I check have suggested that the current audit-logging plugin is buggy, so you may just be experiencing a bug in the plugin. Also, I believe it has been forked and is actively being rewritten (http://jira.grails.org/browse/GPAUDITLOGGING), so you may not want to spend too much time with it right now.
With that said, I scaffolded a simple application with the domain you provided and the plugin did write out the println statements, but it only recorded the updates correctly in the database to the AUDIT_LOG table. The 2 inserts I attempted recorded null for both the NEW_VALUE and PROPERTY_NAME.

Resources