Grails isDirty() not working with associations - grails

in my app, I'm updating an object Voucher which has 1:1 association to Patient entity. In my controller, I call "voucherInstance.properties = params" to bind the new values. But when I change the Patient in Voucher (not saving it yet), and then I call isDirty('patient'), which IMO should return true in this case, it actually returns false.
Also, the getPersistentValue('patient') returns the changed value, not the original one. Do I undestand these methods correctly?
Thanks,
Lojza
In my controller class:
def update() {
Voucher voucherInstance = voucherService.get(id)
voucherInstance.properties = params // patient is being sent from view by params.patient.id
voucherService.update(voucherInstance)
}
In my VoucherService class:
public Voucher update(Voucher voucher) {
if (voucher.isDirty('patient')) { // returns false
// do something
Patient oldPatient = voucher.getPersistentValue('patient') // returns the updated patient
}
voucher.save(flush: true)
}

The correct use here should be voucherInstance.patient.isDirty. The parameterized version of isDirty is meant for bean fields iirc.

I did some more googling and found one solution, though not a good one: http://stuff4j.blogspot.com/2011/05/i-encountered-few-times-strange.html
def update() {
Voucher voucherInstance = voucherService.get(id)
voucherInstance.patient = null
voucherInstance.properties = params // patient is being sent from view by params.patient.id
voucherService.update(voucherInstance)
}
This seems to work. But I have to explicitly set all associations to null before I can update them.

Related

Grails: Does the addTo() method not work on newly created objects?

I'm using Grails 2.5.5, and I have a simple scenario here: A has many Bs.
And so I have a transactional service method which basically does the following:.
A a
if (isNew) {
a = new A()
} else {
a = A.findById(someId)
}
List<B> bList = getBsFromSomeOtherMethod()
bList.each { a.addToBs(it) }
a.save(failOnError: true)
The interesting thing is that if I create a new object (as in, isNew is true in the above logic), then I get the following exception when save() is called: reassociated object has dirty collection reference (or an array).
However, if I get an object which already exists in the DB, then everything works perfectly.
The workaround I found is that if I save the new object before adding Bs to A, then things work. But I would rather not have to call save() twice, and the code is just a lot cleaner if the call was just at the end.
I've googled the exception but nothing seems to explain what's going on here.
Can somebody help out with this?
Like others have said in the comments, you need to save the object so it exists in the database, and has an id. When you have a A has many Bs relationship, a new table in the database (something like a_b) is created to map A.id to B.id, which is why you can't add Bs to A without saving first.
A a
if (isNew) {
a = new A()
a.save()
} else {
a = A.findById(someId)
}
List<B> bList = getBsFromSomeOtherMethod()
bList.each { a.addToBs(it) }
a.save(failOnError: true)
Use findOrSaveBy for such an operations. You will get the proper object from db or persist new one:
def a = A.findOrSaveByField(field)
List<B> bList = getBsFromSomeOtherMethod()
bList.each { a.addToBs(it) }
a.save(failOnError: true)

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

Exclude property from updating when SaveChanges() is called

