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
Related
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 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
The task is to support usernames being stored in the database as they are originally typed upon creation, and to check for uniqueness using the 'ilike' criteria in place of 'eq'.
In digging around, I saw 2 implementations of UniqueConstraint, one provided by grails-datastore-gorm and one provided by grails-hibernate. I understand that I need to perform this lookup in a block where I reset the FlushMode on the session object, so hibernate does not persist the change to the db before the data is validated. Here is the custom validator I wrote:
#Override
protected void processValidate(final Object target, final Object propertyValue, Errors errors) {
def reject = false
doWithManualSessionIfAppropriate {
def targetId = null
try {
targetId = InvokerHelper.invokeMethod(target, 'ident', null)
} catch (Exception e) {
throw new GrailsRuntimeException('Could not determine id of target')
}
def results = []
results += constraintOwningClass."findAllBy${GrailsNameUtils.getClassName(constraintPropertyName, '')}Ilike"(propertyValue)
reject = results.any {
try {
def existingId = InvokerHelper.invokeMethod(it, 'ident', null)
targetId != existingId
} catch (Exception e) {
// the existing field is not in the db
false
}
}
}
if (reject) {
errors.rejectValue(constraintPropertyName, 'unique')
}
}
This made our integration tests pass, but when I used it in a controller that calls importFrom to validate the username, invokeMethod fails to find an id on the command object. For example:
RealUser.groovy:
class RealUser {
String username, passwordHash
static constraints = {
username iunique: true // where iunique is the name of my registered constraint
}
}
UserController.groovy:
#Validateable
class UserCommandObject {
String username, password, passwordConfirmation
static constraints = {
importFrom RealUser
}
}
When I had unique: true on RealUser, the UserCommandObject validated just fine. When I changed it to my custom validator iunique: true, UserCommandObject complains that it's not a domain class.
I can't possibly see how unique: true works for command objects, since both implementations I see only work on domain objects (and throw a GrailsRuntimeException when called with no domain object).
Any ideas? Am I thinking too much into this?
Approaching from the other point of view, is there any compelling reason to not support storing usernames as-entered, and just calling lower() on the input before validating?
Would findByPropertyNameIlike(val) within a custom validator do the trick?
static constraints = {
propertyName blank: false, validator: { val, obj, errors ->
def results = this.findByPropertyNameIlike(val)
if(results) {
errors.rejectValue('propertyName', 'unique.propertyName')
return false
}
return true
}
}
I am having a problem with transaction in Grails. I want to save a list of object to DB by a checking condition at each object. All these process I want to put to one transaction, it means if the k-th object does not satisfied the checking condition, all previous objects (from the first object to the (k-1)th one) will be rolled back from DB. Here is my example:
static transactional = true
public void saveManyPeople() {
// ...
List<People> peoples = new ArraysList();
for(i = 0, i < n, i++) {
People newPeople = createPeopleFromRawData(); // return a people object in memory
if(<checking-condition>) {
newPeople.save(flush : false)
} else {
throw new MyCustomizedException() // MyCustomizedException has extended from RuntimException
}
}
// ...
}
As you may see, I set transactional variable to true and I've tried to use flush : true and flush : false, but it didn't work as I want. I've read this article Rolling back a transaction in a Grails Service
And the author recommended that the service method should throw a RuntimeException then the process will be rollbacked. But if I want to throw another exception, so what I have to do?
Could you please give me some suggestions on this problem?
Thank you so much!
You can throw any exception that extends from RuntimeException to rollback the transaction. Or you can use Programmatic Transactions, using withTransation, to have more control over the transaction.
Could you verify that saveManyPeople() is within a Service and not a Controller?
The static transactional = true isn't respected in a Controller. I am suspecting that this is the issue.
If you need to have transactional support with the controller, you could always use DomainClass.withTransaction. Reference Documentation
Example:
Account.withTransaction { status ->
def source = Account.get(params.from)
def dest = Account.get(params.to)
def amount = params.amount.toInteger()
if(source.active) {
source.balance -= amount
if(dest.active) {
dest.amount += amount
}
else {
status.setRollbackOnly()
}
}
}