Why are setters in Grails called twice on save? - grails

Look at the following Grails domain class, which modifies a value within a setter, if the object is saved the first time (if it has no id):
class Idtest {
String name
void setName(String name) {
if(!this.id)
this.name = name + "TEST"
else
this.name = name
}
}
If I generate views and controller with generate-all, start the app, and enter "hello" in the generated form, "helloTESTTEST" is saved.
The save function looks like this:
def save = {
def idtestInstance = new Idtest(params)
if (idtestInstance.save(flush: true)) {
flash.message = "${message(code: 'default.created.message', args: [message(code: 'idtest.label', default: 'Idtest'), idtestInstance.id])}"
redirect(action: "show", id: idtestInstance.id)
}
else {
render(view: "create", model: [idtestInstance: idtestInstance])
}
}
Why is the setter called twice?

Instead of doing
if(!this.id){ }
You should use beforeInsert()
GORM Advanced Features

I believe it is called once when you create the object and it is saved
Then it would be called again when you retrieved the object from the database.
So..
On saving to to the database the property is set, lets assume a counter = counter + 1. so now one is saved to the database.
When you retrieve the object from the database, the domain object setter will be called again thereby incrementing the counter again counter++
This is all an assumption since the there is no controller code here for us to see how you are creating and or saving the objects so dont bash me if I am completely off

Related

Preventing a write to the database from Grails controller

I have a small Grails project I'm writing as a learning exercise. It gathers some user input from a form (eg. enter two numbers to add), then it calls a service to process that data (eg. add the two numbers), and finally it shows the results on a another page.
When I turned on SQL logging I noticed that the data the user is entering is being saved to the database before the call to the service method inside the controller.
How do I prevent this? I would like ONE write to the database after the call to the service method is complete and there are no errors.
Save method from controller code:
def save() {
def myInstance = new myDomainClass(params)
myInstance.sessionId = session.id
myService argh = new myService()
// wtf does this do?
if (!myInstance.save(flush: true)) {
render(view: "create", model: [myInstance: myInstance])
return
}
// Execute the api and process the results. what is writing the user input to the database before this call?!?!?!?!
def results1 = argh.executeApi(myInstance)
// if the results are null throw an error
if (results1 == null) {
flash.message = message(code: 'api.null.error')
render(view: "create", model: [apiInstance: apiInstance])
return
} else {
forward(action: "list", id: 2, model: [apiInstanceList: Api.list(params), apiInstanceTotal: Api.count()])
}
}
Pointers or help appreciated.
Calling .save(flush:true) will automatically save the myInstance instance to the database at that point. You will want to move the .save(flush:true) to after the service method, and since you said you wanted to make sure there were no errors, you would want to add it to your conditional:
def save() {
def myInstance = new myDomainClass(params)
myInstance.sessionId = session.id
myService argh = new myService()
// Execute the api and process the results. what is writing the user input to the database before this call?!?!?!?!
def results1 = argh.executeApi(myInstance)
// if the results are null throw an error
if (results1 == null) {
flash.message = message(code: 'api.null.error')
render(view: "create", model: [apiInstance: apiInstance])
return
} else {
// save here when you know there are no errors
if (!myInstance.save(flush: true)) {
render(view: "create", model: [myInstance: myInstance])
return
}
forward(action: "list", id: 2, model: [apiInstanceList: Api.list(params), apiInstanceTotal: Api.count()])
}
}

How to modify the value of the ID column (or columns) in the edit view in Grails 3?

How do you allow the values of the ID columns to be modified at runtime in Grails 3? In my case, I have a table with a composite primary key consisting of three columns. I manually specified them to appear in the edit view and my update() method code looks like this:
#Transactional
def update(AliasFrequencyDict aliasFrequencyDict) {
aliasFrequencyDict = AliasFrequencyDict.get( new AliasFrequencyDict(params) )
if (aliasFrequencyDict == null) {
notFound()
return
}
aliasFrequencyDict.properties = params
try {
aliasFrequencyDict.save(insert: false, flush: true)
} catch (ValidationException e) {
respond aliasFrequencyDict.errors, view:'edit'
return
}
request.withFormat {
form multipartForm {
flash.message = message(code: 'default.updated.message', args: [message(code: 'aliasFrequencyDict.label', default: 'AliasFrequencyDict'), aliasFrequencyDict.getPK()])
redirect(action: 'show', params: params)
}
'*'{ respond aliasFrequencyDict, [status: OK] }
}
}
...and when I go into edit view and edit the property not being part of the key, it saves correctly but as soon as I change one of the other ones, I get a message reading that Grails could not found an AliasFreuqencyDict instance of ID null. I guess the problem is that, once you change one of such columns, the ID changes too and Grails somehow doesn't know how to switch from the primary key that the object used to have to the new ones.
How should I modify the controller to allow for editing the part-of-key columns too, then?
The (most obvious) problem here is the way you're looking up the one you want to update. If you share your domain model for AliasFrequencyDict I can be more specific, but say you have a composite key of First and Last and an instance where [first:"John", last:"Doe"].
Now you update that to set first=Jane and press update... you are creating a new AliasFrequencyDict for "Jane Doe" and using that in the lookup of .get(), of course that's not going to return an instance of AliasFrequencyDict. If you're changing the value of a PK field, you'll need to pass in the old values to look up the object, and then set the new values accordingly.

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 behavior method AddTo*