There appears to be two ways to update a disconnected Entity Framework entity using the "attach" method.
Method One is to simply set the disconnected entity's state as modified:
myDbContext.Dogs.Attach(dog);
myDbContext.Entry(dog).State = EntityState.Modified;
myDbContext.SaveChanges();
This will save all fields on the "dog" object. But say you are doing this from an mvc web page where you only allow editing of Dog.Name, and the only Dog property contained on the page is Name. Then one could do Method Two:
myDbContext.Dogs.Attach(dog);
myDbContext.Entry(dog).Property(o => o.Name).CurrentValue = dog.Name;
myDbContext.Entry(dog).Property(o => o.Name).IsModified = true;
myDbContext.SaveChanges();
Method Two could get quite verbose when there are a lot of properties to update. This prompted me to attempt Method Three, setting IsModified = false on the properties I don't want to change. This does not work, throwing the runtime error "Setting IsModified to false for a modified property is not supported":
myDbContext.Dogs.Attach(dog);
myDbContext.Entry(dog).State = EntityState.Modified;
myDbContext.Entry(dog).Property(o => o.Owner).IsModified = false;
myDbContext.SaveChanges();
I'd much prefer to use Method One everywhere, but there are many instances where my asp.net mvc view does not contain every scalar property of the Dog class.
My questions are:
Are there any attributes I could use on the POCO class that would tell Entity Framework that I never want the property to up updated? Eg, [NeverUpdate]. I am aware of the [NotMapped] attribute, but that is not what I need.
Failing that, is there any way I can use Method One above (myDbContext.Entry(dog).State = EntityState.Modified;
) and exclude fields that I don't want updated?
P.S. I am aware of another way, to not use "attach" and simply fetch a fresh object from the database, update the desired properties, and save. That is what I am doing, but I'm curious if there is a way to use "attach," thus avoiding that extra trip to the database, but do it in a way that is not so verbose as Method Two above. By "fetch a fresh object" I mean:
Dog dbDog = myDbContext.Dogs.FirstOrDefault(d => d.ID = dog.ID);
dbDog.Name = dog.Name;
myDbContext.SaveChanges();
The following may work works.
myDbContext.Dogs.Attach(dog);
myDbContext.Entry(dog).State = EntityState.Modified;
var objectContext = ((IObjectContextAdapter) myDbContext).ObjectContext;
foreach (var entry in objectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Modified).Where(entity => entity.Entity.GetType() == typeof(Dogs)))
{
// You need to give Foreign Key Property name
// instead of Navigation Property name
entry.RejectPropertyChanges("OwnerID");
}
myDbContext.SaveChanges();
If you want to do it in a single line, use the following extension method:
public static void DontUpdateProperty<TEntity>(this DbContext context, string propertyName)
{
var objectContext = ((IObjectContextAdapter) context).ObjectContext;
foreach (var entry in objectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Modified).Where(entity => entity.Entity.GetType() == typeof(TEntity)))
{
entry.RejectPropertyChanges(propertyName);
}
}
And use it like this
// After you modify some POCOs
myDbContext.DontUpdateProperty<Dogs>("OwnerID");
myDbContext.SaveChanges();
As you can see, you can modify this solution to fit your needs, e.g. use string[] properties instead of string propertyName as the argument.
Suggested Approach
A better solution would be to use an Attribute as you suggested ([NeverUpdate]). To make it work, you need to use SavingChanges event (check my blog):
void ObjectContext_SavingChanges(object sender, System.Data.Objects.SavingChangesEventArgs e)
{
ObjectContext context = sender as ObjectContext;
if(context != null)
{
foreach(ObjectStateEntry entry in context.ObjectStateManager.GetObjectStateEntries(EntityState.Modified))
{
var type = typeof(entry.Entity);
var properties = type.GetProperties();
foreach( var property in properties )
{
var attributes = property.GetCustomAttributes(typeof(NeverUpdateAttribute), false);
if(attributes.Length > 0)
entry.RejectPropertyChanges(property.Name);
}
}
}
}
// Check Microsoft documentation on how to create custom attributes:
// http://msdn.microsoft.com/en-us/library/sw480ze8(v=vs.80).aspx
public class NeverUpdateAttribute: SystemAttribute
{
}
//In your POCO
public class Dogs
{
[NeverUpdate]
public int OwnerID { get; set; }
}
Warning: I did not compile this code. I'm not at home :/
Warning 2: I have just read the MSDN documentation and it says:
ObjectStateEntry.RejectPropertyChanges Method
Rejects any changes made to the property with the given name since the
property was last loaded, attached, saved, or changes were accepted.
The orginal value of the property is stored and the property will no
longer be marked as modified.
I am not sure what its behavior would be in the case of attaching a modified entity. I will try this tomorrow.
Warning 3: I have tried it now. This solution works. Property that is rejected with RejectPropertyChanges() method are not updated in the persistence unit (database).
HOWEVER, if the entity that is updated is attached by calling Attach(), the current context remains dirty after SaveChanges(). Assume that the following row exists in the database:
Dogs
ID: 1
Name: Max
OwnerID: 1
Consider the following code:
var myDog = new Dogs();
myDog.ID = 1;
myDog.Name = Achilles;
myDog.OwnerID = 2;
myDbContext.Dogs.Attach(myDog);
myDbContext.Entry(myDog).State = EntityState.Modified;
myDbContext.SaveChanges();
The current state of database after SaveChanges():
Dogs:
ID: 1
Name: Achilles
OwnerID: 1
The current state of myDbContext after SaveChanges():
var ownerId = myDog.OwnerID; // it is 2
var status = myDbContext.Entry(myDog).State; // it is Unchanged
So what you should do? Detach it after SaveChanges():
Dogs myDog = new Dogs();
//Set properties
...
myDbContext.Dogs.Attach(myDog);
myDbContext.Entry(myDog).State = EntityState.Modified;
myDbContext.SaveChanges();
myDbContext.Entry(myDog).State = EntityState.Detached;

