Grails issue with unique/save/update - grails

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

Related

grails: dont save similar domain object again, use present object

The problem:
I have "route" domain, that has stations (domains).
If I save routes, I should save stations too. (used cascade: 'all' mapping)
But, if station with the same name already exists in DB, I want to use it, and don't create such station anymore.
so the route, I save, must save only new stations.
example:
domain:
class Route {
String name
List<Station> stations
static mapping = {
stations cascade: 'all', lazy: false
}
static hasMany = [
stations: Station
]
}
class Station {
String name
}
controller/service:
def route = new Route()
route.stations.add(new Station("stationOne"))
route.stations.add(new Station("stationTwo"))
route.save()
//now in db there are 2 stations.
//now create new route with 2 stations, with one similar to already saved ('stationOne').
def route2 = new Route()
route2.stations.add(new Station("stationOne")) //<-- this one i want to be replaced
// with the inDB one, if i save the route
// in DB must be only one "stationOne"
// and every route must point to it,
// not own "stationOne", if the route saved
route2.stations.add(new Station("stationThree"))
route2.save()
//now i wish in DB there are only 3 stations.
//and route2 has both from DB. And the reference (in list) from route2 to "stationOne"
//inMemory object is now replaced with reference to inDB station object.
i could write code, like "replaceWithDBStationReferences(route)"
But my project is enough big, for testing such things in code.
is it possible to define this somewhere in domain? or any other solutions?
You can make use of the findOrCreate dynamic methods:
route2.stations.add(Station.findOrCreateByName("stationOne"))
findOrCreateByName is similar to findByName except that where the plain finder would return null (if nothing is found) the findOrCreate will create a new transient instance based on the query parameters, in this case a new Station(name:'stationOne').
There's also findOrSaveBy... which does the same but also saves the new instance before returning it.
If you need to use the existing Route from the DB you should just need to fetch it first. Something like this:
def route = Route.findByName(name)
def stations = getNewAndModifiedStations()
stations.each { station ->
def existingStation = route.stations.find { station.name?.equals(it.name) }
if (existingStation) {
// if station is found
existingStation.updateProperties(station)
} else {
// new station here
route.stations.add(station)
}
}
route.save()

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.

Entity Framework 4 and Repository Pattern problem