I'm an instability in my addTo* method which I have a record that constantly have to make the relationship information in one instance . The problem is that when I place multiple inserts in a short time , they cease to be persisted in the database and not after a they return , they are simply discarded time.
My simple class is ' Occurrence ' and ' Monitoring ' , whenever I make a call I have to register it and it occurred on and after 2 consecutive records the 3rd no longer persists in the database and I lose the record .
Class Occurrence implements Serializable{
...
hasMany = [accompaniments: Monitoring]
...
}
Class Monitoring implements Serializable{
...
belongsTo = [occurrence : Occurrence]
...
}
Have the controller looks like this:
def regMonitoring(Long id){
def chamadoInstance = Occurrence.get(id)
if (!chamadoInstance) {
flash.message = message(code: 'default.not.found.message', args: [message(code: 'Occurrence .label', default: 'Occurrence '), id])
return
}
chamadoInstance.status = StatusChamado.findByCodigo("MOV")
if (!chamadoInstance.save(flush: true)) {
render(view: "editMonitoring", model: [chamadoInstance: chamadoInstance])
return
}
def mov = chamadoService.regMonitoring("") //returns an instance of Customer with preset output values ​​.
chamadoInstance.addToAccompaniments (mov)
redirect(action: "showChamado", id: chamadoInstance.id)
}
Add in a save of your domain instance after you add to the collection.
chamadoInstance.addToAccompaniments (mov)
chamadoInstance.save(flush: true)
This should solve the issue.

Grails update() cannot locate record, show() and edit() can

I've got a fairly simple Grails app against a DB2 database. Everything works fine except when I try to update a record. show() and edit() are able to find the record , but the update fails saying that it cannot be located. Here's the edit:
def edit()
{
def flatAdjustmentInstance = FlatAdjustment.get( new FlatAdjustment(compPayeeID: params["compPayeeID"], effectiveQuarterBeginDate: params["effectiveQuarterBeginDate"]) ) //here are your inbound params
if(!flatAdjustmentInstance)
{
flash.message = "MRI Modifier Record not found with ${params}"
redirect(action:"list")
}
else
{
return [ flatAdjustmentInstance: flatAdjustmentInstance]
}
}
Now the update()
def flatAdjustmentInstance = FlatAdjustment.get( new FlatAdjustment(compPayeeID: params["compPayeeID"], effectiveQuarterBeginDate: params["effectiveQuarterBeginDate"]) ) //here are your inbound params
//def flatAdjustmentInstance = new FlatAdjustment(compPayeeID: params["compPayeeID"], effectiveQuarterBeginDate: params["effectiveQuarterBeginDate"])
if (!flatAdjustmentInstance)
{
flash.message = message(code: 'default.not.found.message', args: [message(code: 'flatAdjustment.label', default: 'FlatAdjustment'), params])
redirect(action: "list")
return
}
As I said, all controller methods/closures create the instance of the domain object in the same way and all work correctly except for update(). Any ideas?
.get() retrieves an existing object based on its ID. You cannot pass a new object to that method.
FlatAdjustment flatAdjustmentInstance = FlatAdjustment.findByCompPayeeIDAndEffectiveQuarterBeginDate(params.compPayeeID, params.date("effectiveQuarterBeginDate", "INSERTYOURDATEFORMATHERE"))
I figured this one out, and it was a really bone-headed mistake. The form I had created was passing in the compPayeeID, but since the field is not editable by the user, I changed it from an input to a textField. That meant is was no longer being passed in with the grails params object. I changed it back, set the field to read only, and now all is well.

Resources