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.
Related
What is the correct way to handle domain save errors on domain classes under the hasMany relationship? It seems that calling save() on the owning side of the relation will return true even if there are validation errors on the owned objects.
How should I test this in running code or integration test?
I have reduced my problem to the following simple case.
class User {
static hasMany = [emails: Email]
static constraints = { }
}
.
class Email {
static belongsTo = [user: User]
String emailAddress
static constraints = {
emailAddress unique: true
}
}
Here are two suggestions on how to do this. Neither is really elegant.
First one calls save() individually to each on the hasMany relationship. What is good here is that we get the exact error out of the test case. This pretty cumbersome.
#Test
void testHasManyConstraintsOwned(){
def john = new User(login: 'johnDoe')
def email = new Email(emailAddress: 'john#gmail.com')
def duplicateEmail = new Email(emailAddress: 'john#gmail.com')
john.save() // otherwise: NULL not allowed for column "USER_ID" is thrown for email.save()
john.addToEmails(email).addToEmails(duplicateEmail)
assert email.save()
assert !duplicateEmail.save()
assert "unique"== duplicateEmail.errors.getFieldError("emailAddress").code
}
Another approach uses try/catch to detect the excepted fail. Problem here is that we have no way of knowing what went wrong and thus cannot actually test that the domain constraints are working as we expect.
#Test
void testHasManyConstraintsOwningExcp(){
def john = new User(login: 'johnDoe')
def email = new Email(emailAddress: 'john#gmail.com')
def duplicateEmail = new Email(emailAddress: 'john#gmail.com')
john.addToEmails(email).addToEmails(duplicateEmail)
try {
john.save(flush: true, failOnError: true)
assert false // should not reach here
}catch(Exception e){
}
}
What is the correct way to test and to react in the application code?
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.
I'm designing a system in which posts/discussions between users can be upgraded to become tickets. At one particular place I'm trying to create a one-to-one optional relationship but am running into certain issues. A condensed version of the entities in the spotlight is given below.
Rules:
A Post can become a Ticket if required. (optional)
A Ticket must have a Post. (mandatory)
Post.groovy
class Post {
String title
String description
String postedBy
Ticket ticket
static hasMany = [comments: Comment]
static constraints = {
title(blank:false)
description(blank:false)
postedBy(blank:false)
ticket (nullable:true,unique:true)
}
}
Ticket.groovy
class Ticket {
String title
String description
String postedBy
Post post
static hasMany = [responses: Response]
static constraints = {
title(blank:false)
description(blank:false)
postedBy(blank:false)
post (nullable:false,unique:true)
}
}
This works to some extent. I can:
Create a Post leaving the ticket attribute null If and when the post is upgraded to become a ticket
I can explicitly set the Post's ticket attribute to point to the parent ticket.
However, this mapping isn't enforced at the domain level. It leaves room for a situation where Ticket1 points to Post1, but Post1 points to Ticket2 instead.
I tried using a static hasOne = [post: Post] in the Ticket class but later learned that it mandates the presence of a static belongsTo = [ticket: Ticket] in the Post class and this becomes a mandatory 1-to-1 relationship which is not what I'm looking for.
Is there a way to achieve this 1-to-1 optional mapping in this scenario? Any pointers would be most helpful.
You could consider making a custom validator like
class Post {
// Other fields
Ticket ticket
static constraints = {
// Other constraints
ticket (nullable:true,unique:true, validator: { val, obj ->
if(val) {
return val.post == obj
}
})
}
}
Would this solve your problem?
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.
I'm having an issue with grails. I have a domain that looks like:
class Book {
static belongsTo = Author
String toString() { title }
Author bookAuthor
String title
String currentPage
static constraints = {
bookAuthor()
title(unique:true)
currentPage()
}
}
The main thing to note is that I have title(unique:true) to avoid from adding the same book twice. However, this is causing issues. In the controller I have created:
def populate = {
def bookInstance = new Book()
def dir = 'C:/currentBooks.txt'
def bookList
bookList = readFile(dir) //read file and push values into bookList
int numOfBooks = bookList.size()
numOfBooks.times {
bookInstance.setBookAuthor(bookList.author[it])
bookInstance.setTitle(bookList.title[it])
bookInstance.setCurrentPage(bookList.title[it])
bookInstance.save()
}
}
I call populate to read a file and populate the database with new Books. The problem is that I want to update it with new values. For instance, lets say that the book already exists in the database but I have read farther into the book and want to change the currentPage so the data is changed in the file and populate is called but doesn't update the page because the title already exists.
Can someone explain how to update the results with the new values?
First of all, you need a key for your Book domain object. You have the title marked as unique, which suggests you want to use that to uniquely identify a Book. I'd recommend against that (what happens when two books have the same title?) and use the id grails provides by default. That means you'll have to store the id in your currentBooks.txt in addition to your other fields.
Once you've got an id, you can try loading an existing record from the database. If not, create a new one. For Example:
def dir = 'C:/currentBooks.txt'
def bookList
bookList = readFile(dir) //read file and push values into bookList
int numOfBooks = bookList.size()
numOfBooks.times {
def bookInstance.get(bookList.id[it])
if (!bookInstance) {
bookInstance = new Book()
}
bookInstance.setBookAuthor(bookList.author[it])
bookInstance.setTitle(bookList.title[it])
bookInstance.setCurrentPage(bookList.title[it])
bookInstance.save()
}
Alternatively, you could use the title as the id. This is a bad idea as indicated above, but it saves having to keep track of a separate id and change the format of currentBooks.txt. With Book defined as below, you could call Book.get(bookList.title[it]):
class Book {
static belongsTo = Author
String toString() { title }
Author bookAuthor
String title
String currentPage
static constraints = {
bookAuthor()
title(unique:true)
currentPage()
}
static mapping = {
id name: 'title', generator: 'assigned'
}
}