Weird afterInsert / afterUpdate loop in Grails - grails

I have a Note domain class, and when a new note is saved I need to create for it a NoteEvent, recording for posterity that the note has been created. Note has a collection of NoteEvents, and each NoteEvent keeps track of which Note it belongs to.
The Note class:
class Note {
String noteText
Date dateCreated
static hasMany = [events : NoteEvent]
}
The NoteEvent class:
class NoteEvent {
Date dateCreated
String type
static belongsTo = [note : Note]
}
To handle the saving of new NoteEvents when a note was created, I was using afterInsert, because I’m saving note instances all over the place (it would be repetitive and time-consuming to have specific event-creating code after each saving of a new note), and beforeInsert obviously is not dealing with a persisted instance of Note yet — there will be nothing for the NoteEvent to have as its note.
So now my Note class is:
class Note {
String noteText
Date dateCreated
static hasMany = [events : NoteEvent]
def afterInsert = {
def event = new NoteEvent(type: "CREATED")
addToEvents(event)
save()
}
}
But I also need to create a NoteEvent when one of these notes is updated, and this is where confusion and dismay and a significant lack of coffee come in. To attach a new “updated” NoteEvent to a note when it was updated, I brilliantly decided to use afterUpdate, again so as to avoid having the event creation code sprinkled all over the app whenever I needed to update a Note instance.
So now, for Note, I have:
class Note {
String noteText
Date dateCreated
static hasMany = [events : NoteEvent]
def afterInsert = {
def event = new NoteEvent(type: "CREATED")
addToEvents(event)
save()
}
def afterUpdate = {
def event = new NoteEvent(type: "UPDATED")
addToEvents(event)
save()
}
}
To add a new event to a note’s collection, I’m using the dynamic addTo() methods, which then require a save() of the instance. But in the case of an “after” event, this is a second call to save(). Thus when I first save a new instance and the afterInsert is called, the just-saved instance is immediately saved again, which causes the afterUpdate event to be fired, and now I have two note events: the “created” one from when I just saved the note, and an “updated” one from when the “created” one caused the note to be saved again.
It’s not clear to me how using “before” events instead could help in this situation. How else can I do this?

You can actually use beforeInsert and beforeUpdate methods. This is because the addTo* method does not require Note to be a persisted instance.
The NoteEvent will save when the Note saves because the NoteEvent is added before the Note is saved in the beforeUpdate method. Check out the addTo* docs for a longer explanation.
I was able to get both of the following Note classes to work how I believe you want them to. I did run into one issue where when updating Note two NoteEvent objects would be added. I was able to fix this by making sure that the update method of the controller was using noteInstance.save() instead of noteInstance.save(flush:true).
class Note {
String noteText
Date dateCreated
static hasMany = [events : NoteEvent]
def beforeInsert = {
def event = new NoteEvent(type: "CREATED")
addToEvents(event)
}
def beforeUpdate = {
def event = new NoteEvent(type: "UPDATED")
addToEvents(event)
}
}
If you want a more condensed version the addTo* method knows what type of object is being added you can just use the Map constructor of NoteEvent
class Note {
String noteText
Date dateCreated
static hasMany = [events : NoteEvent]
def beforeInsert = {
addToEvents(type: "CREATED")
}
def beforeUpdate = {
addToEvents(type: "UPDATED")
}
}

There's probably a way to do this, possible using beforeInsert and beforeUpdate since those wouldn't require saving the Note instance. The typical way to do secondary updates/inserts like this is to use withNewSession but in this case I'm not sure that it makes sense because that's more for creating an independent object, and you'd need to re-load the Note in the new session. Not that bad, but not performant.
One way to do this would be to remove the collection and save NoteEvent instances directly:
class Note {
String noteText
Date dateCreated
Set<NoteEvent> getEvents() {
NoteEvent.findAllByNote(this)
}
def afterInsert() {
new NoteEvent(type: "CREATED", note: this).save()
}
def afterUpdate() {
new NoteEvent(type: "UPDATED", note: this).save()
}
}
class NoteEvent {
Date dateCreated
String type
Note note
}
You lose cascading, so you'd want to delete a Note instance in a transactional service method so you can delete its associated NoteEvents. But that's really the solution to the whole problem. Just delete the afterInsert and afterUpdate callbacks and do all the work (creates, updates, and deletes) in transactional service methods. Whenever you do multiple database updates you should do them transactionally so they all succeed or all fail. This also meets your anti-clutter requirement since all of the work is encapsulated in the service.

