This is my domain model, a survey has many questions, and each question has many repsonses :
class Survey {
String name
String customerName
static hasMany = [questions: SurveyQuestion]
static constraints = {
}
}
class SurveyQuestion {
String question
static hasMany = [responses : SurveyQuestionResponse]
static belongsTo = [survey: Survey]
static constraints = {
}
}
class SurveyQuestionResponse {
String description
static belongsTo = [question: SurveyQuestion]
static constraints = {
}
}
In my controller, I have a method that takes in the ID for a Survey, looks it up, then builds a question from another request parameter, tries to add the question to the survey and save it:
def addQuestion =
{
def question = new SurveyQuestion(question:params.question)
def theSurvey = Survey.get(params.id)
theSurvey.addToQuestions(question) //fails on this line
theSurvey.save(flush:true)
redirect(action: showSurvey, params:[id:theSurvey.id])
}
However, it fails and returns this :
No signature of method: roosearch.Survey.addToQuestions() is
applicable for argument types: (roosearch.SurveyQuestion) values:
[roosearch.SurveyQuestion : null] Possible solutions:
addToQuestions(java.lang.Object), getQuestions()
I'm not quite understanding what I'm doing wrong here, I've tried various alternative ways to create the question, even instantiating one manually with a literal string, but it always gives the same error.
Can anyone please advise me?
Thanks
The problem is that you dont have "question" saved so it is not in the database yet. Try to save first the "question" and then add it to the survey.
Something like this:
def addQuestion =
{
def question = new SurveyQuestion(question:params.question).save()
def theSurvey = Survey.get(params.id)
theSurvey.addToQuestions(question)
theSurvey.save(flush:true)
redirect(action: showSurvey, params:[id:theSurvey.id])
}
(I don't have enough points to comment, so I'll "answer").
First, it does look "OK".
I've learned to take the error messages at their face value. For some reason, it thinks that "question" is null. I'm guessing that you can insert some logging and see that it isn't.
At this point, I would try saving question first, see that it saves correctly and gets assigned and id, and then call the addToQuestions.
can you try asserting if the SurveyQuestion actually got created using the input parameters?
e.g.
assert question
right after the line
def question = new SurveyQuestion(question:params.question)
and as suggested by #alcoholiday try some logging as well.
or a simple
println params
could give you a quick peek
Related
Using Grails 2.5.6 here. I'm trying to access a Set of Strings off of my domain class in the beforeDelete GORM event. I'm seeing the deletes for this set getting issued in the database log before even getting to my breakpoint in the beforeDelete.
I'm getting a NullPointerException on my println(strings) below in my domain class.
My test domain class looks like
class DeleteTest {
Integer id
Set<String> stringSet
String prop1
String prop2
static hasMany = [stringSet: String]
static constraints = {
prop1(maxSize: 20)
prop2(maxSize: 20)
}
static mapping = {
stringSet(joinTable: [column: 'delete_test_string_set', length: 15])
}
def beforeDelete() {
withNewSession {
Set<String> strings = this."stringSet"
println(strings)
}
}
}
And I've made a test controller like this.
class DeleteTestController {
def create() {
DeleteTest test = null
DeleteTest.withTransaction {
test = new DeleteTest(
prop1: 'Test',
prop2: 'another test',
stringSet: ['str1', 'str2', 'str3']
).save()
}
render (test as JSON)
}
def delete() {
DeleteTest test = DeleteTest.findByProp1('Test')
DeleteTest.withTransaction {
test.delete()
}
render(test as JSON)
}
}
How can I get my stringSet in the beforeDelete event?
One easy way is to make sure to load stringSet before calling the delete. However, there are clearly some odd behaviors going on here and I'll describe what I have found so far.
Simple Answer
def delete() {
DeleteTest test = DeleteTest.findByProp1('Test')
test.stringSet?.size() // <-- force load here
DeleteTest.withTransaction {
test.delete()
}
render(test as JSON)
}
Other Considerations
I tried making stringSet eager loaded. This did not work as expected and in the beforeDelete code it would often be a single value or blank.
I also tried making StringSet a Set where I defined a single GORM object MyString containing the value. This did work (though I had to make it eagerly fetched), but I did not consider this to be a valid solution for your case since I assume you have data already and can't just replace it.
Based on some debug digging, I'm guessing (but it really is just a guess) that the collection is deleted before the beforeDelete event fires, and so it can't be lazily loaded at that point even in a new transaction. I would expect that someone else could weigh in on whether that's right or not, but grails 2 expertise is getting harder to find these days.
I have the following domain classes (Only trying to show what is needed to get the idea) :
class Scholarship {
static hasMany = [grades:Grade]
}
and
class Grade {
String id
String description
}
In words I would like to, "Get all scholarships where the associated grade_id = myId". I would like to accomplish this using grails domain classes and not using sql. Any help appreciated
Are you looking for something like this?...
def results = Scholarship.withCriteria {
grades {
// myId must be defined somewhere above...
idEq myId
}
}
EDIT
A comment below adds to the original question and asks what if another relationship was expressed like this...
class Scholarship {
static hasMany = [grades:Grade,majors:Major]
}
The query I show above would still be exactly the same. The fact that there is a majors collection would not be relevant unless you wanted to include some attribute of Major to also be part of the criteria, which could look something like this...
def results = Scholarship.withCriteria {
grades {
// myId must be defined somewhere above...
idEq myId
}
majors {
// only return Scholarship instances which
// contain a Major with the name 'Mechanical Engineering'
eq 'name', 'Mechanical Engineering'
}
}
I hope that helps.
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.
Working in Grails 2.2
I have a situation where I need to be able to handle an unknown number of CommitteeMembers in the view. These need to be both created and displayed.
Each one has the usual attributes - name, address, contact information, userid.
I understand that if I name form fields the same name, Grails will return a collection for me to iterate over. In this case, however, I am faced with this situation:
cm_firstname
cm_lastname
cm_address
cm_email
cm_userid
So does this mean I will be given collections of each of these fields? That is not as useful as there is no way to corelate the various firstnames with the correct lastnames, etc.
I am enjoying Grails and am looking forward to your feedback.
You can use Grails Command objects to do this work for you. Here's an example in a SO question. Basically you will have a single collection of CommitteeMembers that will be populated in your controller thorugh data binding.
As #Gregg says, in the view you need the fields to have an index.
class MyDomain {
String name
}
class MyDomainCommand {
List<MyDomain> instances = ListUtils.lazyList([], FactoryUtils.instantiateFactory(MyDomain))
}
class MyController {
def save() {
MyDomainCommand command = new MyDomainCommand()
bindData(command, params, [include: 'instances'])
}
}
I'll tell you what I do, which may or may not be the best option. I do this mainly because I don't like data binding.
For your case as an example, I would name my fields: "cm.firstName, cm.lastName, cm.address, cm.email, cm.userId".
If you are in a service:
GrailsWebRequest webUtils = WebUtils.retrieveGrailsWebRequest()
List committeeMembers = [].withDefault {new GrailsParameterMap([:], webUtils.getCurrentRequest())}
In a controller:
List committeeMembers = [].withDefault {new GrailsParameterMap([:], request)}
Then
params.cm.each { k, v ->
if (v instanceof String[]) {
v.eachWithIndex { val, idx ->
committeeMembers[idx]."$k" = val
}
}
else {
committeeMembers[0]."$k" = v
}
}
Then you can do:
committeeMembers.each {
<Create from it.firstName, it.lastName, etc>
}
I'm trying to instantiate an object and set a single attribute on it, which comes from a request parameter, like so :
println "Question text from the request :" + params.question
def question = new SurveyQuestion()
question.question = params.question
println "this is our question" + question
This is my output in the console :
Question text from the request :test this is our
questionroosearch.SurveyQuestion : null
And this is the SurveyQuestion class :
class SurveyQuestion {
String question
static hasMany = [responses : SurveyQuestionResponse]
static belongsTo = [survey: Survey]
static constraints = {
}
}
The above seems to compile ok, however I get further classcast exceptions when I do a redirect at the end of my action, I believe this is due to the instantiating and setting of that SurveyQuestion, as if I comment out the above I don't get this failure behaviour.
Am I instantiating the SurveyQuestion object correctly? Why does it display as null when I print it to the console? Is that normal behaviour? At the least I'd expect it to print the object reference as Java would?
Thanks
The default toString() method on a domain instance will return a string which looks like class.name: id. As your newly created domain instance doesn't have id set is shows null.
Try overriding toString() in your SurveyQuestion domain:
String toString() {
return question
}