I am having trouble understanding if I am doing this correctly or not. I have 3 entities that are dependent on each other. I am trying to add new objects to these entities and then call save changes ultimately adding the corresponding records to the tables honoring the FK constraints.
I am getting the error:
The relationship between the two objects cannot be defined because they are attached to different ObjectContext objects.
In my code I am parsing some XML with linq while adding the new objects to the context as I go. In my service layer I have the following method to handle processing the incoming data.
public void ProcessSurvey(int surveyContentId, int caseNo, string surveyTitle, string reportVersion, string reportXml)
{
// get surveyid
var surveyContent = _surveyContentRepository.GetSurveyContent(surveyContentId);
// create response obj
var surveyResponse = new SurveyResponse()
{
SurveyId = surveyContent.SurveyId,
CaseNo = caseNo,
SurveyTitle = surveyTitle,
ReportVersion = reportVersion,
Created = DateTime.Now,
ResponseXML = reportXml
};
// add response obj to context?
_surveyResponseRepository.Add(surveyResponse);
// get the questions elements from the xml data
var questions = SurveyResponseHelper.GetResponseQuestions(reportXml);
// iterate over questions
foreach (XElement question in questions)
{
SurveyQuestion thisSurveyQuestion = SurveyResponseHelper.ProcSurveyQuestion(question, surveyContentId);
// add question?
_surveyQuestionRepository.Add(thisSurveyQuestion);
// get question answer
SurveyAnswer thisSurveyAnswer = SurveyResponseHelper.GetAnswer(question);
//update the answer with the question and response obj to satisfy the FK reference
thisSurveyAnswer.SurveyQuestion = thisSurveyQuestion;
thisSurveyAnswer.SurveyResponse = surveyResponse; // This is where it breaks ERRROR: The relationship between the two objects cannot be defined because they are attached to different ObjectContext objects
_surveyAnswerRepository.Add(thisSurveyAnswer);
}
//commit
_surveyAnswerRepository.Save();
}
My Repositories look like this..
public interface ISurveyAnswerRepository
{
void Add(SurveyAnswer surveyAnswer);
void Save();
}
public class SurveyAnswerRepository : Repository, ISurveyAnswerRepository
{
//private DiversionProgramsEntities _db;
public SurveyAnswerRepository()
{
//_db = new DiversionProgramsEntities();
}
public void Add(SurveyAnswer surveyAnswer)
{
this.DataContext.SurveyAnswers.AddObject(surveyAnswer);
}
public void Save()
{
this.DataContext.SaveChanges();
}
my base repository
public class Repository
{
private DiversionProgramsEntities _dataContext;
public DiversionProgramsEntities DataContext
{
get { return _dataContext ?? (_dataContext = DatabaseFactory.CreateContext()); }
}
}
and static class / method to create the context
public static class DatabaseFactory
{
public static DiversionProgramsEntities CreateContext()
{
return new DiversionProgramsEntities();
}
}
here is my helper code..
public class SurveyResponseHelper
{
public static IEnumerable<XElement> GetResponseQuestions(string xmlResponseData)
{
XElement xmlData = XElement.Parse(xmlResponseData);
var questions = from n in xmlData.Descendants()
where n.Parent.Name.LocalName == "questions"
select n;
return questions;
}
public static SurveyQuestion ProcSurveyQuestion(XElement question, int surveyContentId)
{
// get the question type
var questionType = question.Name.LocalName;
// get question element text. This is the actual question text
var questionText = question.Elements().Where(e => e.Name.LocalName == "direction").SingleOrDefault().Value;
// check to see if this question exists in the data table, if it does then we will use the questionid from that which will get used to tie the SurveyAnswer to this question.
// if question does not already exist then a new one will be created
SurveyQuestionRepository surveyQuestionRepository = new SurveyQuestionRepository();
SurveyQuestion surveyQuestion;
surveyQuestion = surveyQuestionRepository.GetSurveyQuestion(surveyContentId, questionType, questionText);
if (surveyQuestion == null)
{
surveyQuestion = new SurveyQuestion()
{
QuestionText = questionText,
QuestionType = questionType,
SurveyContentId = surveyContentId
};
}
return surveyQuestion;
}
public static SurveyAnswer GetAnswer(XElement question)
{
// get the answer index value
var answers = question.Elements().Where(e => e.Name.LocalName == "answers").SingleOrDefault();
int userAnswerIndex = Int32.Parse(answers.Attribute("userAnswerIndex").Value);
// move the answers to an array so we can use the index to get the correct answer
XElement[] answersArray = answers.Elements().ToArray();
SurveyAnswer answer = new SurveyAnswer()
{
AnswerText = answersArray[userAnswerIndex].Value
};
return answer;
}
}
It looks like the error is describing perfectly what is going on. In the following line:
var questions = SurveyResponseHelper.GetResponseQuestions(reportXml);
You are getting a question from another class. That class probably creates it's own object context.
You can't attach a question to the answer if they are from different object contexts.
To solve this, the easiest way is to add a parameter to your methods GetResponseQuestions for the datacontext, so your other method can use that the repositories datacontext to get the questions.
Also, various IoC methods would simplify this.
Where does your _surveyContentRepository come from? If it's static I could see a scenario where that holds on to a SurveyContent object which is attached to one DiversionProgramsEntities, and your ProcSurveyQuestion() method finds and returns an existing SurveyQuestion, attached to a different DiversionProgramsEntities.
Other than that, I think a general pointer I can give you is to assign objects to each other using the objects themselves rather than the object Ids, so instead of:
var surveyResponse = new SurveyResponse { SurveyId = surveyContent.SurveyId }
...use:
var surveyResponse = new SurveyResponse { Survey = surveyContent }
This automatically adds your new SurveyResponse object to the same object context to which the SurveyContent object belongs, and means you don't have to manually add anything to a repository. You can assemble your entire object graph like this, then call Save() on the repository you used to retrieve the first object to save the whole thing.
As #TimHoolihan stated the issue is that you are not using the same Data Context for accessing the Survey Responses and Survey Questions and actually I believe the issue lines in the line below from the ProcSurveyQuestion method.
SurveyQuestionRepository surveyQuestionRepository = new SurveyQuestionRepository();
I see that you have a singleton DataContext in the DiversionProgramsEntities class, but I cannot infer from your code if the SurveyQuestionRepository and SurveryResponseRepositories are also using that same context. Based on the error you are getting, I am guessing that they are using separate contexts, so again as #TimHoolihan suggested, you need to modify your code to use the same context for both.
You should also look into the UnitOfWork pattern as this is what you are trying to accomplish here, but you do not have a common context to track all of your changes across.

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.

Saving associated domain classes in Grails

I'm struggling to get association right on Grails. Let's say I have two domain classes:
class Engine {
String name
int numberOfCylinders = 4
static constraints = {
name(blank:false, nullable:false)
numberOfCylinders(range:4..8)
}
}
class Car {
int year
String brand
Engine engine = new Engine(name:"Default Engine")
static constraints = {
engine(nullable:false)
brand(blank:false, nullable:false)
year(nullable:false)
}
}
The idea is that users can create cars without creating an engine first, and those cars get a default engine. In the CarController I have:
def save = {
def car = new Car(params)
if(!car.hasErrors() && car.save()){
flash.message = "Car saved"
redirect(action:index)
}else{
render(view:'create', model:[car:car])
}
}
When trying to save, I get a null value exception on the Car.engine field, so obviously the default engine is not created and saved. I tried to manually create the engine:
def save = {
def car = new Car(params)
car.engine = new Engine(name: "Default Engine")
if(!car.hasErrors() && car.save()){
flash.message = "Car saved"
redirect(action:index)
}else{
render(view:'create', model:[car:car])
}
}
Didn't work either. Is Grails not able to save associated classes? How could I implement such feature?
I think you need a belongsTo in your Engine ie
static belongsTo = [car:Car]
Hope this helps.
For what is worth, I finally nailed it.
The exception I got when trying to save a car was
not-null property references a null or
transient value
It was obvious that the engine was null when trying to save, but why? Turns out you have to do:
def car = new Car(params)
car.engine = new Engine(name: "Default Engine")
car.engine.save()
Since engine doesn't belongs to a Car, you don't get cascade save/update/delete which is fine in my case. The solution is to manually save the engine and then save the car.

Resources