grails: data saved even if I set an error - grails

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
}

Related

GORM instance auto persist to database on update issue

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

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

How to intercept delete and advise user it is not possible?

I am starting to develop with Breeze.js and ASP MVC+WebApi Controllers. I am concerned about securities, as we should all be concerned about the possibility of a hacker coming into play. Now I did find the BeforeSaveEntity intercept and it does seem to be exactly what I want to use on the server side. I managed to get the security I want on the server side, but how do I deal with it on the client side, in my case with AngularJS, what and how should I catch the output and deal with it? Let me show you some code sample I use on the server side:
public class ConferenceContextProvider : EFContextProvider<ConferenceContext>
{
public ConferenceContextProvider() : base() { }
// Creating the BeforeSaveEntity for Security purposes, see more details at http://www.breezejs.com/documentation/efcontextprovider#SaveInterception
protected override bool BeforeSaveEntity(EntityInfo entityInfo)
{
// return false if we don’t want the entity saved.if (entityInfo.Entity.GetType() == typeof(Role)
&& entityInfo.EntityState == EntityState.Deleted)
{
return false;
}
else
{
return true;
}
}
protected override Dictionary<Type, List<EntityInfo>> BeforeSaveEntities(Dictionary<Type, List<EntityInfo>> saveMap)
{
// return a map of those entities we want saved.
return saveMap;
}
}
and then on client side with AngularJS
// AngularJS DataService
function saveChanges() {
if (manager.hasChanges()) {
var promise =
manager.saveChanges()
.catch(queryFailed)
.finally(function (data) {
toastr.success('Save to DB Succeeded');
});
} else {
toastr.warning("Nothing to save");
};
}
How do I catch the result and deal with it? With Firebug I can see that the POST returns a JSON object with Entities array being filled (if user has access) or that same array being empty (if user has access denied). But if multiple changes happen, then the array might be filled with a portion of it applied. So what is the best approach on the client side with an access denied? Can someone give me a proper sample code on how to deal with acces denied? Thanks for your help
Overriding the BeforeSaveEntity method will mean that on the server once the payload it received your server will call the BeforeSaveEntity method once for each entity before the entity is saved. As the docs show if you return false it will simply not save the entity. Take note of the following line -
If the method returns false then the entity will be excluded from the
save. If the method throws an exception, the entire save is aborted
and the exception is returned to the client.
If you throw an HTTP error I think it should propagate properly you should be able to catch that error client side and display it. This is assuming if a payload contains an entity to delete you want to cancel the whole save.
Example -
protected override bool BeforeSaveEntity(EntityInfo entityInfo)
{
// throw http exception if there is an entity flagged for deletion
if (entityInfo.Entity.GetType() == typeof(Role)&& entityInfo.EntityState == EntityState.Deleted)
{
var response = new HttpResponseMessage(HttpStatusCode.BadRequest){ Content = new StringContent("Cannot delete an entity") };
throw new HttpResponseException(response);
}
else
{
return true;
}
}
And on your client-side query where you have a queryFailed method (pseudo code, examine the error that is thrown to construct this properly) -
function queryFailed (error) {
alert('Query failed - ' + error.message);
}
If you want to save all the other entities but this one and then return custom errors in the response you can do that as well but that will take additional customization and that would probably be a much more detailed answer

Grails-null id in com.easytha.Student entry (don't flush the Session after an exception occurs)

I have already seen several threads for this issue and none could rescue
I have the following in my DomainClass
def afterInsert() {
elasticSearchService.index(this)
}
Where elasticsaerch is a service and I have added it to the static transient list. It seems that after calling the index method successfully it throws this exception
Message: null id in com.easytha.Student entry (don't flush the Session after an exception occurs)
This is the code of index method
def index(object) {
try {
if(object==null) {
throw new NullPointerException()
}
if(!(object instanceof Collection || object instanceof Object[])) {
IndexResponse response = client.prepareIndex(grailsApplication.config.esIndexName, object.getClass().getName(),object.id.toString())
.setSource((object as JSON).toString() )
.execute().actionGet()
println "object indexed successfully"
}else if(object instanceof Collection || object instanceof Object[]) {
for (var in object) {
index(var)
}
}
}catch(e) {
e.printStackTrace();
}
}
"object indexed successfully" is printed in the console.
The bootstrap.groovy has the following
Student student4 = new Student(firstName:'Sapan',lastName:'Parikh',email:'sapan.parikh#eclinicalworks.com',password:'test123')
student4.save(failOnError : true)
UPDATE
I tried Student.withNewSession { elasticSearchService.index(this) } which worked.
It's stabbing at things but maybe shift the save to happening within a transaction:
Student.withTransaction {
student4.save()
}
I've seen this pop up unexpectedly when doing things outside of services (that should, imo, be in services).
Summing up some subsequent discussion:
The student model was saved throughout the application, so it wasn't suitable to shift all the saves to services or wrap in transaction blocks. The OP notes that moving the original reindexing code into a new session fixed it everywhere.
def afterInsert() {
elasticSearchService.index(this)
}

Grails Custom Validation - Query inside validation check - What happens while updating?

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

Resources