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
}
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 a User class with a resetPasswordToken attribute, that is a UUID set when a user tries to reset his password.
On Grails 2.5.6 I had something like this that worked OK:
class UserController {
def forgotPassword(String email)
{
...
def user = User.findByEmail(email)
user.setPasswordToken()
user.save(flush: true()
...
}
}
class User {
...
String resetPasswordToken
static transients = ['passwordToken']
def setPasswordToken()
{
...
this.resetPasswordToken = (java.util.UUID.randomUUID() as String)
}
}
Now I migrated that to GRails 3.3.10 and the resetPasswordToken is NULL on the database after the forgotPassword action is invoked. If I do a println after the user.setPasswordToken() is invoked, I can see the resetPasswordToken is set to an UUID, but is not in the DB. Also checked for errors on the save, and there are no errors.
Strange thing, if I do user.resetPasswordToken = "xxxx" in the controller, the value is saved into the database correctly.
Not sure what is going on with the value set in the setPasswordToken() not being saved into the DB. Any pointers?
See the comment at https://github.com/grails/grails-data-mapping/issues/961#issuecomment-309379214. The issue you are experiencing is one of dirty checking, which changed in GORM 6.1.
Consider this code...
class Person {
String name
String email
void updateName(String newName) {
this.name = newName
}
static constraints = {
email email: true
}
}
That updateName method will not result in the name property being marked as dirty. The following code would result in the name property being marked as dirty:
class Person {
String name
String email
void updateName(String newName) {
setName newName
}
static constraints = {
email email: true
}
}
If you really want to turn on the old way of dirty checking you can do that per the instructions in the comment I linked above but be aware of the performance penalty of doing so. The recommended approach would be to use the setter or to explicitly mark the property as dirty using the markDirty method.
I hope that helps.
I can't override grails getter method and becoming crazy.
What I want is use a double value and a string value to get a formatted data, but when I'm typing my override method into String, the double value is null and when into double, it obviously get me an error because a String is returned !
I get a domain class like that :
class Project {
...
String currency
Double initialTargetAmount
...
}
First Override method (initialTargetAmount is null in this case) :
//#Override Comment or uncomment it does not make any change to the behaviour
public String getInitialTargetAmount() {
println "${initialTargetAmount} ${currency}" // display "null EUR"
"${initialTargetAmount} ${currency}" // initialTargetAmount is null
}
Second method :
//#Override Comment or uncomment it does not make any change to the behaviour
public Double getInitialTargetAmount() {
println "${initialTargetAmount} ${currency}" // display "1000.00 EUR"
"${initialTargetAmount} ${currency}" // Error : String given, Double expected
}
Any help is welcome.
Snite
Groovy has dynamic getters and setters.
So, initalTargetAmount field "creates" automatically a Double getInitialTargetAmount method. Which is why it works when you have the Double return Type. But when you set String, getInitialTargetAmount automatically refers to a field String initalTargetAmount which doesn't exist
Try changing the name of the method, for example getInitialAmountWithCurrency() and it will work. Maybe your best bet will be to override toString() method ^^
Your getter should be always the same type of your field, and it's noot a good approach to change the getter like this, because Grails (Hibernate internally) will understand that your object instance changed and will try to update it ( it will check the old and new values).
You're trying in fact is to have a String representation of your amount, so you have a couple of options to this:
1 - A new method
Creating a new method that returns String will not interfere in the hibernate flow and you can use it anywere.
class Project {
...
String currency
Double initialTargetAmount
...
String displayTargetAmount() {
"${initialTargetAmount} ${currency}"
}
}
2 - TagLib
Depending on your needs, you could create a TagLib to make this custom representations of your class. This can include html formatting.
class ProjectTagLib {
static namespace = "proj"
def displayAmount = { attrs ->
if(!attrs.project) {
throwTagErrro("Attribute project must be defined.")
}
Project project = attrs.remove('project')
//just an example of html
out << "<p>${project.initialTargetAmount} , ${project.currency}</p>"
}
}
Similar to my last question (Grails databinding: creating instances of an abstract class), I want to use data binding with a class that contains a collection of abstract classes with a hasMany relationship, but in this case, instead of using a List, I'm using a Map.
I created a smalll project with a failing integration test to show the problem that can be found in Github, run it with:
grails test-app -integration -echoOut DataBinding
Anyway, I'll explain the problem by describing the classes and the test here:
class LocalizableContent {
Map contentByLocale = [:].withDefault { locale -> new Text() }
static hasMany = [ contentByLocale : Content ]
}
abstract class Content {
static belongsTo = [ localizableContent : LocalizableContent ]
static constraints = {
localizableContent nullable:true
}
}
class Text extends Content {
String text
}
As you can see, I'm already using the withDefault trick, but apparently it's not being called by Grails / Spring (I even tried to throw an exception in the default closure to verify that the code is not executed).
For the sake of the test, I also created a LocalizableContentController which is empty. With all that, the following integration test then fails:
void testMapDatabinding() {
def rawParams = [ 'contentByLocale[en].text': 'Content' ]
def controller = new LocalizableContentController()
controller.request.addParameters(rawParams)
controller.request.setAttribute(GrailsApplicationAttributes.CONTROLLER, controller)
def localizableContent = new LocalizableContent(controller.params)
assert localizableContent?.contentByLocale['en']?.text == 'Content'
}
It says that localizableContent.contentByLocale is a map which looks like ['en': null], so apparently the data binding is understanding the map syntax and trying to create an entry for the 'en' key. But is not trying first to get the entry for that key, since the withDefault is not being called.
The following one tests that the withDefault works fine, and it passes:
void testMapByDefaultWithNoDatabinding() {
assert new LocalizableContent().contentByLocale['en']?.getClass() == Text
}
What am I missing here?
withDefault is nothing but a pattern to provide a valid value if you face an unknown key. For example, consider the below use case:
def map = [:].withDefault{k->
println k //Should print 'a'
10
}
map.test = 32
assert map.test == 32
assert map.a == 10
It takes the unknown key as the parameter, you cannot pass in any value to it, which is kind of logical, because it provides a default value instead of a value being provided.
In your case, the data binding would work if set the value to Text like:
Map contentByLocale = [:].withDefault { locale ->
//locale is the key. 'en' in this case
new Text(locale: locale, text: 'Content')
}
provided you have your Text class defined as
class Text extends Content{
String locale
String text
}
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