"because I’m saving note instances all over the place"
Could I ask where you are saving them? I would avoid saving your domain instances in your controllers. If you are saving them all over the place it might be worth looking at your overall design.
Personally, I would favour creating some sort of NoteService where I would centralise CRUD operations if possible. An example service would be:
class NoteService
{
Note create (String noteText)
{
Note note = new Note(noteText: noteText)
.addToEvents(new NoteEvent(type: NoteEvent.CREATED))
.save()
}
Note update (int id, String noteText)
{
Note note = Note.findById(id)
note.setNoteText(noteText)
note.addToEvents(new NoteEvent(type: NoteEvent.UPDATED))
.save()
}
....
}
The reason I prefer my above approach is that it scales better if you find yourself then wanting to do more in response to these events and avoids duplication of code.
Another approach could be to do the logging in filters. However, this might be tricky/messy if you are saving your note instances in many places.
Otherwise I'd look into using the beforeInsert/beforeUpdate functions as mentioned above.

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 'false' unique error

I have the following domain classes (shortened version)
class TalkingThread {
static hasMany = [comments:Comment]
Set comments = []
Long uniqueHash
}
and
class Comment {
static belongsTo = [talkingThread:TalkingThread]
static hasOne = [author:CommentAuthor]
Long uniqueHash
static constraints = {
uniqueHash(unique:true)
}
}
and
class CommentAuthor {
static hasMany = [comments:Comment]
Long hash
String name
String webpage
}
the following methods
public TalkingThread removeAllComments(TalkingThread thread){
def commentsBuf = []
commentsBuf += thread.comments
commentsBuf.each{
it.author.removeFromComments(it)
thread.removeFromComments(it)
it.delete()
}
if(!thread.save()){
thread.errors.allErrors.each{
println it
}
throw new RuntimeException("removeAllComments")
}
return post
}
public addComments(TalkingThread thread, def commentDetails){
commentDetails.each{
def comment = contructComment(it,thread)
if(!comment.save()){
comment.errors.allErrors.each{ println it}
throw new RuntimeException("addComments")
}
thread.addToComments(comment)
}
return thread
}
Sometimes I need to remove all of the comments from a TalkingThread and add comments that share the same uniqueHashes. So I call the removeAllComments(..) method, and then the addComments(..) method. This causes a
Comment.uniqueHash.unique.error.uniqueHash which caused by a supposedly deleted comment and a 'fresh' comment being added.
Should I be flushing? Maybe there is something wrong with my domain classes?
Edit Expansion of question.
Maybe this is a different question, but I thought that the session has deleted all associations and objects. Therefore the session state is aware that all TalkingThread comments have been deleted. Of course this has not been reflected in the database. I also assumed that the 'saving' of new Comments would be valid given that such 'saving' is consistent with the session state. However such 'saving' would be inconsistent with the database state. Therefore, my understanding of how grails validates objects in relation to session and database state is flawed! Any help in understanding the process of validating saves with respect to session and database states would also be appreciated.
If you want to remove all the Comments from a TalkingThread then you can use Hibernate's cascade behaviour.
Add
static mapping = {
comments cascade: 'all-delete-orphan'
}
to TalkingThread and then you can call comments.clear() followed by thread.save() which will delete the comments that were in the association.
There's a good article on Grails one-to-many-relationships here. The official Grails docs on it are here.

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!

Grails service not saving Domain Object When triggered by Message Queue

