0

I have a simple problem. I need to manage a hasMany collection on a domain object. I thought I was doing it correct, but it didn't work. I found another post but it is very dated and does not work (Handling parameters from dynamic form for one-to-many relationships in grails)

class User{
   static hasMany = ['prefs': Preference] 
}
class Preference{
  Boolean email
  Boolean site
  Date dateCreated
  Date lastUpdated

}

GSP

<g:each var="t" in="${user.prefs}" status="idx">            
  <li>
    <input type='hidden' name="prefs[${idx}].id" value="${t.id}"/>
    Email: <g:checkBox name="prefs[${idx}].email" value="${t.email}" />
    Site: <g:checkBox name="prefs[${idx}].site" value="${t.site}" /><
  </li>
</g:each>

Controller:

log.info(user.prefs)
user.properties = params
if(!user.save()){ ... }

It then errors out:

UserController - [Preference : 3, Preference : 4]

Error 2013-06-04 21:54:41,405 [http-bio-8080-exec-12] ERROR errors.GrailsExceptionResolver

IndexOutOfBoundsException occurred when processing request: [POST] /user/prefs - parameters: prefs[0].email:
id: 2
prefs[1].site: on
prefs[0].email: on
_prefs[1].site:
_prefs[1].email:
prefs[1].id: 3
_prefs[0].site:
prefs[0].site: on
prefs[0].id: 4
Index: 1, Size: 1. Stacktrace follows:
Message: Index: 1, Size: 1


Community
  • 1
  • 1
Nix
  • 57,072
  • 29
  • 149
  • 198
  • Does it error out saving the user after setting the properties? logs for `user.prefs` never showed `id` 1 and 2. Can you show the controller code and the POST url? BTW, if you are concerned about a particular order of which is has to be shown in gsp, then add `List prefs` in `User` domain. Adding `prefs` as items to a list in `User` maintains the index. – dmahapatro Jun 05 '13 at 03:00
  • The logs for user.prefs shows `UserController - [Preference : 3, Preference : 4] ` which happens to be the two that are assigned. Its not showing the idx, its showing the surroget key. – Nix Jun 05 '13 at 03:18
  • Can we have a look at the controller action as well? – dmahapatro Jun 05 '13 at 03:58
  • What part of the controller? I posted the two lines – Nix Jun 05 '13 at 12:12
  • The action method and the POST URL. Your setup works great for me without any issue when I test it. To replicate the problem I am trying to clone your setup line by line. – dmahapatro Jun 05 '13 at 12:18
  • I just updated my domain/gsp to be in the exact order i have it in mine. hopefully that was messing you up. – Nix Jun 05 '13 at 13:01

2 Answers2

1

I found this problem few days ago. And spent 2 days to fix it.

You have to make sure that children index in params is ordered same as parent's object. In this case, the children's order is [Preference: 3, Preference: 4]. But the params order is prefs[0].id = 4, prefs[1].id = 3. The order is different.

I have to reorder the children index in params before bind them.

Meam
  • 257
  • 3
  • 12
  • You are right, there has to be a better way to do this. Mine succeeds when they are in the right order, but fails if they are not. It looks like its randomly sorting my by updateDate. – Nix Jun 05 '13 at 13:11
0

So I ended up fixing this and it was a hybrid approach. @Meam was correct, it was an ordering issue, but instead of worrying about ordering I just used @dmahapatro 's approach and set Preference to be a list. A side note on this is the definition of List preference has to come before the static hasMany definition. Or you will get a random error when trying to create User. Lastly, when you originally setup the relationship you have to use addTo to link the two...

class User{
  List preference
  static hasMany = ['prefs': Preference] 
}

//another thing I did not know in order to originaly link 
//the two when using lists, you have to use addTo...
user.addToPrefs(
    new Preference(email: true, site:false)
)

The last thing I wanted to mention is there is a bug in <g:checkbox> if you use it with a hasMany like I did it will not work if you try to uncheck the value. I was able to work around it via coping the code from github for FormTagLib. And then updated the code with the other post i was reading https://stackoverflow.com/a/2942667/256793 that has a solution but it was a little dated. So here is the updated version:

 //old code
 //out  << "<input type=\"hidden\" name=\"_${name}\""
 //new...
 def begin =  name.lastIndexOf('.') +1
 def tail =  name.substring( begin);
 out << "<input type=\"hidden\" name=\"${name.replace(  tail, "_" + tail  )}\" "
Community
  • 1
  • 1
Nix
  • 57,072
  • 29
  • 149
  • 198