I have some working code but I'm a little fuzzy on GORM and cascading saves. Here is my object model:
class Profile {
PhotoAlbum photoAlbum
static constraints = {
photoAlbum(nullable:true)
}
}
class PhotoAlbum {
static hasMany = [photos:Photo]
static belongsTo = [profile:Profile]
}
class Photo {
static belongsTo = PhotoAlbum
}
And here is my working code to save a new photo object:
Photo photo = new Photo()
if (!profile.photoAlbum) { profile.photoAlbum = new PhotoAlbum(profile:profile) }
profile.photoAlbum.addToPhotos(photo)
if (!photo.save()) {
def json = [
status:'fail',
message:messageSource.getMessage('label.photo.validate.failed.message', [photo.errorStrings].toArray(), LocaleContextHolder.locale)
]
return json
}
if (!profile.photoAlbum.save()) {
def json = [
status:'fail',
message:messageSource.getMessage('label.photo.validate.failed.message', [profile.photoAlbum.errorStrings].toArray(), LocaleContextHolder.locale)
]
return json
}
if (!profile.save()) {
def json = [
status:'fail',
message:messageSource.getMessage('label.photo.validate.failed.message', [profile.errorStrings].toArray(), LocaleContextHolder.locale)
]
return json
}
else {
def json = [
status:'success',
message:messageSource.getMessage('label.photo.insert.success.message', null, LocaleContextHolder.locale)
]
return json
}
It seems like a lot of code and error checking to save a new photo object. When I look at grails examples on websites and in books, I don't see a lot of error checking. In my case if a photo cannot be saved, the service has to return a json error string to the client. I have read the GORM Gotchas 2 post, but I'm still not clear on cascading saves.
I did find that if I don't call photo.save() and profile.photoAlbum.save() and just call profile.save() I get the transient exception. So I added in photo.save() and profile.photoAlbum.save() to get everything working.
As #Cregg mentioned you should move all your logic to service, in order to handle all calls to DB in one transaction. To not get the transient exception try to use following:
profile.save(flush: true, failOnError:true)
You should create ErrorController to handle all your exceptions. See example.
Related
Grails 2.3.7, Java 1.7, tomcat
For no apparent reason(that I can see) the reviewResults property of the Review domain sometimes saves as the ordinal value of the enum.
The database schema says that the column is a varchar type.
Sometimes NA is saved as NA, however, sometimes it is saved as 0.
This is the same with passed, except passed is saved as 1 sometimes.
Any ideas why this might be happening?
abstract class Work {
// Nothing defined in here related
// to the review domain enum.
}
The Domain class in question.
class Review extends Work {
Results reviewResults
String notes
enum Results {
NA,
Passed,
Error
}
static constraints = {
reviewResults(nullable: true, enumType: 'string')
}
// Is this redundant if it is already declared in the constraints?
static mapping = {
reviewResults enumType: 'string'
}
}
The Domains related controller.
// Reviews created with Quartz job, results property is not set.
class ReviewController {
def reviewService
def show(Long id) {
def reviewInstance = Review.get(id)
def reviewResultsOptions = []
reviewResultsOptions.addAll(com.mycompany.app.Review.Results.values())
[reviewInstance: reviewInstance, reviewResultsOptions: reviewResultsOptions]
}
def closeWork(Long id, String reviewResults, String notes) {
def review = Review.get(id)
review.reviewResults = Review.Results.valueOf(reviewResults)
def result = reviewService.closeReview(review, notes)
}
}
The service for the controller/domain.
class ReviewService {
def workService
Review closeReview(Review work, String notes) {
work.notes = notes
workService.closeWork(work)
return work.errors.allErrors.empty ? work : null
}
}
The service where the Review object is finally saved.
class WorkService {
Tracking closeWork(Work workInstance) {
def tracked = new Tracking()
workInstance.setStatus(status)
workIntance.setClosed(new Date())
WorkInstance.save(flush: true)
// Set some tracking properties and save and return the tracking object.
}
}
I have a domain class similar to the following:
class Record {
Long id
List numbers = []
String description
void recordNumber(Long number) {
//requirements, validations, etc.
numbers << number
}
}
Then I defined a Web service similar to the code below:
class RecordController extends RestfulController {
def recordNumber(Record record) {
def number = getNumberFromRequest() //request.JSON, request.XML, etc.
if (record) {
record.recordNumber(number)
record.save(flush: true, failOnError: true)
}
}
}
However, the numbers on the list don't seem to get saved, because when I retrieve a Record, the list empty. I have test for the code and it seems ok. Could it also be that the list is lazily loaded?
You are saving a new record instance each time the action is called. You should load it out of the DB instead:
def recordNumber( Long id ){
def record = Record.get id
def number = getNumberFromRequest() //request.JSON, request.XML, etc.
//....
}
So based on this answer from a previous StackOverflow question, I updated the code as follows:
class Record {
static hasMany = [numbers: Long]
Long id
String description
void recordNumber(Long number) {
//requirements, validations, etc.
addToNumbers number
}
}
It would seem that if a collection is meant to be persistent, it has to be declared this way, or be mapped in some other methods; I'm just not sure what those other methods are.
I am implementing a simple chat system in Grails as part of an existing application.
The main classes to consider are:
User.groovy
class User {
...
static hasMany = [
...
chatMessages : ChatMessage,
conversationParticipations:ConversationParticipation
]
static constraints = {
...
}
}
ChatConversation.groovy
class ChatConversation {
static hasMany = [
conversationParticipations:ConversationParticipation,
chatMessages:ChatMessage
]
static constraints = {
}
}
ConversationParticipation.groovy - the intermediate class to remove the many-many between User and ChatConversation
class ConversationParticipation {
ChatMessageBuffer chatMessageBuffer
static constraints = {
chatMessageBuffer nullable : true
}
static belongsTo = [
user:User,
chatConversation:ChatConversation
]
}
ChatMessageBuffer.groovy - used to hold chat messages, not yet read by a Conversation Participant
class ChatMessageBuffer {
static hasMany = [
chatMessages : ChatMessage
]
static belongsTo = [
conversationParticipation:ConversationParticipation
]
static constraints = {
chatMessages nullable : true
conversationParticipation nullable : true
}
}
In a service I am calling methods to create a conversation and then to send any sent messages to the ChatMessageBuffers for that conversation like this
def createChatConversation(chatDetails)
{
def chatConversation = new ChatConversation()
chatConversation.save(flush:true, failOnError:true)
new ConversationParticipation(
user:getCurrentUser(),
chatConversation:chatConversation,
chatMessageBuffer:new ChatMessageBuffer()
).save(flush:true, failOnError:true)
new ConversationParticipation(
user:User.get(chatDetails.id),
chatConversation:chatConversation,
chatMessageBuffer: new ChatMessageBuffer()
).save(flush:true, failOnError:true)
return chatConversation
}
def sendMessage(chatMessageDetails)
{
//save the message
def chatMessage = new ChatMessage(
body:chatMessageDetails.chatMessage,
dateSent: new Date(),
user:getCurrentUser(),
chatConversation:ChatConversation.get(chatMessageDetails.chatConversationId)
).save(flush:true,failOnError : true)
//add the message to the message buffer for each participant of the conversation.
ConversationParticipation.findAllByChatConversation(
ChatConversation.get(chatMessageDetails.chatConversationId)
).each {
if(it.chatMessageBuffer.addToChatMessages(chatMessage).save(flush:true, failOnError:true))
{
println"adding to ${it.chatMessageBuffer.id}"
println"added to : ${it.chatMessageBuffer.dump()}"
}
}
def chatMessageBuffer = ChatMessageBuffer.get(1)
println"service : message buffer ${chatMessageBuffer.id}: ${chatMessageBuffer.dump()}"
return chatMessage
}
As you can see on creating a ConversationParticipation object, I am also creating a ChatMessageBuffer, which is cascading save when I call save on the new ConversationParticipation.
My problem is when I am adding the ChatMessages to the two ChatMessageBuffers, the first ChatMessageBuffer is not saving, but the second is. So when I go to add another ChatMessage to the same buffers, the first buffer is empty, but the second buffer contains the previously added ChatMessage(s).
Does anybody have any idea where I am going wrong? Why the first one will not save/update?
I know there are several questions on this subject but none of them seem to work for me. I have a Grails app with the following Domain objects:
class Tag {
String name
}
class SystemTag extends Tag {
// Will have additional properties here...just placeholder for now
}
class Location {
String name
Set<Tag> tags = []
static hasMany = [tags: Tag]
}
I am trying to query for all Location objects that have been tagged by 1 or more tags:
class LocationQueryTests {
#Test
public void testTagsQuery() {
def tag = new SystemTag(name: "My Locations").save(failOnError: true)
def locationNames = ["L1","L2","L3","L4","L5"]
def locations = []
locationNames.each {
locations << new Location(name: it).save(failOnError: true)
}
(2..4).each {
locations[it].tags << tag
locations[it].save(failOnError: true)
}
def results = Location.withCriteria {
tags {
'in'('name', [tag.name])
}
}
assertEquals(3, results.size()) // Returning 0 results
}
}
I have validated that the data is being created/setup correctly...5 Location objects created and the last 3 of them are tagged.
I don't see what's wrong with the above query. I would really like to stay away from HQL and I believe that should be possible here.
Welcome to hibernate.
The save method informs the persistence context that an instance should be saved or updated. The object will not be persisted immediately unless the flush argument is used
if you do not use flush it does the saves in batches so when you setup your query right after the save it appears that the data is not there.
you need to add
locations[it].save(failOnError: true, flush:true)
You should use addTo* for adds a domain class relationship for one-to-many or many-to-many relationship.
(2..4).each {
locations[it].addToTags(tag)
}
I have the following classes:
class Employer {
static hasMany = [employees: Employee]
}
class Employee {
String name
static belongsTo = [employer: Employer]
}
I try to save some JSON from the front end (the actual code is a bit more dynamic):
params = {
employer: 1,
name: 'Test'
}
def save = {
def employee = new Employee()
employee.properties = params;
employee.save()
}
However, the save fails because the employer cannot be set from the id. (Failed to convert property value of type 'java.lang.Integer' to required type 'Employer') Is there a way to make that work?
The structure of your JSON is incorrect. It would need to be something more like:
'employer':{
'class':'Employer','id':1
}