I have a grails application that has a service that creates reports. The report is defined as:
class Report {
Date createDate
String reportType
List contents
static constraints = {
}
}
The service generates a report and populates contents as a list that is returned by createCriteria.
My problem is that my service claims to be saving the Report, no errors turn up, logging says that its all there, but when I go to call show from the controller on that report, it says contents is null.
Another relevant bit, my Service is called by an ActiveMQ message queue. The message originating from my report controller.
Controller:
class ReportController {
def scaffold = Report
def show = {
def rep = Report.get(params.id)
log.info("Report is " + (rep? "not null" : "null")) //says report is not null
log.info("Report content is " + (rep.contents? "not null" : "null")) //always says report.contents is null.
redirect(action: rep.reportType, model: [results: rep.contents, resultsTotal: rep.contents.size()])
}
}
My service that creates the report:
class ReportService {
static transactional = false
static expose = ['jms']
static destination = "Report"
void onMessage(msg)
{
this."$msg.reportType"(msg)
}
void totalQuery(msg)
{
def results = Result.createCriteria().list {
//This returns exactly what i need.
}
Report.withTransaction() {
def rep = new Report(createDate: new Date(), reportType: "totalQuery", contents: results)
log.info("Validation results: ${rep.validate()}")
if( !rep.save(flush: true) ) {
rep.errors.each {
log.error(it)
}
}
}
}
Is there something obvious that I'm missing here? My thought is that since all my unit tests work, that the hibernate context is not being passed through the message queue. But that would generate Exceptions wouldn't it? I've been beating my head on this problem for days, so a point in the right direction would be great.
Thanks,
You can't define an arbitrary List like that, so it's getting ignored and treated as transient. You'd get the same behavior if you had a def name field, since in both cases Hibernate doesn't know the data type, so it has no idea how to map it to the database.
If you want to refer to a collection of Results, then you need a hasMany:
class Report {
Date createDate
String reportType
static hasMany = [contents: Result]
}
If you need the ordered list, then also add in a List field with the same name, and instead of creating a Set (the default), it will be a List:
class Report {
Date createDate
String reportType
List contents
static hasMany = [contents: Result]
}
Your unit tests work because you're not accessing a database or using Hibernate. I think it's best to always integration test domain classes so you at least use the in-memory database, and mock the domain classes when testing controllers, services, etc.

Can I serialize an object if I didn't write the class used to instantiate that object?

I've a simple class
[Serializable]
public class MyClass
{
public String FirstName { get; set: }
public String LastName { get; set: }
//Bellow is what I would like to do
//But, it's not working
//I get an exception
ContactDataContext db = new ContactDataContext();
public void Save()
{
Contact contact = new Contact();
contact.FirstName = FirstName;
contact.LastName = LastName;
db.Contacts.InsertOnSubmit(contact);
db.SubmitChanges();
}
}
I wanted to attach a Save method to the class so that I could call it on each object. When I introduced the above statement which contains ContactDataContext, I got the following error "In assembly ... PublicKeyToken=null' is not marked as serializable"
It's clear that the DataContext class is generated by the framework (). I checked and did not see where that class was marked serialize.
What can I do to overcome that? What's the rule when I'm not the author of a class? Just go ahead and mark the DataContext class as serializable, and pretend that everything will work?
Thanks for helping
It might be worth taking a step back and seeing if what you want to achieve is really valid.
Generally, a serializable class is used for data transport between two layers. It is more likely to be a simple class that only holds data.
It seems a little out of place for it to hold the ability to persist to a database. It is not likely that both ends of the pipe actually have access to the database, and it seems very unlikely that they would both have the ability to persist data.
I wonder if it's worth factoring the save out to a repository. So have a repository class that will accept the data transfer object, construct the database object and save it.
This will simplify your code and completely avoid the problem you're having. It will also greatly enhance testability.
The problem is that the db field gets serialized, while clearly it doesn't need to be serialized (it's instantiated once the object is created).
Therefore, you should decorate it with the NonSerialized attribute:
[NonSerialized]
ContactDataContext db = new ContactDataContext();
[Update]
To make sure the db field is accesable after object initialization, you should use a lazy loading property and use this property instead of the field:
[NonSerialized]
ContactDataContext db = null;
[NonSerialized]
private ContactDataContext {
get {
if (db == null) {
db = new ContactDataContext();
}
return db;
}
set {
db = value;
}
}
public void Save()
{
Contact contact = new Contact();
contact.FirstName = FirstName;
contact.LastName = LastName;
Db.Contacts.InsertOnSubmit(contact);
Db.SubmitChanges();
}
[Update2]
You can serialize most objects, as long as it has a public parameterless constructor (or no constructor at all) and no properties/fields that cannot be serialized but require serializing. If the class itself is not marked as [Serializable], then you can do this yourself using a partial class. If the class has properties/fields that cannot be serialized, then you might achieve this by inheriting the class and overriding these properties/fields to decorate them as [NonSerialized].
You can create a surrogate that knows how to serialize the dodgy classes - see here for an example
I think you do need to decorate the base class, however, the DataContext auto generated classes are marked as partial. Have you tried doing something like:
[Serializable]
public partial class ContactDataContext
{
}
Not sure if it would work but its worth a try.

Resources