knockout js .net load page with and without model

I am trying to reuse the same form for adding and editing employee information. I am using knockout js and on my view I make the knockout model:
var koModel = new EmployeeModel(div);
and if I want to populate the fields from the server I want to do something like this:
var koModel = new EmployeeModel(unserializedModelFromController, div);
I was wondering what is the best way to distinguish if the request is for a new employee or if it is to edit an existing employee.
If you turn your parameters around you can write a single constructor function.
var EmployeeModel = function(div, model) {
if (model) {
// Existing model has been passed, it's an edit request
} else {
// No model has been passed, it's a new request
}
}
This can be called like:
new EmployeeModel(div);
or
new EmployeeModel(div, model);
You can send a parameter with a default value to the view.
If you are editing an employee, you can send the value of id, you're creating not send.
The function that receives a request to store or edit could have a default value.
public void SaveOrEditEmployee(int id=0, ...) //id=0 is a default value
{
if(id==0)
{
//SaveEmployee
}else
{
//EditEmployee
Employee empl = (x => employee.id == id);
...
}
}
Or you can do likewise, receive full model and assess whether the property 'id' already exists in your database

Do I need a double save() after a domain modification using afterInsert()?

I have a domain class that modifies one of its properties in the afterInsert event.
A small example:
class Transaction {
Long transactionId
static constraints = {
transactionId nullable: true
}
def afterInsert() {
// copy the record id to transactionId;
transactionId = id
}
}
Whenever I save the domain object (transaction.save(flush: true)) in
my unit tests, all is well, and the transactionId is updated. But when I try to find the saved record using Transaction.findByTransactionId(), I get no results:
// do something
transaction.save(flush: true)
Transaction transaction = Transaction.findByTransactionId(1)
// !! no results; transaction == null
And I have to do a double save() before I can find the record using findByTransactionId():
// do something
transaction.save(flush: true)
transaction.save(flush: true)
Transaction transaction = Transaction.findByTransactionId(1)
// !! it works....
The double save() seems awkward. Any suggestions on how to eliminate the need for it?
The call to save() will return the persisted entity if validation passes, so there isn’t any reason to look it up separately afterwards. I think that your problem is that you’re re-instantiating the transaction variable (using that same name). If you must look it up (I don’t suggest doing so), call it something else. Also, the 1 id that you’re looking up may not exist if the column is an AUTO-INCREMENT.
def a = a.save(flush: true)
a?.refresh() // for afterInsert()
Transaction b = (a == null) ? null : Transaction.findByTransactionId(a.id)
// (Why look it up? You already have it.)
Update:
Because you’re using afterInsert(), Hibernate may not realize that it needs to refresh the object. Try using the refresh() method after you call save().
This small piece of code makes it obviously work:
def afterInsert() {
transactionId = id
save() // we need to call save to persist the changens made to the object
}
So calling save in the afterInsert is needed to persist the changes made in afterInsert!

Resources