I have a custom validation in controller because I need to validate with database.
def update(Object instance) {
if (instance == null) {
notFound()
return
}
if (instance.hasErrors()) {
//redirect code here
return
}
def obj = objService.someMethod()
//some validation code here
if(someCheck){
// if i discard it wont work
instance.discard()
flash.error = message(code: 'message.code')
//render code here
return
}
In the above code even the instance.discard() does not work after the database access is performed. The changed data is automatically saved even there is no save method call. The same instance.discard() will work if there is no database access is performed. How do I discard the changed value to be persisted to the database when there is a validation failure.
Add the annotation #Transactional(readonly = true) above your update action. That should do the trick.
This is the in built feature of Grails that automatically persist the instance to the database.
Please read my answer here for more detail
https://stackoverflow.com/a/32659803/2405040
Related
I am using grails-2.1.1. When I load the edit page, I am assigning some value in the edit action in controller. But it is updating my table! although I am not saving. How can I stop it?
Here is my code below. My edit action in controller:
def edit() {
def accTxnMstInstance = AccTxnMst.get(params.id)
if (!accTxnMstInstance) {
flash.message = message(code: 'default.not.found.message', args: [message(code: 'accTxnMst.label', default: 'AccTxnMst'), params.id])
redirect(action: "list")
return
}
accTxnMstInstance?.accTxnDtls?.each {
if (it?.debitCoa != null && it?.debitCoa != "") {
String debitCoaVal = ""
List<String> items = Arrays.asList(it?.debitCoa?.split("\\s*~\\s*"))
items.each {
List itemList = new ArrayList()
List<String> subItems = Arrays.asList(it.split("\\^"))
subItems.each {
itemList.add(it)
}
itemList.add("false")
itemList.add("0")
itemList.each {
debitCoaVal += it.toString() + "^"
}
debitCoaVal += "~"
}
it?.debitCoa = debitCoaVal
debitCoaVal = ""
}
if (it?.creditCoa != null && it?.creditCoa != "") {
String creditCoaVal = ""
List<String> items = Arrays.asList(it?.creditCoa?.split("\\s*~\\s*"))
items.each {
List itemList = new ArrayList()
List<String> subItems = Arrays.asList(it.split("\\^"))
subItems.each {
itemList.add(it)
}
itemList.add("false")
itemList.add("0")
itemList.each {
creditCoaVal += it.toString() + "^"
}
creditCoaVal += "~"
}
it?.creditCoa = creditCoaVal
creditCoaVal = ""
}
}
[accTxnMstInstance: accTxnMstInstance]
}
You can see that I am not saving after assigning the value just passing to view.
Grails uses the Open Session In View (OSIV) pattern, where at the beginning of the web request a Hibernate session is opened (and stored in a thread-local to make it easily accessible) and at the end of the request as long as there wasn't an exception, the Hibernate session is flushed and closed. During any flush, Hibernate looks at all "active" object instances and loops through each persistent property to see if it is "dirty". If so, even though you didn't explicitly call save(), your changes will be pushed to the database for you. This is possible because when Hibernate creates an instance from a database row it caches the original data to compare later to the potentially-changed instance properties.
A lot of the time this is helpful behavior, but in cases like this it gets in the way. There are lots of fixes though. One drastic one is to disable OSIV, but this is generally a bad idea unless you know what you're doing. In this case there are two things you can try that should work.
One is to change AccTxnMst.get(params.id) to AccTxnMst.read(params.id). This will not cause the instance to be strictly "read-only" because you can still explicitly call save() and if something was modified, all of the instance changes will be persisted. But the caching of the original data isn't done for instances retrieved using read(), and there's no dirty checking during flush for these instances (which isn't possible anyway since there's no cached data to compare with).
Using read() is a good idea in general when retrieving instances that are not going to be updated (whether you make property changes or not), and makes the code more self-documenting.
Another option is to call discard() on the instance before the controller action finishes. This "detaches" the instance from the Hibernate session, so when the OSIV filter runs at the end of the request and flushes the Hibernate session, your instance won't be considered dirty since Hibernate won't have access to it.
read() only makes sense for individual instances retrieved by id, whereas discard() is useful for any instance, e.g. if they're in a mapped collection or were retrieved by a non-id query (e.g. a dynamic finder, criteria query, etc.)
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)
}
I'm using grails 2.4.2 and have in my controller-update method the following code:
#Transactional
def update(ErtIncommingInvoice ertIncommingInvoiceInstance) {
if (ertIncommingInvoiceInstance == null) {
notFound()
return
}
// Concurrent-Update Test
if (ertIncommingInvoiceInstance.version != params.version as int) {
flash.warning = "Another user changed the record! (Concurrent Update Error)"
ertIncommingInvoiceInstance.errors.rejectValue("ertInfo", "concurrent.update.error")
respond ertIncommingInvoiceInstance.errors, view:'edit'
return
}
even in the case, the error is detected and the errors-object is set and the method-flow does not execute the
ertIncommingInvoiceInstance.save flush:true, failOnError: true
the data is already changed in the database.
The edit-view is shown, but doesn't display the error, only the flash-message.
Where's my error in reasoning?
Grails will call validate before any save and overwrite whatever you set in the errors object. Additionally, Grails will call save automatically on your objects after your method finishes. You should either call discard() on any objects you've changed but don't wish to persist or create a transaction using a withTransaction block and manually roll it back.
As #Gregor Petrin answered, I use now the following code, to check for a concurrent-update and redisplay the changed data from the other user...:
#Transactional
def update(ErtIncommingInvoice ertIncommingInvoiceInstance) {
if (ertIncommingInvoiceInstance == null) {
notFound()
return
}
// Concurrent-Update Test
if (ertIncommingInvoiceInstance.version != params.version as int) {
ertIncommingInvoiceInstance.discard()
ertIncommingInvoiceInstance = ErtIncommingInvoice.get(params.id)
ertIncommingInvoiceInstance.errors.reject("concurrent.update.error")
respond ertIncommingInvoiceInstance.errors, view:'edit'
return
}
I have a domain class like this:
class Domain {
String a
int b
String c
...
def afterInsert(){
def anotherDomain = new AnotherDomain()
anotherDomain.x=1
anotherDomain.y=2
if(anotherDomain.save()){
println("OK")
}else{
println("ERROR")
}
}
}
It prints "OK", I can even print the anotherDomain Object, and everything seems ok, no errors, nothing, but the anotherDomain Object doesn't persist in the database
You cannot persist the domain to database unless you try to save withNewSession.
def beforeInsert(){
def anotherDomain = new AnotherDomain()
anotherDomain.x=1
anotherDomain.y=2
AnotherDomain.withNewSession{
if(anotherDomain.save()){
println("OK")
}else{
println("ERROR")
}
}
}
}
All events are fired when domain object is flushed to database. Existing session is used for the flush. The same session cannot be used to handle save() on another domain. A new session has to used to handle the persistence of AnotherDomain.
UPDATE
Using beforeInsert event makes more sense than afterInsert. If x and y are dependent of any persisted value property of Domain they can very well be fetched from hibernate cache instead of going to db.
Had same problem here and just .withNewSession wasn't enough. I've putted .save(flush: true) and everything is working fine.
def afterInsert() {
AnotherDomain.withNewSession {
new AnotherDomain(attribute1: value1, attribute2: value 2).save(flush: true)
}
}
I have a custom validator like -
validator: { userEmail, userAccount ->
if (userAccount.authenticationChannel == "ABC") {
boolean valid = true;
UserAccount.withNewSession {
if (UserAccount.findByEmail(userEmail)){
valid = false;
}
else if (UserAccount.findByName(userEmail)) {
valid = false;
}
...
So basically, I need some validation based on some condition and in my validation I need to execute a query.
But, now if I do -
def admin = new UserAccount(firstname:'Admin',email:'admin#example.com')
admin.save(flush:true)
admin.addToAuthorities("ADMIN").save(flush:true)
It fails.
Grails is running the validation, even on update and since email exists validation fails. How is this different if I do
email {unique:true}
Is Grails saying that I cannot write a custom validator which checks uniqueness.
Not sure if this is your issue or not, but when I tried to create a validation like this (ie. one that does queries against the database), I would get a StackOverflowError. The reason is that, when you run a query (like findByEmail), Hibernate will try to flush the session, which will cause it to validate all transient objects, which in turn calls your custom validator again, resulting in infinite recursion.
The trick to prevent this is to set the flush mode of the session to "manual" for a short time while running the queries. This prevents Hibernate from trying to flush the session before running the queries. The side-effect is that your query won't return entities you created in the current session but haven't been persisted (flushed) back to the database yet.
UserAccount.withNewSession { session ->
session.flushMode = FlushMode.MANUAL
try {
if (UserAccount.findByEmail(userEmail)){
valid = false;
}
else if (UserAccount.findByName(userEmail)) {
valid = false;
}
}
finally {
session.setFlushMode(FlushMode.AUTO);
}
}
See UniqueConstraint for an example of how this is done.
An alternative might be to do the checks in the save method.
def save = {
..
if (some_checks_succeed(userEmail, userAccount)) {
admin.save(flush: true)
}
..
}
def some_checks_succeed = { String userEmail, String userAccount ->
boolean valid = true;
if (userAccount.authenticationChannel == "ABC") {
UserAccount.withNewSession {
if (UserAccount.findByEmail(userEmail)) {
valid = false;
} else if (UserAccount.findByName(userEmail)) {
valid = false;
}
..
}
return valid
}
Some modifications might be necessary, but the above code gives you an example
Thanks. I could get this to work.The admin.save() calls validation both on insert and update. I handled both the cases (insert and update) and was able to get this working.
Thanks